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 top->SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); 72 layout->AddView(top); 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 top->SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); 103 layout->AddChild(top); 104 105 BMessage viewState; 106 int32 count = 0; 107 ActivityView *aview; 108 BRect rect; 109 for (int32 i = 0; settings.FindMessage("activity view", i, &viewState) 110 == B_OK; i++) { 111 aview = new ActivityView("ActivityMonitor", &viewState); 112 if (!rect.IsValid()) 113 rect = aview->Bounds(); 114 else 115 rect.OffsetBySelf(0.0, aview->Bounds().Height()); 116 top->AddChild(aview); 117 count++; 118 } 119 if (count == 0) 120 top->AddChild(new ActivityView("ActivityMonitor", NULL)); 121 122 #endif 123 // add menu 124 125 // "File" menu 126 BMenu* menu = new BMenu(B_TRANSLATE("File")); 127 menu->AddItem(new BMenuItem(B_TRANSLATE("Add graph"), 128 new BMessage(kMsgAddView))); 129 menu->AddSeparatorItem(); 130 131 menu->AddItem(new BMenuItem(B_TRANSLATE("Quit"), 132 new BMessage(B_QUIT_REQUESTED), 'Q')); 133 menu->SetTargetForItems(this); 134 menuBar->AddItem(menu); 135 136 // "Settings" menu 137 menu = new BMenu(B_TRANSLATE("Settings")); 138 menu->AddItem(new BMenuItem(B_TRANSLATE("Settings" B_UTF8_ELLIPSIS), 139 new BMessage(kMsgShowSettings))); 140 141 menu->AddSeparatorItem(); 142 fAlwaysOnTop = new BMenuItem(B_TRANSLATE("Always on top"), new BMessage(kMsgAlwaysOnTop)); 143 _SetAlwaysOnTop(settings.GetBool("always on top", false)); 144 menu->AddItem(fAlwaysOnTop); 145 146 menu->SetTargetForItems(this); 147 menuBar->AddItem(menu); 148 } 149 150 151 ActivityWindow::~ActivityWindow() 152 { 153 } 154 155 156 void 157 ActivityWindow::MessageReceived(BMessage* message) 158 { 159 if (message->WasDropped()) { 160 _MessageDropped(message); 161 return; 162 } 163 164 switch (message->what) { 165 case B_REFS_RECEIVED: 166 case B_SIMPLE_DATA: 167 _MessageDropped(message); 168 break; 169 170 case kMsgAddView: 171 { 172 #ifdef __HAIKU__ 173 BView* firstView = fLayout->View()->ChildAt(0); 174 175 _AddDefaultView(); 176 177 if (firstView != NULL) 178 ResizeBy(0, firstView->Bounds().Height() + fLayout->Spacing()); 179 #endif 180 break; 181 } 182 183 case kMsgRemoveView: 184 { 185 #ifdef __HAIKU__ 186 BView* view; 187 if (message->FindPointer("view", (void**)&view) != B_OK) 188 break; 189 190 view->RemoveSelf(); 191 ResizeBy(0, -view->Bounds().Height() - fLayout->Spacing()); 192 delete view; 193 #endif 194 break; 195 } 196 197 case kMsgShowSettings: 198 { 199 if (fSettingsWindow.IsValid()) { 200 // Just bring the window to front (via scripting) 201 BMessage toFront(B_SET_PROPERTY); 202 toFront.AddSpecifier("Active"); 203 toFront.AddBool("data", true); 204 fSettingsWindow.SendMessage(&toFront); 205 } else { 206 // Open new settings window 207 BWindow* window = new SettingsWindow(this); 208 window->Show(); 209 210 fSettingsWindow = window; 211 } 212 break; 213 } 214 215 case kMsgAlwaysOnTop: 216 { 217 _SetAlwaysOnTop(!fAlwaysOnTop->IsMarked()); 218 break; 219 } 220 221 case kMsgTimeIntervalUpdated: 222 BroadcastToActivityViews(message); 223 break; 224 225 default: 226 BWindow::MessageReceived(message); 227 break; 228 } 229 } 230 231 232 bool 233 ActivityWindow::QuitRequested() 234 { 235 _SaveSettings(); 236 be_app->PostMessage(B_QUIT_REQUESTED); 237 return true; 238 } 239 240 241 int32 242 ActivityWindow::ActivityViewCount() const 243 { 244 #ifdef __HAIKU__ 245 return fLayout->View()->CountChildren(); 246 #else 247 return 1; 248 #endif 249 } 250 251 252 ActivityView* 253 ActivityWindow::ActivityViewAt(int32 index) const 254 { 255 return dynamic_cast<ActivityView*>(fLayout->View()->ChildAt(index)); 256 } 257 258 259 bool 260 ActivityWindow::IsAlwaysOnTop() const 261 { 262 return fAlwaysOnTop->IsMarked(); 263 } 264 265 266 void 267 ActivityWindow::BroadcastToActivityViews(BMessage* message, BView* exceptToView) 268 { 269 BView* view; 270 for (int32 i = 0; (view = ActivityViewAt(i)) != NULL; i++) { 271 if (view != exceptToView) 272 PostMessage(message, view); 273 } 274 } 275 276 277 bigtime_t 278 ActivityWindow::RefreshInterval() const 279 { 280 ActivityView* view = ActivityViewAt(0); 281 if (view != 0) 282 return view->RefreshInterval(); 283 284 return 100000; 285 } 286 287 288 status_t 289 ActivityWindow::_OpenSettings(BFile& file, uint32 mode) 290 { 291 BPath path; 292 if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK) 293 return B_ERROR; 294 295 path.Append("ActivityMonitor settings"); 296 297 return file.SetTo(path.Path(), mode); 298 } 299 300 301 status_t 302 ActivityWindow::_LoadSettings(BMessage& settings) 303 { 304 BFile file; 305 status_t status = _OpenSettings(file, B_READ_ONLY); 306 if (status < B_OK) 307 return status; 308 309 return settings.Unflatten(&file); 310 } 311 312 313 status_t 314 ActivityWindow::_SaveSettings() 315 { 316 BFile file; 317 status_t status = _OpenSettings(file, B_WRITE_ONLY | B_CREATE_FILE 318 | B_ERASE_FILE); 319 if (status < B_OK) 320 return status; 321 322 BMessage settings('actm'); 323 status = settings.AddRect("window frame", Frame()); 324 if (status != B_OK) 325 return status; 326 327 status = settings.SetBool("always on top", fAlwaysOnTop->IsMarked()); 328 if (status != B_OK) 329 return status; 330 331 #ifdef __HAIKU__ 332 BView* top = fLayout->View(); 333 #else 334 BView* top = ChildAt(0); 335 #endif 336 int32 count = top->CountChildren(); 337 for (int32 i = 0; i < count; i++) { 338 ActivityView* view = dynamic_cast<ActivityView*>(top->ChildAt(i)); 339 if (view == NULL) 340 continue; 341 342 BMessage viewState; 343 status = view->SaveState(viewState); 344 if (status == B_OK) 345 status = settings.AddMessage("activity view", &viewState); 346 if (status != B_OK) 347 break; 348 } 349 350 if (status == B_OK) 351 status = settings.Flatten(&file); 352 353 return status; 354 } 355 356 357 void 358 ActivityWindow::_AddDefaultView() 359 { 360 BMessage settings; 361 settings.AddInt64("refresh interval", RefreshInterval()); 362 363 ActivityView* view = new ActivityView("ActivityMonitor", &settings); 364 365 switch (ActivityViewCount()) { 366 case 0: 367 // The first view defaults to memory usage 368 view->AddDataSource(new UsedMemoryDataSource()); 369 view->AddDataSource(new CachedMemoryDataSource()); 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 1: 377 default: 378 // Everything beyond that defaults to a CPU usage view 379 view->AddDataSource(new CPUUsageDataSource()); 380 break; 381 } 382 383 fLayout->AddItem(view->CreateHistoryLayoutItem()); 384 fLayout->AddItem(view->CreateLegendLayoutItem()); 385 } 386 387 388 void 389 ActivityWindow::_MessageDropped(BMessage* message) 390 { 391 entry_ref ref; 392 if (message->FindRef("refs", &ref) != B_OK) { 393 // TODO: If app, then launch it, and add ActivityView for this one? 394 } 395 } 396 397 398 void 399 ActivityWindow::_SetAlwaysOnTop(bool alwaysOnTop) 400 { 401 SetFeel(alwaysOnTop ? B_FLOATING_ALL_WINDOW_FEEL : B_NORMAL_WINDOW_FEEL); 402 fAlwaysOnTop->SetMarked(alwaysOnTop); 403 if (fSettingsWindow.IsValid() && alwaysOnTop) { 404 // Change the settings window feel to modal (via scripting) 405 BMessage toFront(B_SET_PROPERTY); 406 toFront.AddSpecifier("Feel"); 407 toFront.AddInt32("data", B_MODAL_ALL_WINDOW_FEEL); 408 fSettingsWindow.SendMessage(&toFront); 409 } 410 } 411