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
31static const uint32 kMsgAddView = 'advw';
32static const uint32 kMsgAlwaysOnTop = 'alot';
33static const uint32 kMsgShowSettings = 'shst';
34
35
36ActivityWindow::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
113ActivityWindow::~ActivityWindow()
114{
115}
116
117
118void
119ActivityWindow::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
194bool
195ActivityWindow::QuitRequested()
196{
197	_SaveSettings();
198	be_app->PostMessage(B_QUIT_REQUESTED);
199	return true;
200}
201
202
203int32
204ActivityWindow::ActivityViewCount() const
205{
206#ifdef __HAIKU__
207	return fLayout->View()->CountChildren();
208#else
209	return 1;
210#endif
211}
212
213
214ActivityView*
215ActivityWindow::ActivityViewAt(int32 index) const
216{
217	return dynamic_cast<ActivityView*>(fLayout->View()->ChildAt(index));
218}
219
220
221bool
222ActivityWindow::IsAlwaysOnTop() const
223{
224	return fAlwaysOnTop->IsMarked();
225}
226
227
228void
229ActivityWindow::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
239bigtime_t
240ActivityWindow::RefreshInterval() const
241{
242	ActivityView* view = ActivityViewAt(0);
243	if (view != 0)
244		return view->RefreshInterval();
245
246	return 100000;
247}
248
249
250status_t
251ActivityWindow::_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
263status_t
264ActivityWindow::_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
275status_t
276ActivityWindow::_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
319void
320ActivityWindow::_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
354void
355ActivityWindow::_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
364void
365ActivityWindow::_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