xref: /haiku/src/apps/activitymonitor/ActivityWindow.cpp (revision 220c5364ab445eb20376f964ade2f7d3a09e163a)
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