repository_infos: Enable automatic updates on the main Haiku repostiory.
[haiku.git] / src / apps / activitymonitor / ActivityView.cpp
blobe744a9645335db826f378a73d8ee911cef79d9d7
1 /*
2 * Copyright 2008-2015, Axel Dörfler, axeld@pinc-software.de.
3 * Distributed under the terms of the MIT License.
4 */
7 #include "ActivityView.h"
9 #include <new>
10 #include <stdio.h>
11 #include <stdlib.h>
12 #include <vector>
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>
30 #include "ActivityMonitor.h"
31 #include "ActivityWindow.h"
32 #include "SettingsWindow.h"
33 #include "SystemInfo.h"
34 #include "SystemInfoHandler.h"
36 #undef B_TRANSLATION_CONTEXT
37 #define B_TRANSLATION_CONTEXT "ActivityView"
39 template<typename ObjectType>
40 class ListAddDeleter {
41 public:
42 ListAddDeleter(BObjectList<ObjectType>& list, ObjectType* object,
43 int32 spot)
45 fList(list),
46 fObject(object)
48 if (fObject != NULL && !fList.AddItem(fObject, spot)) {
49 delete fObject;
50 fObject = NULL;
54 ~ListAddDeleter()
56 if (fObject != NULL) {
57 fList.RemoveItem(fObject);
58 delete fObject;
62 bool Failed() const
64 return fObject == NULL;
67 void Detach()
69 fObject = NULL;
72 private:
73 BObjectList<ObjectType>& fList;
74 ObjectType* fObject;
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.
82 class Scale {
83 public:
84 Scale(scale_type type);
86 int64 MinimumValue() const { return fMinimumValue; }
87 int64 MaximumValue() const { return fMaximumValue; }
89 void Update(int64 value);
91 private:
92 scale_type fType;
93 int64 fMinimumValue;
94 int64 fMaximumValue;
95 bool fInitialized;
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.
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.
104 class ViewHistory {
105 public:
106 ViewHistory();
108 int64 ValueAt(int32 x);
110 int32 Start() const
111 { return fValues.Size()
112 - fValues.CountItems(); }
114 void Update(DataHistory* history, int32 width,
115 int32 resolution, bigtime_t toTime,
116 bigtime_t step, bigtime_t refresh);
118 private:
119 CircularBuffer<int64> fValues;
120 int32 fResolution;
121 bigtime_t fRefresh;
122 bigtime_t fLastTime;
125 struct data_item {
126 bigtime_t time;
127 int64 value;
130 #ifdef __HAIKU__
131 class ActivityView::HistoryLayoutItem : public BAbstractLayoutItem {
132 public:
133 HistoryLayoutItem(ActivityView* parent);
135 virtual bool IsVisible();
136 virtual void SetVisible(bool visible);
138 virtual BRect Frame();
139 virtual void SetFrame(BRect frame);
141 virtual BView* View();
143 virtual BSize BasePreferredSize();
145 private:
146 ActivityView* fParent;
147 BRect fFrame;
150 class ActivityView::LegendLayoutItem : public BAbstractLayoutItem {
151 public:
152 LegendLayoutItem(ActivityView* parent);
154 virtual bool IsVisible();
155 virtual void SetVisible(bool visible);
157 virtual BRect Frame();
158 virtual void SetFrame(BRect frame);
160 virtual BView* View();
162 virtual BSize BaseMinSize();
163 virtual BSize BaseMaxSize();
164 virtual BSize BasePreferredSize();
165 virtual BAlignment BaseAlignment();
167 private:
168 ActivityView* fParent;
169 BRect fFrame;
171 #endif
173 const bigtime_t kInitialRefreshInterval = 250000LL;
175 const uint32 kMsgToggleDataSource = 'tgds';
176 const uint32 kMsgToggleLegend = 'tglg';
177 const uint32 kMsgUpdateResolution = 'ures';
179 extern const char* kAppName;
180 extern const char* kSignature;
183 Scale::Scale(scale_type type)
185 fType(type),
186 fMinimumValue(0),
187 fMaximumValue(0),
188 fInitialized(false)
193 void
194 Scale::Update(int64 value)
196 if (!fInitialized || fMinimumValue > value)
197 fMinimumValue = value;
198 if (!fInitialized || fMaximumValue < value)
199 fMaximumValue = value;
201 fInitialized = true;
205 // #pragma mark -
208 ViewHistory::ViewHistory()
210 fValues(1),
211 fResolution(-1),
212 fRefresh(-1),
213 fLastTime(0)
218 int64
219 ViewHistory::ValueAt(int32 x)
221 int64* value = fValues.ItemAt(x - Start());
222 if (value != NULL)
223 return *value;
225 return 0;
229 void
230 ViewHistory::Update(DataHistory* history, int32 width, int32 resolution,
231 bigtime_t toTime, bigtime_t step, bigtime_t refresh)
233 if (width > 16384) {
234 // ignore this - it seems the view hasn't been layouted yet
235 return;
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;
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;
254 int32 updateWidth = int32((toTime - fLastTime) / step);
255 if (updateWidth < 1)
256 return;
258 if (updateWidth > (int32)fValues.Size()) {
259 updateWidth = fValues.Size();
260 fLastTime = toTime - updateWidth * step;
263 for (int32 i = 0; i < updateWidth; i++) {
264 int64 value = history->ValueAt(fLastTime += step);
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++;
273 value /= count;
276 fValues.AddItem(value);
281 // #pragma mark -
284 DataHistory::DataHistory(bigtime_t memorize, bigtime_t interval)
286 fBuffer(10000),
287 fMinimumValue(0),
288 fMaximumValue(0),
289 fRefreshInterval(interval),
290 fLastIndex(-1),
291 fScale(NULL)
296 DataHistory::~DataHistory()
301 void
302 DataHistory::AddValue(bigtime_t time, int64 value)
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);
311 data_item item = {time, value};
312 fBuffer.AddItem(item);
316 int64
317 DataHistory::ValueAt(bigtime_t time)
319 int32 left = 0;
320 int32 right = fBuffer.CountItems() - 1;
321 data_item* item = NULL;
323 while (left <= right) {
324 int32 index = (left + right) / 2;
325 item = fBuffer.ItemAt(index);
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;
342 // search in right part
343 left = index + 1;
347 return 0;
351 int64
352 DataHistory::MaximumValue() const
354 if (fScale != NULL)
355 return fScale->MaximumValue();
357 return fMaximumValue;
361 int64
362 DataHistory::MinimumValue() const
364 if (fScale != NULL)
365 return fScale->MinimumValue();
367 return fMinimumValue;
371 bigtime_t
372 DataHistory::Start() const
374 if (fBuffer.CountItems() == 0)
375 return 0;
377 return fBuffer.ItemAt(0)->time;
381 bigtime_t
382 DataHistory::End() const
384 if (fBuffer.CountItems() == 0)
385 return 0;
387 return fBuffer.ItemAt(fBuffer.CountItems() - 1)->time;
391 void
392 DataHistory::SetRefreshInterval(bigtime_t interval)
394 // TODO: adjust buffer size
398 void
399 DataHistory::SetScale(Scale* scale)
401 fScale = scale;
405 // #pragma mark -
408 #ifdef __HAIKU__
409 ActivityView::HistoryLayoutItem::HistoryLayoutItem(ActivityView* parent)
411 fParent(parent),
412 fFrame()
417 bool
418 ActivityView::HistoryLayoutItem::IsVisible()
420 return !fParent->IsHidden(fParent);
424 void
425 ActivityView::HistoryLayoutItem::SetVisible(bool visible)
427 // not allowed
431 BRect
432 ActivityView::HistoryLayoutItem::Frame()
434 return fFrame;
438 void
439 ActivityView::HistoryLayoutItem::SetFrame(BRect frame)
441 fFrame = frame;
442 fParent->_UpdateFrame();
446 BView*
447 ActivityView::HistoryLayoutItem::View()
449 return fParent;
453 BSize
454 ActivityView::HistoryLayoutItem::BasePreferredSize()
456 BSize size(BaseMaxSize());
457 return size;
461 // #pragma mark -
464 ActivityView::LegendLayoutItem::LegendLayoutItem(ActivityView* parent)
466 fParent(parent),
467 fFrame()
472 bool
473 ActivityView::LegendLayoutItem::IsVisible()
475 return !fParent->IsHidden(fParent);
479 void
480 ActivityView::LegendLayoutItem::SetVisible(bool visible)
482 // not allowed
486 BRect
487 ActivityView::LegendLayoutItem::Frame()
489 return fFrame;
493 void
494 ActivityView::LegendLayoutItem::SetFrame(BRect frame)
496 fFrame = frame;
497 fParent->_UpdateFrame();
501 BView*
502 ActivityView::LegendLayoutItem::View()
504 return fParent;
508 BSize
509 ActivityView::LegendLayoutItem::BaseMinSize()
511 // TODO: Cache the info. Might be too expensive for this call.
512 BSize size;
513 size.width = 80;
514 size.height = fParent->_LegendHeight();
516 return size;
520 BSize
521 ActivityView::LegendLayoutItem::BaseMaxSize()
523 BSize size(BaseMinSize());
524 size.width = B_SIZE_UNLIMITED;
525 return size;
529 BSize
530 ActivityView::LegendLayoutItem::BasePreferredSize()
532 BSize size(BaseMinSize());
533 return size;
537 BAlignment
538 ActivityView::LegendLayoutItem::BaseAlignment()
540 return BAlignment(B_ALIGN_USE_FULL_WIDTH, B_ALIGN_USE_FULL_HEIGHT);
542 #endif
545 // #pragma mark -
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;
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")
559 _Init(settings);
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);
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")
579 SetLowUIColor(B_PANEL_BACKGROUND_COLOR);
581 _Init(settings);
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);
592 ActivityView::ActivityView(BMessage* archive)
593 : BView(archive)
595 _Init(archive);
599 ActivityView::~ActivityView()
601 delete fOffscreen;
602 delete fSystemInfoHandler;
606 void
607 ActivityView::_Init(const BMessage* settings)
609 fHistoryBackgroundColor = (rgb_color){255, 255, 240};
610 fLegendBackgroundColor = LowColor();
611 // the low color is restored by the BView unarchiving
612 fOffscreen = NULL;
613 #ifdef __HAIKU__
614 fHistoryLayoutItem = NULL;
615 fLegendLayoutItem = NULL;
616 #endif
617 SetViewColor(B_TRANSPARENT_COLOR);
619 fLastRefresh = 0;
620 fDrawResolution = 1;
621 fZooming = false;
623 fSystemInfoHandler = new SystemInfoHandler;
625 if (settings == NULL
626 || settings->FindInt64("refresh interval", &fRefreshInterval) != B_OK)
627 fRefreshInterval = kInitialRefreshInterval;
629 if (settings == NULL
630 || settings->FindBool("show legend", &fShowLegend) != B_OK)
631 fShowLegend = true;
633 if (settings == NULL)
634 return;
636 ssize_t colorLength;
637 rgb_color *color;
638 if (settings->FindData("history background color", B_RGB_COLOR_TYPE,
639 (const void **)&color, &colorLength) == B_OK
640 && colorLength == sizeof(rgb_color))
641 fHistoryBackgroundColor = *color;
643 const char* name;
644 for (int32 i = 0; settings->FindString("source", i, &name) == B_OK; i++)
645 AddDataSource(DataSource::FindSource(name), settings);
649 status_t
650 ActivityView::Archive(BMessage* into, bool deep) const
652 status_t status;
654 status = BView::Archive(into, deep);
655 if (status < B_OK)
656 return status;
658 status = into->AddString("add_on", kSignature);
659 if (status < B_OK)
660 return status;
662 status = SaveState(*into);
663 if (status < B_OK)
664 return status;
666 return B_OK;
670 BArchivable*
671 ActivityView::Instantiate(BMessage* archive)
673 if (!validate_instantiation(archive, "ActivityView"))
674 return NULL;
676 return new ActivityView(archive);
680 status_t
681 ActivityView::SaveState(BMessage& state) const
683 status_t status = state.AddBool("show legend", fShowLegend);
684 if (status != B_OK)
685 return status;
687 status = state.AddInt64("refresh interval", fRefreshInterval);
688 if (status != B_OK)
689 return status;
691 status = state.AddData("history background color", B_RGB_COLOR_TYPE,
692 &fHistoryBackgroundColor, sizeof(rgb_color));
693 if (status != B_OK)
694 return status;
696 for (int32 i = 0; i < fSources.CountItems(); i++) {
697 DataSource* source = fSources.ItemAt(i);
699 if (!source->PerCPU() || source->CPU() == 0)
700 status = state.AddString("source", source->InternalName());
701 if (status != B_OK)
702 return status;
704 BString name = source->Name();
705 name << " color";
706 rgb_color color = source->Color();
707 state.AddData(name.String(), B_RGB_COLOR_TYPE, &color,
708 sizeof(rgb_color));
710 return B_OK;
714 Scale*
715 ActivityView::_ScaleFor(scale_type type)
717 if (type == kNoScale)
718 return NULL;
720 std::map<scale_type, ::Scale*>::iterator iterator = fScales.find(type);
721 if (iterator != fScales.end())
722 return iterator->second;
724 // add new scale
725 ::Scale* scale = new ::Scale(type);
726 fScales[type] = scale;
728 return scale;
732 #ifdef __HAIKU__
733 BLayoutItem*
734 ActivityView::CreateHistoryLayoutItem()
736 if (fHistoryLayoutItem == NULL)
737 fHistoryLayoutItem = new HistoryLayoutItem(this);
739 return fHistoryLayoutItem;
743 BLayoutItem*
744 ActivityView::CreateLegendLayoutItem()
746 if (fLegendLayoutItem == NULL)
747 fLegendLayoutItem = new LegendLayoutItem(this);
749 return fLegendLayoutItem;
751 #endif
754 DataSource*
755 ActivityView::FindDataSource(const DataSource* search)
757 BAutolock _(fSourcesLock);
759 for (int32 i = fSources.CountItems(); i-- > 0;) {
760 DataSource* source = fSources.ItemAt(i);
761 if (!strcmp(source->Name(), search->Name()))
762 return source;
765 return NULL;
769 status_t
770 ActivityView::AddDataSource(const DataSource* source, const BMessage* state)
772 if (source == NULL)
773 return B_BAD_VALUE;
775 BAutolock _(fSourcesLock);
777 // Search for the correct insert spot to maintain the order of the sources
778 int32 insert = DataSource::IndexOf(source);
779 for (int32 i = 0; i < fSources.CountItems() && i < insert; i++) {
780 DataSource* before = fSources.ItemAt(i);
781 if (DataSource::IndexOf(before) > insert) {
782 insert = i;
783 break;
786 if (insert > fSources.CountItems())
787 insert = fSources.CountItems();
789 // Generate DataHistory and ViewHistory objects for the source
790 // (one might need one history per CPU)
792 uint32 count = 1;
793 if (source->PerCPU()) {
794 SystemInfo info;
795 count = info.CPUCount();
798 for (uint32 i = 0; i < count; i++) {
799 DataHistory* values = new(std::nothrow) DataHistory(10 * 60000000LL,
800 RefreshInterval());
801 ListAddDeleter<DataHistory> valuesDeleter(fValues, values, insert);
803 ViewHistory* viewValues = new(std::nothrow) ViewHistory;
804 ListAddDeleter<ViewHistory> viewValuesDeleter(fViewValues, viewValues,
805 insert);
807 if (valuesDeleter.Failed() || viewValuesDeleter.Failed())
808 return B_NO_MEMORY;
810 values->SetScale(_ScaleFor(source->ScaleType()));
812 DataSource* copy;
813 if (source->PerCPU())
814 copy = source->CopyForCPU(i);
815 else
816 copy = source->Copy();
818 ListAddDeleter<DataSource> sourceDeleter(fSources, copy, insert);
819 if (sourceDeleter.Failed())
820 return B_NO_MEMORY;
822 BString colorName = source->Name();
823 colorName << " color";
824 if (state != NULL) {
825 const rgb_color* color = NULL;
826 ssize_t colorLength;
827 if (state->FindData(colorName.String(), B_RGB_COLOR_TYPE, i,
828 (const void**)&color, &colorLength) == B_OK
829 && colorLength == sizeof(rgb_color))
830 copy->SetColor(*color);
833 valuesDeleter.Detach();
834 viewValuesDeleter.Detach();
835 sourceDeleter.Detach();
836 insert++;
839 #ifdef __HAIKU__
840 InvalidateLayout();
841 #endif
842 return B_OK;
846 status_t
847 ActivityView::RemoveDataSource(const DataSource* remove)
849 bool removed = false;
851 BAutolock _(fSourcesLock);
853 while (true) {
854 DataSource* source = FindDataSource(remove);
855 if (source == NULL) {
856 if (removed)
857 break;
858 return B_ENTRY_NOT_FOUND;
861 int32 index = fSources.IndexOf(source);
862 if (index < 0)
863 return B_ENTRY_NOT_FOUND;
865 fSources.RemoveItemAt(index);
866 delete source;
867 DataHistory* values = fValues.RemoveItemAt(index);
868 delete values;
869 removed = true;
872 #ifdef __HAIKU__
873 InvalidateLayout();
874 #endif
875 return B_OK;
879 void
880 ActivityView::RemoveAllDataSources()
882 BAutolock _(fSourcesLock);
884 fSources.MakeEmpty();
885 fValues.MakeEmpty();
889 void
890 ActivityView::AttachedToWindow()
892 Looper()->AddHandler(fSystemInfoHandler);
893 fSystemInfoHandler->StartWatching();
895 fRefreshSem = create_sem(0, "refresh sem");
896 fRefreshThread = spawn_thread(&_RefreshThread, "source refresh",
897 B_URGENT_DISPLAY_PRIORITY, this);
898 resume_thread(fRefreshThread);
900 FrameResized(Bounds().Width(), Bounds().Height());
904 void
905 ActivityView::DetachedFromWindow()
907 fSystemInfoHandler->StopWatching();
908 Looper()->RemoveHandler(fSystemInfoHandler);
910 delete_sem(fRefreshSem);
911 wait_for_thread(fRefreshThread, NULL);
915 #ifdef __HAIKU__
916 BSize
917 ActivityView::MinSize()
919 BSize size(32, 32);
920 if (fShowLegend)
921 size.height = _LegendHeight();
923 return size;
925 #endif
928 void
929 ActivityView::FrameResized(float /*width*/, float /*height*/)
931 _UpdateOffscreenBitmap();
935 void
936 ActivityView::_UpdateOffscreenBitmap()
938 BRect frame = _HistoryFrame();
939 frame.OffsetTo(B_ORIGIN);
941 if (fOffscreen != NULL && frame == fOffscreen->Bounds())
942 return;
944 delete fOffscreen;
946 // create offscreen bitmap
948 fOffscreen = new(std::nothrow) BBitmap(frame, B_BITMAP_ACCEPTS_VIEWS,
949 B_RGB32);
950 if (fOffscreen == NULL || fOffscreen->InitCheck() != B_OK) {
951 delete fOffscreen;
952 fOffscreen = NULL;
953 return;
956 BView* view = new BView(frame, NULL, B_FOLLOW_NONE, B_SUBPIXEL_PRECISE);
957 view->SetViewColor(fHistoryBackgroundColor);
958 view->SetLowColor(view->ViewColor());
959 fOffscreen->AddChild(view);
963 BView*
964 ActivityView::_OffscreenView()
966 if (fOffscreen == NULL)
967 return NULL;
969 return fOffscreen->ChildAt(0);
973 void
974 ActivityView::MouseDown(BPoint where)
976 int32 buttons = B_SECONDARY_MOUSE_BUTTON;
977 if (Looper() != NULL && Looper()->CurrentMessage() != NULL)
978 Looper()->CurrentMessage()->FindInt32("buttons", &buttons);
980 if (buttons == B_PRIMARY_MOUSE_BUTTON) {
981 fZoomPoint = where;
982 fOriginalResolution = fDrawResolution;
983 fZooming = true;
984 SetMouseEventMask(B_POINTER_EVENTS);
985 return;
988 BPopUpMenu *menu = new BPopUpMenu(B_EMPTY_STRING, false, false);
989 menu->SetFont(be_plain_font);
991 BMenu* additionalMenu = new BMenu(B_TRANSLATE("Additional items"));
992 additionalMenu->SetFont(be_plain_font);
994 SystemInfo info;
995 BMenuItem* item;
997 for (int32 i = 0; i < DataSource::CountSources(); i++) {
998 const DataSource* source = DataSource::SourceAt(i);
1000 if (source->MultiCPUOnly() && info.CPUCount() == 1)
1001 continue;
1003 BMessage* message = new BMessage(kMsgToggleDataSource);
1004 message->AddInt32("index", i);
1006 item = new BMenuItem(source->Name(), message);
1007 if (FindDataSource(source))
1008 item->SetMarked(true);
1010 if (source->Primary())
1011 menu->AddItem(item);
1012 else
1013 additionalMenu->AddItem(item);
1016 menu->AddItem(new BMenuItem(additionalMenu));
1017 menu->AddSeparatorItem();
1018 menu->AddItem(item = new BMenuItem(B_TRANSLATE("Show legend"),
1019 new BMessage(kMsgToggleLegend)));
1020 item->SetMarked(fShowLegend);
1022 menu->SetTargetForItems(this);
1023 additionalMenu->SetTargetForItems(this);
1025 ActivityWindow* window = dynamic_cast<ActivityWindow*>(Window());
1026 if (window != NULL && window->ActivityViewCount() > 1) {
1027 menu->AddSeparatorItem();
1028 BMessage* message = new BMessage(kMsgRemoveView);
1029 message->AddPointer("view", this);
1030 menu->AddItem(item = new BMenuItem(B_TRANSLATE("Remove graph"),
1031 message));
1032 item->SetTarget(window);
1035 ConvertToScreen(&where);
1036 menu->Go(where, true, false, true);
1040 void
1041 ActivityView::MouseUp(BPoint where)
1043 fZooming = false;
1047 void
1048 ActivityView::MouseMoved(BPoint where, uint32 transit,
1049 const BMessage* dragMessage)
1051 if (!fZooming)
1052 return;
1054 int32 shift = int32(where.x - fZoomPoint.x) / 25;
1055 int32 resolution;
1056 if (shift > 0)
1057 resolution = fOriginalResolution << shift;
1058 else
1059 resolution = fOriginalResolution >> -shift;
1061 _UpdateResolution(resolution);
1065 void
1066 ActivityView::MessageReceived(BMessage* message)
1068 // if a color is dropped, use it as background
1069 if (message->WasDropped()) {
1070 rgb_color* color;
1071 ssize_t size;
1072 if (message->FindData("RGBColor", B_RGB_COLOR_TYPE, 0,
1073 (const void**)&color, &size) == B_OK
1074 && size == sizeof(rgb_color)) {
1075 BPoint dropPoint = message->DropPoint();
1076 ConvertFromScreen(&dropPoint);
1078 if (_HistoryFrame().Contains(dropPoint)) {
1079 fHistoryBackgroundColor = *color;
1080 Invalidate(_HistoryFrame());
1081 } else {
1082 // check each legend color box
1083 BAutolock _(fSourcesLock);
1085 BRect legendFrame = _LegendFrame();
1086 for (int32 i = 0; i < fSources.CountItems(); i++) {
1087 BRect frame = _LegendColorFrameAt(legendFrame, i);
1088 if (frame.Contains(dropPoint)) {
1089 fSources.ItemAt(i)->SetColor(*color);
1090 Invalidate(_HistoryFrame());
1091 Invalidate(frame);
1092 return;
1096 if (dynamic_cast<ActivityMonitor*>(be_app) == NULL) {
1097 // allow background color change in the replicant only
1098 fLegendBackgroundColor = *color;
1099 SetLowColor(fLegendBackgroundColor);
1100 Invalidate(legendFrame);
1103 return;
1107 switch (message->what) {
1108 case B_ABOUT_REQUESTED:
1110 BAboutWindow* window = new BAboutWindow(kAppName, kSignature);
1112 const char* authors[] = {
1113 "Axel Dörfler",
1114 NULL
1117 window->AddCopyright(2008, "Haiku, Inc.");
1118 window->AddAuthors(authors);
1120 window->Show();
1122 break;
1124 case kMsgUpdateResolution:
1126 int32 resolution;
1127 if (message->FindInt32("resolution", &resolution) != B_OK)
1128 break;
1130 _UpdateResolution(resolution, false);
1131 break;
1134 case kMsgTimeIntervalUpdated:
1135 bigtime_t interval;
1136 if (message->FindInt64("interval", &interval) != B_OK)
1137 break;
1139 if (interval < 10000)
1140 interval = 10000;
1142 atomic_set64(&fRefreshInterval, interval);
1143 break;
1145 case kMsgToggleDataSource:
1147 int32 index;
1148 if (message->FindInt32("index", &index) != B_OK)
1149 break;
1151 const DataSource* baseSource = DataSource::SourceAt(index);
1152 if (baseSource == NULL)
1153 break;
1155 DataSource* source = FindDataSource(baseSource);
1156 if (source == NULL)
1157 AddDataSource(baseSource);
1158 else
1159 RemoveDataSource(baseSource);
1161 Invalidate();
1162 break;
1165 case kMsgToggleLegend:
1166 fShowLegend = !fShowLegend;
1167 Invalidate();
1168 break;
1170 case B_MOUSE_WHEEL_CHANGED:
1172 float deltaY = 0.0f;
1173 if (message->FindFloat("be:wheel_delta_y", &deltaY) != B_OK
1174 || deltaY == 0.0f)
1175 break;
1177 int32 resolution = fDrawResolution;
1178 if (deltaY > 0)
1179 resolution *= 2;
1180 else
1181 resolution /= 2;
1183 _UpdateResolution(resolution);
1184 break;
1187 default:
1188 BView::MessageReceived(message);
1189 break;
1194 void
1195 ActivityView::_UpdateFrame()
1197 #ifdef __HAIKU__
1198 if (fLegendLayoutItem == NULL || fHistoryLayoutItem == NULL)
1199 return;
1201 BRect historyFrame = fHistoryLayoutItem->Frame();
1202 BRect legendFrame = fLegendLayoutItem->Frame();
1203 #else
1204 BRect historyFrame = Bounds();
1205 BRect legendFrame = Bounds();
1206 historyFrame.bottom -= 2 * Bounds().Height() / 3;
1207 legendFrame.top += Bounds().Height() / 3;
1208 #endif
1209 MoveTo(historyFrame.left, historyFrame.top);
1210 ResizeTo(legendFrame.left + legendFrame.Width() - historyFrame.left,
1211 legendFrame.top + legendFrame.Height() - historyFrame.top);
1215 BRect
1216 ActivityView::_HistoryFrame() const
1218 BRect frame = Bounds();
1220 if (fShowLegend) {
1221 BRect legendFrame = _LegendFrame();
1222 frame.bottom = legendFrame.top - 1;
1225 frame.InsetBy(2, 2);
1227 return frame;
1231 float
1232 ActivityView::_LegendHeight() const
1234 font_height fontHeight;
1235 GetFontHeight(&fontHeight);
1237 BAutolock _(fSourcesLock);
1239 int32 rows = (fSources.CountItems() + 1) / 2;
1241 int32 boldMargin = Parent()
1242 && (Parent()->Flags() & B_DRAW_ON_CHILDREN) != 0 ? 2 : 0;
1244 return rows * (4 + ceilf(fontHeight.ascent)
1245 + ceilf(fontHeight.descent) + ceilf(fontHeight.leading)) + boldMargin;
1249 BRect
1250 ActivityView::_LegendFrame() const
1252 float height;
1253 #ifdef __HAIKU__
1254 if (fLegendLayoutItem != NULL)
1255 height = fLegendLayoutItem->Frame().Height();
1256 else
1257 #endif
1258 height = _LegendHeight();
1260 BRect frame = Bounds();
1261 frame.bottom -= kDraggerSize;
1262 frame.top = frame.bottom - height;
1264 return frame;
1268 BRect
1269 ActivityView::_LegendFrameAt(BRect frame, int32 index) const
1271 int32 column = index & 1;
1272 int32 row = index / 2;
1273 if (column == 0) {
1274 // Use the full width if there is only one item
1275 if (fSources.CountItems() != 1)
1276 frame.right = frame.left + floorf(frame.Width() / 2) - 5;
1277 } else
1278 frame.left = frame.right - floorf(frame.Width() / 2) + 5;
1280 BAutolock _(fSourcesLock);
1282 int32 rows = (fSources.CountItems() + 1) / 2;
1283 float height = floorf((frame.Height() - 5) / rows);
1285 frame.top = frame.top + 5 + row * height;
1286 frame.bottom = frame.top + height - 1;
1288 return frame;
1292 BRect
1293 ActivityView::_LegendColorFrameAt(BRect frame, int32 index) const
1295 frame = _LegendFrameAt(frame, index);
1296 frame.InsetBy(1, 1);
1297 frame.right = frame.left + frame.Height();
1299 return frame;
1303 float
1304 ActivityView::_PositionForValue(DataSource* source, DataHistory* values,
1305 int64 value)
1307 int64 min = source->Minimum();
1308 int64 max = source->Maximum();
1309 if (source->AdaptiveScale()) {
1310 int64 adaptiveMax = int64(values->MaximumValue() * 1.2);
1311 if (adaptiveMax < max)
1312 max = adaptiveMax;
1315 if (value > max)
1316 value = max;
1317 if (value < min)
1318 value = min;
1320 float height = _HistoryFrame().Height();
1321 return height - (value - min) * height / (max - min);
1325 void
1326 ActivityView::_DrawHistory(bool drawBackground)
1328 _UpdateOffscreenBitmap();
1330 BView* view = this;
1331 if (fOffscreen != NULL) {
1332 fOffscreen->Lock();
1333 view = _OffscreenView();
1336 BRect frame = _HistoryFrame();
1337 BRect outerFrame = frame.InsetByCopy(-2, -2);
1339 // draw the outer frame
1340 uint32 flags = 0;
1341 if (!drawBackground)
1342 flags |= BControlLook::B_BLEND_FRAME;
1343 be_control_look->DrawTextControlBorder(this, outerFrame,
1344 outerFrame, fLegendBackgroundColor, flags);
1346 // convert to offscreen view if necessary
1347 if (view != this)
1348 frame.OffsetTo(B_ORIGIN);
1350 view->SetLowColor(fHistoryBackgroundColor);
1351 view->FillRect(frame, B_SOLID_LOW);
1353 uint32 step = 2;
1354 uint32 resolution = fDrawResolution;
1355 if (fDrawResolution > 1) {
1356 step = 1;
1357 resolution--;
1360 // We would get a negative number of steps which isn't a good idea.
1361 if (frame.IntegerWidth() <= 10)
1362 return;
1364 uint32 width = frame.IntegerWidth() - 10;
1365 uint32 steps = width / step;
1366 bigtime_t timeStep = RefreshInterval() * resolution;
1367 bigtime_t now = system_time();
1369 // Draw scale
1370 // TODO: add second markers?
1372 view->SetPenSize(1);
1374 rgb_color scaleColor = view->LowColor();
1375 uint32 average = (scaleColor.red + scaleColor.green + scaleColor.blue) / 3;
1376 if (average < 96)
1377 scaleColor = tint_color(scaleColor, B_LIGHTEN_2_TINT);
1378 else
1379 scaleColor = tint_color(scaleColor, B_DARKEN_2_TINT);
1381 view->SetHighColor(scaleColor);
1382 view->StrokeLine(BPoint(frame.left, frame.top + frame.Height() / 2),
1383 BPoint(frame.right, frame.top + frame.Height() / 2));
1385 // Draw values
1387 view->SetPenSize(1.5);
1388 BAutolock _(fSourcesLock);
1390 for (uint32 i = fSources.CountItems(); i-- > 0;) {
1391 ViewHistory* viewValues = fViewValues.ItemAt(i);
1392 DataSource* source = fSources.ItemAt(i);
1393 DataHistory* values = fValues.ItemAt(i);
1395 viewValues->Update(values, steps, fDrawResolution, now, timeStep,
1396 RefreshInterval());
1398 if (viewValues->Start() >= (int32)steps - 1)
1399 continue;
1401 uint32 x = viewValues->Start() * step;
1403 bool first = true;
1405 view->SetHighColor(source->Color());
1406 view->SetLineMode(B_BUTT_CAP, B_ROUND_JOIN);
1407 view->MovePenTo(B_ORIGIN);
1409 try {
1410 view->BeginLineArray(steps - viewValues->Start() - 1);
1412 BPoint prev;
1414 for (uint32 j = viewValues->Start(); j < steps; x += step, j++) {
1415 float y = _PositionForValue(source, values,
1416 viewValues->ValueAt(j));
1418 if (first) {
1419 first = false;
1420 } else
1421 view->AddLine(prev, BPoint(x, y), source->Color());
1423 prev.Set(x, y);
1426 } catch (std::bad_alloc) {
1427 // Not enough memory to allocate the line array.
1428 // TODO we could try to draw using the slower but less memory
1429 // consuming solution using StrokeLine.
1432 view->EndLineArray();
1435 // TODO: add marks when an app started or quit
1436 view->Sync();
1437 if (fOffscreen != NULL) {
1438 fOffscreen->Unlock();
1439 DrawBitmap(fOffscreen, outerFrame.LeftTop());
1444 void
1445 ActivityView::_UpdateResolution(int32 resolution, bool broadcast)
1447 if (resolution < 1)
1448 resolution = 1;
1449 if (resolution > 128)
1450 resolution = 128;
1452 if (resolution == fDrawResolution)
1453 return;
1455 ActivityWindow* window = dynamic_cast<ActivityWindow*>(Window());
1456 if (broadcast && window != NULL) {
1457 BMessage update(kMsgUpdateResolution);
1458 update.AddInt32("resolution", resolution);
1459 window->BroadcastToActivityViews(&update, this);
1462 fDrawResolution = resolution;
1463 Invalidate();
1467 void
1468 ActivityView::Draw(BRect updateRect)
1470 bool drawBackground = true;
1471 if (Parent() && (Parent()->Flags() & B_DRAW_ON_CHILDREN) != 0)
1472 drawBackground = false;
1474 _DrawHistory(drawBackground);
1476 if (!fShowLegend)
1477 return;
1479 // draw legend
1480 BRect legendFrame = _LegendFrame();
1481 if (LowUIColor() == B_NO_COLOR)
1482 SetLowColor(fLegendBackgroundColor);
1484 if (drawBackground) {
1485 BRect backgroundFrame(legendFrame);
1486 backgroundFrame.bottom += kDraggerSize;
1487 FillRect(backgroundFrame, B_SOLID_LOW);
1490 BAutolock _(fSourcesLock);
1492 font_height fontHeight;
1493 GetFontHeight(&fontHeight);
1495 for (int32 i = 0; i < fSources.CountItems(); i++) {
1496 DataSource* source = fSources.ItemAt(i);
1497 DataHistory* values = fValues.ItemAt(i);
1498 BRect frame = _LegendFrameAt(legendFrame, i);
1500 // draw color box
1501 BRect colorBox = _LegendColorFrameAt(legendFrame, i);
1502 BRect rect = colorBox;
1503 uint32 flags = BControlLook::B_BLEND_FRAME;
1504 be_control_look->DrawTextControlBorder(this, rect,
1505 rect, fLegendBackgroundColor, flags);
1506 SetHighColor(source->Color());
1507 FillRect(rect);
1509 // show current value and label
1510 float y = frame.top + ceilf(fontHeight.ascent);
1511 int64 value = values->ValueAt(values->End());
1512 BString text;
1513 source->Print(text, value);
1514 float width = StringWidth(text.String());
1516 BString label = source->Label();
1517 float possibleLabelWidth = frame.right - colorBox.right - 12 - width;
1518 // TODO: TruncateString() is broken... remove + 5 when fixed!
1519 if (ceilf(StringWidth(label.String()) + 5) > possibleLabelWidth)
1520 label = source->ShortLabel();
1521 TruncateString(&label, B_TRUNCATE_MIDDLE, possibleLabelWidth);
1523 if (drawBackground)
1524 SetHighColor(ui_color(B_PANEL_TEXT_COLOR));
1526 if (be_control_look == NULL) {
1527 DrawString(label.String(), BPoint(6 + colorBox.right, y));
1528 DrawString(text.String(), BPoint(frame.right - width, y));
1529 } else {
1530 be_control_look->DrawLabel(this, label.String(),
1531 Parent()->ViewColor(), 0, BPoint(6 + colorBox.right, y));
1532 be_control_look->DrawLabel(this, text.String(),
1533 Parent()->ViewColor(), 0, BPoint(frame.right - width, y));
1539 void
1540 ActivityView::_Refresh()
1542 bigtime_t lastTimeout = system_time() - RefreshInterval();
1543 BMessenger target(this);
1545 while (true) {
1546 status_t status = acquire_sem_etc(fRefreshSem, 1, B_ABSOLUTE_TIMEOUT,
1547 lastTimeout + RefreshInterval());
1548 if (status == B_OK || status == B_BAD_SEM_ID)
1549 break;
1550 if (status == B_INTERRUPTED)
1551 continue;
1553 SystemInfo info(fSystemInfoHandler);
1554 lastTimeout += RefreshInterval();
1556 fSourcesLock.Lock();
1558 for (uint32 i = fSources.CountItems(); i-- > 0;) {
1559 DataSource* source = fSources.ItemAt(i);
1560 DataHistory* values = fValues.ItemAt(i);
1562 int64 value = source->NextValue(info);
1563 values->AddValue(info.Time(), value);
1566 fSourcesLock.Unlock();
1568 target.SendMessage(B_INVALIDATE);
1573 /*static*/ status_t
1574 ActivityView::_RefreshThread(void* self)
1576 ((ActivityView*)self)->_Refresh();
1577 return B_OK;