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