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_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 fOffscreen; 602 delete fSystemInfoHandler; 603 } 604 605 606 void 607 ActivityView::_Init(const BMessage* settings) 608 { 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); 618 SetFlags(Flags() | B_TRANSPARENT_BACKGROUND); 619 620 fLastRefresh = 0; 621 fDrawResolution = 1; 622 fZooming = false; 623 624 fSystemInfoHandler = new SystemInfoHandler; 625 626 if (settings == NULL 627 || settings->FindInt64("refresh interval", &fRefreshInterval) != B_OK) 628 fRefreshInterval = kInitialRefreshInterval; 629 630 if (settings == NULL 631 || settings->FindBool("show legend", &fShowLegend) != B_OK) 632 fShowLegend = true; 633 634 if (settings == NULL) 635 return; 636 637 ssize_t colorLength; 638 rgb_color *color; 639 if (settings->FindData("history background color", B_RGB_COLOR_TYPE, 640 (const void **)&color, &colorLength) == B_OK 641 && colorLength == sizeof(rgb_color)) 642 fHistoryBackgroundColor = *color; 643 644 const char* name; 645 for (int32 i = 0; settings->FindString("source", i, &name) == B_OK; i++) 646 AddDataSource(DataSource::FindSource(name), settings); 647 } 648 649 650 status_t 651 ActivityView::Archive(BMessage* into, bool deep) const 652 { 653 status_t status; 654 655 status = BView::Archive(into, deep); 656 if (status < B_OK) 657 return status; 658 659 status = into->AddString("add_on", kSignature); 660 if (status < B_OK) 661 return status; 662 663 status = SaveState(*into); 664 if (status < B_OK) 665 return status; 666 667 return B_OK; 668 } 669 670 671 BArchivable* 672 ActivityView::Instantiate(BMessage* archive) 673 { 674 if (!validate_instantiation(archive, "ActivityView")) 675 return NULL; 676 677 return new ActivityView(archive); 678 } 679 680 681 status_t 682 ActivityView::SaveState(BMessage& state) const 683 { 684 status_t status = state.AddBool("show legend", fShowLegend); 685 if (status != B_OK) 686 return status; 687 688 status = state.AddInt64("refresh interval", fRefreshInterval); 689 if (status != B_OK) 690 return status; 691 692 status = state.AddData("history background color", B_RGB_COLOR_TYPE, 693 &fHistoryBackgroundColor, sizeof(rgb_color)); 694 if (status != B_OK) 695 return status; 696 697 for (int32 i = 0; i < fSources.CountItems(); i++) { 698 DataSource* source = fSources.ItemAt(i); 699 700 if (!source->PerCPU() || source->CPU() == 0) 701 status = state.AddString("source", source->InternalName()); 702 if (status != B_OK) 703 return status; 704 705 BString name = source->Name(); 706 name << " color"; 707 rgb_color color = source->Color(); 708 state.AddData(name.String(), B_RGB_COLOR_TYPE, &color, 709 sizeof(rgb_color)); 710 } 711 return B_OK; 712 } 713 714 715 Scale* 716 ActivityView::_ScaleFor(scale_type type) 717 { 718 if (type == kNoScale) 719 return NULL; 720 721 std::map<scale_type, ::Scale*>::iterator iterator = fScales.find(type); 722 if (iterator != fScales.end()) 723 return iterator->second; 724 725 // add new scale 726 ::Scale* scale = new ::Scale(type); 727 fScales[type] = scale; 728 729 return scale; 730 } 731 732 733 #ifdef __HAIKU__ 734 BLayoutItem* 735 ActivityView::CreateHistoryLayoutItem() 736 { 737 if (fHistoryLayoutItem == NULL) 738 fHistoryLayoutItem = new HistoryLayoutItem(this); 739 740 return fHistoryLayoutItem; 741 } 742 743 744 BLayoutItem* 745 ActivityView::CreateLegendLayoutItem() 746 { 747 if (fLegendLayoutItem == NULL) 748 fLegendLayoutItem = new LegendLayoutItem(this); 749 750 return fLegendLayoutItem; 751 } 752 #endif 753 754 755 DataSource* 756 ActivityView::FindDataSource(const DataSource* search) 757 { 758 BAutolock _(fSourcesLock); 759 760 for (int32 i = fSources.CountItems(); i-- > 0;) { 761 DataSource* source = fSources.ItemAt(i); 762 if (!strcmp(source->Name(), search->Name())) 763 return source; 764 } 765 766 return NULL; 767 } 768 769 770 status_t 771 ActivityView::AddDataSource(const DataSource* source, const BMessage* state) 772 { 773 if (source == NULL) 774 return B_BAD_VALUE; 775 776 BAutolock _(fSourcesLock); 777 778 // Search for the correct insert spot to maintain the order of the sources 779 int32 insert = DataSource::IndexOf(source); 780 for (int32 i = 0; i < fSources.CountItems() && i < insert; i++) { 781 DataSource* before = fSources.ItemAt(i); 782 if (DataSource::IndexOf(before) > insert) { 783 insert = i; 784 break; 785 } 786 } 787 if (insert > fSources.CountItems()) 788 insert = fSources.CountItems(); 789 790 // Generate DataHistory and ViewHistory objects for the source 791 // (one might need one history per CPU) 792 793 uint32 count = 1; 794 if (source->PerCPU()) { 795 SystemInfo info; 796 count = info.CPUCount(); 797 } 798 799 for (uint32 i = 0; i < count; i++) { 800 DataHistory* values = new(std::nothrow) DataHistory(10 * 60000000LL, 801 RefreshInterval()); 802 ListAddDeleter<DataHistory> valuesDeleter(fValues, values, insert); 803 804 ViewHistory* viewValues = new(std::nothrow) ViewHistory; 805 ListAddDeleter<ViewHistory> viewValuesDeleter(fViewValues, viewValues, 806 insert); 807 808 if (valuesDeleter.Failed() || viewValuesDeleter.Failed()) 809 return B_NO_MEMORY; 810 811 values->SetScale(_ScaleFor(source->ScaleType())); 812 813 DataSource* copy; 814 if (source->PerCPU()) 815 copy = source->CopyForCPU(i); 816 else 817 copy = source->Copy(); 818 819 ListAddDeleter<DataSource> sourceDeleter(fSources, copy, insert); 820 if (sourceDeleter.Failed()) 821 return B_NO_MEMORY; 822 823 BString colorName = source->Name(); 824 colorName << " color"; 825 if (state != NULL) { 826 const rgb_color* color = NULL; 827 ssize_t colorLength; 828 if (state->FindData(colorName.String(), B_RGB_COLOR_TYPE, i, 829 (const void**)&color, &colorLength) == B_OK 830 && colorLength == sizeof(rgb_color)) 831 copy->SetColor(*color); 832 } 833 834 valuesDeleter.Detach(); 835 viewValuesDeleter.Detach(); 836 sourceDeleter.Detach(); 837 insert++; 838 } 839 840 #ifdef __HAIKU__ 841 InvalidateLayout(); 842 #endif 843 return B_OK; 844 } 845 846 847 status_t 848 ActivityView::RemoveDataSource(const DataSource* remove) 849 { 850 bool removed = false; 851 852 BAutolock _(fSourcesLock); 853 854 while (true) { 855 DataSource* source = FindDataSource(remove); 856 if (source == NULL) { 857 if (removed) 858 break; 859 return B_ENTRY_NOT_FOUND; 860 } 861 862 int32 index = fSources.IndexOf(source); 863 if (index < 0) 864 return B_ENTRY_NOT_FOUND; 865 866 fSources.RemoveItemAt(index); 867 delete source; 868 DataHistory* values = fValues.RemoveItemAt(index); 869 delete values; 870 removed = true; 871 } 872 873 #ifdef __HAIKU__ 874 InvalidateLayout(); 875 #endif 876 return B_OK; 877 } 878 879 880 void 881 ActivityView::RemoveAllDataSources() 882 { 883 BAutolock _(fSourcesLock); 884 885 fSources.MakeEmpty(); 886 fValues.MakeEmpty(); 887 } 888 889 890 void 891 ActivityView::AttachedToWindow() 892 { 893 Looper()->AddHandler(fSystemInfoHandler); 894 fSystemInfoHandler->StartWatching(); 895 896 fRefreshSem = create_sem(0, "refresh sem"); 897 fRefreshThread = spawn_thread(&_RefreshThread, "source refresh", 898 B_URGENT_DISPLAY_PRIORITY, this); 899 resume_thread(fRefreshThread); 900 901 FrameResized(Bounds().Width(), Bounds().Height()); 902 } 903 904 905 void 906 ActivityView::DetachedFromWindow() 907 { 908 fSystemInfoHandler->StopWatching(); 909 Looper()->RemoveHandler(fSystemInfoHandler); 910 911 delete_sem(fRefreshSem); 912 wait_for_thread(fRefreshThread, NULL); 913 } 914 915 916 #ifdef __HAIKU__ 917 BSize 918 ActivityView::MinSize() 919 { 920 BSize size(32, 32); 921 if (fShowLegend) 922 size.height = _LegendHeight(); 923 924 return size; 925 } 926 #endif 927 928 929 void 930 ActivityView::FrameResized(float /*width*/, float /*height*/) 931 { 932 _UpdateOffscreenBitmap(); 933 } 934 935 936 void 937 ActivityView::_UpdateOffscreenBitmap() 938 { 939 BRect frame = _HistoryFrame(); 940 frame.OffsetTo(B_ORIGIN); 941 942 if (fOffscreen != NULL && frame == fOffscreen->Bounds()) 943 return; 944 945 delete fOffscreen; 946 947 // create offscreen bitmap 948 949 fOffscreen = new(std::nothrow) BBitmap(frame, B_BITMAP_ACCEPTS_VIEWS, 950 B_RGB32); 951 if (fOffscreen == NULL || fOffscreen->InitCheck() != B_OK) { 952 delete fOffscreen; 953 fOffscreen = NULL; 954 return; 955 } 956 957 BView* view = new BView(frame, NULL, B_FOLLOW_NONE, B_SUBPIXEL_PRECISE); 958 view->SetViewColor(fHistoryBackgroundColor); 959 view->SetLowColor(view->ViewColor()); 960 fOffscreen->AddChild(view); 961 } 962 963 964 BView* 965 ActivityView::_OffscreenView() 966 { 967 if (fOffscreen == NULL) 968 return NULL; 969 970 return fOffscreen->ChildAt(0); 971 } 972 973 974 void 975 ActivityView::MouseDown(BPoint where) 976 { 977 int32 buttons = B_SECONDARY_MOUSE_BUTTON; 978 if (Looper() != NULL && Looper()->CurrentMessage() != NULL) 979 Looper()->CurrentMessage()->FindInt32("buttons", &buttons); 980 981 if (buttons == B_PRIMARY_MOUSE_BUTTON) { 982 fZoomPoint = where; 983 fOriginalResolution = fDrawResolution; 984 fZooming = true; 985 SetMouseEventMask(B_POINTER_EVENTS); 986 return; 987 } 988 989 BPopUpMenu *menu = new BPopUpMenu(B_EMPTY_STRING, false, false); 990 menu->SetFont(be_plain_font); 991 992 BMenu* additionalMenu = new BMenu(B_TRANSLATE("Additional items")); 993 additionalMenu->SetFont(be_plain_font); 994 995 SystemInfo info; 996 BMenuItem* item; 997 998 for (int32 i = 0; i < DataSource::CountSources(); i++) { 999 const DataSource* source = DataSource::SourceAt(i); 1000 1001 if (source->MultiCPUOnly() && info.CPUCount() == 1) 1002 continue; 1003 1004 BMessage* message = new BMessage(kMsgToggleDataSource); 1005 message->AddInt32("index", i); 1006 1007 item = new BMenuItem(source->Name(), message); 1008 if (FindDataSource(source)) 1009 item->SetMarked(true); 1010 1011 if (source->Primary()) 1012 menu->AddItem(item); 1013 else 1014 additionalMenu->AddItem(item); 1015 } 1016 1017 menu->AddItem(new BMenuItem(additionalMenu)); 1018 menu->AddSeparatorItem(); 1019 menu->AddItem(item = new BMenuItem(B_TRANSLATE("Show legend"), 1020 new BMessage(kMsgToggleLegend))); 1021 item->SetMarked(fShowLegend); 1022 1023 menu->SetTargetForItems(this); 1024 additionalMenu->SetTargetForItems(this); 1025 1026 ActivityWindow* window = dynamic_cast<ActivityWindow*>(Window()); 1027 if (window != NULL && window->ActivityViewCount() > 1) { 1028 menu->AddSeparatorItem(); 1029 BMessage* message = new BMessage(kMsgRemoveView); 1030 message->AddPointer("view", this); 1031 menu->AddItem(item = new BMenuItem(B_TRANSLATE("Remove graph"), 1032 message)); 1033 item->SetTarget(window); 1034 } 1035 1036 ConvertToScreen(&where); 1037 menu->Go(where, true, false, true); 1038 } 1039 1040 1041 void 1042 ActivityView::MouseUp(BPoint where) 1043 { 1044 fZooming = false; 1045 } 1046 1047 1048 void 1049 ActivityView::MouseMoved(BPoint where, uint32 transit, 1050 const BMessage* dragMessage) 1051 { 1052 if (!fZooming) 1053 return; 1054 1055 int32 shift = int32(where.x - fZoomPoint.x) / 25; 1056 int32 resolution; 1057 if (shift > 0) 1058 resolution = fOriginalResolution << shift; 1059 else 1060 resolution = fOriginalResolution >> -shift; 1061 1062 _UpdateResolution(resolution); 1063 } 1064 1065 1066 void 1067 ActivityView::MessageReceived(BMessage* message) 1068 { 1069 // if a color is dropped, use it as background 1070 if (message->WasDropped()) { 1071 rgb_color* color; 1072 ssize_t size; 1073 if (message->FindData("RGBColor", B_RGB_COLOR_TYPE, 0, 1074 (const void**)&color, &size) == B_OK 1075 && size == sizeof(rgb_color)) { 1076 BPoint dropPoint = message->DropPoint(); 1077 ConvertFromScreen(&dropPoint); 1078 1079 if (_HistoryFrame().Contains(dropPoint)) { 1080 fHistoryBackgroundColor = *color; 1081 Invalidate(_HistoryFrame()); 1082 } else { 1083 // check each legend color box 1084 BAutolock _(fSourcesLock); 1085 1086 BRect legendFrame = _LegendFrame(); 1087 for (int32 i = 0; i < fSources.CountItems(); i++) { 1088 BRect frame = _LegendColorFrameAt(legendFrame, i); 1089 if (frame.Contains(dropPoint)) { 1090 fSources.ItemAt(i)->SetColor(*color); 1091 Invalidate(_HistoryFrame()); 1092 Invalidate(frame); 1093 return; 1094 } 1095 } 1096 1097 if (dynamic_cast<ActivityMonitor*>(be_app) == NULL) { 1098 // allow background color change in the replicant only 1099 fLegendBackgroundColor = *color; 1100 SetLowColor(fLegendBackgroundColor); 1101 Invalidate(legendFrame); 1102 } 1103 } 1104 return; 1105 } 1106 } 1107 1108 switch (message->what) { 1109 case B_ABOUT_REQUESTED: 1110 { 1111 BAboutWindow* window = new BAboutWindow(kAppName, kSignature); 1112 1113 const char* authors[] = { 1114 "Axel Dörfler", 1115 NULL 1116 }; 1117 1118 window->AddCopyright(2008, "Haiku, Inc."); 1119 window->AddAuthors(authors); 1120 1121 window->Show(); 1122 1123 break; 1124 } 1125 case kMsgUpdateResolution: 1126 { 1127 int32 resolution; 1128 if (message->FindInt32("resolution", &resolution) != B_OK) 1129 break; 1130 1131 _UpdateResolution(resolution, false); 1132 break; 1133 } 1134 1135 case kMsgTimeIntervalUpdated: 1136 bigtime_t interval; 1137 if (message->FindInt64("interval", &interval) != B_OK) 1138 break; 1139 1140 if (interval < 10000) 1141 interval = 10000; 1142 1143 atomic_set64(&fRefreshInterval, interval); 1144 break; 1145 1146 case kMsgToggleDataSource: 1147 { 1148 int32 index; 1149 if (message->FindInt32("index", &index) != B_OK) 1150 break; 1151 1152 const DataSource* baseSource = DataSource::SourceAt(index); 1153 if (baseSource == NULL) 1154 break; 1155 1156 DataSource* source = FindDataSource(baseSource); 1157 if (source == NULL) 1158 AddDataSource(baseSource); 1159 else 1160 RemoveDataSource(baseSource); 1161 1162 Invalidate(); 1163 break; 1164 } 1165 1166 case kMsgToggleLegend: 1167 fShowLegend = !fShowLegend; 1168 Invalidate(); 1169 break; 1170 1171 case B_MOUSE_WHEEL_CHANGED: 1172 { 1173 float deltaY = 0.0f; 1174 if (message->FindFloat("be:wheel_delta_y", &deltaY) != B_OK 1175 || deltaY == 0.0f) 1176 break; 1177 1178 int32 resolution = fDrawResolution; 1179 if (deltaY > 0) 1180 resolution *= 2; 1181 else 1182 resolution /= 2; 1183 1184 _UpdateResolution(resolution); 1185 break; 1186 } 1187 1188 default: 1189 BView::MessageReceived(message); 1190 break; 1191 } 1192 } 1193 1194 1195 void 1196 ActivityView::_UpdateFrame() 1197 { 1198 #ifdef __HAIKU__ 1199 if (fLegendLayoutItem == NULL || fHistoryLayoutItem == NULL) 1200 return; 1201 1202 BRect historyFrame = fHistoryLayoutItem->Frame(); 1203 BRect legendFrame = fLegendLayoutItem->Frame(); 1204 #else 1205 BRect historyFrame = Bounds(); 1206 BRect legendFrame = Bounds(); 1207 historyFrame.bottom -= 2 * Bounds().Height() / 3; 1208 legendFrame.top += Bounds().Height() / 3; 1209 #endif 1210 MoveTo(historyFrame.left, historyFrame.top); 1211 ResizeTo(legendFrame.left + legendFrame.Width() - historyFrame.left, 1212 legendFrame.top + legendFrame.Height() - historyFrame.top); 1213 } 1214 1215 1216 BRect 1217 ActivityView::_HistoryFrame() const 1218 { 1219 BRect frame = Bounds(); 1220 1221 if (fShowLegend) { 1222 BRect legendFrame = _LegendFrame(); 1223 frame.bottom = legendFrame.top - 1; 1224 } 1225 1226 frame.InsetBy(2, 2); 1227 1228 return frame; 1229 } 1230 1231 1232 float 1233 ActivityView::_LegendHeight() const 1234 { 1235 font_height fontHeight; 1236 GetFontHeight(&fontHeight); 1237 1238 BAutolock _(fSourcesLock); 1239 1240 int32 rows = (fSources.CountItems() + 1) / 2; 1241 1242 int32 boldMargin = Parent() 1243 && (Parent()->Flags() & B_DRAW_ON_CHILDREN) != 0 ? 2 : 0; 1244 1245 return rows * (4 + ceilf(fontHeight.ascent) 1246 + ceilf(fontHeight.descent) + ceilf(fontHeight.leading)) + boldMargin; 1247 } 1248 1249 1250 BRect 1251 ActivityView::_LegendFrame() const 1252 { 1253 float height; 1254 #ifdef __HAIKU__ 1255 if (fLegendLayoutItem != NULL) 1256 height = fLegendLayoutItem->Frame().Height(); 1257 else 1258 #endif 1259 height = _LegendHeight(); 1260 1261 BRect frame = Bounds(); 1262 frame.bottom -= kDraggerSize; 1263 frame.top = frame.bottom - height; 1264 1265 return frame; 1266 } 1267 1268 1269 BRect 1270 ActivityView::_LegendFrameAt(BRect frame, int32 index) const 1271 { 1272 int32 column = index & 1; 1273 int32 row = index / 2; 1274 if (column == 0) { 1275 // Use the full width if there is only one item 1276 if (fSources.CountItems() != 1) 1277 frame.right = frame.left + floorf(frame.Width() / 2) - 5; 1278 } else 1279 frame.left = frame.right - floorf(frame.Width() / 2) + 5; 1280 1281 BAutolock _(fSourcesLock); 1282 1283 int32 rows = (fSources.CountItems() + 1) / 2; 1284 float height = floorf((frame.Height() - 5) / rows); 1285 1286 frame.top = frame.top + 5 + row * height; 1287 frame.bottom = frame.top + height - 1; 1288 1289 return frame; 1290 } 1291 1292 1293 BRect 1294 ActivityView::_LegendColorFrameAt(BRect frame, int32 index) const 1295 { 1296 frame = _LegendFrameAt(frame, index); 1297 frame.InsetBy(1, 1); 1298 frame.right = frame.left + frame.Height(); 1299 1300 return frame; 1301 } 1302 1303 1304 float 1305 ActivityView::_PositionForValue(DataSource* source, DataHistory* values, 1306 int64 value) 1307 { 1308 int64 min = source->Minimum(); 1309 int64 max = source->Maximum(); 1310 if (source->AdaptiveScale()) { 1311 int64 adaptiveMax = int64(values->MaximumValue() * 1.2); 1312 if (adaptiveMax < max) 1313 max = adaptiveMax; 1314 } 1315 1316 if (value > max) 1317 value = max; 1318 if (value < min) 1319 value = min; 1320 1321 float height = _HistoryFrame().Height(); 1322 return height - (value - min) * height / (max - min); 1323 } 1324 1325 1326 void 1327 ActivityView::_DrawHistory() 1328 { 1329 _UpdateOffscreenBitmap(); 1330 1331 BView* view = this; 1332 if (fOffscreen != NULL) { 1333 fOffscreen->Lock(); 1334 view = _OffscreenView(); 1335 } 1336 1337 BRect frame = _HistoryFrame(); 1338 BRect outerFrame = frame.InsetByCopy(-2, -2); 1339 1340 // draw the outer frame 1341 uint32 flags = BControlLook::B_BLEND_FRAME; 1342 be_control_look->DrawTextControlBorder(this, outerFrame, 1343 outerFrame, fLegendBackgroundColor, flags); 1344 1345 // convert to offscreen view if necessary 1346 if (view != this) 1347 frame.OffsetTo(B_ORIGIN); 1348 1349 view->SetLowColor(fHistoryBackgroundColor); 1350 view->FillRect(frame, B_SOLID_LOW); 1351 1352 uint32 step = 2; 1353 uint32 resolution = fDrawResolution; 1354 if (fDrawResolution > 1) { 1355 step = 1; 1356 resolution--; 1357 } 1358 1359 // We would get a negative number of steps which isn't a good idea. 1360 if (frame.IntegerWidth() <= 10) 1361 return; 1362 1363 uint32 width = frame.IntegerWidth() - 10; 1364 uint32 steps = width / step; 1365 bigtime_t timeStep = RefreshInterval() * resolution; 1366 bigtime_t now = system_time(); 1367 1368 // Draw scale 1369 // TODO: add second markers? 1370 1371 view->SetPenSize(1); 1372 1373 rgb_color scaleColor = view->LowColor(); 1374 uint32 average = (scaleColor.red + scaleColor.green + scaleColor.blue) / 3; 1375 if (average < 96) 1376 scaleColor = tint_color(scaleColor, B_LIGHTEN_2_TINT); 1377 else 1378 scaleColor = tint_color(scaleColor, B_DARKEN_2_TINT); 1379 1380 view->SetHighColor(scaleColor); 1381 view->StrokeLine(BPoint(frame.left, frame.top + frame.Height() / 2), 1382 BPoint(frame.right, frame.top + frame.Height() / 2)); 1383 1384 // Draw values 1385 1386 view->SetPenSize(1.5); 1387 BAutolock _(fSourcesLock); 1388 1389 for (uint32 i = fSources.CountItems(); i-- > 0;) { 1390 ViewHistory* viewValues = fViewValues.ItemAt(i); 1391 DataSource* source = fSources.ItemAt(i); 1392 DataHistory* values = fValues.ItemAt(i); 1393 1394 viewValues->Update(values, steps, fDrawResolution, now, timeStep, 1395 RefreshInterval()); 1396 1397 if (viewValues->Start() >= (int32)steps - 1) 1398 continue; 1399 1400 uint32 x = viewValues->Start() * step; 1401 1402 bool first = true; 1403 1404 view->SetHighColor(source->Color()); 1405 view->SetLineMode(B_BUTT_CAP, B_ROUND_JOIN); 1406 view->MovePenTo(B_ORIGIN); 1407 1408 try { 1409 view->BeginLineArray(steps - viewValues->Start() - 1); 1410 1411 BPoint prev; 1412 1413 for (uint32 j = viewValues->Start(); j < steps; x += step, j++) { 1414 float y = _PositionForValue(source, values, 1415 viewValues->ValueAt(j)); 1416 1417 if (first) { 1418 first = false; 1419 } else 1420 view->AddLine(prev, BPoint(x, y), source->Color()); 1421 1422 prev.Set(x, y); 1423 } 1424 1425 } catch (std::bad_alloc&) { 1426 // Not enough memory to allocate the line array. 1427 // TODO we could try to draw using the slower but less memory 1428 // consuming solution using StrokeLine. 1429 } 1430 1431 view->EndLineArray(); 1432 } 1433 1434 // TODO: add marks when an app started or quit 1435 view->Sync(); 1436 if (fOffscreen != NULL) { 1437 fOffscreen->Unlock(); 1438 DrawBitmap(fOffscreen, outerFrame.LeftTop()); 1439 } 1440 } 1441 1442 1443 void 1444 ActivityView::_UpdateResolution(int32 resolution, bool broadcast) 1445 { 1446 if (resolution < 1) 1447 resolution = 1; 1448 if (resolution > 128) 1449 resolution = 128; 1450 1451 if (resolution == fDrawResolution) 1452 return; 1453 1454 ActivityWindow* window = dynamic_cast<ActivityWindow*>(Window()); 1455 if (broadcast && window != NULL) { 1456 BMessage update(kMsgUpdateResolution); 1457 update.AddInt32("resolution", resolution); 1458 window->BroadcastToActivityViews(&update, this); 1459 } 1460 1461 fDrawResolution = resolution; 1462 Invalidate(); 1463 } 1464 1465 1466 void 1467 ActivityView::Draw(BRect updateRect) 1468 { 1469 _DrawHistory(); 1470 1471 if (!fShowLegend) 1472 return; 1473 1474 // draw legend 1475 BRect legendFrame = _LegendFrame(); 1476 if (LowUIColor() == B_NO_COLOR) 1477 SetLowColor(fLegendBackgroundColor); 1478 1479 BAutolock _(fSourcesLock); 1480 1481 font_height fontHeight; 1482 GetFontHeight(&fontHeight); 1483 1484 for (int32 i = 0; i < fSources.CountItems(); i++) { 1485 DataSource* source = fSources.ItemAt(i); 1486 DataHistory* values = fValues.ItemAt(i); 1487 BRect frame = _LegendFrameAt(legendFrame, i); 1488 1489 // draw color box 1490 BRect colorBox = _LegendColorFrameAt(legendFrame, i); 1491 BRect rect = colorBox; 1492 uint32 flags = BControlLook::B_BLEND_FRAME; 1493 be_control_look->DrawTextControlBorder(this, rect, 1494 rect, fLegendBackgroundColor, flags); 1495 SetHighColor(source->Color()); 1496 FillRect(rect); 1497 1498 // show current value and label 1499 float y = frame.top + ceilf(fontHeight.ascent); 1500 int64 value = values->ValueAt(values->End()); 1501 BString text; 1502 source->Print(text, value); 1503 float width = StringWidth(text.String()); 1504 1505 BString label = source->Label(); 1506 float possibleLabelWidth = frame.right - colorBox.right - 12 - width; 1507 if (ceilf(StringWidth(label.String())) > possibleLabelWidth) 1508 label = source->ShortLabel(); 1509 TruncateString(&label, B_TRUNCATE_MIDDLE, possibleLabelWidth); 1510 1511 if (be_control_look == NULL) { 1512 DrawString(label.String(), BPoint(6 + colorBox.right, y)); 1513 DrawString(text.String(), BPoint(frame.right - width, y)); 1514 } else { 1515 be_control_look->DrawLabel(this, label.String(), 1516 Parent()->ViewColor(), 0, BPoint(6 + colorBox.right, y)); 1517 be_control_look->DrawLabel(this, text.String(), 1518 Parent()->ViewColor(), 0, BPoint(frame.right - width, y)); 1519 } 1520 } 1521 } 1522 1523 1524 void 1525 ActivityView::_Refresh() 1526 { 1527 bigtime_t lastTimeout = system_time() - RefreshInterval(); 1528 BMessenger target(this); 1529 1530 while (true) { 1531 status_t status = acquire_sem_etc(fRefreshSem, 1, B_ABSOLUTE_TIMEOUT, 1532 lastTimeout + RefreshInterval()); 1533 if (status == B_OK || status == B_BAD_SEM_ID) 1534 break; 1535 if (status == B_INTERRUPTED) 1536 continue; 1537 1538 SystemInfo info(fSystemInfoHandler); 1539 lastTimeout += RefreshInterval(); 1540 1541 fSourcesLock.Lock(); 1542 1543 for (uint32 i = fSources.CountItems(); i-- > 0;) { 1544 DataSource* source = fSources.ItemAt(i); 1545 DataHistory* values = fValues.ItemAt(i); 1546 1547 int64 value = source->NextValue(info); 1548 values->AddValue(info.Time(), value); 1549 } 1550 1551 fSourcesLock.Unlock(); 1552 1553 target.SendMessage(B_INVALIDATE); 1554 } 1555 } 1556 1557 1558 /*static*/ status_t 1559 ActivityView::_RefreshThread(void* self) 1560 { 1561 ((ActivityView*)self)->_Refresh(); 1562 return B_OK; 1563 } 1564