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