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