xref: /haiku/src/apps/activitymonitor/ActivityView.cpp (revision 9e25244c5e9051f6cd333820d6332397361abd6c)
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 
39 template<typename ObjectType>
40 class ListAddDeleter {
41 public:
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 
72 private:
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 */
82 class Scale {
83 public:
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 
91 private:
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 */
104 class ViewHistory {
105 public:
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 
118 private:
119 			CircularBuffer<int64> fValues;
120 			int32				fResolution;
121 			bigtime_t			fRefresh;
122 			bigtime_t			fLastTime;
123 };
124 
125 struct data_item {
126 	bigtime_t	time;
127 	int64		value;
128 };
129 
130 #ifdef __HAIKU__
131 class ActivityView::HistoryLayoutItem : public BAbstractLayoutItem {
132 public:
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 
145 private:
146 	ActivityView*			fParent;
147 	BRect					fFrame;
148 };
149 
150 class ActivityView::LegendLayoutItem : public BAbstractLayoutItem {
151 public:
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 
167 private:
168 	ActivityView*			fParent;
169 	BRect					fFrame;
170 };
171 #endif
172 
173 const bigtime_t kInitialRefreshInterval = 250000LL;
174 
175 const uint32 kMsgToggleDataSource = 'tgds';
176 const uint32 kMsgToggleLegend = 'tglg';
177 const uint32 kMsgUpdateResolution = 'ures';
178 
179 extern const char* kAppName;
180 extern const char* kSignature;
181 
182 
183 Scale::Scale(scale_type type)
184 	:
185 	fType(type),
186 	fMinimumValue(0),
187 	fMaximumValue(0),
188 	fInitialized(false)
189 {
190 }
191 
192 
193 void
194 Scale::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 
208 ViewHistory::ViewHistory()
209 	:
210 	fValues(1),
211 	fResolution(-1),
212 	fRefresh(-1),
213 	fLastTime(0)
214 {
215 }
216 
217 
218 int64
219 ViewHistory::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 
229 void
230 ViewHistory::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 
284 DataHistory::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 
296 DataHistory::~DataHistory()
297 {
298 }
299 
300 
301 void
302 DataHistory::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 
316 int64
317 DataHistory::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 
351 int64
352 DataHistory::MaximumValue() const
353 {
354 	if (fScale != NULL)
355 		return fScale->MaximumValue();
356 
357 	return fMaximumValue;
358 }
359 
360 
361 int64
362 DataHistory::MinimumValue() const
363 {
364 	if (fScale != NULL)
365 		return fScale->MinimumValue();
366 
367 	return fMinimumValue;
368 }
369 
370 
371 bigtime_t
372 DataHistory::Start() const
373 {
374 	if (fBuffer.CountItems() == 0)
375 		return 0;
376 
377 	return fBuffer.ItemAt(0)->time;
378 }
379 
380 
381 bigtime_t
382 DataHistory::End() const
383 {
384 	if (fBuffer.CountItems() == 0)
385 		return 0;
386 
387 	return fBuffer.ItemAt(fBuffer.CountItems() - 1)->time;
388 }
389 
390 
391 void
392 DataHistory::SetRefreshInterval(bigtime_t interval)
393 {
394 	// TODO: adjust buffer size
395 }
396 
397 
398 void
399 DataHistory::SetScale(Scale* scale)
400 {
401 	fScale = scale;
402 }
403 
404 
405 //	#pragma mark -
406 
407 
408 #ifdef __HAIKU__
409 ActivityView::HistoryLayoutItem::HistoryLayoutItem(ActivityView* parent)
410 	:
411 	fParent(parent),
412 	fFrame()
413 {
414 }
415 
416 
417 bool
418 ActivityView::HistoryLayoutItem::IsVisible()
419 {
420 	return !fParent->IsHidden(fParent);
421 }
422 
423 
424 void
425 ActivityView::HistoryLayoutItem::SetVisible(bool visible)
426 {
427 	// not allowed
428 }
429 
430 
431 BRect
432 ActivityView::HistoryLayoutItem::Frame()
433 {
434 	return fFrame;
435 }
436 
437 
438 void
439 ActivityView::HistoryLayoutItem::SetFrame(BRect frame)
440 {
441 	fFrame = frame;
442 	fParent->_UpdateFrame();
443 }
444 
445 
446 BView*
447 ActivityView::HistoryLayoutItem::View()
448 {
449 	return fParent;
450 }
451 
452 
453 BSize
454 ActivityView::HistoryLayoutItem::BasePreferredSize()
455 {
456 	BSize size(BaseMaxSize());
457 	return size;
458 }
459 
460 
461 //	#pragma mark -
462 
463 
464 ActivityView::LegendLayoutItem::LegendLayoutItem(ActivityView* parent)
465 	:
466 	fParent(parent),
467 	fFrame()
468 {
469 }
470 
471 
472 bool
473 ActivityView::LegendLayoutItem::IsVisible()
474 {
475 	return !fParent->IsHidden(fParent);
476 }
477 
478 
479 void
480 ActivityView::LegendLayoutItem::SetVisible(bool visible)
481 {
482 	// not allowed
483 }
484 
485 
486 BRect
487 ActivityView::LegendLayoutItem::Frame()
488 {
489 	return fFrame;
490 }
491 
492 
493 void
494 ActivityView::LegendLayoutItem::SetFrame(BRect frame)
495 {
496 	fFrame = frame;
497 	fParent->_UpdateFrame();
498 }
499 
500 
501 BView*
502 ActivityView::LegendLayoutItem::View()
503 {
504 	return fParent;
505 }
506 
507 
508 BSize
509 ActivityView::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 
520 BSize
521 ActivityView::LegendLayoutItem::BaseMaxSize()
522 {
523 	BSize size(BaseMinSize());
524 	size.width = B_SIZE_UNLIMITED;
525 	return size;
526 }
527 
528 
529 BSize
530 ActivityView::LegendLayoutItem::BasePreferredSize()
531 {
532 	BSize size(BaseMinSize());
533 	return size;
534 }
535 
536 
537 BAlignment
538 ActivityView::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 
548 const rgb_color kWhite = (rgb_color){255, 255, 255, 255};
549 const rgb_color kBlack = (rgb_color){0, 0, 0, 255};
550 const float kDraggerSize = 7;
551 
552 
553 ActivityView::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 
570 ActivityView::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 
592 ActivityView::ActivityView(BMessage* archive)
593 	: BView(archive)
594 {
595 	_Init(archive);
596 }
597 
598 
599 ActivityView::~ActivityView()
600 {
601 	delete fOffscreen;
602 	delete fSystemInfoHandler;
603 }
604 
605 
606 void
607 ActivityView::_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 
650 status_t
651 ActivityView::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 
671 BArchivable*
672 ActivityView::Instantiate(BMessage* archive)
673 {
674 	if (!validate_instantiation(archive, "ActivityView"))
675 		return NULL;
676 
677 	return new ActivityView(archive);
678 }
679 
680 
681 status_t
682 ActivityView::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 
715 Scale*
716 ActivityView::_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__
734 BLayoutItem*
735 ActivityView::CreateHistoryLayoutItem()
736 {
737 	if (fHistoryLayoutItem == NULL)
738 		fHistoryLayoutItem = new HistoryLayoutItem(this);
739 
740 	return fHistoryLayoutItem;
741 }
742 
743 
744 BLayoutItem*
745 ActivityView::CreateLegendLayoutItem()
746 {
747 	if (fLegendLayoutItem == NULL)
748 		fLegendLayoutItem = new LegendLayoutItem(this);
749 
750 	return fLegendLayoutItem;
751 }
752 #endif
753 
754 
755 DataSource*
756 ActivityView::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 
770 status_t
771 ActivityView::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 
847 status_t
848 ActivityView::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 
880 void
881 ActivityView::RemoveAllDataSources()
882 {
883 	BAutolock _(fSourcesLock);
884 
885 	fSources.MakeEmpty();
886 	fValues.MakeEmpty();
887 }
888 
889 
890 void
891 ActivityView::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 
905 void
906 ActivityView::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__
917 BSize
918 ActivityView::MinSize()
919 {
920 	BSize size(32, 32);
921 	if (fShowLegend)
922 		size.height = _LegendHeight();
923 
924 	return size;
925 }
926 #endif
927 
928 
929 void
930 ActivityView::FrameResized(float /*width*/, float /*height*/)
931 {
932 	_UpdateOffscreenBitmap();
933 }
934 
935 
936 void
937 ActivityView::_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 
964 BView*
965 ActivityView::_OffscreenView()
966 {
967 	if (fOffscreen == NULL)
968 		return NULL;
969 
970 	return fOffscreen->ChildAt(0);
971 }
972 
973 
974 void
975 ActivityView::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 
1041 void
1042 ActivityView::MouseUp(BPoint where)
1043 {
1044 	fZooming = false;
1045 }
1046 
1047 
1048 void
1049 ActivityView::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 
1066 void
1067 ActivityView::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 
1195 void
1196 ActivityView::_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 
1216 BRect
1217 ActivityView::_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 
1232 float
1233 ActivityView::_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 
1250 BRect
1251 ActivityView::_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 
1269 BRect
1270 ActivityView::_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 
1293 BRect
1294 ActivityView::_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 
1304 float
1305 ActivityView::_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 
1326 void
1327 ActivityView::_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 
1443 void
1444 ActivityView::_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 
1466 void
1467 ActivityView::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 
1524 void
1525 ActivityView::_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
1559 ActivityView::_RefreshThread(void* self)
1560 {
1561 	((ActivityView*)self)->_Refresh();
1562 	return B_OK;
1563 }
1564