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 SetLowColor(ui_color(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 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(bool drawBackground) 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 = 0; 1342 if (!drawBackground) 1343 flags |= BControlLook::B_BLEND_FRAME; 1344 be_control_look->DrawTextControlBorder(this, outerFrame, 1345 outerFrame, fLegendBackgroundColor, flags); 1346 1347 // convert to offscreen view if necessary 1348 if (view != this) 1349 frame.OffsetTo(B_ORIGIN); 1350 1351 view->SetLowColor(fHistoryBackgroundColor); 1352 view->FillRect(frame, B_SOLID_LOW); 1353 1354 uint32 step = 2; 1355 uint32 resolution = fDrawResolution; 1356 if (fDrawResolution > 1) { 1357 step = 1; 1358 resolution--; 1359 } 1360 1361 // We would get a negative number of steps which isn't a good idea. 1362 if (frame.IntegerWidth() <= 10) 1363 return; 1364 1365 uint32 width = frame.IntegerWidth() - 10; 1366 uint32 steps = width / step; 1367 bigtime_t timeStep = RefreshInterval() * resolution; 1368 bigtime_t now = system_time(); 1369 1370 // Draw scale 1371 // TODO: add second markers? 1372 1373 view->SetPenSize(1); 1374 1375 rgb_color scaleColor = view->LowColor(); 1376 uint32 average = (scaleColor.red + scaleColor.green + scaleColor.blue) / 3; 1377 if (average < 96) 1378 scaleColor = tint_color(scaleColor, B_LIGHTEN_2_TINT); 1379 else 1380 scaleColor = tint_color(scaleColor, B_DARKEN_2_TINT); 1381 1382 view->SetHighColor(scaleColor); 1383 view->StrokeLine(BPoint(frame.left, frame.top + frame.Height() / 2), 1384 BPoint(frame.right, frame.top + frame.Height() / 2)); 1385 1386 // Draw values 1387 1388 view->SetPenSize(1.5); 1389 BAutolock _(fSourcesLock); 1390 1391 for (uint32 i = fSources.CountItems(); i-- > 0;) { 1392 ViewHistory* viewValues = fViewValues.ItemAt(i); 1393 DataSource* source = fSources.ItemAt(i); 1394 DataHistory* values = fValues.ItemAt(i); 1395 1396 viewValues->Update(values, steps, fDrawResolution, now, timeStep, 1397 RefreshInterval()); 1398 1399 if (viewValues->Start() >= (int32)steps - 1) 1400 continue; 1401 1402 uint32 x = viewValues->Start() * step; 1403 1404 bool first = true; 1405 1406 view->SetHighColor(source->Color()); 1407 view->SetLineMode(B_BUTT_CAP, B_ROUND_JOIN); 1408 view->MovePenTo(B_ORIGIN); 1409 1410 try { 1411 view->BeginLineArray(steps - viewValues->Start() - 1); 1412 1413 BPoint prev; 1414 1415 for (uint32 j = viewValues->Start(); j < steps; x += step, j++) { 1416 float y = _PositionForValue(source, values, 1417 viewValues->ValueAt(j)); 1418 1419 if (first) { 1420 first = false; 1421 } else 1422 view->AddLine(prev, BPoint(x, y), source->Color()); 1423 1424 prev.Set(x, y); 1425 } 1426 1427 } catch (std::bad_alloc) { 1428 // Not enough memory to allocate the line array. 1429 // TODO we could try to draw using the slower but less memory 1430 // consuming solution using StrokeLine. 1431 } 1432 1433 view->EndLineArray(); 1434 } 1435 1436 // TODO: add marks when an app started or quit 1437 view->Sync(); 1438 if (fOffscreen != NULL) { 1439 fOffscreen->Unlock(); 1440 DrawBitmap(fOffscreen, outerFrame.LeftTop()); 1441 } 1442 } 1443 1444 1445 void 1446 ActivityView::_UpdateResolution(int32 resolution, bool broadcast) 1447 { 1448 if (resolution < 1) 1449 resolution = 1; 1450 if (resolution > 128) 1451 resolution = 128; 1452 1453 if (resolution == fDrawResolution) 1454 return; 1455 1456 ActivityWindow* window = dynamic_cast<ActivityWindow*>(Window()); 1457 if (broadcast && window != NULL) { 1458 BMessage update(kMsgUpdateResolution); 1459 update.AddInt32("resolution", resolution); 1460 window->BroadcastToActivityViews(&update, this); 1461 } 1462 1463 fDrawResolution = resolution; 1464 Invalidate(); 1465 } 1466 1467 1468 void 1469 ActivityView::Draw(BRect updateRect) 1470 { 1471 bool drawBackground = true; 1472 if (Parent() && (Parent()->Flags() & B_DRAW_ON_CHILDREN) != 0) 1473 drawBackground = false; 1474 1475 _DrawHistory(drawBackground); 1476 1477 if (!fShowLegend) 1478 return; 1479 1480 // draw legend 1481 BRect legendFrame = _LegendFrame(); 1482 SetLowColor(fLegendBackgroundColor); 1483 if (drawBackground) { 1484 BRect backgroundFrame(legendFrame); 1485 backgroundFrame.bottom += kDraggerSize; 1486 FillRect(backgroundFrame, B_SOLID_LOW); 1487 } 1488 1489 BAutolock _(fSourcesLock); 1490 1491 font_height fontHeight; 1492 GetFontHeight(&fontHeight); 1493 1494 for (int32 i = 0; i < fSources.CountItems(); i++) { 1495 DataSource* source = fSources.ItemAt(i); 1496 DataHistory* values = fValues.ItemAt(i); 1497 BRect frame = _LegendFrameAt(legendFrame, i); 1498 1499 // draw color box 1500 BRect colorBox = _LegendColorFrameAt(legendFrame, i); 1501 BRect rect = colorBox; 1502 uint32 flags = BControlLook::B_BLEND_FRAME; 1503 be_control_look->DrawTextControlBorder(this, rect, 1504 rect, fLegendBackgroundColor, flags); 1505 SetHighColor(source->Color()); 1506 FillRect(rect); 1507 1508 // show current value and label 1509 float y = frame.top + ceilf(fontHeight.ascent); 1510 int64 value = values->ValueAt(values->End()); 1511 BString text; 1512 source->Print(text, value); 1513 float width = StringWidth(text.String()); 1514 1515 BString label = source->Label(); 1516 float possibleLabelWidth = frame.right - colorBox.right - 12 - width; 1517 // TODO: TruncateString() is broken... remove + 5 when fixed! 1518 if (ceilf(StringWidth(label.String()) + 5) > possibleLabelWidth) 1519 label = source->ShortLabel(); 1520 TruncateString(&label, B_TRUNCATE_MIDDLE, possibleLabelWidth); 1521 1522 if (drawBackground) 1523 SetHighColor(ui_color(B_CONTROL_TEXT_COLOR)); 1524 1525 if (be_control_look == NULL) { 1526 DrawString(label.String(), BPoint(6 + colorBox.right, y)); 1527 DrawString(text.String(), BPoint(frame.right - width, y)); 1528 } else { 1529 be_control_look->DrawLabel(this, label.String(), 1530 Parent()->ViewColor(), 0, BPoint(6 + colorBox.right, y)); 1531 be_control_look->DrawLabel(this, text.String(), 1532 Parent()->ViewColor(), 0, BPoint(frame.right - width, y)); 1533 } 1534 } 1535 } 1536 1537 1538 void 1539 ActivityView::_Refresh() 1540 { 1541 bigtime_t lastTimeout = system_time() - RefreshInterval(); 1542 BMessenger target(this); 1543 1544 while (true) { 1545 status_t status = acquire_sem_etc(fRefreshSem, 1, B_ABSOLUTE_TIMEOUT, 1546 lastTimeout + RefreshInterval()); 1547 if (status == B_OK || status == B_BAD_SEM_ID) 1548 break; 1549 if (status == B_INTERRUPTED) 1550 continue; 1551 1552 SystemInfo info(fSystemInfoHandler); 1553 lastTimeout += RefreshInterval(); 1554 1555 fSourcesLock.Lock(); 1556 1557 for (uint32 i = fSources.CountItems(); i-- > 0;) { 1558 DataSource* source = fSources.ItemAt(i); 1559 DataHistory* values = fValues.ItemAt(i); 1560 1561 int64 value = source->NextValue(info); 1562 values->AddValue(info.Time(), value); 1563 } 1564 1565 fSourcesLock.Unlock(); 1566 1567 target.SendMessage(B_INVALIDATE); 1568 } 1569 } 1570 1571 1572 /*static*/ status_t 1573 ActivityView::_RefreshThread(void* self) 1574 { 1575 ((ActivityView*)self)->_Refresh(); 1576 return B_OK; 1577 } 1578