1 /* 2 * Copyright 2008-2015, Axel Dörfler, axeld@pinc-software.de. 3 * Distributed under the terms of the MIT License. 4 */ 5 6 7 #include "ActivityWindow.h" 8 9 #include <stdio.h> 10 11 #include <Application.h> 12 #include <Catalog.h> 13 #include <File.h> 14 #include <FindDirectory.h> 15 #ifdef __HAIKU__ 16 #include <GroupLayout.h> 17 #endif 18 #include <Menu.h> 19 #include <MenuBar.h> 20 #include <MenuItem.h> 21 #include <Path.h> 22 #include <Roster.h> 23 24 #include "ActivityMonitor.h" 25 #include "ActivityView.h" 26 #include "DataSource.h" 27 #include "SettingsWindow.h" 28 29 #undef B_TRANSLATION_CONTEXT 30 #define B_TRANSLATION_CONTEXT "ActivityWindow" 31 32 33 static const uint32 kMsgAddView = 'advw'; 34 static const uint32 kMsgAlwaysOnTop = 'alot'; 35 static const uint32 kMsgShowSettings = 'shst'; 36 37 38 ActivityWindow::ActivityWindow() 39 : 40 BWindow(BRect(100, 100, 500, 350), B_TRANSLATE_SYSTEM_NAME("ActivityMonitor"), 41 B_TITLED_WINDOW, B_ASYNCHRONOUS_CONTROLS | B_QUIT_ON_WINDOW_CLOSE) 42 { 43 BMessage settings; 44 _LoadSettings(settings); 45 46 BRect frame; 47 if (settings.FindRect("window frame", &frame) == B_OK) { 48 MoveTo(frame.LeftTop()); 49 ResizeTo(frame.Width(), frame.Height()); 50 } else { 51 float scaling = be_plain_font->Size() / 12.0f; 52 ResizeTo(Frame().Width() * scaling, Frame().Height() * scaling); 53 CenterOnScreen(); 54 } 55 56 #ifdef __HAIKU__ 57 BGroupLayout* layout = new BGroupLayout(B_VERTICAL, 0); 58 SetLayout(layout); 59 60 // create GUI 61 62 BMenuBar* menuBar = new BMenuBar("menu"); 63 layout->AddView(menuBar); 64 65 fLayout = new BGroupLayout(B_VERTICAL); 66 float inset = ceilf(be_plain_font->Size() * 0.7); 67 fLayout->SetInsets(B_USE_WINDOW_SPACING); 68 fLayout->SetSpacing(inset); 69 70 BView* top = new BView("top", 0, fLayout); 71 layout->AddView(top); 72 top->SetViewUIColor(B_PANEL_BACKGROUND_COLOR); 73 74 BMessage viewState; 75 int32 count = 0; 76 for (int32 i = 0; settings.FindMessage("activity view", i, &viewState) 77 == B_OK; i++) { 78 ActivityView* view = new ActivityView("ActivityMonitor", &viewState); 79 fLayout->AddItem(view->CreateHistoryLayoutItem()); 80 fLayout->AddItem(view->CreateLegendLayoutItem()); 81 count++; 82 } 83 if (count == 0) { 84 // Add default views (memory & CPU usage) 85 _AddDefaultView(); 86 _AddDefaultView(); 87 } 88 #else // !__HAIKU__ 89 BView *layout = new BView(Bounds(), "topmost", B_FOLLOW_NONE, 0); 90 AddChild(layout); 91 92 // create GUI 93 BRect mbRect(Bounds()); 94 mbRect.bottom = 10; 95 BMenuBar* menuBar = new BMenuBar(mbRect, "menu"); 96 layout->AddChild(menuBar); 97 98 BRect topRect(Bounds()); 99 topRect.top = menuBar->Bounds().bottom + 1; 100 101 BView* top = new BView(topRect, "top", B_FOLLOW_ALL, 0); 102 layout->AddChild(top); 103 104 BMessage viewState; 105 int32 count = 0; 106 ActivityView *aview; 107 BRect rect; 108 for (int32 i = 0; settings.FindMessage("activity view", i, &viewState) 109 == B_OK; i++) { 110 aview = new ActivityView("ActivityMonitor", &viewState); 111 if (!rect.IsValid()) 112 rect = aview->Bounds(); 113 else 114 rect.OffsetBySelf(0.0, aview->Bounds().Height()); 115 top->AddChild(aview); 116 count++; 117 } 118 if (count == 0) 119 top->AddChild(new ActivityView("ActivityMonitor", NULL)); 120 121 #endif 122 // add menu 123 124 // "File" menu 125 BMenu* menu = new BMenu(B_TRANSLATE("File")); 126 menu->AddItem(new BMenuItem(B_TRANSLATE("Add graph"), 127 new BMessage(kMsgAddView))); 128 menu->AddSeparatorItem(); 129 130 menu->AddItem(new BMenuItem(B_TRANSLATE("Quit"), 131 new BMessage(B_QUIT_REQUESTED), 'Q')); 132 menu->SetTargetForItems(this); 133 menuBar->AddItem(menu); 134 135 // "Settings" menu 136 menu = new BMenu(B_TRANSLATE("Settings")); 137 menu->AddItem(new BMenuItem(B_TRANSLATE("Settings" B_UTF8_ELLIPSIS), 138 new BMessage(kMsgShowSettings), ',')); 139 140 menu->AddSeparatorItem(); 141 fAlwaysOnTop = new BMenuItem(B_TRANSLATE("Always on top"), new BMessage(kMsgAlwaysOnTop)); 142 _SetAlwaysOnTop(settings.GetBool("always on top", false)); 143 menu->AddItem(fAlwaysOnTop); 144 145 menu->SetTargetForItems(this); 146 menuBar->AddItem(menu); 147 } 148 149 150 ActivityWindow::~ActivityWindow() 151 { 152 } 153 154 155 void 156 ActivityWindow::MessageReceived(BMessage* message) 157 { 158 if (message->WasDropped()) { 159 _MessageDropped(message); 160 return; 161 } 162 163 switch (message->what) { 164 case B_REFS_RECEIVED: 165 case B_SIMPLE_DATA: 166 _MessageDropped(message); 167 break; 168 169 case kMsgAddView: 170 { 171 #ifdef __HAIKU__ 172 BView* firstView = fLayout->View()->ChildAt(0); 173 174 _AddDefaultView(); 175 176 if (firstView != NULL) 177 ResizeBy(0, firstView->Bounds().Height() + fLayout->Spacing()); 178 #endif 179 break; 180 } 181 182 case kMsgRemoveView: 183 { 184 #ifdef __HAIKU__ 185 BView* view; 186 if (message->FindPointer("view", (void**)&view) != B_OK) 187 break; 188 189 view->RemoveSelf(); 190 ResizeBy(0, -view->Bounds().Height() - fLayout->Spacing()); 191 delete view; 192 #endif 193 break; 194 } 195 196 case kMsgShowSettings: 197 { 198 if (fSettingsWindow.IsValid()) { 199 // Just bring the window to front (via scripting) 200 BMessage toFront(B_SET_PROPERTY); 201 toFront.AddSpecifier("Active"); 202 toFront.AddBool("data", true); 203 fSettingsWindow.SendMessage(&toFront); 204 } else { 205 // Open new settings window 206 BWindow* window = new SettingsWindow(this); 207 window->Show(); 208 209 fSettingsWindow = window; 210 } 211 break; 212 } 213 214 case kMsgAlwaysOnTop: 215 { 216 _SetAlwaysOnTop(!fAlwaysOnTop->IsMarked()); 217 break; 218 } 219 220 case kMsgTimeIntervalUpdated: 221 BroadcastToActivityViews(message); 222 break; 223 224 default: 225 BWindow::MessageReceived(message); 226 break; 227 } 228 } 229 230 231 bool 232 ActivityWindow::QuitRequested() 233 { 234 _SaveSettings(); 235 be_app->PostMessage(B_QUIT_REQUESTED); 236 return true; 237 } 238 239 240 int32 241 ActivityWindow::ActivityViewCount() const 242 { 243 #ifdef __HAIKU__ 244 return fLayout->View()->CountChildren(); 245 #else 246 return 1; 247 #endif 248 } 249 250 251 ActivityView* 252 ActivityWindow::ActivityViewAt(int32 index) const 253 { 254 return dynamic_cast<ActivityView*>(fLayout->View()->ChildAt(index)); 255 } 256 257 258 bool 259 ActivityWindow::IsAlwaysOnTop() const 260 { 261 return fAlwaysOnTop->IsMarked(); 262 } 263 264 265 void 266 ActivityWindow::BroadcastToActivityViews(BMessage* message, BView* exceptToView) 267 { 268 BView* view; 269 for (int32 i = 0; (view = ActivityViewAt(i)) != NULL; i++) { 270 if (view != exceptToView) 271 PostMessage(message, view); 272 } 273 } 274 275 276 bigtime_t 277 ActivityWindow::RefreshInterval() const 278 { 279 ActivityView* view = ActivityViewAt(0); 280 if (view != 0) 281 return view->RefreshInterval(); 282 283 return 100000; 284 } 285 286 287 status_t 288 ActivityWindow::_OpenSettings(BFile& file, uint32 mode) 289 { 290 BPath path; 291 if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK) 292 return B_ERROR; 293 294 path.Append("ActivityMonitor settings"); 295 296 return file.SetTo(path.Path(), mode); 297 } 298 299 300 status_t 301 ActivityWindow::_LoadSettings(BMessage& settings) 302 { 303 BFile file; 304 status_t status = _OpenSettings(file, B_READ_ONLY); 305 if (status < B_OK) 306 return status; 307 308 return settings.Unflatten(&file); 309 } 310 311 312 status_t 313 ActivityWindow::_SaveSettings() 314 { 315 BFile file; 316 status_t status = _OpenSettings(file, B_WRITE_ONLY | B_CREATE_FILE 317 | B_ERASE_FILE); 318 if (status < B_OK) 319 return status; 320 321 BMessage settings('actm'); 322 status = settings.AddRect("window frame", Frame()); 323 if (status != B_OK) 324 return status; 325 326 status = settings.SetBool("always on top", fAlwaysOnTop->IsMarked()); 327 if (status != B_OK) 328 return status; 329 330 #ifdef __HAIKU__ 331 BView* top = fLayout->View(); 332 #else 333 BView* top = ChildAt(0); 334 #endif 335 int32 count = top->CountChildren(); 336 for (int32 i = 0; i < count; i++) { 337 ActivityView* view = dynamic_cast<ActivityView*>(top->ChildAt(i)); 338 if (view == NULL) 339 continue; 340 341 BMessage viewState; 342 status = view->SaveState(viewState); 343 if (status == B_OK) 344 status = settings.AddMessage("activity view", &viewState); 345 if (status != B_OK) 346 break; 347 } 348 349 if (status == B_OK) 350 status = settings.Flatten(&file); 351 352 return status; 353 } 354 355 356 void 357 ActivityWindow::_AddDefaultView() 358 { 359 BMessage settings; 360 settings.AddInt64("refresh interval", RefreshInterval()); 361 362 ActivityView* view = new ActivityView("ActivityMonitor", &settings); 363 364 switch (ActivityViewCount()) { 365 case 0: 366 // The first view defaults to memory usage 367 view->AddDataSource(new UsedMemoryDataSource()); 368 view->AddDataSource(new CachedMemoryDataSource()); 369 view->AddDataSource(new SwapSpaceDataSource()); 370 break; 371 case 2: 372 // The third view defaults to network in/out 373 view->AddDataSource(new NetworkUsageDataSource(true)); 374 view->AddDataSource(new NetworkUsageDataSource(false)); 375 break; 376 case 3: 377 view->AddDataSource(new CPUFrequencyDataSource()); 378 break; 379 case 1: 380 default: 381 // Everything beyond that defaults to a CPU usage view 382 view->AddDataSource(new CPUUsageDataSource()); 383 break; 384 } 385 386 fLayout->AddItem(view->CreateHistoryLayoutItem()); 387 fLayout->AddItem(view->CreateLegendLayoutItem()); 388 } 389 390 391 void 392 ActivityWindow::_MessageDropped(BMessage* message) 393 { 394 entry_ref ref; 395 if (message->FindRef("refs", &ref) != B_OK) { 396 // TODO: If app, then launch it, and add ActivityView for this one? 397 } 398 } 399 400 401 void 402 ActivityWindow::_SetAlwaysOnTop(bool alwaysOnTop) 403 { 404 SetFeel(alwaysOnTop ? B_FLOATING_ALL_WINDOW_FEEL : B_NORMAL_WINDOW_FEEL); 405 fAlwaysOnTop->SetMarked(alwaysOnTop); 406 if (fSettingsWindow.IsValid() && alwaysOnTop) { 407 // Change the settings window feel to modal (via scripting) 408 BMessage toFront(B_SET_PROPERTY); 409 toFront.AddSpecifier("Feel"); 410 toFront.AddInt32("data", B_MODAL_ALL_WINDOW_FEEL); 411 fSettingsWindow.SendMessage(&toFront); 412 } 413 } 414