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