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 "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	SetLowUIColor(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
605
606void
607ActivityView::_Init(const BMessage* settings)
608{
609	fHistoryBackgroundColor = (rgb_color){255, 255, 240};
610	fLegendBackgroundColor = LowColor();
611		// the low color is restored by the BView unarchiving
612	fOffscreen = NULL;
613#ifdef __HAIKU__
614	fHistoryLayoutItem = NULL;
615	fLegendLayoutItem = NULL;
616#endif
617	SetViewColor(B_TRANSPARENT_COLOR);
618	SetFlags(Flags() | B_TRANSPARENT_BACKGROUND);
619
620	fLastRefresh = 0;
621	fDrawResolution = 1;
622	fZooming = false;
623
624	fSystemInfoHandler = new SystemInfoHandler;
625
626	if (settings == NULL
627		|| settings->FindInt64("refresh interval", &fRefreshInterval) != B_OK)
628		fRefreshInterval = kInitialRefreshInterval;
629
630	if (settings == NULL
631		|| settings->FindBool("show legend", &fShowLegend) != B_OK)
632		fShowLegend = true;
633
634	if (settings == NULL)
635		return;
636
637	ssize_t colorLength;
638	rgb_color *color;
639	if (settings->FindData("history background color", B_RGB_COLOR_TYPE,
640			(const void **)&color, &colorLength) == B_OK
641		&& colorLength == sizeof(rgb_color))
642		fHistoryBackgroundColor = *color;
643
644	const char* name;
645	for (int32 i = 0; settings->FindString("source", i, &name) == B_OK; i++)
646		AddDataSource(DataSource::FindSource(name), settings);
647}
648
649
650status_t
651ActivityView::Archive(BMessage* into, bool deep) const
652{
653	status_t status;
654
655	status = BView::Archive(into, deep);
656	if (status < B_OK)
657		return status;
658
659	status = into->AddString("add_on", kSignature);
660	if (status < B_OK)
661		return status;
662
663	status = SaveState(*into);
664	if (status < B_OK)
665		return status;
666
667	return B_OK;
668}
669
670
671BArchivable*
672ActivityView::Instantiate(BMessage* archive)
673{
674	if (!validate_instantiation(archive, "ActivityView"))
675		return NULL;
676
677	return new ActivityView(archive);
678}
679
680
681status_t
682ActivityView::SaveState(BMessage& state) const
683{
684	status_t status = state.AddBool("show legend", fShowLegend);
685	if (status != B_OK)
686		return status;
687
688	status = state.AddInt64("refresh interval", fRefreshInterval);
689	if (status != B_OK)
690		return status;
691
692	status = state.AddData("history background color", B_RGB_COLOR_TYPE,
693		&fHistoryBackgroundColor, sizeof(rgb_color));
694	if (status != B_OK)
695		return status;
696
697	for (int32 i = 0; i < fSources.CountItems(); i++) {
698		DataSource* source = fSources.ItemAt(i);
699
700		if (!source->PerCPU() || source->CPU() == 0)
701			status = state.AddString("source", source->InternalName());
702		if (status != B_OK)
703			return status;
704
705		BString name = source->Name();
706		name << " color";
707		rgb_color color = source->Color();
708		state.AddData(name.String(), B_RGB_COLOR_TYPE, &color,
709			sizeof(rgb_color));
710	}
711	return B_OK;
712}
713
714
715Scale*
716ActivityView::_ScaleFor(scale_type type)
717{
718	if (type == kNoScale)
719		return NULL;
720
721	std::map<scale_type, ::Scale*>::iterator iterator = fScales.find(type);
722	if (iterator != fScales.end())
723		return iterator->second;
724
725	// add new scale
726	::Scale* scale = new ::Scale(type);
727	fScales[type] = scale;
728
729	return scale;
730}
731
732
733#ifdef __HAIKU__
734BLayoutItem*
735ActivityView::CreateHistoryLayoutItem()
736{
737	if (fHistoryLayoutItem == NULL)
738		fHistoryLayoutItem = new HistoryLayoutItem(this);
739
740	return fHistoryLayoutItem;
741}
742
743
744BLayoutItem*
745ActivityView::CreateLegendLayoutItem()
746{
747	if (fLegendLayoutItem == NULL)
748		fLegendLayoutItem = new LegendLayoutItem(this);
749
750	return fLegendLayoutItem;
751}
752#endif
753
754
755DataSource*
756ActivityView::FindDataSource(const DataSource* search)
757{
758	BAutolock _(fSourcesLock);
759
760	for (int32 i = fSources.CountItems(); i-- > 0;) {
761		DataSource* source = fSources.ItemAt(i);
762		if (!strcmp(source->Name(), search->Name()))
763			return source;
764	}
765
766	return NULL;
767}
768
769
770status_t
771ActivityView::AddDataSource(const DataSource* source, const BMessage* state)
772{
773	if (source == NULL)
774		return B_BAD_VALUE;
775
776	BAutolock _(fSourcesLock);
777
778	// Search for the correct insert spot to maintain the order of the sources
779	int32 insert = DataSource::IndexOf(source);
780	for (int32 i = 0; i < fSources.CountItems() && i < insert; i++) {
781		DataSource* before = fSources.ItemAt(i);
782		if (DataSource::IndexOf(before) > insert) {
783			insert = i;
784			break;
785		}
786	}
787	if (insert > fSources.CountItems())
788		insert = fSources.CountItems();
789
790	// Generate DataHistory and ViewHistory objects for the source
791	// (one might need one history per CPU)
792
793	uint32 count = 1;
794	if (source->PerCPU()) {
795		SystemInfo info;
796		count = info.CPUCount();
797	}
798
799	for (uint32 i = 0; i < count; i++) {
800		DataHistory* values = new(std::nothrow) DataHistory(10 * 60000000LL,
801			RefreshInterval());
802		ListAddDeleter<DataHistory> valuesDeleter(fValues, values, insert);
803
804		ViewHistory* viewValues = new(std::nothrow) ViewHistory;
805		ListAddDeleter<ViewHistory> viewValuesDeleter(fViewValues, viewValues,
806			insert);
807
808		if (valuesDeleter.Failed() || viewValuesDeleter.Failed())
809			return B_NO_MEMORY;
810
811		values->SetScale(_ScaleFor(source->ScaleType()));
812
813		DataSource* copy;
814		if (source->PerCPU())
815			copy = source->CopyForCPU(i);
816		else
817			copy = source->Copy();
818
819		ListAddDeleter<DataSource> sourceDeleter(fSources, copy, insert);
820		if (sourceDeleter.Failed())
821			return B_NO_MEMORY;
822
823		BString colorName = source->Name();
824		colorName << " color";
825		if (state != NULL) {
826			const rgb_color* color = NULL;
827			ssize_t colorLength;
828			if (state->FindData(colorName.String(), B_RGB_COLOR_TYPE, i,
829					(const void**)&color, &colorLength) == B_OK
830				&& colorLength == sizeof(rgb_color))
831				copy->SetColor(*color);
832		}
833
834		valuesDeleter.Detach();
835		viewValuesDeleter.Detach();
836		sourceDeleter.Detach();
837		insert++;
838	}
839
840#ifdef __HAIKU__
841	InvalidateLayout();
842#endif
843	return B_OK;
844}
845
846
847status_t
848ActivityView::RemoveDataSource(const DataSource* remove)
849{
850	bool removed = false;
851
852	BAutolock _(fSourcesLock);
853
854	while (true) {
855		DataSource* source = FindDataSource(remove);
856		if (source == NULL) {
857			if (removed)
858				break;
859			return B_ENTRY_NOT_FOUND;
860		}
861
862		int32 index = fSources.IndexOf(source);
863		if (index < 0)
864			return B_ENTRY_NOT_FOUND;
865
866		fSources.RemoveItemAt(index);
867		delete source;
868		DataHistory* values = fValues.RemoveItemAt(index);
869		delete values;
870		removed = true;
871	}
872
873#ifdef __HAIKU__
874	InvalidateLayout();
875#endif
876	return B_OK;
877}
878
879
880void
881ActivityView::RemoveAllDataSources()
882{
883	BAutolock _(fSourcesLock);
884
885	fSources.MakeEmpty();
886	fValues.MakeEmpty();
887}
888
889
890void
891ActivityView::AttachedToWindow()
892{
893	Looper()->AddHandler(fSystemInfoHandler);
894	fSystemInfoHandler->StartWatching();
895
896	fRefreshSem = create_sem(0, "refresh sem");
897	fRefreshThread = spawn_thread(&_RefreshThread, "source refresh",
898		B_URGENT_DISPLAY_PRIORITY, this);
899	resume_thread(fRefreshThread);
900
901	FrameResized(Bounds().Width(), Bounds().Height());
902}
903
904
905void
906ActivityView::DetachedFromWindow()
907{
908	fSystemInfoHandler->StopWatching();
909	Looper()->RemoveHandler(fSystemInfoHandler);
910
911	delete_sem(fRefreshSem);
912	wait_for_thread(fRefreshThread, NULL);
913}
914
915
916#ifdef __HAIKU__
917BSize
918ActivityView::MinSize()
919{
920	BSize size(32, 32);
921	if (fShowLegend)
922		size.height = _LegendHeight();
923
924	return size;
925}
926#endif
927
928
929void
930ActivityView::FrameResized(float /*width*/, float /*height*/)
931{
932	_UpdateOffscreenBitmap();
933}
934
935
936void
937ActivityView::_UpdateOffscreenBitmap()
938{
939	BRect frame = _HistoryFrame();
940	frame.OffsetTo(B_ORIGIN);
941
942	if (fOffscreen != NULL && frame == fOffscreen->Bounds())
943		return;
944
945	delete fOffscreen;
946
947	// create offscreen bitmap
948
949	fOffscreen = new(std::nothrow) BBitmap(frame, B_BITMAP_ACCEPTS_VIEWS,
950		B_RGB32);
951	if (fOffscreen == NULL || fOffscreen->InitCheck() != B_OK) {
952		delete fOffscreen;
953		fOffscreen = NULL;
954		return;
955	}
956
957	BView* view = new BView(frame, NULL, B_FOLLOW_NONE, B_SUBPIXEL_PRECISE);
958	view->SetViewColor(fHistoryBackgroundColor);
959	view->SetLowColor(view->ViewColor());
960	fOffscreen->AddChild(view);
961}
962
963
964BView*
965ActivityView::_OffscreenView()
966{
967	if (fOffscreen == NULL)
968		return NULL;
969
970	return fOffscreen->ChildAt(0);
971}
972
973
974void
975ActivityView::MouseDown(BPoint where)
976{
977	int32 buttons = B_SECONDARY_MOUSE_BUTTON;
978	if (Looper() != NULL && Looper()->CurrentMessage() != NULL)
979		Looper()->CurrentMessage()->FindInt32("buttons", &buttons);
980
981	if (buttons == B_PRIMARY_MOUSE_BUTTON) {
982		fZoomPoint = where;
983		fOriginalResolution = fDrawResolution;
984		fZooming = true;
985		SetMouseEventMask(B_POINTER_EVENTS);
986		return;
987	}
988
989	BPopUpMenu *menu = new BPopUpMenu(B_EMPTY_STRING, false, false);
990	menu->SetFont(be_plain_font);
991
992	BMenu* additionalMenu = new BMenu(B_TRANSLATE("Additional items"));
993	additionalMenu->SetFont(be_plain_font);
994
995	SystemInfo info;
996	BMenuItem* item;
997
998	for (int32 i = 0; i < DataSource::CountSources(); i++) {
999		const DataSource* source = DataSource::SourceAt(i);
1000
1001		if (source->MultiCPUOnly() && info.CPUCount() == 1)
1002			continue;
1003
1004		BMessage* message = new BMessage(kMsgToggleDataSource);
1005		message->AddInt32("index", i);
1006
1007		item = new BMenuItem(source->Name(), message);
1008		if (FindDataSource(source))
1009			item->SetMarked(true);
1010
1011		if (source->Primary())
1012			menu->AddItem(item);
1013		else
1014			additionalMenu->AddItem(item);
1015	}
1016
1017	menu->AddItem(new BMenuItem(additionalMenu));
1018	menu->AddSeparatorItem();
1019	menu->AddItem(item = new BMenuItem(B_TRANSLATE("Show legend"),
1020		new BMessage(kMsgToggleLegend)));
1021	item->SetMarked(fShowLegend);
1022
1023	menu->SetTargetForItems(this);
1024	additionalMenu->SetTargetForItems(this);
1025
1026	ActivityWindow* window = dynamic_cast<ActivityWindow*>(Window());
1027	if (window != NULL && window->ActivityViewCount() > 1) {
1028		menu->AddSeparatorItem();
1029		BMessage* message = new BMessage(kMsgRemoveView);
1030		message->AddPointer("view", this);
1031		menu->AddItem(item = new BMenuItem(B_TRANSLATE("Remove graph"),
1032			message));
1033		item->SetTarget(window);
1034	}
1035
1036	ConvertToScreen(&where);
1037	menu->Go(where, true, false, true);
1038}
1039
1040
1041void
1042ActivityView::MouseUp(BPoint where)
1043{
1044	fZooming = false;
1045}
1046
1047
1048void
1049ActivityView::MouseMoved(BPoint where, uint32 transit,
1050	const BMessage* dragMessage)
1051{
1052	if (!fZooming)
1053		return;
1054
1055	int32 shift = int32(where.x - fZoomPoint.x) / 25;
1056	int32 resolution;
1057	if (shift > 0)
1058		resolution = fOriginalResolution << shift;
1059	else
1060		resolution = fOriginalResolution >> -shift;
1061
1062	_UpdateResolution(resolution);
1063}
1064
1065
1066void
1067ActivityView::MessageReceived(BMessage* message)
1068{
1069	// if a color is dropped, use it as background
1070	if (message->WasDropped()) {
1071		rgb_color* color;
1072		ssize_t size;
1073		if (message->FindData("RGBColor", B_RGB_COLOR_TYPE, 0,
1074				(const void**)&color, &size) == B_OK
1075			&& size == sizeof(rgb_color)) {
1076			BPoint dropPoint = message->DropPoint();
1077			ConvertFromScreen(&dropPoint);
1078
1079			if (_HistoryFrame().Contains(dropPoint)) {
1080				fHistoryBackgroundColor = *color;
1081				Invalidate(_HistoryFrame());
1082			} else {
1083				// check each legend color box
1084				BAutolock _(fSourcesLock);
1085
1086				BRect legendFrame = _LegendFrame();
1087				for (int32 i = 0; i < fSources.CountItems(); i++) {
1088					BRect frame = _LegendColorFrameAt(legendFrame, i);
1089					if (frame.Contains(dropPoint)) {
1090						fSources.ItemAt(i)->SetColor(*color);
1091						Invalidate(_HistoryFrame());
1092						Invalidate(frame);
1093						return;
1094					}
1095				}
1096
1097				if (dynamic_cast<ActivityMonitor*>(be_app) == NULL) {
1098					// allow background color change in the replicant only
1099					fLegendBackgroundColor = *color;
1100					SetLowColor(fLegendBackgroundColor);
1101					Invalidate(legendFrame);
1102				}
1103			}
1104			return;
1105		}
1106	}
1107
1108	switch (message->what) {
1109		case B_ABOUT_REQUESTED:
1110		{
1111			BAboutWindow* window = new BAboutWindow(kAppName, kSignature);
1112
1113			const char* authors[] = {
1114				"Axel D��rfler",
1115				NULL
1116			};
1117
1118			window->AddCopyright(2008, "Haiku, Inc.");
1119			window->AddAuthors(authors);
1120
1121			window->Show();
1122
1123			break;
1124		}
1125		case kMsgUpdateResolution:
1126		{
1127			int32 resolution;
1128			if (message->FindInt32("resolution", &resolution) != B_OK)
1129				break;
1130
1131			_UpdateResolution(resolution, false);
1132			break;
1133		}
1134
1135		case kMsgTimeIntervalUpdated:
1136			bigtime_t interval;
1137			if (message->FindInt64("interval", &interval) != B_OK)
1138				break;
1139
1140			if (interval < 10000)
1141				interval = 10000;
1142
1143			atomic_set64(&fRefreshInterval, interval);
1144			break;
1145
1146		case kMsgToggleDataSource:
1147		{
1148			int32 index;
1149			if (message->FindInt32("index", &index) != B_OK)
1150				break;
1151
1152			const DataSource* baseSource = DataSource::SourceAt(index);
1153			if (baseSource == NULL)
1154				break;
1155
1156			DataSource* source = FindDataSource(baseSource);
1157			if (source == NULL)
1158				AddDataSource(baseSource);
1159			else
1160				RemoveDataSource(baseSource);
1161
1162			Invalidate();
1163			break;
1164		}
1165
1166		case kMsgToggleLegend:
1167			fShowLegend = !fShowLegend;
1168			Invalidate();
1169			break;
1170
1171		case B_MOUSE_WHEEL_CHANGED:
1172		{
1173			float deltaY = 0.0f;
1174			if (message->FindFloat("be:wheel_delta_y", &deltaY) != B_OK
1175				|| deltaY == 0.0f)
1176				break;
1177
1178			int32 resolution = fDrawResolution;
1179			if (deltaY > 0)
1180				resolution *= 2;
1181			else
1182				resolution /= 2;
1183
1184			_UpdateResolution(resolution);
1185			break;
1186		}
1187
1188		default:
1189			BView::MessageReceived(message);
1190			break;
1191	}
1192}
1193
1194
1195void
1196ActivityView::_UpdateFrame()
1197{
1198#ifdef __HAIKU__
1199	if (fLegendLayoutItem == NULL || fHistoryLayoutItem == NULL)
1200		return;
1201
1202	BRect historyFrame = fHistoryLayoutItem->Frame();
1203	BRect legendFrame = fLegendLayoutItem->Frame();
1204#else
1205	BRect historyFrame = Bounds();
1206	BRect legendFrame = Bounds();
1207	historyFrame.bottom -= 2 * Bounds().Height() / 3;
1208	legendFrame.top += Bounds().Height() / 3;
1209#endif
1210	MoveTo(historyFrame.left, historyFrame.top);
1211	ResizeTo(legendFrame.left + legendFrame.Width() - historyFrame.left,
1212		legendFrame.top + legendFrame.Height() - historyFrame.top);
1213}
1214
1215
1216BRect
1217ActivityView::_HistoryFrame() const
1218{
1219	BRect frame = Bounds();
1220
1221	if (fShowLegend) {
1222		BRect legendFrame = _LegendFrame();
1223		frame.bottom = legendFrame.top - 1;
1224	}
1225
1226	frame.InsetBy(2, 2);
1227
1228	return frame;
1229}
1230
1231
1232float
1233ActivityView::_LegendHeight() const
1234{
1235	font_height fontHeight;
1236	GetFontHeight(&fontHeight);
1237
1238	BAutolock _(fSourcesLock);
1239
1240	int32 rows = (fSources.CountItems() + 1) / 2;
1241
1242	int32 boldMargin = Parent()
1243		&& (Parent()->Flags() & B_DRAW_ON_CHILDREN) != 0 ? 2 : 0;
1244
1245	return rows * (4 + ceilf(fontHeight.ascent)
1246		+ ceilf(fontHeight.descent) + ceilf(fontHeight.leading)) + boldMargin;
1247}
1248
1249
1250BRect
1251ActivityView::_LegendFrame() const
1252{
1253	float height;
1254#ifdef __HAIKU__
1255	if (fLegendLayoutItem != NULL)
1256		height = fLegendLayoutItem->Frame().Height();
1257	else
1258#endif
1259		height = _LegendHeight();
1260
1261	BRect frame = Bounds();
1262	frame.bottom -= kDraggerSize;
1263	frame.top = frame.bottom - height;
1264
1265	return frame;
1266}
1267
1268
1269BRect
1270ActivityView::_LegendFrameAt(BRect frame, int32 index) const
1271{
1272	int32 column = index & 1;
1273	int32 row = index / 2;
1274	if (column == 0) {
1275		// Use the full width if there is only one item
1276		if (fSources.CountItems() != 1)
1277			frame.right = frame.left + floorf(frame.Width() / 2) - 5;
1278	} else
1279		frame.left = frame.right - floorf(frame.Width() / 2) + 5;
1280
1281	BAutolock _(fSourcesLock);
1282
1283	int32 rows = (fSources.CountItems() + 1) / 2;
1284	float height = floorf((frame.Height() - 5) / rows);
1285
1286	frame.top = frame.top + 5 + row * height;
1287	frame.bottom = frame.top + height - 1;
1288
1289	return frame;
1290}
1291
1292
1293BRect
1294ActivityView::_LegendColorFrameAt(BRect frame, int32 index) const
1295{
1296	frame = _LegendFrameAt(frame, index);
1297	frame.InsetBy(1, 1);
1298	frame.right = frame.left + frame.Height();
1299
1300	return frame;
1301}
1302
1303
1304float
1305ActivityView::_PositionForValue(DataSource* source, DataHistory* values,
1306	int64 value)
1307{
1308	int64 min = source->Minimum();
1309	int64 max = source->Maximum();
1310	if (source->AdaptiveScale()) {
1311		int64 adaptiveMax = int64(values->MaximumValue() * 1.2);
1312		if (adaptiveMax < max)
1313			max = adaptiveMax;
1314	}
1315
1316	if (value > max)
1317		value = max;
1318	if (value < min)
1319		value = min;
1320
1321	float height = _HistoryFrame().Height();
1322	return height - (value - min) * height / (max - min);
1323}
1324
1325
1326void
1327ActivityView::_DrawHistory()
1328{
1329	_UpdateOffscreenBitmap();
1330
1331	BView* view = this;
1332	if (fOffscreen != NULL) {
1333		fOffscreen->Lock();
1334		view = _OffscreenView();
1335	}
1336
1337	BRect frame = _HistoryFrame();
1338	BRect outerFrame = frame.InsetByCopy(-2, -2);
1339
1340	// draw the outer frame
1341	uint32 flags = BControlLook::B_BLEND_FRAME;
1342	be_control_look->DrawTextControlBorder(this, outerFrame,
1343		outerFrame, fLegendBackgroundColor, flags);
1344
1345	// convert to offscreen view if necessary
1346	if (view != this)
1347		frame.OffsetTo(B_ORIGIN);
1348
1349	view->SetLowColor(fHistoryBackgroundColor);
1350	view->FillRect(frame, B_SOLID_LOW);
1351
1352	uint32 step = 2;
1353	uint32 resolution = fDrawResolution;
1354	if (fDrawResolution > 1) {
1355		step = 1;
1356		resolution--;
1357	}
1358
1359	// We would get a negative number of steps which isn't a good idea.
1360	if (frame.IntegerWidth() <= 10)
1361		return;
1362
1363	uint32 width = frame.IntegerWidth() - 10;
1364	uint32 steps = width / step;
1365	bigtime_t timeStep = RefreshInterval() * resolution;
1366	bigtime_t now = system_time();
1367
1368	// Draw scale
1369	// TODO: add second markers?
1370
1371	view->SetPenSize(1);
1372
1373	rgb_color scaleColor = view->LowColor();
1374	uint32 average = (scaleColor.red + scaleColor.green + scaleColor.blue) / 3;
1375	if (average < 96)
1376		scaleColor = tint_color(scaleColor, B_LIGHTEN_2_TINT);
1377	else
1378		scaleColor = tint_color(scaleColor, B_DARKEN_2_TINT);
1379
1380	view->SetHighColor(scaleColor);
1381	view->StrokeLine(BPoint(frame.left, frame.top + frame.Height() / 2),
1382		BPoint(frame.right, frame.top + frame.Height() / 2));
1383
1384	// Draw values
1385
1386	view->SetPenSize(1.5);
1387	BAutolock _(fSourcesLock);
1388
1389	for (uint32 i = fSources.CountItems(); i-- > 0;) {
1390		ViewHistory* viewValues = fViewValues.ItemAt(i);
1391		DataSource* source = fSources.ItemAt(i);
1392		DataHistory* values = fValues.ItemAt(i);
1393
1394		viewValues->Update(values, steps, fDrawResolution, now, timeStep,
1395			RefreshInterval());
1396
1397		if (viewValues->Start() >= (int32)steps - 1)
1398			continue;
1399
1400		uint32 x = viewValues->Start() * step;
1401
1402		bool first = true;
1403
1404		view->SetHighColor(source->Color());
1405		view->SetLineMode(B_BUTT_CAP, B_ROUND_JOIN);
1406		view->MovePenTo(B_ORIGIN);
1407
1408		try {
1409			view->BeginLineArray(steps - viewValues->Start() - 1);
1410
1411			BPoint prev;
1412
1413			for (uint32 j = viewValues->Start(); j < steps; x += step, j++) {
1414				float y = _PositionForValue(source, values,
1415					viewValues->ValueAt(j));
1416
1417				if (first) {
1418					first = false;
1419				} else
1420					view->AddLine(prev, BPoint(x, y), source->Color());
1421
1422				prev.Set(x, y);
1423			}
1424
1425		} catch (std::bad_alloc&) {
1426			// Not enough memory to allocate the line array.
1427			// TODO we could try to draw using the slower but less memory
1428			// consuming solution using StrokeLine.
1429		}
1430
1431		view->EndLineArray();
1432	}
1433
1434	// TODO: add marks when an app started or quit
1435	view->Sync();
1436	if (fOffscreen != NULL) {
1437		fOffscreen->Unlock();
1438		DrawBitmap(fOffscreen, outerFrame.LeftTop());
1439	}
1440}
1441
1442
1443void
1444ActivityView::_UpdateResolution(int32 resolution, bool broadcast)
1445{
1446	if (resolution < 1)
1447		resolution = 1;
1448	if (resolution > 128)
1449		resolution = 128;
1450
1451	if (resolution == fDrawResolution)
1452		return;
1453
1454	ActivityWindow* window = dynamic_cast<ActivityWindow*>(Window());
1455	if (broadcast && window != NULL) {
1456		BMessage update(kMsgUpdateResolution);
1457		update.AddInt32("resolution", resolution);
1458		window->BroadcastToActivityViews(&update, this);
1459	}
1460
1461	fDrawResolution = resolution;
1462	Invalidate();
1463}
1464
1465
1466void
1467ActivityView::Draw(BRect updateRect)
1468{
1469	_DrawHistory();
1470
1471	if (!fShowLegend)
1472		return;
1473
1474	// draw legend
1475	BRect legendFrame = _LegendFrame();
1476	if (LowUIColor() == B_NO_COLOR)
1477		SetLowColor(fLegendBackgroundColor);
1478
1479	BAutolock _(fSourcesLock);
1480
1481	font_height fontHeight;
1482	GetFontHeight(&fontHeight);
1483
1484	for (int32 i = 0; i < fSources.CountItems(); i++) {
1485		DataSource* source = fSources.ItemAt(i);
1486		DataHistory* values = fValues.ItemAt(i);
1487		BRect frame = _LegendFrameAt(legendFrame, i);
1488
1489		// draw color box
1490		BRect colorBox = _LegendColorFrameAt(legendFrame, i);
1491		BRect rect = colorBox;
1492		uint32 flags = BControlLook::B_BLEND_FRAME;
1493		be_control_look->DrawTextControlBorder(this, rect,
1494			rect, fLegendBackgroundColor, flags);
1495		SetHighColor(source->Color());
1496		FillRect(rect);
1497
1498		// show current value and label
1499		float y = frame.top + ceilf(fontHeight.ascent);
1500		int64 value = values->ValueAt(values->End());
1501		BString text;
1502		source->Print(text, value);
1503		float width = StringWidth(text.String());
1504
1505		BString label = source->Label();
1506		float possibleLabelWidth = frame.right - colorBox.right - 12 - width;
1507		if (ceilf(StringWidth(label.String())) > possibleLabelWidth)
1508			label = source->ShortLabel();
1509		TruncateString(&label, B_TRUNCATE_MIDDLE, possibleLabelWidth);
1510
1511		if (be_control_look == NULL) {
1512			DrawString(label.String(), BPoint(6 + colorBox.right, y));
1513			DrawString(text.String(), BPoint(frame.right - width, y));
1514		} else {
1515			be_control_look->DrawLabel(this, label.String(),
1516				Parent()->ViewColor(), 0, BPoint(6 + colorBox.right, y));
1517			be_control_look->DrawLabel(this, text.String(),
1518				Parent()->ViewColor(), 0, BPoint(frame.right - width, y));
1519		}
1520	}
1521}
1522
1523
1524void
1525ActivityView::_Refresh()
1526{
1527	bigtime_t lastTimeout = system_time() - RefreshInterval();
1528	BMessenger target(this);
1529
1530	while (true) {
1531		status_t status = acquire_sem_etc(fRefreshSem, 1, B_ABSOLUTE_TIMEOUT,
1532			lastTimeout + RefreshInterval());
1533		if (status == B_OK || status == B_BAD_SEM_ID)
1534			break;
1535		if (status == B_INTERRUPTED)
1536			continue;
1537
1538		SystemInfo info(fSystemInfoHandler);
1539		lastTimeout += RefreshInterval();
1540
1541		fSourcesLock.Lock();
1542
1543		for (uint32 i = fSources.CountItems(); i-- > 0;) {
1544			DataSource* source = fSources.ItemAt(i);
1545			DataHistory* values = fValues.ItemAt(i);
1546
1547			int64 value = source->NextValue(info);
1548			values->AddValue(info.Time(), value);
1549		}
1550
1551		fSourcesLock.Unlock();
1552
1553		target.SendMessage(B_INVALIDATE);
1554	}
1555}
1556
1557
1558/*static*/ status_t
1559ActivityView::_RefreshThread(void* self)
1560{
1561	((ActivityView*)self)->_Refresh();
1562	return B_OK;
1563}
1564