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 619 fLastRefresh = 0; 620 fDrawResolution = 1; 621 fZooming = false; 622 623 fSystemInfoHandler = new SystemInfoHandler; 624 625 if (settings == NULL 626 || settings->FindInt64("refresh interval", &fRefreshInterval) != B_OK) 627 fRefreshInterval = kInitialRefreshInterval; 628 629 if (settings == NULL 630 || settings->FindBool("show legend", &fShowLegend) != B_OK) 631 fShowLegend = true; 632 633 if (settings == NULL) 634 return; 635 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; 642 643 const char* name; 644 for (int32 i = 0; settings->FindString("source", i, &name) == B_OK; i++) 645 AddDataSource(DataSource::FindSource(name), settings); 646 } 647 648 649 status_t 650 ActivityView::Archive(BMessage* into, bool deep) const 651 { 652 status_t status; 653 654 status = BView::Archive(into, deep); 655 if (status < B_OK) 656 return status; 657 658 status = into->AddString("add_on", kSignature); 659 if (status < B_OK) 660 return status; 661 662 status = SaveState(*into); 663 if (status < B_OK) 664 return status; 665 666 return B_OK; 667 } 668 669 670 BArchivable* 671 ActivityView::Instantiate(BMessage* archive) 672 { 673 if (!validate_instantiation(archive, "ActivityView")) 674 return NULL; 675 676 return new ActivityView(archive); 677 } 678 679 680 status_t 681 ActivityView::SaveState(BMessage& state) const 682 { 683 status_t status = state.AddBool("show legend", fShowLegend); 684 if (status != B_OK) 685 return status; 686 687 status = state.AddInt64("refresh interval", fRefreshInterval); 688 if (status != B_OK) 689 return status; 690 691 status = state.AddData("history background color", B_RGB_COLOR_TYPE, 692 &fHistoryBackgroundColor, sizeof(rgb_color)); 693 if (status != B_OK) 694 return status; 695 696 for (int32 i = 0; i < fSources.CountItems(); i++) { 697 DataSource* source = fSources.ItemAt(i); 698 699 if (!source->PerCPU() || source->CPU() == 0) 700 status = state.AddString("source", source->InternalName()); 701 if (status != B_OK) 702 return status; 703 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)); 709 } 710 return B_OK; 711 } 712 713 714 Scale* 715 ActivityView::_ScaleFor(scale_type type) 716 { 717 if (type == kNoScale) 718 return NULL; 719 720 std::map<scale_type, ::Scale*>::iterator iterator = fScales.find(type); 721 if (iterator != fScales.end()) 722 return iterator->second; 723 724 // add new scale 725 ::Scale* scale = new ::Scale(type); 726 fScales[type] = scale; 727 728 return scale; 729 } 730 731 732 #ifdef __HAIKU__ 733 BLayoutItem* 734 ActivityView::CreateHistoryLayoutItem() 735 { 736 if (fHistoryLayoutItem == NULL) 737 fHistoryLayoutItem = new HistoryLayoutItem(this); 738 739 return fHistoryLayoutItem; 740 } 741 742 743 BLayoutItem* 744 ActivityView::CreateLegendLayoutItem() 745 { 746 if (fLegendLayoutItem == NULL) 747 fLegendLayoutItem = new LegendLayoutItem(this); 748 749 return fLegendLayoutItem; 750 } 751 #endif 752 753 754 DataSource* 755 ActivityView::FindDataSource(const DataSource* search) 756 { 757 BAutolock _(fSourcesLock); 758 759 for (int32 i = fSources.CountItems(); i-- > 0;) { 760 DataSource* source = fSources.ItemAt(i); 761 if (!strcmp(source->Name(), search->Name())) 762 return source; 763 } 764 765 return NULL; 766 } 767 768 769 status_t 770 ActivityView::AddDataSource(const DataSource* source, const BMessage* state) 771 { 772 if (source == NULL) 773 return B_BAD_VALUE; 774 775 BAutolock _(fSourcesLock); 776 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; 784 } 785 } 786 if (insert > fSources.CountItems()) 787 insert = fSources.CountItems(); 788 789 // Generate DataHistory and ViewHistory objects for the source 790 // (one might need one history per CPU) 791 792 uint32 count = 1; 793 if (source->PerCPU()) { 794 SystemInfo info; 795 count = info.CPUCount(); 796 } 797 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); 802 803 ViewHistory* viewValues = new(std::nothrow) ViewHistory; 804 ListAddDeleter<ViewHistory> viewValuesDeleter(fViewValues, viewValues, 805 insert); 806 807 if (valuesDeleter.Failed() || viewValuesDeleter.Failed()) 808 return B_NO_MEMORY; 809 810 values->SetScale(_ScaleFor(source->ScaleType())); 811 812 DataSource* copy; 813 if (source->PerCPU()) 814 copy = source->CopyForCPU(i); 815 else 816 copy = source->Copy(); 817 818 ListAddDeleter<DataSource> sourceDeleter(fSources, copy, insert); 819 if (sourceDeleter.Failed()) 820 return B_NO_MEMORY; 821 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); 831 } 832 833 valuesDeleter.Detach(); 834 viewValuesDeleter.Detach(); 835 sourceDeleter.Detach(); 836 insert++; 837 } 838 839 #ifdef __HAIKU__ 840 InvalidateLayout(); 841 #endif 842 return B_OK; 843 } 844 845 846 status_t 847 ActivityView::RemoveDataSource(const DataSource* remove) 848 { 849 bool removed = false; 850 851 BAutolock _(fSourcesLock); 852 853 while (true) { 854 DataSource* source = FindDataSource(remove); 855 if (source == NULL) { 856 if (removed) 857 break; 858 return B_ENTRY_NOT_FOUND; 859 } 860 861 int32 index = fSources.IndexOf(source); 862 if (index < 0) 863 return B_ENTRY_NOT_FOUND; 864 865 fSources.RemoveItemAt(index); 866 delete source; 867 DataHistory* values = fValues.RemoveItemAt(index); 868 delete values; 869 removed = true; 870 } 871 872 #ifdef __HAIKU__ 873 InvalidateLayout(); 874 #endif 875 return B_OK; 876 } 877 878 879 void 880 ActivityView::RemoveAllDataSources() 881 { 882 BAutolock _(fSourcesLock); 883 884 fSources.MakeEmpty(); 885 fValues.MakeEmpty(); 886 } 887 888 889 void 890 ActivityView::AttachedToWindow() 891 { 892 Looper()->AddHandler(fSystemInfoHandler); 893 fSystemInfoHandler->StartWatching(); 894 895 fRefreshSem = create_sem(0, "refresh sem"); 896 fRefreshThread = spawn_thread(&_RefreshThread, "source refresh", 897 B_URGENT_DISPLAY_PRIORITY, this); 898 resume_thread(fRefreshThread); 899 900 FrameResized(Bounds().Width(), Bounds().Height()); 901 } 902 903 904 void 905 ActivityView::DetachedFromWindow() 906 { 907 fSystemInfoHandler->StopWatching(); 908 Looper()->RemoveHandler(fSystemInfoHandler); 909 910 delete_sem(fRefreshSem); 911 wait_for_thread(fRefreshThread, NULL); 912 } 913 914 915 #ifdef __HAIKU__ 916 BSize 917 ActivityView::MinSize() 918 { 919 BSize size(32, 32); 920 if (fShowLegend) 921 size.height = _LegendHeight(); 922 923 return size; 924 } 925 #endif 926 927 928 void 929 ActivityView::FrameResized(float /*width*/, float /*height*/) 930 { 931 _UpdateOffscreenBitmap(); 932 } 933 934 935 void 936 ActivityView::_UpdateOffscreenBitmap() 937 { 938 BRect frame = _HistoryFrame(); 939 frame.OffsetTo(B_ORIGIN); 940 941 if (fOffscreen != NULL && frame == fOffscreen->Bounds()) 942 return; 943 944 delete fOffscreen; 945 946 // create offscreen bitmap 947 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; 954 } 955 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); 960 } 961 962 963 BView* 964 ActivityView::_OffscreenView() 965 { 966 if (fOffscreen == NULL) 967 return NULL; 968 969 return fOffscreen->ChildAt(0); 970 } 971 972 973 void 974 ActivityView::MouseDown(BPoint where) 975 { 976 int32 buttons = B_SECONDARY_MOUSE_BUTTON; 977 if (Looper() != NULL && Looper()->CurrentMessage() != NULL) 978 Looper()->CurrentMessage()->FindInt32("buttons", &buttons); 979 980 if (buttons == B_PRIMARY_MOUSE_BUTTON) { 981 fZoomPoint = where; 982 fOriginalResolution = fDrawResolution; 983 fZooming = true; 984 SetMouseEventMask(B_POINTER_EVENTS); 985 return; 986 } 987 988 BPopUpMenu *menu = new BPopUpMenu(B_EMPTY_STRING, false, false); 989 menu->SetFont(be_plain_font); 990 991 BMenu* additionalMenu = new BMenu(B_TRANSLATE("Additional items")); 992 additionalMenu->SetFont(be_plain_font); 993 994 SystemInfo info; 995 BMenuItem* item; 996 997 for (int32 i = 0; i < DataSource::CountSources(); i++) { 998 const DataSource* source = DataSource::SourceAt(i); 999 1000 if (source->MultiCPUOnly() && info.CPUCount() == 1) 1001 continue; 1002 1003 BMessage* message = new BMessage(kMsgToggleDataSource); 1004 message->AddInt32("index", i); 1005 1006 item = new BMenuItem(source->Name(), message); 1007 if (FindDataSource(source)) 1008 item->SetMarked(true); 1009 1010 if (source->Primary()) 1011 menu->AddItem(item); 1012 else 1013 additionalMenu->AddItem(item); 1014 } 1015 1016 menu->AddItem(new BMenuItem(additionalMenu)); 1017 menu->AddSeparatorItem(); 1018 menu->AddItem(new BMenuItem(fShowLegend ? 1019 B_TRANSLATE("Hide legend") : B_TRANSLATE("Show legend"), 1020 new BMessage(kMsgToggleLegend))); 1021 1022 menu->SetTargetForItems(this); 1023 additionalMenu->SetTargetForItems(this); 1024 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); 1033 } 1034 1035 ConvertToScreen(&where); 1036 menu->Go(where, true, false, true); 1037 } 1038 1039 1040 void 1041 ActivityView::MouseUp(BPoint where) 1042 { 1043 fZooming = false; 1044 } 1045 1046 1047 void 1048 ActivityView::MouseMoved(BPoint where, uint32 transit, 1049 const BMessage* dragMessage) 1050 { 1051 if (!fZooming) 1052 return; 1053 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; 1060 1061 _UpdateResolution(resolution); 1062 } 1063 1064 1065 void 1066 ActivityView::MessageReceived(BMessage* message) 1067 { 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); 1077 1078 if (_HistoryFrame().Contains(dropPoint)) { 1079 fHistoryBackgroundColor = *color; 1080 Invalidate(_HistoryFrame()); 1081 } else { 1082 // check each legend color box 1083 BAutolock _(fSourcesLock); 1084 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; 1093 } 1094 } 1095 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); 1101 } 1102 } 1103 return; 1104 } 1105 } 1106 1107 switch (message->what) { 1108 case B_ABOUT_REQUESTED: 1109 { 1110 BAboutWindow* window = new BAboutWindow(kAppName, kSignature); 1111 1112 const char* authors[] = { 1113 "Axel Dörfler", 1114 NULL 1115 }; 1116 1117 window->AddCopyright(2008, "Haiku, Inc."); 1118 window->AddAuthors(authors); 1119 1120 window->Show(); 1121 1122 break; 1123 } 1124 case kMsgUpdateResolution: 1125 { 1126 int32 resolution; 1127 if (message->FindInt32("resolution", &resolution) != B_OK) 1128 break; 1129 1130 _UpdateResolution(resolution, false); 1131 break; 1132 } 1133 1134 case kMsgTimeIntervalUpdated: 1135 bigtime_t interval; 1136 if (message->FindInt64("interval", &interval) != B_OK) 1137 break; 1138 1139 if (interval < 10000) 1140 interval = 10000; 1141 1142 atomic_set64(&fRefreshInterval, interval); 1143 break; 1144 1145 case kMsgToggleDataSource: 1146 { 1147 int32 index; 1148 if (message->FindInt32("index", &index) != B_OK) 1149 break; 1150 1151 const DataSource* baseSource = DataSource::SourceAt(index); 1152 if (baseSource == NULL) 1153 break; 1154 1155 DataSource* source = FindDataSource(baseSource); 1156 if (source == NULL) 1157 AddDataSource(baseSource); 1158 else 1159 RemoveDataSource(baseSource); 1160 1161 Invalidate(); 1162 break; 1163 } 1164 1165 case kMsgToggleLegend: 1166 fShowLegend = !fShowLegend; 1167 Invalidate(); 1168 break; 1169 1170 case B_MOUSE_WHEEL_CHANGED: 1171 { 1172 float deltaY = 0.0f; 1173 if (message->FindFloat("be:wheel_delta_y", &deltaY) != B_OK 1174 || deltaY == 0.0f) 1175 break; 1176 1177 int32 resolution = fDrawResolution; 1178 if (deltaY > 0) 1179 resolution *= 2; 1180 else 1181 resolution /= 2; 1182 1183 _UpdateResolution(resolution); 1184 break; 1185 } 1186 1187 default: 1188 BView::MessageReceived(message); 1189 break; 1190 } 1191 } 1192 1193 1194 void 1195 ActivityView::_UpdateFrame() 1196 { 1197 #ifdef __HAIKU__ 1198 if (fLegendLayoutItem == NULL || fHistoryLayoutItem == NULL) 1199 return; 1200 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); 1212 } 1213 1214 1215 BRect 1216 ActivityView::_HistoryFrame() const 1217 { 1218 BRect frame = Bounds(); 1219 1220 if (fShowLegend) { 1221 BRect legendFrame = _LegendFrame(); 1222 frame.bottom = legendFrame.top - 1; 1223 } 1224 1225 frame.InsetBy(2, 2); 1226 1227 return frame; 1228 } 1229 1230 1231 float 1232 ActivityView::_LegendHeight() const 1233 { 1234 font_height fontHeight; 1235 GetFontHeight(&fontHeight); 1236 1237 BAutolock _(fSourcesLock); 1238 1239 int32 rows = (fSources.CountItems() + 1) / 2; 1240 1241 int32 boldMargin = Parent() 1242 && (Parent()->Flags() & B_DRAW_ON_CHILDREN) != 0 ? 2 : 0; 1243 1244 return rows * (4 + ceilf(fontHeight.ascent) 1245 + ceilf(fontHeight.descent) + ceilf(fontHeight.leading)) + boldMargin; 1246 } 1247 1248 1249 BRect 1250 ActivityView::_LegendFrame() const 1251 { 1252 float height; 1253 #ifdef __HAIKU__ 1254 if (fLegendLayoutItem != NULL) 1255 height = fLegendLayoutItem->Frame().Height(); 1256 else 1257 #endif 1258 height = _LegendHeight(); 1259 1260 BRect frame = Bounds(); 1261 frame.bottom -= kDraggerSize; 1262 frame.top = frame.bottom - height; 1263 1264 return frame; 1265 } 1266 1267 1268 BRect 1269 ActivityView::_LegendFrameAt(BRect frame, int32 index) const 1270 { 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; 1279 1280 BAutolock _(fSourcesLock); 1281 1282 int32 rows = (fSources.CountItems() + 1) / 2; 1283 float height = floorf((frame.Height() - 5) / rows); 1284 1285 frame.top = frame.top + 5 + row * height; 1286 frame.bottom = frame.top + height - 1; 1287 1288 return frame; 1289 } 1290 1291 1292 BRect 1293 ActivityView::_LegendColorFrameAt(BRect frame, int32 index) const 1294 { 1295 frame = _LegendFrameAt(frame, index); 1296 frame.InsetBy(1, 1); 1297 frame.right = frame.left + frame.Height(); 1298 1299 return frame; 1300 } 1301 1302 1303 float 1304 ActivityView::_PositionForValue(DataSource* source, DataHistory* values, 1305 int64 value) 1306 { 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; 1313 } 1314 1315 if (value > max) 1316 value = max; 1317 if (value < min) 1318 value = min; 1319 1320 float height = _HistoryFrame().Height(); 1321 return height - (value - min) * height / (max - min); 1322 } 1323 1324 1325 void 1326 ActivityView::_DrawHistory(bool drawBackground) 1327 { 1328 _UpdateOffscreenBitmap(); 1329 1330 BView* view = this; 1331 if (fOffscreen != NULL) { 1332 fOffscreen->Lock(); 1333 view = _OffscreenView(); 1334 } 1335 1336 BRect frame = _HistoryFrame(); 1337 BRect outerFrame = frame.InsetByCopy(-2, -2); 1338 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); 1345 1346 // convert to offscreen view if necessary 1347 if (view != this) 1348 frame.OffsetTo(B_ORIGIN); 1349 1350 view->SetLowColor(fHistoryBackgroundColor); 1351 view->FillRect(frame, B_SOLID_LOW); 1352 1353 uint32 step = 2; 1354 uint32 resolution = fDrawResolution; 1355 if (fDrawResolution > 1) { 1356 step = 1; 1357 resolution--; 1358 } 1359 1360 // We would get a negative number of steps which isn't a good idea. 1361 if (frame.IntegerWidth() <= 10) 1362 return; 1363 1364 uint32 width = frame.IntegerWidth() - 10; 1365 uint32 steps = width / step; 1366 bigtime_t timeStep = RefreshInterval() * resolution; 1367 bigtime_t now = system_time(); 1368 1369 // Draw scale 1370 // TODO: add second markers? 1371 1372 view->SetPenSize(1); 1373 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); 1380 1381 view->SetHighColor(scaleColor); 1382 view->StrokeLine(BPoint(frame.left, frame.top + frame.Height() / 2), 1383 BPoint(frame.right, frame.top + frame.Height() / 2)); 1384 1385 // Draw values 1386 1387 view->SetPenSize(1.5); 1388 BAutolock _(fSourcesLock); 1389 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); 1394 1395 viewValues->Update(values, steps, fDrawResolution, now, timeStep, 1396 RefreshInterval()); 1397 1398 if (viewValues->Start() >= (int32)steps - 1) 1399 continue; 1400 1401 uint32 x = viewValues->Start() * step; 1402 1403 bool first = true; 1404 1405 view->SetHighColor(source->Color()); 1406 view->SetLineMode(B_BUTT_CAP, B_ROUND_JOIN); 1407 view->MovePenTo(B_ORIGIN); 1408 1409 try { 1410 view->BeginLineArray(steps - viewValues->Start() - 1); 1411 1412 BPoint prev; 1413 1414 for (uint32 j = viewValues->Start(); j < steps; x += step, j++) { 1415 float y = _PositionForValue(source, values, 1416 viewValues->ValueAt(j)); 1417 1418 if (first) { 1419 first = false; 1420 } else 1421 view->AddLine(prev, BPoint(x, y), source->Color()); 1422 1423 prev.Set(x, y); 1424 } 1425 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. 1430 } 1431 1432 view->EndLineArray(); 1433 } 1434 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()); 1440 } 1441 } 1442 1443 1444 void 1445 ActivityView::_UpdateResolution(int32 resolution, bool broadcast) 1446 { 1447 if (resolution < 1) 1448 resolution = 1; 1449 if (resolution > 128) 1450 resolution = 128; 1451 1452 if (resolution == fDrawResolution) 1453 return; 1454 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); 1460 } 1461 1462 fDrawResolution = resolution; 1463 Invalidate(); 1464 } 1465 1466 1467 void 1468 ActivityView::Draw(BRect updateRect) 1469 { 1470 bool drawBackground = true; 1471 if (Parent() && (Parent()->Flags() & B_DRAW_ON_CHILDREN) != 0) 1472 drawBackground = false; 1473 1474 _DrawHistory(drawBackground); 1475 1476 if (!fShowLegend) 1477 return; 1478 1479 // draw legend 1480 BRect legendFrame = _LegendFrame(); 1481 if (LowUIColor() == B_NO_COLOR) 1482 SetLowColor(fLegendBackgroundColor); 1483 1484 if (drawBackground) { 1485 BRect backgroundFrame(legendFrame); 1486 backgroundFrame.bottom += kDraggerSize; 1487 FillRect(backgroundFrame, B_SOLID_LOW); 1488 } 1489 1490 BAutolock _(fSourcesLock); 1491 1492 font_height fontHeight; 1493 GetFontHeight(&fontHeight); 1494 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); 1499 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); 1508 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()); 1515 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); 1522 1523 if (drawBackground) 1524 SetHighColor(ui_color(B_PANEL_TEXT_COLOR)); 1525 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)); 1534 } 1535 } 1536 } 1537 1538 1539 void 1540 ActivityView::_Refresh() 1541 { 1542 bigtime_t lastTimeout = system_time() - RefreshInterval(); 1543 BMessenger target(this); 1544 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; 1552 1553 SystemInfo info(fSystemInfoHandler); 1554 lastTimeout += RefreshInterval(); 1555 1556 fSourcesLock.Lock(); 1557 1558 for (uint32 i = fSources.CountItems(); i-- > 0;) { 1559 DataSource* source = fSources.ItemAt(i); 1560 DataHistory* values = fValues.ItemAt(i); 1561 1562 int64 value = source->NextValue(info); 1563 values->AddValue(info.Time(), value); 1564 } 1565 1566 fSourcesLock.Unlock(); 1567 1568 target.SendMessage(B_INVALIDATE); 1569 } 1570 } 1571 1572 1573 /*static*/ status_t 1574 ActivityView::_RefreshThread(void* self) 1575 { 1576 ((ActivityView*)self)->_Refresh(); 1577 return B_OK; 1578 } 1579