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 "ActivityView.h"
8
9#include <new>
10#include <stdio.h>
11#include <stdlib.h>
12#include <vector>
13
14#ifdef __HAIKU__
15#	include <AboutWindow.h>
16#	include <AbstractLayoutItem.h>
17#	include <ControlLook.h>
18#endif
19#include <Application.h>
20#include <Autolock.h>
21#include <Bitmap.h>
22#include <Catalog.h>
23#include <Dragger.h>
24#include <MenuItem.h>
25#include <MessageRunner.h>
26#include <PopUpMenu.h>
27#include <Shape.h>
28#include <String.h>
29
30#include "ActivityMonitor.h"
31#include "ActivityWindow.h"
32#include "SettingsWindow.h"
33#include "SystemInfo.h"
34#include "SystemInfoHandler.h"
35
36#undef B_TRANSLATION_CONTEXT
37#define B_TRANSLATION_CONTEXT "ActivityView"
38
39template<typename ObjectType>
40class ListAddDeleter {
41public:
42	ListAddDeleter(BObjectList<ObjectType>& list, ObjectType* object,
43			int32 spot)
44		:
45		fList(list),
46		fObject(object)
47	{
48		if (fObject != NULL && !fList.AddItem(fObject, spot)) {
49			delete fObject;
50			fObject = NULL;
51		}
52	}
53
54	~ListAddDeleter()
55	{
56		if (fObject != NULL) {
57			fList.RemoveItem(fObject);
58			delete fObject;
59		}
60	}
61
62	bool Failed() const
63	{
64		return fObject == NULL;
65	}
66
67	void Detach()
68	{
69		fObject = NULL;
70	}
71
72private:
73	BObjectList<ObjectType>&	fList;
74	ObjectType*					fObject;
75};
76
77
78/*!	This class manages the scale of a history with a dynamic scale.
79	Every history value will be input via Update(), and the minimum/maximum
80	is computed from that.
81*/
82class Scale {
83public:
84								Scale(scale_type type);
85
86			int64				MinimumValue() const { return fMinimumValue; }
87			int64				MaximumValue() const { return fMaximumValue; }
88
89			void				Update(int64 value);
90
91private:
92			scale_type			fType;
93			int64				fMinimumValue;
94			int64				fMaximumValue;
95			bool				fInitialized;
96};
97
98/*!	Stores the interpolated on screen view values. This is done so that the
99	interpolation is fixed, and does not change when being scrolled.
100
101	We could also just do this by making sure we always ask for the same
102	interval only, but this way we also save the interpolation.
103*/
104class ViewHistory {
105public:
106								ViewHistory();
107
108			int64				ValueAt(int32 x);
109
110			int32				Start() const
111									{ return fValues.Size()
112										- fValues.CountItems(); }
113
114			void				Update(DataHistory* history, int32 width,
115									int32 resolution, bigtime_t toTime,
116									bigtime_t step, bigtime_t refresh);
117
118private:
119			CircularBuffer<int64> fValues;
120			int32				fResolution;
121			bigtime_t			fRefresh;
122			bigtime_t			fLastTime;
123};
124
125struct data_item {
126	bigtime_t	time;
127	int64		value;
128};
129
130#ifdef __HAIKU__
131class ActivityView::HistoryLayoutItem : public BAbstractLayoutItem {
132public:
133							HistoryLayoutItem(ActivityView* parent);
134
135	virtual	bool			IsVisible();
136	virtual	void			SetVisible(bool visible);
137
138	virtual	BRect			Frame();
139	virtual	void			SetFrame(BRect frame);
140
141	virtual	BView*			View();
142
143	virtual	BSize			BasePreferredSize();
144
145private:
146	ActivityView*			fParent;
147	BRect					fFrame;
148};
149
150class ActivityView::LegendLayoutItem : public BAbstractLayoutItem {
151public:
152							LegendLayoutItem(ActivityView* parent);
153
154	virtual	bool			IsVisible();
155	virtual	void			SetVisible(bool visible);
156
157	virtual	BRect			Frame();
158	virtual	void			SetFrame(BRect frame);
159
160	virtual	BView*			View();
161
162	virtual	BSize			BaseMinSize();
163	virtual	BSize			BaseMaxSize();
164	virtual	BSize			BasePreferredSize();
165	virtual	BAlignment		BaseAlignment();
166
167private:
168	ActivityView*			fParent;
169	BRect					fFrame;
170};
171#endif
172
173const bigtime_t kInitialRefreshInterval = 250000LL;
174
175const uint32 kMsgToggleDataSource = 'tgds';
176const uint32 kMsgToggleLegend = 'tglg';
177const uint32 kMsgUpdateResolution = 'ures';
178
179extern const char* kAppName;
180extern const char* kSignature;
181
182
183Scale::Scale(scale_type type)
184	:
185	fType(type),
186	fMinimumValue(0),
187	fMaximumValue(0),
188	fInitialized(false)
189{
190}
191
192
193void
194Scale::Update(int64 value)
195{
196	if (!fInitialized || fMinimumValue > value)
197		fMinimumValue = value;
198	if (!fInitialized || fMaximumValue < value)
199		fMaximumValue = value;
200
201	fInitialized = true;
202}
203
204
205//	#pragma mark -
206
207
208ViewHistory::ViewHistory()
209	:
210	fValues(1),
211	fResolution(-1),
212	fRefresh(-1),
213	fLastTime(0)
214{
215}
216
217
218int64
219ViewHistory::ValueAt(int32 x)
220{
221	int64* value = fValues.ItemAt(x - Start());
222	if (value != NULL)
223		return *value;
224
225	return 0;
226}
227
228
229void
230ViewHistory::Update(DataHistory* history, int32 width, int32 resolution,
231	bigtime_t toTime, bigtime_t step, bigtime_t refresh)
232{
233	if (width > 16384) {
234		// ignore this - it seems the view hasn't been layouted yet
235		return;
236	}
237
238	// Check if we need to invalidate the existing values
239	if ((int32)fValues.Size() != width
240		|| fResolution != resolution
241		|| fRefresh != refresh) {
242		fValues.SetSize(width);
243		fResolution = resolution;
244		fRefresh = refresh;
245		fLastTime = 0;
246	}
247
248	// Compute how many new values we need to retrieve
249	if (fLastTime < history->Start())
250		fLastTime = history->Start();
251	if (fLastTime > history->End())
252		return;
253
254	int32 updateWidth = int32((toTime - fLastTime) / step);
255	if (updateWidth < 1)
256		return;
257
258	if (updateWidth > (int32)fValues.Size()) {
259		updateWidth = fValues.Size();
260		fLastTime = toTime - updateWidth * step;
261	}
262
263	for (int32 i = 0; i < updateWidth; i++) {
264		int64 value = history->ValueAt(fLastTime += step);
265
266		if (step > refresh) {
267			uint32 count = 1;
268			for (bigtime_t offset = refresh; offset < step; offset += refresh) {
269				// TODO: handle int64 overflow correctly!
270				value += history->ValueAt(fLastTime + offset);
271				count++;
272			}
273			value /= count;
274		}
275
276		fValues.AddItem(value);
277	}
278}
279
280
281//	#pragma mark -
282
283
284DataHistory::DataHistory(bigtime_t memorize, bigtime_t interval)
285	:
286	fBuffer(10000),
287	fMinimumValue(0),
288	fMaximumValue(0),
289	fRefreshInterval(interval),
290	fLastIndex(-1),
291	fScale(NULL)
292{
293}
294
295
296DataHistory::~DataHistory()
297{
298}
299
300
301void
302DataHistory::AddValue(bigtime_t time, int64 value)
303{
304	if (fBuffer.IsEmpty() || fMaximumValue < value)
305		fMaximumValue = value;
306	if (fBuffer.IsEmpty() || fMinimumValue > value)
307		fMinimumValue = value;
308	if (fScale != NULL)
309		fScale->Update(value);
310
311	data_item item = {time, value};
312	fBuffer.AddItem(item);
313}
314
315
316int64
317DataHistory::ValueAt(bigtime_t time)
318{
319	int32 left = 0;
320	int32 right = fBuffer.CountItems() - 1;
321	data_item* item = NULL;
322
323	while (left <= right) {
324		int32 index = (left + right) / 2;
325		item = fBuffer.ItemAt(index);
326
327		if (item->time > time) {
328			// search in left part
329			right = index - 1;
330		} else {
331			data_item* nextItem = fBuffer.ItemAt(index + 1);
332			if (nextItem == NULL)
333				return item->value;
334			if (nextItem->time > time) {
335				// found item
336				int64 value = item->value;
337				value += int64(double(nextItem->value - value)
338					/ (nextItem->time - item->time) * (time - item->time));
339				return value;
340			}
341
342			// search in right part
343			left = index + 1;
344		}
345	}
346
347	return 0;
348}
349
350
351int64
352DataHistory::MaximumValue() const
353{
354	if (fScale != NULL)
355		return fScale->MaximumValue();
356
357	return fMaximumValue;
358}
359
360
361int64
362DataHistory::MinimumValue() const
363{
364	if (fScale != NULL)
365		return fScale->MinimumValue();
366
367	return fMinimumValue;
368}
369
370
371bigtime_t
372DataHistory::Start() const
373{
374	if (fBuffer.CountItems() == 0)
375		return 0;
376
377	return fBuffer.ItemAt(0)->time;
378}
379
380
381bigtime_t
382DataHistory::End() const
383{
384	if (fBuffer.CountItems() == 0)
385		return 0;
386
387	return fBuffer.ItemAt(fBuffer.CountItems() - 1)->time;
388}
389
390
391void
392DataHistory::SetRefreshInterval(bigtime_t interval)
393{
394	// TODO: adjust buffer size
395}
396
397
398void
399DataHistory::SetScale(Scale* scale)
400{
401	fScale = scale;
402}
403
404
405//	#pragma mark -
406
407
408#ifdef __HAIKU__
409ActivityView::HistoryLayoutItem::HistoryLayoutItem(ActivityView* parent)
410	:
411	fParent(parent),
412	fFrame()
413{
414}
415
416
417bool
418ActivityView::HistoryLayoutItem::IsVisible()
419{
420	return !fParent->IsHidden(fParent);
421}
422
423
424void
425ActivityView::HistoryLayoutItem::SetVisible(bool visible)
426{
427	// not allowed
428}
429
430
431BRect
432ActivityView::HistoryLayoutItem::Frame()
433{
434	return fFrame;
435}
436
437
438void
439ActivityView::HistoryLayoutItem::SetFrame(BRect frame)
440{
441	fFrame = frame;
442	fParent->_UpdateFrame();
443}
444
445
446BView*
447ActivityView::HistoryLayoutItem::View()
448{
449	return fParent;
450}
451
452
453BSize
454ActivityView::HistoryLayoutItem::BasePreferredSize()
455{
456	BSize size(BaseMaxSize());
457	return size;
458}
459
460
461//	#pragma mark -
462
463
464ActivityView::LegendLayoutItem::LegendLayoutItem(ActivityView* parent)
465	:
466	fParent(parent),
467	fFrame()
468{
469}
470
471
472bool
473ActivityView::LegendLayoutItem::IsVisible()
474{
475	return !fParent->IsHidden(fParent);
476}
477
478
479void
480ActivityView::LegendLayoutItem::SetVisible(bool visible)
481{
482	// not allowed
483}
484
485
486BRect
487ActivityView::LegendLayoutItem::Frame()
488{
489	return fFrame;
490}
491
492
493void
494ActivityView::LegendLayoutItem::SetFrame(BRect frame)
495{
496	fFrame = frame;
497	fParent->_UpdateFrame();
498}
499
500
501BView*
502ActivityView::LegendLayoutItem::View()
503{
504	return fParent;
505}
506
507
508BSize
509ActivityView::LegendLayoutItem::BaseMinSize()
510{
511	// TODO: Cache the info. Might be too expensive for this call.
512	BSize size;
513	size.width = 80;
514	size.height = fParent->_LegendHeight();
515
516	return size;
517}
518
519
520BSize
521ActivityView::LegendLayoutItem::BaseMaxSize()
522{
523	BSize size(BaseMinSize());
524	size.width = B_SIZE_UNLIMITED;
525	return size;
526}
527
528
529BSize
530ActivityView::LegendLayoutItem::BasePreferredSize()
531{
532	BSize size(BaseMinSize());
533	return size;
534}
535
536
537BAlignment
538ActivityView::LegendLayoutItem::BaseAlignment()
539{
540	return BAlignment(B_ALIGN_USE_FULL_WIDTH, B_ALIGN_USE_FULL_HEIGHT);
541}
542#endif
543
544
545//	#pragma mark -
546
547
548const rgb_color kWhite = (rgb_color){255, 255, 255, 255};
549const rgb_color kBlack = (rgb_color){0, 0, 0, 255};
550const float kDraggerSize = 7;
551
552
553ActivityView::ActivityView(BRect frame, const char* name,
554		const BMessage* settings, uint32 resizingMode)
555	: BView(frame, name, resizingMode,
556		B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE | B_FRAME_EVENTS),
557	fSourcesLock("data sources")
558{
559	_Init(settings);
560
561	BRect rect(Bounds());
562	rect.top = rect.bottom - kDraggerSize;
563	rect.left = rect.right - kDraggerSize;
564	BDragger* dragger = new BDragger(rect, this,
565		B_FOLLOW_RIGHT | B_FOLLOW_BOTTOM);
566	AddChild(dragger);
567}
568
569
570ActivityView::ActivityView(const char* name, const BMessage* settings)
571#ifdef __HAIKU__
572	: BView(name, B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE | B_FRAME_EVENTS),
573#else
574	: BView(BRect(0, 0, 300, 200), name, B_FOLLOW_NONE,
575		B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE | B_FRAME_EVENTS),
576#endif
577	fSourcesLock("data sources")
578{
579	SetLowColor(ui_color(B_PANEL_BACKGROUND_COLOR));
580
581	_Init(settings);
582
583	BRect rect(Bounds());
584	rect.top = rect.bottom - kDraggerSize;
585	rect.left = rect.right - kDraggerSize;
586	BDragger* dragger = new BDragger(rect, this,
587		B_FOLLOW_RIGHT | B_FOLLOW_BOTTOM);
588	AddChild(dragger);
589}
590
591
592ActivityView::ActivityView(BMessage* archive)
593	: BView(archive)
594{
595	_Init(archive);
596}
597
598
599ActivityView::~ActivityView()
600{
601	delete fOffscreen;
602	delete fSystemInfoHandler;
603
604	// replicant deleted, destroy the about window
605	if (fAboutWindow != NULL && fAboutWindow->Lock())
606		fAboutWindow->Quit();
607}
608
609
610void
611ActivityView::_Init(const BMessage* settings)
612{
613	fHistoryBackgroundColor = (rgb_color){255, 255, 240};
614	fLegendBackgroundColor = LowColor();
615		// the low color is restored by the BView unarchiving
616	fOffscreen = NULL;
617#ifdef __HAIKU__
618	fHistoryLayoutItem = NULL;
619	fLegendLayoutItem = NULL;
620#endif
621	SetViewColor(B_TRANSPARENT_COLOR);
622
623	fLastRefresh = 0;
624	fDrawResolution = 1;
625	fZooming = false;
626
627	fSystemInfoHandler = new SystemInfoHandler;
628
629	if (settings == NULL
630		|| settings->FindInt64("refresh interval", &fRefreshInterval) != B_OK)
631		fRefreshInterval = kInitialRefreshInterval;
632
633	if (settings == NULL
634		|| settings->FindBool("show legend", &fShowLegend) != B_OK)
635		fShowLegend = true;
636
637	if (settings == NULL)
638		return;
639
640	ssize_t colorLength;
641	rgb_color *color;
642	if (settings->FindData("history background color", B_RGB_COLOR_TYPE,
643			(const void **)&color, &colorLength) == B_OK
644		&& colorLength == sizeof(rgb_color))
645		fHistoryBackgroundColor = *color;
646
647	const char* name;
648	for (int32 i = 0; settings->FindString("source", i, &name) == B_OK; i++)
649		AddDataSource(DataSource::FindSource(name), settings);
650
651	fAboutWindow = NULL;
652}
653
654
655status_t
656ActivityView::Archive(BMessage* into, bool deep) const
657{
658	status_t status;
659
660	status = BView::Archive(into, deep);
661	if (status < B_OK)
662		return status;
663
664	status = into->AddString("add_on", kSignature);
665	if (status < B_OK)
666		return status;
667
668	status = SaveState(*into);
669	if (status < B_OK)
670		return status;
671
672	return B_OK;
673}
674
675
676BArchivable*
677ActivityView::Instantiate(BMessage* archive)
678{
679	if (!validate_instantiation(archive, "ActivityView"))
680		return NULL;
681
682	return new ActivityView(archive);
683}
684
685
686status_t
687ActivityView::SaveState(BMessage& state) const
688{
689	status_t status = state.AddBool("show legend", fShowLegend);
690	if (status != B_OK)
691		return status;
692
693	status = state.AddInt64("refresh interval", fRefreshInterval);
694	if (status != B_OK)
695		return status;
696
697	status = state.AddData("history background color", B_RGB_COLOR_TYPE,
698		&fHistoryBackgroundColor, sizeof(rgb_color));
699	if (status != B_OK)
700		return status;
701
702	for (int32 i = 0; i < fSources.CountItems(); i++) {
703		DataSource* source = fSources.ItemAt(i);
704
705		if (!source->PerCPU() || source->CPU() == 0)
706			status = state.AddString("source", source->InternalName());
707		if (status != B_OK)
708			return status;
709
710		BString name = source->Name();
711		name << " color";
712		rgb_color color = source->Color();
713		state.AddData(name.String(), B_RGB_COLOR_TYPE, &color,
714			sizeof(rgb_color));
715	}
716	return B_OK;
717}
718
719
720Scale*
721ActivityView::_ScaleFor(scale_type type)
722{
723	if (type == kNoScale)
724		return NULL;
725
726	std::map<scale_type, ::Scale*>::iterator iterator = fScales.find(type);
727	if (iterator != fScales.end())
728		return iterator->second;
729
730	// add new scale
731	::Scale* scale = new ::Scale(type);
732	fScales[type] = scale;
733
734	return scale;
735}
736
737
738#ifdef __HAIKU__
739BLayoutItem*
740ActivityView::CreateHistoryLayoutItem()
741{
742	if (fHistoryLayoutItem == NULL)
743		fHistoryLayoutItem = new HistoryLayoutItem(this);
744
745	return fHistoryLayoutItem;
746}
747
748
749BLayoutItem*
750ActivityView::CreateLegendLayoutItem()
751{
752	if (fLegendLayoutItem == NULL)
753		fLegendLayoutItem = new LegendLayoutItem(this);
754
755	return fLegendLayoutItem;
756}
757#endif
758
759
760DataSource*
761ActivityView::FindDataSource(const DataSource* search)
762{
763	BAutolock _(fSourcesLock);
764
765	for (int32 i = fSources.CountItems(); i-- > 0;) {
766		DataSource* source = fSources.ItemAt(i);
767		if (!strcmp(source->Name(), search->Name()))
768			return source;
769	}
770
771	return NULL;
772}
773
774
775status_t
776ActivityView::AddDataSource(const DataSource* source, const BMessage* state)
777{
778	if (source == NULL)
779		return B_BAD_VALUE;
780
781	BAutolock _(fSourcesLock);
782
783	// Search for the correct insert spot to maintain the order of the sources
784	int32 insert = DataSource::IndexOf(source);
785	for (int32 i = 0; i < fSources.CountItems() && i < insert; i++) {
786		DataSource* before = fSources.ItemAt(i);
787		if (DataSource::IndexOf(before) > insert) {
788			insert = i;
789			break;
790		}
791	}
792	if (insert > fSources.CountItems())
793		insert = fSources.CountItems();
794
795	// Generate DataHistory and ViewHistory objects for the source
796	// (one might need one history per CPU)
797
798	uint32 count = 1;
799	if (source->PerCPU()) {
800		SystemInfo info;
801		count = info.CPUCount();
802	}
803
804	for (uint32 i = 0; i < count; i++) {
805		DataHistory* values = new(std::nothrow) DataHistory(10 * 60000000LL,
806			RefreshInterval());
807		ListAddDeleter<DataHistory> valuesDeleter(fValues, values, insert);
808
809		ViewHistory* viewValues = new(std::nothrow) ViewHistory;
810		ListAddDeleter<ViewHistory> viewValuesDeleter(fViewValues, viewValues,
811			insert);
812
813		if (valuesDeleter.Failed() || viewValuesDeleter.Failed())
814			return B_NO_MEMORY;
815
816		values->SetScale(_ScaleFor(source->ScaleType()));
817
818		DataSource* copy;
819		if (source->PerCPU())
820			copy = source->CopyForCPU(i);
821		else
822			copy = source->Copy();
823
824		ListAddDeleter<DataSource> sourceDeleter(fSources, copy, insert);
825		if (sourceDeleter.Failed())
826			return B_NO_MEMORY;
827
828		BString colorName = source->Name();
829		colorName << " color";
830		if (state != NULL) {
831			const rgb_color* color = NULL;
832			ssize_t colorLength;
833			if (state->FindData(colorName.String(), B_RGB_COLOR_TYPE, i,
834					(const void**)&color, &colorLength) == B_OK
835				&& colorLength == sizeof(rgb_color))
836				copy->SetColor(*color);
837		}
838
839		valuesDeleter.Detach();
840		viewValuesDeleter.Detach();
841		sourceDeleter.Detach();
842		insert++;
843	}
844
845#ifdef __HAIKU__
846	InvalidateLayout();
847#endif
848	return B_OK;
849}
850
851
852status_t
853ActivityView::RemoveDataSource(const DataSource* remove)
854{
855	bool removed = false;
856
857	BAutolock _(fSourcesLock);
858
859	while (true) {
860		DataSource* source = FindDataSource(remove);
861		if (source == NULL) {
862			if (removed)
863				break;
864			return B_ENTRY_NOT_FOUND;
865		}
866
867		int32 index = fSources.IndexOf(source);
868		if (index < 0)
869			return B_ENTRY_NOT_FOUND;
870
871		fSources.RemoveItemAt(index);
872		delete source;
873		DataHistory* values = fValues.RemoveItemAt(index);
874		delete values;
875		removed = true;
876	}
877
878#ifdef __HAIKU__
879	InvalidateLayout();
880#endif
881	return B_OK;
882}
883
884
885void
886ActivityView::RemoveAllDataSources()
887{
888	BAutolock _(fSourcesLock);
889
890	fSources.MakeEmpty();
891	fValues.MakeEmpty();
892}
893
894
895void
896ActivityView::AttachedToWindow()
897{
898	Looper()->AddHandler(fSystemInfoHandler);
899	fSystemInfoHandler->StartWatching();
900
901	fRefreshSem = create_sem(0, "refresh sem");
902	fRefreshThread = spawn_thread(&_RefreshThread, "source refresh",
903		B_URGENT_DISPLAY_PRIORITY, this);
904	resume_thread(fRefreshThread);
905
906	FrameResized(Bounds().Width(), Bounds().Height());
907}
908
909
910void
911ActivityView::DetachedFromWindow()
912{
913	fSystemInfoHandler->StopWatching();
914	Looper()->RemoveHandler(fSystemInfoHandler);
915
916	delete_sem(fRefreshSem);
917	wait_for_thread(fRefreshThread, NULL);
918}
919
920
921#ifdef __HAIKU__
922BSize
923ActivityView::MinSize()
924{
925	BSize size(32, 32);
926	if (fShowLegend)
927		size.height = _LegendHeight();
928
929	return size;
930}
931#endif
932
933
934void
935ActivityView::FrameResized(float /*width*/, float /*height*/)
936{
937	_UpdateOffscreenBitmap();
938}
939
940
941void
942ActivityView::_UpdateOffscreenBitmap()
943{
944	BRect frame = _HistoryFrame();
945	frame.OffsetTo(B_ORIGIN);
946
947	if (fOffscreen != NULL && frame == fOffscreen->Bounds())
948		return;
949
950	delete fOffscreen;
951
952	// create offscreen bitmap
953
954	fOffscreen = new(std::nothrow) BBitmap(frame, B_BITMAP_ACCEPTS_VIEWS,
955		B_RGB32);
956	if (fOffscreen == NULL || fOffscreen->InitCheck() != B_OK) {
957		delete fOffscreen;
958		fOffscreen = NULL;
959		return;
960	}
961
962	BView* view = new BView(frame, NULL, B_FOLLOW_NONE, B_SUBPIXEL_PRECISE);
963	view->SetViewColor(fHistoryBackgroundColor);
964	view->SetLowColor(view->ViewColor());
965	fOffscreen->AddChild(view);
966}
967
968
969BView*
970ActivityView::_OffscreenView()
971{
972	if (fOffscreen == NULL)
973		return NULL;
974
975	return fOffscreen->ChildAt(0);
976}
977
978
979void
980ActivityView::MouseDown(BPoint where)
981{
982	int32 buttons = B_SECONDARY_MOUSE_BUTTON;
983	if (Looper() != NULL && Looper()->CurrentMessage() != NULL)
984		Looper()->CurrentMessage()->FindInt32("buttons", &buttons);
985
986	if (buttons == B_PRIMARY_MOUSE_BUTTON) {
987		fZoomPoint = where;
988		fOriginalResolution = fDrawResolution;
989		fZooming = true;
990		SetMouseEventMask(B_POINTER_EVENTS);
991		return;
992	}
993
994	BPopUpMenu *menu = new BPopUpMenu(B_EMPTY_STRING, false, false);
995	menu->SetFont(be_plain_font);
996
997	BMenu* additionalMenu = new BMenu(B_TRANSLATE("Additional items"));
998	additionalMenu->SetFont(be_plain_font);
999
1000	SystemInfo info;
1001	BMenuItem* item;
1002
1003	for (int32 i = 0; i < DataSource::CountSources(); i++) {
1004		const DataSource* source = DataSource::SourceAt(i);
1005
1006		if (source->MultiCPUOnly() && info.CPUCount() == 1)
1007			continue;
1008
1009		BMessage* message = new BMessage(kMsgToggleDataSource);
1010		message->AddInt32("index", i);
1011
1012		item = new BMenuItem(source->Name(), message);
1013		if (FindDataSource(source))
1014			item->SetMarked(true);
1015
1016		if (source->Primary())
1017			menu->AddItem(item);
1018		else
1019			additionalMenu->AddItem(item);
1020	}
1021
1022	menu->AddItem(new BMenuItem(additionalMenu));
1023	menu->AddSeparatorItem();
1024	menu->AddItem(new BMenuItem(fShowLegend ?
1025		B_TRANSLATE("Hide legend") : B_TRANSLATE("Show legend"),
1026		new BMessage(kMsgToggleLegend)));
1027
1028	menu->SetTargetForItems(this);
1029	additionalMenu->SetTargetForItems(this);
1030
1031	ActivityWindow* window = dynamic_cast<ActivityWindow*>(Window());
1032	if (window != NULL && window->ActivityViewCount() > 1) {
1033		menu->AddSeparatorItem();
1034		BMessage* message = new BMessage(kMsgRemoveView);
1035		message->AddPointer("view", this);
1036		menu->AddItem(item = new BMenuItem(B_TRANSLATE("Remove graph"),
1037			message));
1038		item->SetTarget(window);
1039	}
1040
1041	ConvertToScreen(&where);
1042	menu->Go(where, true, false, true);
1043}
1044
1045
1046void
1047ActivityView::MouseUp(BPoint where)
1048{
1049	fZooming = false;
1050}
1051
1052
1053void
1054ActivityView::MouseMoved(BPoint where, uint32 transit,
1055	const BMessage* dragMessage)
1056{
1057	if (!fZooming)
1058		return;
1059
1060	int32 shift = int32(where.x - fZoomPoint.x) / 25;
1061	int32 resolution;
1062	if (shift > 0)
1063		resolution = fOriginalResolution << shift;
1064	else
1065		resolution = fOriginalResolution >> -shift;
1066
1067	_UpdateResolution(resolution);
1068}
1069
1070
1071void
1072ActivityView::MessageReceived(BMessage* message)
1073{
1074	// if a color is dropped, use it as background
1075	if (message->WasDropped()) {
1076		rgb_color* color;
1077		ssize_t size;
1078		if (message->FindData("RGBColor", B_RGB_COLOR_TYPE, 0,
1079				(const void**)&color, &size) == B_OK
1080			&& size == sizeof(rgb_color)) {
1081			BPoint dropPoint = message->DropPoint();
1082			ConvertFromScreen(&dropPoint);
1083
1084			if (_HistoryFrame().Contains(dropPoint)) {
1085				fHistoryBackgroundColor = *color;
1086				Invalidate(_HistoryFrame());
1087			} else {
1088				// check each legend color box
1089				BAutolock _(fSourcesLock);
1090
1091				BRect legendFrame = _LegendFrame();
1092				for (int32 i = 0; i < fSources.CountItems(); i++) {
1093					BRect frame = _LegendColorFrameAt(legendFrame, i);
1094					if (frame.Contains(dropPoint)) {
1095						fSources.ItemAt(i)->SetColor(*color);
1096						Invalidate(_HistoryFrame());
1097						Invalidate(frame);
1098						return;
1099					}
1100				}
1101
1102				if (dynamic_cast<ActivityMonitor*>(be_app) == NULL) {
1103					// allow background color change in the replicant only
1104					fLegendBackgroundColor = *color;
1105					SetLowColor(fLegendBackgroundColor);
1106					Invalidate(legendFrame);
1107				}
1108			}
1109			return;
1110		}
1111	}
1112
1113	switch (message->what) {
1114		case B_ABOUT_REQUESTED:
1115			if (fAboutWindow == NULL) {
1116				const char* authors[] = {
1117					"Axel Dörfler",
1118					NULL
1119				};
1120
1121				fAboutWindow = new BAboutWindow(kAppName, kSignature);
1122				fAboutWindow->AddCopyright(2008, "Haiku, Inc.");
1123				fAboutWindow->AddAuthors(authors);
1124				fAboutWindow->Show();
1125			} else if (fAboutWindow->IsHidden())
1126				fAboutWindow->Show();
1127			else
1128				fAboutWindow->Activate();
1129
1130			break;
1131
1132		case kMsgUpdateResolution:
1133		{
1134			int32 resolution;
1135			if (message->FindInt32("resolution", &resolution) != B_OK)
1136				break;
1137
1138			_UpdateResolution(resolution, false);
1139			break;
1140		}
1141
1142		case kMsgTimeIntervalUpdated:
1143			bigtime_t interval;
1144			if (message->FindInt64("interval", &interval) != B_OK)
1145				break;
1146
1147			if (interval < 10000)
1148				interval = 10000;
1149
1150			atomic_set64(&fRefreshInterval, interval);
1151			break;
1152
1153		case kMsgToggleDataSource:
1154		{
1155			int32 index;
1156			if (message->FindInt32("index", &index) != B_OK)
1157				break;
1158
1159			const DataSource* baseSource = DataSource::SourceAt(index);
1160			if (baseSource == NULL)
1161				break;
1162
1163			DataSource* source = FindDataSource(baseSource);
1164			if (source == NULL)
1165				AddDataSource(baseSource);
1166			else
1167				RemoveDataSource(baseSource);
1168
1169			Invalidate();
1170			break;
1171		}
1172
1173		case kMsgToggleLegend:
1174			fShowLegend = !fShowLegend;
1175			Invalidate();
1176			break;
1177
1178		case B_MOUSE_WHEEL_CHANGED:
1179		{
1180			float deltaY = 0.0f;
1181			if (message->FindFloat("be:wheel_delta_y", &deltaY) != B_OK
1182				|| deltaY == 0.0f)
1183				break;
1184
1185			int32 resolution = fDrawResolution;
1186			if (deltaY > 0)
1187				resolution *= 2;
1188			else
1189				resolution /= 2;
1190
1191			_UpdateResolution(resolution);
1192			break;
1193		}
1194
1195		default:
1196			BView::MessageReceived(message);
1197			break;
1198	}
1199}
1200
1201
1202void
1203ActivityView::_UpdateFrame()
1204{
1205#ifdef __HAIKU__
1206	if (fLegendLayoutItem == NULL || fHistoryLayoutItem == NULL)
1207		return;
1208
1209	BRect historyFrame = fHistoryLayoutItem->Frame();
1210	BRect legendFrame = fLegendLayoutItem->Frame();
1211#else
1212	BRect historyFrame = Bounds();
1213	BRect legendFrame = Bounds();
1214	historyFrame.bottom -= 2 * Bounds().Height() / 3;
1215	legendFrame.top += Bounds().Height() / 3;
1216#endif
1217	MoveTo(historyFrame.left, historyFrame.top);
1218	ResizeTo(legendFrame.left + legendFrame.Width() - historyFrame.left,
1219		legendFrame.top + legendFrame.Height() - historyFrame.top);
1220}
1221
1222
1223BRect
1224ActivityView::_HistoryFrame() const
1225{
1226	BRect frame = Bounds();
1227
1228	if (fShowLegend) {
1229		BRect legendFrame = _LegendFrame();
1230		frame.bottom = legendFrame.top - 1;
1231	}
1232
1233	frame.InsetBy(2, 2);
1234
1235	return frame;
1236}
1237
1238
1239float
1240ActivityView::_LegendHeight() const
1241{
1242	font_height fontHeight;
1243	GetFontHeight(&fontHeight);
1244
1245	BAutolock _(fSourcesLock);
1246
1247	int32 rows = (fSources.CountItems() + 1) / 2;
1248
1249	int32 boldMargin = Parent()
1250		&& (Parent()->Flags() & B_DRAW_ON_CHILDREN) != 0 ? 2 : 0;
1251
1252	return rows * (4 + ceilf(fontHeight.ascent)
1253		+ ceilf(fontHeight.descent) + ceilf(fontHeight.leading)) + boldMargin;
1254}
1255
1256
1257BRect
1258ActivityView::_LegendFrame() const
1259{
1260	float height;
1261#ifdef __HAIKU__
1262	if (fLegendLayoutItem != NULL)
1263		height = fLegendLayoutItem->Frame().Height();
1264	else
1265#endif
1266		height = _LegendHeight();
1267
1268	BRect frame = Bounds();
1269	frame.bottom -= kDraggerSize;
1270	frame.top = frame.bottom - height;
1271
1272	return frame;
1273}
1274
1275
1276BRect
1277ActivityView::_LegendFrameAt(BRect frame, int32 index) const
1278{
1279	int32 column = index & 1;
1280	int32 row = index / 2;
1281	if (column == 0)
1282		frame.right = frame.left + floorf(frame.Width() / 2) - 5;
1283	else
1284		frame.left = frame.right - floorf(frame.Width() / 2) + 5;
1285
1286	BAutolock _(fSourcesLock);
1287
1288	int32 rows = (fSources.CountItems() + 1) / 2;
1289	float height = floorf((frame.Height() - 5) / rows);
1290
1291	frame.top = frame.top + 5 + row * height;
1292	frame.bottom = frame.top + height - 1;
1293
1294	return frame;
1295}
1296
1297
1298BRect
1299ActivityView::_LegendColorFrameAt(BRect frame, int32 index) const
1300{
1301	frame = _LegendFrameAt(frame, index);
1302	frame.InsetBy(1, 1);
1303	frame.right = frame.left + frame.Height();
1304
1305	return frame;
1306}
1307
1308
1309float
1310ActivityView::_PositionForValue(DataSource* source, DataHistory* values,
1311	int64 value)
1312{
1313	int64 min = source->Minimum();
1314	int64 max = source->Maximum();
1315	if (source->AdaptiveScale()) {
1316		int64 adaptiveMax = int64(values->MaximumValue() * 1.2);
1317		if (adaptiveMax < max)
1318			max = adaptiveMax;
1319	}
1320
1321	if (value > max)
1322		value = max;
1323	if (value < min)
1324		value = min;
1325
1326	float height = _HistoryFrame().Height();
1327	return height - (value - min) * height / (max - min);
1328}
1329
1330
1331void
1332ActivityView::_DrawHistory(bool drawBackground)
1333{
1334	_UpdateOffscreenBitmap();
1335
1336	BView* view = this;
1337	if (fOffscreen != NULL) {
1338		fOffscreen->Lock();
1339		view = _OffscreenView();
1340	}
1341
1342	BRect frame = _HistoryFrame();
1343	BRect outerFrame = frame.InsetByCopy(-2, -2);
1344
1345	// draw the outer frame
1346	uint32 flags = 0;
1347	if (!drawBackground)
1348		flags |= BControlLook::B_BLEND_FRAME;
1349	be_control_look->DrawTextControlBorder(this, outerFrame,
1350		outerFrame, fLegendBackgroundColor, flags);
1351
1352	// convert to offscreen view if necessary
1353	if (view != this)
1354		frame.OffsetTo(B_ORIGIN);
1355
1356	view->SetLowColor(fHistoryBackgroundColor);
1357	view->FillRect(frame, B_SOLID_LOW);
1358
1359	uint32 step = 2;
1360	uint32 resolution = fDrawResolution;
1361	if (fDrawResolution > 1) {
1362		step = 1;
1363		resolution--;
1364	}
1365
1366	uint32 width = frame.IntegerWidth() - 10;
1367	uint32 steps = width / step;
1368	bigtime_t timeStep = RefreshInterval() * resolution;
1369	bigtime_t now = system_time();
1370
1371	// Draw scale
1372	// TODO: add second markers?
1373
1374	view->SetPenSize(1);
1375
1376	rgb_color scaleColor = view->LowColor();
1377	uint32 average = (scaleColor.red + scaleColor.green + scaleColor.blue) / 3;
1378	if (average < 96)
1379		scaleColor = tint_color(scaleColor, B_LIGHTEN_2_TINT);
1380	else
1381		scaleColor = tint_color(scaleColor, B_DARKEN_2_TINT);
1382
1383	view->SetHighColor(scaleColor);
1384	view->StrokeLine(BPoint(frame.left, frame.top + frame.Height() / 2),
1385		BPoint(frame.right, frame.top + frame.Height() / 2));
1386
1387	// Draw values
1388
1389	view->SetPenSize(1.5);
1390	BAutolock _(fSourcesLock);
1391
1392	for (uint32 i = fSources.CountItems(); i-- > 0;) {
1393		ViewHistory* viewValues = fViewValues.ItemAt(i);
1394		DataSource* source = fSources.ItemAt(i);
1395		DataHistory* values = fValues.ItemAt(i);
1396
1397		viewValues->Update(values, steps, fDrawResolution, now, timeStep,
1398			RefreshInterval());
1399
1400		uint32 x = viewValues->Start() * step;
1401		BShape shape;
1402		bool first = true;
1403
1404		for (uint32 i = viewValues->Start(); i < steps; x += step, i++) {
1405			float y = _PositionForValue(source, values,
1406				viewValues->ValueAt(i));
1407
1408			if (first) {
1409				shape.MoveTo(BPoint(x, y));
1410				first = false;
1411			} else
1412				shape.LineTo(BPoint(x, y));
1413		}
1414
1415		view->SetHighColor(source->Color());
1416		view->SetLineMode(B_BUTT_CAP, B_ROUND_JOIN);
1417		view->MovePenTo(B_ORIGIN);
1418		view->StrokeShape(&shape);
1419	}
1420
1421	// TODO: add marks when an app started or quit
1422	view->Sync();
1423	if (fOffscreen != NULL) {
1424		fOffscreen->Unlock();
1425		DrawBitmap(fOffscreen, outerFrame.LeftTop());
1426	}
1427}
1428
1429
1430void
1431ActivityView::_UpdateResolution(int32 resolution, bool broadcast)
1432{
1433	if (resolution < 1)
1434		resolution = 1;
1435	if (resolution > 128)
1436		resolution = 128;
1437
1438	if (resolution == fDrawResolution)
1439		return;
1440
1441	ActivityWindow* window = dynamic_cast<ActivityWindow*>(Window());
1442	if (broadcast && window != NULL) {
1443		BMessage update(kMsgUpdateResolution);
1444		update.AddInt32("resolution", resolution);
1445		window->BroadcastToActivityViews(&update, this);
1446	}
1447
1448	fDrawResolution = resolution;
1449	Invalidate();
1450}
1451
1452
1453void
1454ActivityView::Draw(BRect updateRect)
1455{
1456	bool drawBackground = true;
1457	if (Parent() && (Parent()->Flags() & B_DRAW_ON_CHILDREN) != 0)
1458		drawBackground = false;
1459
1460	_DrawHistory(drawBackground);
1461
1462	if (!fShowLegend)
1463		return;
1464
1465	// draw legend
1466	BRect legendFrame = _LegendFrame();
1467	SetLowColor(fLegendBackgroundColor);
1468	if (drawBackground) {
1469		BRect backgroundFrame(legendFrame);
1470		backgroundFrame.bottom += kDraggerSize;
1471		FillRect(backgroundFrame, B_SOLID_LOW);
1472	}
1473
1474	BAutolock _(fSourcesLock);
1475
1476	font_height fontHeight;
1477	GetFontHeight(&fontHeight);
1478
1479	for (int32 i = 0; i < fSources.CountItems(); i++) {
1480		DataSource* source = fSources.ItemAt(i);
1481		DataHistory* values = fValues.ItemAt(i);
1482		BRect frame = _LegendFrameAt(legendFrame, i);
1483
1484		// draw color box
1485		BRect colorBox = _LegendColorFrameAt(legendFrame, i);
1486		BRect rect = colorBox;
1487		uint32 flags = BControlLook::B_BLEND_FRAME;
1488		be_control_look->DrawTextControlBorder(this, rect,
1489			rect, fLegendBackgroundColor, flags);
1490		SetHighColor(source->Color());
1491		FillRect(rect);
1492
1493		// show current value and label
1494		float y = frame.top + ceilf(fontHeight.ascent);
1495		int64 value = values->ValueAt(values->End());
1496		BString text;
1497		source->Print(text, value);
1498		float width = StringWidth(text.String());
1499
1500		BString label = source->Label();
1501		float possibleLabelWidth = frame.right - colorBox.right - 12 - width;
1502		// TODO: TruncateString() is broken... remove + 5 when fixed!
1503		if (ceilf(StringWidth(label.String()) + 5) > possibleLabelWidth)
1504			label = source->ShortLabel();
1505		TruncateString(&label, B_TRUNCATE_MIDDLE, possibleLabelWidth);
1506
1507		if (drawBackground)
1508			SetHighColor(ui_color(B_CONTROL_TEXT_COLOR));
1509
1510		if (be_control_look == NULL) {
1511			DrawString(label.String(), BPoint(6 + colorBox.right, y));
1512			DrawString(text.String(), BPoint(frame.right - width, y));
1513		} else {
1514			be_control_look->DrawLabel(this, label.String(),
1515				Parent()->ViewColor(), 0, BPoint(6 + colorBox.right, y));
1516			be_control_look->DrawLabel(this, text.String(),
1517				Parent()->ViewColor(), 0, BPoint(frame.right - width, y));
1518		}
1519	}
1520}
1521
1522
1523void
1524ActivityView::_Refresh()
1525{
1526	bigtime_t lastTimeout = system_time() - RefreshInterval();
1527	BMessenger target(this);
1528
1529	while (true) {
1530		status_t status = acquire_sem_etc(fRefreshSem, 1, B_ABSOLUTE_TIMEOUT,
1531			lastTimeout + RefreshInterval());
1532		if (status == B_OK || status == B_BAD_SEM_ID)
1533			break;
1534		if (status == B_INTERRUPTED)
1535			continue;
1536
1537		SystemInfo info(fSystemInfoHandler);
1538		lastTimeout += RefreshInterval();
1539
1540		fSourcesLock.Lock();
1541
1542		for (uint32 i = fSources.CountItems(); i-- > 0;) {
1543			DataSource* source = fSources.ItemAt(i);
1544			DataHistory* values = fValues.ItemAt(i);
1545
1546			int64 value = source->NextValue(info);
1547			values->AddValue(info.Time(), value);
1548		}
1549
1550		fSourcesLock.Unlock();
1551
1552		target.SendMessage(B_INVALIDATE);
1553	}
1554}
1555
1556
1557/*static*/ status_t
1558ActivityView::_RefreshThread(void* self)
1559{
1560	((ActivityView*)self)->_Refresh();
1561	return B_OK;
1562}
1563