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