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