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