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