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