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