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