xref: /haiku/src/apps/activitymonitor/ActivityView.cpp (revision a1163de83ea633463a79de234b8742ee106531b2)
1 /*
2  * Copyright 2008-2009, 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 <AbstractLayoutItem.h>
16 #	include <ControlLook.h>
17 #endif
18 #include <Application.h>
19 #include <Autolock.h>
20 #include <Bitmap.h>
21 #include <Dragger.h>
22 #include <MenuItem.h>
23 #include <MessageRunner.h>
24 #include <PopUpMenu.h>
25 #include <Shape.h>
26 #include <String.h>
27 
28 #include "ActivityMonitor.h"
29 #include "ActivityWindow.h"
30 #include "SettingsWindow.h"
31 #include "SystemInfo.h"
32 #include "SystemInfoHandler.h"
33 
34 
35 template<typename ObjectType>
36 class ListAddDeleter {
37 public:
38 	ListAddDeleter(BObjectList<ObjectType>& list, ObjectType* object,
39 			int32 spot)
40 		:
41 		fList(list),
42 		fObject(object)
43 	{
44 		if (fObject != NULL && !fList.AddItem(fObject, spot)) {
45 			delete fObject;
46 			fObject = NULL;
47 		}
48 	}
49 
50 	~ListAddDeleter()
51 	{
52 		if (fObject != NULL) {
53 			fList.RemoveItem(fObject);
54 			delete fObject;
55 		}
56 	}
57 
58 	bool Failed() const
59 	{
60 		return fObject == NULL;
61 	}
62 
63 	void Detach()
64 	{
65 		fObject = NULL;
66 	}
67 
68 private:
69 	BObjectList<ObjectType>&	fList;
70 	ObjectType*					fObject;
71 };
72 
73 
74 /*!	This class manages the scale of a history with a dynamic scale.
75 	Every history value will be input via Update(), and the minimum/maximum
76 	is computed from that.
77 */
78 class Scale {
79 public:
80 								Scale(scale_type type);
81 
82 			int64				MinimumValue() const { return fMinimumValue; }
83 			int64				MaximumValue() const { return fMaximumValue; }
84 
85 			void				Update(int64 value);
86 
87 private:
88 			scale_type			fType;
89 			int64				fMinimumValue;
90 			int64				fMaximumValue;
91 			bool				fInitialized;
92 };
93 
94 /*!	Stores the interpolated on screen view values. This is done so that the
95 	interpolation is fixed, and does not change when being scrolled.
96 
97 	We could also just do this by making sure we always ask for the same
98 	interval only, but this way we also save the interpolation.
99 */
100 class ViewHistory {
101 public:
102 								ViewHistory();
103 
104 			int64				ValueAt(int32 x);
105 
106 			int32				Start() const
107 									{ return fValues.Size()
108 										- fValues.CountItems(); }
109 
110 			void				Update(DataHistory* history, int32 width,
111 									int32 resolution, bigtime_t toTime,
112 									bigtime_t step, bigtime_t refresh);
113 
114 private:
115 			CircularBuffer<int64> fValues;
116 			int32				fResolution;
117 			bigtime_t			fRefresh;
118 			bigtime_t			fLastTime;
119 };
120 
121 struct data_item {
122 	bigtime_t	time;
123 	int64		value;
124 };
125 
126 #ifdef __HAIKU__
127 class ActivityView::HistoryLayoutItem : public BAbstractLayoutItem {
128 public:
129 							HistoryLayoutItem(ActivityView* parent);
130 
131 	virtual	bool			IsVisible();
132 	virtual	void			SetVisible(bool visible);
133 
134 	virtual	BRect			Frame();
135 	virtual	void			SetFrame(BRect frame);
136 
137 	virtual	BView*			View();
138 
139 	virtual	BSize			BasePreferredSize();
140 
141 private:
142 	ActivityView*			fParent;
143 	BRect					fFrame;
144 };
145 
146 class ActivityView::LegendLayoutItem : public BAbstractLayoutItem {
147 public:
148 							LegendLayoutItem(ActivityView* parent);
149 
150 	virtual	bool			IsVisible();
151 	virtual	void			SetVisible(bool visible);
152 
153 	virtual	BRect			Frame();
154 	virtual	void			SetFrame(BRect frame);
155 
156 	virtual	BView*			View();
157 
158 	virtual	BSize			BaseMinSize();
159 	virtual	BSize			BaseMaxSize();
160 	virtual	BSize			BasePreferredSize();
161 	virtual	BAlignment		BaseAlignment();
162 
163 private:
164 	ActivityView*			fParent;
165 	BRect					fFrame;
166 };
167 #endif
168 
169 const bigtime_t kInitialRefreshInterval = 250000LL;
170 
171 const uint32 kMsgToggleDataSource = 'tgds';
172 const uint32 kMsgToggleLegend = 'tglg';
173 const uint32 kMsgUpdateResolution = 'ures';
174 
175 extern const char* kSignature;
176 
177 
178 Scale::Scale(scale_type type)
179 	:
180 	fType(type),
181 	fMinimumValue(0),
182 	fMaximumValue(0),
183 	fInitialized(false)
184 {
185 }
186 
187 
188 void
189 Scale::Update(int64 value)
190 {
191 	if (!fInitialized || fMinimumValue > value)
192 		fMinimumValue = value;
193 	if (!fInitialized || fMaximumValue < value)
194 		fMaximumValue = value;
195 
196 	fInitialized = true;
197 }
198 
199 
200 //	#pragma mark -
201 
202 
203 ViewHistory::ViewHistory()
204 	:
205 	fValues(1),
206 	fResolution(-1),
207 	fRefresh(-1),
208 	fLastTime(0)
209 {
210 }
211 
212 
213 int64
214 ViewHistory::ValueAt(int32 x)
215 {
216 	int64* value = fValues.ItemAt(x - Start());
217 	if (value != NULL)
218 		return *value;
219 
220 	return 0;
221 }
222 
223 
224 void
225 ViewHistory::Update(DataHistory* history, int32 width, int32 resolution,
226 	bigtime_t toTime, bigtime_t step, bigtime_t refresh)
227 {
228 	if (width > 16384) {
229 		// ignore this - it seems the view hasn't been layouted yet
230 		return;
231 	}
232 
233 	// Check if we need to invalidate the existing values
234 	if ((int32)fValues.Size() != width
235 		|| fResolution != resolution
236 		|| fRefresh != refresh) {
237 		fValues.SetSize(width);
238 		fResolution = resolution;
239 		fRefresh = refresh;
240 		fLastTime = 0;
241 	}
242 
243 	// Compute how many new values we need to retrieve
244 	if (fLastTime < history->Start())
245 		fLastTime = history->Start();
246 	if (fLastTime > history->End())
247 		return;
248 
249 	int32 updateWidth = int32((toTime - fLastTime) / step);
250 	if (updateWidth < 1)
251 		return;
252 
253 	if (updateWidth > (int32)fValues.Size()) {
254 		updateWidth = fValues.Size();
255 		fLastTime = toTime - updateWidth * step;
256 	}
257 
258 	for (int32 i = 0; i < updateWidth; i++) {
259 		int64 value = history->ValueAt(fLastTime += step);
260 
261 		if (step > refresh) {
262 			uint32 count = 1;
263 			for (bigtime_t offset = refresh; offset < step; offset += refresh) {
264 				// TODO: handle int64 overflow correctly!
265 				value += history->ValueAt(fLastTime + offset);
266 				count++;
267 			}
268 			value /= count;
269 		}
270 
271 		fValues.AddItem(value);
272 	}
273 }
274 
275 
276 //	#pragma mark -
277 
278 
279 DataHistory::DataHistory(bigtime_t memorize, bigtime_t interval)
280 	:
281 	fBuffer(10000),
282 	fMinimumValue(0),
283 	fMaximumValue(0),
284 	fRefreshInterval(interval),
285 	fLastIndex(-1),
286 	fScale(NULL)
287 {
288 }
289 
290 
291 DataHistory::~DataHistory()
292 {
293 }
294 
295 
296 void
297 DataHistory::AddValue(bigtime_t time, int64 value)
298 {
299 	if (fBuffer.IsEmpty() || fMaximumValue < value)
300 		fMaximumValue = value;
301 	if (fBuffer.IsEmpty() || fMinimumValue > value)
302 		fMinimumValue = value;
303 	if (fScale != NULL)
304 		fScale->Update(value);
305 
306 	data_item item = {time, value};
307 	fBuffer.AddItem(item);
308 }
309 
310 
311 int64
312 DataHistory::ValueAt(bigtime_t time)
313 {
314 	int32 left = 0;
315 	int32 right = fBuffer.CountItems() - 1;
316 	data_item* item = NULL;
317 
318 	while (left <= right) {
319 		int32 index = (left + right) / 2;
320 		item = fBuffer.ItemAt(index);
321 
322 		if (item->time > time) {
323 			// search in left part
324 			right = index - 1;
325 		} else {
326 			data_item* nextItem = fBuffer.ItemAt(index + 1);
327 			if (nextItem == NULL)
328 				return item->value;
329 			if (nextItem->time > time) {
330 				// found item
331 				int64 value = item->value;
332 				value += int64(double(nextItem->value - value)
333 					/ (nextItem->time - item->time) * (time - item->time));
334 				return value;
335 			}
336 
337 			// search in right part
338 			left = index + 1;
339 		}
340 	}
341 
342 	return 0;
343 }
344 
345 
346 int64
347 DataHistory::MaximumValue() const
348 {
349 	if (fScale != NULL)
350 		return fScale->MaximumValue();
351 
352 	return fMaximumValue;
353 }
354 
355 
356 int64
357 DataHistory::MinimumValue() const
358 {
359 	if (fScale != NULL)
360 		return fScale->MinimumValue();
361 
362 	return fMinimumValue;
363 }
364 
365 
366 bigtime_t
367 DataHistory::Start() const
368 {
369 	if (fBuffer.CountItems() == 0)
370 		return 0;
371 
372 	return fBuffer.ItemAt(0)->time;
373 }
374 
375 
376 bigtime_t
377 DataHistory::End() const
378 {
379 	if (fBuffer.CountItems() == 0)
380 		return 0;
381 
382 	return fBuffer.ItemAt(fBuffer.CountItems() - 1)->time;
383 }
384 
385 
386 void
387 DataHistory::SetRefreshInterval(bigtime_t interval)
388 {
389 	// TODO: adjust buffer size
390 }
391 
392 
393 void
394 DataHistory::SetScale(Scale* scale)
395 {
396 	fScale = scale;
397 }
398 
399 
400 //	#pragma mark -
401 
402 
403 #ifdef __HAIKU__
404 ActivityView::HistoryLayoutItem::HistoryLayoutItem(ActivityView* parent)
405 	:
406 	fParent(parent),
407 	fFrame()
408 {
409 }
410 
411 
412 bool
413 ActivityView::HistoryLayoutItem::IsVisible()
414 {
415 	return !fParent->IsHidden(fParent);
416 }
417 
418 
419 void
420 ActivityView::HistoryLayoutItem::SetVisible(bool visible)
421 {
422 	// not allowed
423 }
424 
425 
426 BRect
427 ActivityView::HistoryLayoutItem::Frame()
428 {
429 	return fFrame;
430 }
431 
432 
433 void
434 ActivityView::HistoryLayoutItem::SetFrame(BRect frame)
435 {
436 	fFrame = frame;
437 	fParent->_UpdateFrame();
438 }
439 
440 
441 BView*
442 ActivityView::HistoryLayoutItem::View()
443 {
444 	return fParent;
445 }
446 
447 
448 BSize
449 ActivityView::HistoryLayoutItem::BasePreferredSize()
450 {
451 	BSize size(BaseMaxSize());
452 	return size;
453 }
454 
455 
456 //	#pragma mark -
457 
458 
459 ActivityView::LegendLayoutItem::LegendLayoutItem(ActivityView* parent)
460 	:
461 	fParent(parent),
462 	fFrame()
463 {
464 }
465 
466 
467 bool
468 ActivityView::LegendLayoutItem::IsVisible()
469 {
470 	return !fParent->IsHidden(fParent);
471 }
472 
473 
474 void
475 ActivityView::LegendLayoutItem::SetVisible(bool visible)
476 {
477 	// not allowed
478 }
479 
480 
481 BRect
482 ActivityView::LegendLayoutItem::Frame()
483 {
484 	return fFrame;
485 }
486 
487 
488 void
489 ActivityView::LegendLayoutItem::SetFrame(BRect frame)
490 {
491 	fFrame = frame;
492 	fParent->_UpdateFrame();
493 }
494 
495 
496 BView*
497 ActivityView::LegendLayoutItem::View()
498 {
499 	return fParent;
500 }
501 
502 
503 BSize
504 ActivityView::LegendLayoutItem::BaseMinSize()
505 {
506 	// TODO: Cache the info. Might be too expensive for this call.
507 	BSize size;
508 	size.width = 80;
509 	size.height = fParent->_LegendHeight();
510 
511 	return size;
512 }
513 
514 
515 BSize
516 ActivityView::LegendLayoutItem::BaseMaxSize()
517 {
518 	BSize size(BaseMinSize());
519 	size.width = B_SIZE_UNLIMITED;
520 	return size;
521 }
522 
523 
524 BSize
525 ActivityView::LegendLayoutItem::BasePreferredSize()
526 {
527 	BSize size(BaseMinSize());
528 	return size;
529 }
530 
531 
532 BAlignment
533 ActivityView::LegendLayoutItem::BaseAlignment()
534 {
535 	return BAlignment(B_ALIGN_USE_FULL_WIDTH, B_ALIGN_USE_FULL_HEIGHT);
536 }
537 #endif
538 
539 
540 //	#pragma mark -
541 
542 
543 ActivityView::ActivityView(BRect frame, const char* name,
544 		const BMessage* settings, uint32 resizingMode)
545 	: BView(frame, name, resizingMode,
546 		B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE | B_FRAME_EVENTS),
547 	fSourcesLock("data sources")
548 {
549 	_Init(settings);
550 
551 	BRect rect(Bounds());
552 	rect.top = rect.bottom - 7;
553 	rect.left = rect.right - 7;
554 	BDragger* dragger = new BDragger(rect, this,
555 		B_FOLLOW_RIGHT | B_FOLLOW_BOTTOM);
556 	AddChild(dragger);
557 }
558 
559 
560 ActivityView::ActivityView(const char* name, const BMessage* settings)
561 #ifdef __HAIKU__
562 	: BView(name, B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE | B_FRAME_EVENTS),
563 #else
564 	: BView(BRect(0, 0, 300, 200), name, B_FOLLOW_NONE,
565 		B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE | B_FRAME_EVENTS),
566 #endif
567 	fSourcesLock("data sources")
568 {
569 	SetLowColor(ui_color(B_PANEL_BACKGROUND_COLOR));
570 
571 	_Init(settings);
572 
573 	BRect rect(Bounds());
574 	rect.top = rect.bottom - 7;
575 	rect.left = rect.right - 7;
576 	BDragger* dragger = new BDragger(rect, this,
577 		B_FOLLOW_RIGHT | B_FOLLOW_BOTTOM);
578 	AddChild(dragger);
579 }
580 
581 
582 ActivityView::ActivityView(BMessage* archive)
583 	: BView(archive)
584 {
585 	_Init(archive);
586 }
587 
588 
589 ActivityView::~ActivityView()
590 {
591 	delete fOffscreen;
592 	delete fSystemInfoHandler;
593 }
594 
595 
596 void
597 ActivityView::_Init(const BMessage* settings)
598 {
599 	fHistoryBackgroundColor = (rgb_color){255, 255, 240};
600 	fLegendBackgroundColor = LowColor();
601 		// the low color is restored by the BView unarchiving
602 	fOffscreen = NULL;
603 #ifdef __HAIKU__
604 	fHistoryLayoutItem = NULL;
605 	fLegendLayoutItem = NULL;
606 #endif
607 	SetViewColor(B_TRANSPARENT_COLOR);
608 
609 	fLastRefresh = 0;
610 	fDrawResolution = 1;
611 	fZooming = false;
612 
613 	fSystemInfoHandler = new SystemInfoHandler;
614 
615 	if (settings == NULL
616 		|| settings->FindInt64("refresh interval", &fRefreshInterval) != B_OK)
617 		fRefreshInterval = kInitialRefreshInterval;
618 
619 	if (settings == NULL
620 		|| settings->FindBool("show legend", &fShowLegend) != B_OK)
621 		fShowLegend = true;
622 
623 	if (settings == NULL)
624 		return;
625 
626 	ssize_t colorLength;
627 	rgb_color *color;
628 	if (settings->FindData("history background color", B_RGB_COLOR_TYPE,
629 			(const void **)&color, &colorLength) == B_OK
630 		&& colorLength == sizeof(rgb_color))
631 		fHistoryBackgroundColor = *color;
632 
633 	const char* name;
634 	for (int32 i = 0; settings->FindString("source", i, &name) == B_OK; i++) {
635 		AddDataSource(DataSource::FindSource(name), settings);
636 	}
637 }
638 
639 
640 status_t
641 ActivityView::Archive(BMessage* into, bool deep) const
642 {
643 	status_t status;
644 
645 	status = BView::Archive(into, deep);
646 	if (status < B_OK)
647 		return status;
648 
649 	status = into->AddString("add_on", kSignature);
650 	if (status < B_OK)
651 		return status;
652 
653 	status = SaveState(*into);
654 	if (status < B_OK)
655 		return status;
656 
657 	return B_OK;
658 }
659 
660 
661 BArchivable*
662 ActivityView::Instantiate(BMessage* archive)
663 {
664 	if (!validate_instantiation(archive, "ActivityView"))
665 		return NULL;
666 
667 	return new ActivityView(archive);
668 }
669 
670 
671 status_t
672 ActivityView::SaveState(BMessage& state) const
673 {
674 	status_t status = state.AddBool("show legend", fShowLegend);
675 	if (status != B_OK)
676 		return status;
677 
678 	status = state.AddInt64("refresh interval", fRefreshInterval);
679 	if (status != B_OK)
680 		return status;
681 
682 	status = state.AddData("history background color", B_RGB_COLOR_TYPE,
683 		&fHistoryBackgroundColor, sizeof(rgb_color));
684 	if (status != B_OK)
685 		return status;
686 
687 	for (int32 i = 0; i < fSources.CountItems(); i++) {
688 		DataSource* source = fSources.ItemAt(i);
689 
690 		if (!source->PerCPU() || source->CPU() == 0)
691 			status = state.AddString("source", source->Name());
692 		if (status != B_OK)
693 			return status;
694 
695 		BString name = source->Name();
696 		name << " color";
697 		rgb_color color = source->Color();
698 		state.AddData(name.String(), B_RGB_COLOR_TYPE, &color,
699 			sizeof(rgb_color));
700 	}
701 	return B_OK;
702 }
703 
704 
705 Scale*
706 ActivityView::_ScaleFor(scale_type type)
707 {
708 	if (type == kNoScale)
709 		return NULL;
710 
711 	std::map<scale_type, ::Scale*>::iterator iterator = fScales.find(type);
712 	if (iterator != fScales.end())
713 		return iterator->second;
714 
715 	// add new scale
716 	::Scale* scale = new ::Scale(type);
717 	fScales[type] = scale;
718 
719 	return scale;
720 }
721 
722 
723 #ifdef __HAIKU__
724 BLayoutItem*
725 ActivityView::CreateHistoryLayoutItem()
726 {
727 	if (fHistoryLayoutItem == NULL)
728 		fHistoryLayoutItem = new HistoryLayoutItem(this);
729 
730 	return fHistoryLayoutItem;
731 }
732 
733 
734 BLayoutItem*
735 ActivityView::CreateLegendLayoutItem()
736 {
737 	if (fLegendLayoutItem == NULL)
738 		fLegendLayoutItem = new LegendLayoutItem(this);
739 
740 	return fLegendLayoutItem;
741 }
742 #endif
743 
744 
745 DataSource*
746 ActivityView::FindDataSource(const DataSource* search)
747 {
748 	BAutolock _(fSourcesLock);
749 
750 	for (int32 i = fSources.CountItems(); i-- > 0;) {
751 		DataSource* source = fSources.ItemAt(i);
752 		if (!strcmp(source->Name(), search->Name()))
753 			return source;
754 	}
755 
756 	return NULL;
757 }
758 
759 
760 status_t
761 ActivityView::AddDataSource(const DataSource* source, const BMessage* state)
762 {
763 	if (source == NULL)
764 		return B_BAD_VALUE;
765 
766 	BAutolock _(fSourcesLock);
767 
768 	// Search for the correct insert spot to maintain the order of the sources
769 	int32 insert = DataSource::IndexOf(source);
770 	for (int32 i = 0; i < fSources.CountItems() && i < insert; i++) {
771 		DataSource* before = fSources.ItemAt(i);
772 		if (DataSource::IndexOf(before) > insert) {
773 			insert = i;
774 			break;
775 		}
776 	}
777 	if (insert > fSources.CountItems())
778 		insert = fSources.CountItems();
779 
780 	// Generate DataHistory and ViewHistory objects for the source
781 	// (one might need one history per CPU)
782 
783 	uint32 count = 1;
784 	if (source->PerCPU()) {
785 		SystemInfo info;
786 		count = info.CPUCount();
787 	}
788 
789 	for (uint32 i = 0; i < count; i++) {
790 		DataHistory* values = new(std::nothrow) DataHistory(10 * 60000000LL,
791 			RefreshInterval());
792 		ListAddDeleter<DataHistory> valuesDeleter(fValues, values, insert);
793 
794 		ViewHistory* viewValues = new(std::nothrow) ViewHistory;
795 		ListAddDeleter<ViewHistory> viewValuesDeleter(fViewValues, viewValues,
796 			insert);
797 
798 		if (valuesDeleter.Failed() || viewValuesDeleter.Failed())
799 			return B_NO_MEMORY;
800 
801 		values->SetScale(_ScaleFor(source->ScaleType()));
802 
803 		DataSource* copy;
804 		if (source->PerCPU())
805 			copy = source->CopyForCPU(i);
806 		else
807 			copy = source->Copy();
808 
809 		ListAddDeleter<DataSource> sourceDeleter(fSources, copy, insert);
810 		if (sourceDeleter.Failed())
811 			return B_NO_MEMORY;
812 
813 		BString colorName = source->Name();
814 		colorName << " color";
815 		if (state != NULL) {
816 			const rgb_color* color = NULL;
817 			ssize_t colorLength;
818 			if (state->FindData(colorName.String(), B_RGB_COLOR_TYPE, i,
819 					(const void**)&color, &colorLength) == B_OK
820 				&& colorLength == sizeof(rgb_color))
821 				copy->SetColor(*color);
822 		}
823 
824 		valuesDeleter.Detach();
825 		viewValuesDeleter.Detach();
826 		sourceDeleter.Detach();
827 	}
828 
829 #ifdef __HAIKU__
830 	InvalidateLayout();
831 #endif
832 	return B_OK;
833 }
834 
835 
836 status_t
837 ActivityView::RemoveDataSource(const DataSource* remove)
838 {
839 	bool removed = false;
840 
841 	BAutolock _(fSourcesLock);
842 
843 	while (true) {
844 		DataSource* source = FindDataSource(remove);
845 		if (source == NULL) {
846 			if (removed)
847 				break;
848 			return B_ENTRY_NOT_FOUND;
849 		}
850 
851 		int32 index = fSources.IndexOf(source);
852 		if (index < 0)
853 			return B_ENTRY_NOT_FOUND;
854 
855 		fSources.RemoveItemAt(index);
856 		delete source;
857 		DataHistory* values = fValues.RemoveItemAt(index);
858 		delete values;
859 		removed = true;
860 	}
861 
862 #ifdef __HAIKU__
863 	InvalidateLayout();
864 #endif
865 	return B_OK;
866 }
867 
868 
869 void
870 ActivityView::RemoveAllDataSources()
871 {
872 	BAutolock _(fSourcesLock);
873 
874 	fSources.MakeEmpty();
875 	fValues.MakeEmpty();
876 }
877 
878 
879 void
880 ActivityView::AttachedToWindow()
881 {
882 	Looper()->AddHandler(fSystemInfoHandler);
883 	fSystemInfoHandler->StartWatching();
884 
885 	fRefreshSem = create_sem(0, "refresh sem");
886 	fRefreshThread = spawn_thread(&_RefreshThread, "source refresh",
887 		B_URGENT_DISPLAY_PRIORITY, this);
888 	resume_thread(fRefreshThread);
889 
890 	FrameResized(Bounds().Width(), Bounds().Height());
891 }
892 
893 
894 void
895 ActivityView::DetachedFromWindow()
896 {
897 	fSystemInfoHandler->StopWatching();
898 	Looper()->RemoveHandler(fSystemInfoHandler);
899 
900 	delete_sem(fRefreshSem);
901 	wait_for_thread(fRefreshThread, NULL);
902 }
903 
904 
905 #ifdef __HAIKU__
906 BSize
907 ActivityView::MinSize()
908 {
909 	BSize size(32, 32);
910 	if (fShowLegend)
911 		size.height = _LegendHeight();
912 
913 	return size;
914 }
915 #endif
916 
917 
918 void
919 ActivityView::FrameResized(float /*width*/, float /*height*/)
920 {
921 	_UpdateOffscreenBitmap();
922 }
923 
924 
925 void
926 ActivityView::_UpdateOffscreenBitmap()
927 {
928 	BRect frame = _HistoryFrame();
929 	frame.OffsetTo(B_ORIGIN);
930 
931 	if (fOffscreen != NULL && frame == fOffscreen->Bounds())
932 		return;
933 
934 	delete fOffscreen;
935 
936 	// create offscreen bitmap
937 
938 	fOffscreen = new(std::nothrow) BBitmap(frame, B_BITMAP_ACCEPTS_VIEWS,
939 		B_RGB32);
940 	if (fOffscreen == NULL || fOffscreen->InitCheck() != B_OK) {
941 		delete fOffscreen;
942 		fOffscreen = NULL;
943 		return;
944 	}
945 
946 	BView* view = new BView(frame, NULL, B_FOLLOW_NONE, B_SUBPIXEL_PRECISE);
947 	view->SetViewColor(fHistoryBackgroundColor);
948 	view->SetLowColor(view->ViewColor());
949 	fOffscreen->AddChild(view);
950 }
951 
952 
953 BView*
954 ActivityView::_OffscreenView()
955 {
956 	if (fOffscreen == NULL)
957 		return NULL;
958 
959 	return fOffscreen->ChildAt(0);
960 }
961 
962 
963 void
964 ActivityView::MouseDown(BPoint where)
965 {
966 	int32 buttons = B_SECONDARY_MOUSE_BUTTON;
967 	if (Looper() != NULL && Looper()->CurrentMessage() != NULL)
968 		Looper()->CurrentMessage()->FindInt32("buttons", &buttons);
969 
970 	if (buttons == B_PRIMARY_MOUSE_BUTTON) {
971 		fZoomPoint = where;
972 		fOriginalResolution = fDrawResolution;
973 		fZooming = true;
974 		SetMouseEventMask(B_POINTER_EVENTS);
975 		return;
976 	}
977 
978 	BPopUpMenu *menu = new BPopUpMenu(B_EMPTY_STRING, false, false);
979 	menu->SetFont(be_plain_font);
980 
981 	BMenu* additionalMenu = new BMenu("Additional Items");
982 	additionalMenu->SetFont(be_plain_font);
983 
984 	SystemInfo info;
985 	BMenuItem* item;
986 
987 	for (int32 i = 0; i < DataSource::CountSources(); i++) {
988 		const DataSource* source = DataSource::SourceAt(i);
989 
990 		if (source->MultiCPUOnly() && info.CPUCount() == 1)
991 			continue;
992 
993 		BMessage* message = new BMessage(kMsgToggleDataSource);
994 		message->AddInt32("index", i);
995 
996 		item = new BMenuItem(source->Name(), message);
997 		if (FindDataSource(source))
998 			item->SetMarked(true);
999 
1000 		if (source->Primary())
1001 			menu->AddItem(item);
1002 		else
1003 			additionalMenu->AddItem(item);
1004 	}
1005 
1006 	menu->AddItem(new BMenuItem(additionalMenu));
1007 	menu->AddSeparatorItem();
1008 	menu->AddItem(new BMenuItem(fShowLegend ? "Hide Legend" : "Show Legend",
1009 		new BMessage(kMsgToggleLegend)));
1010 
1011 	menu->SetTargetForItems(this);
1012 	additionalMenu->SetTargetForItems(this);
1013 
1014 	ActivityWindow* window = dynamic_cast<ActivityWindow*>(Window());
1015 	if (window != NULL && window->ActivityViewCount() > 1) {
1016 		menu->AddSeparatorItem();
1017 		BMessage* message = new BMessage(kMsgRemoveView);
1018 		message->AddPointer("view", this);
1019 		menu->AddItem(item = new BMenuItem("Remove View", message));
1020 		item->SetTarget(window);
1021 	}
1022 
1023 	ConvertToScreen(&where);
1024 	menu->Go(where, true, false, true);
1025 }
1026 
1027 
1028 void
1029 ActivityView::MouseUp(BPoint where)
1030 {
1031 	fZooming = false;
1032 }
1033 
1034 
1035 void
1036 ActivityView::MouseMoved(BPoint where, uint32 transit,
1037 	const BMessage* dragMessage)
1038 {
1039 	if (!fZooming)
1040 		return;
1041 
1042 	int32 shift = int32(where.x - fZoomPoint.x) / 25;
1043 	int32 resolution;
1044 	if (shift > 0)
1045 		resolution = fOriginalResolution << shift;
1046 	else
1047 		resolution = fOriginalResolution >> -shift;
1048 
1049 	_UpdateResolution(resolution);
1050 }
1051 
1052 
1053 void
1054 ActivityView::MessageReceived(BMessage* message)
1055 {
1056 	// if a color is dropped, use it as background
1057 	if (message->WasDropped()) {
1058 		rgb_color* color;
1059 		ssize_t size;
1060 		if (message->FindData("RGBColor", B_RGB_COLOR_TYPE, 0,
1061 				(const void**)&color, &size) == B_OK
1062 			&& size == sizeof(rgb_color)) {
1063 			BPoint dropPoint = message->DropPoint();
1064 			ConvertFromScreen(&dropPoint);
1065 
1066 			if (_HistoryFrame().Contains(dropPoint)) {
1067 				fHistoryBackgroundColor = *color;
1068 				Invalidate(_HistoryFrame());
1069 			} else {
1070 				// check each legend color box
1071 				BAutolock _(fSourcesLock);
1072 
1073 				BRect legendFrame = _LegendFrame();
1074 				for (int32 i = 0; i < fSources.CountItems(); i++) {
1075 					BRect frame = _LegendColorFrameAt(legendFrame, i);
1076 					if (frame.Contains(dropPoint)) {
1077 						fSources.ItemAt(i)->SetColor(*color);
1078 						Invalidate(_HistoryFrame());
1079 						Invalidate(frame);
1080 						return;
1081 					}
1082 				}
1083 
1084 				if (dynamic_cast<ActivityMonitor*>(be_app) == NULL) {
1085 					// allow background color change in the replicant only
1086 					fLegendBackgroundColor = *color;
1087 					SetLowColor(fLegendBackgroundColor);
1088 					Invalidate(legendFrame);
1089 				}
1090 			}
1091 			return;
1092 		}
1093 	}
1094 
1095 	switch (message->what) {
1096 		case B_ABOUT_REQUESTED:
1097 			ActivityMonitor::ShowAbout();
1098 			break;
1099 
1100 		case kMsgUpdateResolution:
1101 		{
1102 			int32 resolution;
1103 			if (message->FindInt32("resolution", &resolution) != B_OK)
1104 				break;
1105 
1106 			_UpdateResolution(resolution, false);
1107 			break;
1108 		}
1109 
1110 		case kMsgTimeIntervalUpdated:
1111 			bigtime_t interval;
1112 			if (message->FindInt64("interval", &interval) != B_OK)
1113 				break;
1114 
1115 			if (interval < 10000)
1116 				interval = 10000;
1117 
1118 			atomic_set64(&fRefreshInterval, interval);
1119 			break;
1120 
1121 		case kMsgToggleDataSource:
1122 		{
1123 			int32 index;
1124 			if (message->FindInt32("index", &index) != B_OK)
1125 				break;
1126 
1127 			const DataSource* baseSource = DataSource::SourceAt(index);
1128 			if (baseSource == NULL)
1129 				break;
1130 
1131 			DataSource* source = FindDataSource(baseSource);
1132 			if (source == NULL)
1133 				AddDataSource(baseSource);
1134 			else
1135 				RemoveDataSource(baseSource);
1136 
1137 			Invalidate();
1138 			break;
1139 		}
1140 
1141 		case kMsgToggleLegend:
1142 			fShowLegend = !fShowLegend;
1143 			Invalidate();
1144 			break;
1145 
1146 		case B_MOUSE_WHEEL_CHANGED:
1147 		{
1148 			float deltaY = 0.0f;
1149 			if (message->FindFloat("be:wheel_delta_y", &deltaY) != B_OK
1150 				|| deltaY == 0.0f)
1151 				break;
1152 
1153 			int32 resolution = fDrawResolution;
1154 			if (deltaY > 0)
1155 				resolution *= 2;
1156 			else
1157 				resolution /= 2;
1158 
1159 			_UpdateResolution(resolution);
1160 			break;
1161 		}
1162 
1163 		default:
1164 			BView::MessageReceived(message);
1165 			break;
1166 	}
1167 }
1168 
1169 
1170 void
1171 ActivityView::_UpdateFrame()
1172 {
1173 #ifdef __HAIKU__
1174 	if (fLegendLayoutItem == NULL || fHistoryLayoutItem == NULL)
1175 		return;
1176 
1177 	BRect historyFrame = fHistoryLayoutItem->Frame();
1178 	BRect legendFrame = fLegendLayoutItem->Frame();
1179 #else
1180 	BRect historyFrame = Bounds();
1181 	BRect legendFrame = Bounds();
1182 	historyFrame.bottom -= 2 * Bounds().Height() / 3;
1183 	legendFrame.top += Bounds().Height() / 3;
1184 #endif
1185 	MoveTo(historyFrame.left, historyFrame.top);
1186 	ResizeTo(legendFrame.left + legendFrame.Width() - historyFrame.left,
1187 		legendFrame.top + legendFrame.Height() - historyFrame.top);
1188 }
1189 
1190 
1191 BRect
1192 ActivityView::_HistoryFrame() const
1193 {
1194 	BRect frame = Bounds();
1195 
1196 	if (fShowLegend) {
1197 		BRect legendFrame = _LegendFrame();
1198 		frame.bottom = legendFrame.top - 1;
1199 	}
1200 
1201 	frame.InsetBy(2, 2);
1202 
1203 	return frame;
1204 }
1205 
1206 
1207 float
1208 ActivityView::_LegendHeight() const
1209 {
1210 	font_height fontHeight;
1211 	GetFontHeight(&fontHeight);
1212 
1213 	BAutolock _(fSourcesLock);
1214 
1215 	int32 rows = (fSources.CountItems() + 1) / 2;
1216 	return rows * (4 + ceilf(fontHeight.ascent)
1217 		+ ceilf(fontHeight.descent) + ceilf(fontHeight.leading));
1218 }
1219 
1220 
1221 BRect
1222 ActivityView::_LegendFrame() const
1223 {
1224 	float height;
1225 #ifdef __HAIKU__
1226 	if (fLegendLayoutItem != NULL)
1227 		height = fLegendLayoutItem->Frame().Height();
1228 	else
1229 #endif
1230 		height = _LegendHeight();
1231 
1232 	BRect frame = Bounds();
1233 	frame.top = frame.bottom - height;
1234 
1235 	return frame;
1236 }
1237 
1238 
1239 BRect
1240 ActivityView::_LegendFrameAt(BRect frame, int32 index) const
1241 {
1242 	int32 column = index & 1;
1243 	int32 row = index / 2;
1244 	if (column == 0)
1245 		frame.right = frame.left + floorf(frame.Width() / 2) - 5;
1246 	else
1247 		frame.left = frame.right - floorf(frame.Width() / 2) + 5;
1248 
1249 	BAutolock _(fSourcesLock);
1250 
1251 	int32 rows = (fSources.CountItems() + 1) / 2;
1252 	float height = floorf((frame.Height() - 5) / rows);
1253 
1254 	frame.top = frame.top + 5 + row * height;
1255 	frame.bottom = frame.top + height - 1;
1256 
1257 	return frame;
1258 }
1259 
1260 
1261 BRect
1262 ActivityView::_LegendColorFrameAt(BRect frame, int32 index) const
1263 {
1264 	frame = _LegendFrameAt(frame, index);
1265 	frame.InsetBy(1, 1);
1266 	frame.right = frame.left + frame.Height();
1267 
1268 	return frame;
1269 }
1270 
1271 
1272 float
1273 ActivityView::_PositionForValue(DataSource* source, DataHistory* values,
1274 	int64 value)
1275 {
1276 	int64 min = source->Minimum();
1277 	int64 max = source->Maximum();
1278 	if (source->AdaptiveScale()) {
1279 		int64 adaptiveMax = int64(values->MaximumValue() * 1.2);
1280 		if (adaptiveMax < max)
1281 			max = adaptiveMax;
1282 	}
1283 
1284 	if (value > max)
1285 		value = max;
1286 	if (value < min)
1287 		value = min;
1288 
1289 	float height = _HistoryFrame().Height();
1290 	return height - (value - min) * height / (max - min);
1291 }
1292 
1293 
1294 void
1295 ActivityView::_DrawHistory(bool drawBackground)
1296 {
1297 	_UpdateOffscreenBitmap();
1298 
1299 	BView* view = this;
1300 	if (fOffscreen != NULL) {
1301 		fOffscreen->Lock();
1302 		view = _OffscreenView();
1303 	}
1304 
1305 	BRect frame = _HistoryFrame();
1306 	BRect outerFrame = frame.InsetByCopy(-2, -2);
1307 
1308 	// draw the outer frame
1309 	uint32 flags = 0;
1310 	if (!drawBackground)
1311 		flags |= BControlLook::B_BLEND_FRAME;
1312 	be_control_look->DrawTextControlBorder(this, outerFrame,
1313 		outerFrame, fLegendBackgroundColor, flags);
1314 
1315 	// convert to offscreen view if necessary
1316 	if (view != this)
1317 		frame.OffsetTo(B_ORIGIN);
1318 
1319 	view->SetLowColor(fHistoryBackgroundColor);
1320 	view->FillRect(frame, B_SOLID_LOW);
1321 
1322 	uint32 step = 2;
1323 	uint32 resolution = fDrawResolution;
1324 	if (fDrawResolution > 1) {
1325 		step = 1;
1326 		resolution--;
1327 	}
1328 
1329 	uint32 width = frame.IntegerWidth() - 10;
1330 	uint32 steps = width / step;
1331 	bigtime_t timeStep = RefreshInterval() * resolution;
1332 	bigtime_t now = system_time();
1333 
1334 	// Draw scale
1335 	// TODO: add second markers?
1336 
1337 	view->SetPenSize(1);
1338 
1339 	rgb_color scaleColor = view->LowColor();
1340 	uint32 average = (scaleColor.red + scaleColor.green + scaleColor.blue) / 3;
1341 	if (average < 96)
1342 		scaleColor = tint_color(scaleColor, B_LIGHTEN_2_TINT);
1343 	else
1344 		scaleColor = tint_color(scaleColor, B_DARKEN_2_TINT);
1345 
1346 	view->SetHighColor(scaleColor);
1347 	view->StrokeLine(BPoint(frame.left, frame.top + frame.Height() / 2),
1348 		BPoint(frame.right, frame.top + frame.Height() / 2));
1349 
1350 	// Draw values
1351 
1352 	view->SetPenSize(1.5);
1353 	BAutolock _(fSourcesLock);
1354 
1355 	for (uint32 i = fSources.CountItems(); i-- > 0;) {
1356 		ViewHistory* viewValues = fViewValues.ItemAt(i);
1357 		DataSource* source = fSources.ItemAt(i);
1358 		DataHistory* values = fValues.ItemAt(i);
1359 
1360 		viewValues->Update(values, steps, fDrawResolution, now, timeStep,
1361 			RefreshInterval());
1362 
1363 		uint32 x = viewValues->Start() * step;
1364 		BShape shape;
1365 		bool first = true;
1366 
1367 		for (uint32 i = viewValues->Start(); i < steps; x += step, i++) {
1368 			float y = _PositionForValue(source, values,
1369 				viewValues->ValueAt(i));
1370 
1371 			if (first) {
1372 				shape.MoveTo(BPoint(x, y));
1373 				first = false;
1374 			} else
1375 				shape.LineTo(BPoint(x, y));
1376 		}
1377 
1378 		view->SetHighColor(source->Color());
1379 		view->SetLineMode(B_BUTT_CAP, B_ROUND_JOIN);
1380 		view->MovePenTo(B_ORIGIN);
1381 		view->StrokeShape(&shape);
1382 	}
1383 
1384 	// TODO: add marks when an app started or quit
1385 	view->Sync();
1386 	if (fOffscreen != NULL) {
1387 		fOffscreen->Unlock();
1388 		DrawBitmap(fOffscreen, outerFrame.LeftTop());
1389 	}
1390 }
1391 
1392 
1393 void
1394 ActivityView::_UpdateResolution(int32 resolution, bool broadcast)
1395 {
1396 	if (resolution < 1)
1397 		resolution = 1;
1398 	if (resolution > 128)
1399 		resolution = 128;
1400 
1401 	if (resolution == fDrawResolution)
1402 		return;
1403 
1404 	ActivityWindow* window = dynamic_cast<ActivityWindow*>(Window());
1405 	if (broadcast && window != NULL) {
1406 		BMessage update(kMsgUpdateResolution);
1407 		update.AddInt32("resolution", resolution);
1408 		window->BroadcastToActivityViews(&update, this);
1409 	}
1410 
1411 	fDrawResolution = resolution;
1412 	Invalidate();
1413 }
1414 
1415 
1416 void
1417 ActivityView::Draw(BRect updateRect)
1418 {
1419 	bool drawBackground = true;
1420 	if (Parent() && (Parent()->Flags() & B_DRAW_ON_CHILDREN) != 0)
1421 		drawBackground = false;
1422 
1423 	_DrawHistory(drawBackground);
1424 
1425 	if (!fShowLegend)
1426 		return;
1427 
1428 	// draw legend
1429 	BRect legendFrame = _LegendFrame();
1430 	SetLowColor(fLegendBackgroundColor);
1431 	if (drawBackground)
1432 		FillRect(legendFrame, B_SOLID_LOW);
1433 
1434 	BAutolock _(fSourcesLock);
1435 
1436 	font_height fontHeight;
1437 	GetFontHeight(&fontHeight);
1438 
1439 	for (int32 i = 0; i < fSources.CountItems(); i++) {
1440 		DataSource* source = fSources.ItemAt(i);
1441 		DataHistory* values = fValues.ItemAt(i);
1442 		BRect frame = _LegendFrameAt(legendFrame, i);
1443 
1444 		// draw color box
1445 		BRect colorBox = _LegendColorFrameAt(legendFrame, i);
1446 		BRect rect = colorBox;
1447 		uint32 flags = BControlLook::B_BLEND_FRAME;
1448 		be_control_look->DrawTextControlBorder(this, rect,
1449 			rect, fLegendBackgroundColor, flags);
1450 		SetHighColor(source->Color());
1451 		FillRect(rect);
1452 
1453 		// show current value and label
1454 		float y = frame.top + ceilf(fontHeight.ascent);
1455 		int64 value = values->ValueAt(values->End());
1456 		BString text;
1457 		source->Print(text, value);
1458 		float width = StringWidth(text.String());
1459 
1460 		BString label = source->Label();
1461 		float possibleLabelWidth = frame.right - colorBox.right - 12 - width;
1462 		// TODO: TruncateString() is broken... remove + 5 when fixed!
1463 		if (ceilf(StringWidth(label.String()) + 5) > possibleLabelWidth)
1464 			label = source->ShortLabel();
1465 		TruncateString(&label, B_TRUNCATE_MIDDLE, possibleLabelWidth);
1466 
1467 		if (drawBackground)
1468 			SetHighColor(ui_color(B_CONTROL_TEXT_COLOR));
1469 		else {
1470 			SetDrawingMode(B_OP_OVER);
1471 			rgb_color c = Parent()->LowColor();
1472 			if (c.red + c.green + c.blue > 128 * 3)
1473 				SetHighColor(0, 0, 0);
1474 			else
1475 				SetHighColor(255, 255, 255);
1476 		}
1477 		DrawString(label.String(), BPoint(6 + colorBox.right, y));
1478 		DrawString(text.String(), BPoint(frame.right - width, y));
1479 	}
1480 }
1481 
1482 
1483 void
1484 ActivityView::_Refresh()
1485 {
1486 	bigtime_t lastTimeout = system_time() - RefreshInterval();
1487 	BMessenger target(this);
1488 
1489 	while (true) {
1490 		status_t status = acquire_sem_etc(fRefreshSem, 1, B_ABSOLUTE_TIMEOUT,
1491 			lastTimeout + RefreshInterval());
1492 		if (status == B_OK || status == B_BAD_SEM_ID)
1493 			break;
1494 		if (status == B_INTERRUPTED)
1495 			continue;
1496 
1497 		SystemInfo info(fSystemInfoHandler);
1498 		lastTimeout += RefreshInterval();
1499 
1500 		fSourcesLock.Lock();
1501 
1502 		for (uint32 i = fSources.CountItems(); i-- > 0;) {
1503 			DataSource* source = fSources.ItemAt(i);
1504 			DataHistory* values = fValues.ItemAt(i);
1505 
1506 			int64 value = source->NextValue(info);
1507 			values->AddValue(info.Time(), value);
1508 		}
1509 
1510 		fSourcesLock.Unlock();
1511 
1512 		target.SendMessage(B_INVALIDATE);
1513 	}
1514 }
1515 
1516 
1517 /*static*/ status_t
1518 ActivityView::_RefreshThread(void* self)
1519 {
1520 	((ActivityView*)self)->_Refresh();
1521 	return B_OK;
1522 }
1523