1 /* 2 * Copyright 2008-2009, 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 <AbstractLayoutItem.h> 16 # include <ControlLook.h> 17 #endif 18 #include <Application.h> 19 #include <Autolock.h> 20 #include <Bitmap.h> 21 #include <Catalog.h> 22 #include <Dragger.h> 23 #include <MenuItem.h> 24 #include <MessageRunner.h> 25 #include <PopUpMenu.h> 26 #include <Shape.h> 27 #include <String.h> 28 29 #include "ActivityMonitor.h" 30 #include "ActivityWindow.h" 31 #include "SettingsWindow.h" 32 #include "SystemInfo.h" 33 #include "SystemInfoHandler.h" 34 35 #undef B_TRANSLATION_CONTEXT 36 #define B_TRANSLATION_CONTEXT "ActivityView" 37 38 template<typename ObjectType> 39 class ListAddDeleter { 40 public: 41 ListAddDeleter(BObjectList<ObjectType>& list, ObjectType* object, 42 int32 spot) 43 : 44 fList(list), 45 fObject(object) 46 { 47 if (fObject != NULL && !fList.AddItem(fObject, spot)) { 48 delete fObject; 49 fObject = NULL; 50 } 51 } 52 53 ~ListAddDeleter() 54 { 55 if (fObject != NULL) { 56 fList.RemoveItem(fObject); 57 delete fObject; 58 } 59 } 60 61 bool Failed() const 62 { 63 return fObject == NULL; 64 } 65 66 void Detach() 67 { 68 fObject = NULL; 69 } 70 71 private: 72 BObjectList<ObjectType>& fList; 73 ObjectType* fObject; 74 }; 75 76 77 /*! This class manages the scale of a history with a dynamic scale. 78 Every history value will be input via Update(), and the minimum/maximum 79 is computed from that. 80 */ 81 class Scale { 82 public: 83 Scale(scale_type type); 84 85 int64 MinimumValue() const { return fMinimumValue; } 86 int64 MaximumValue() const { return fMaximumValue; } 87 88 void Update(int64 value); 89 90 private: 91 scale_type fType; 92 int64 fMinimumValue; 93 int64 fMaximumValue; 94 bool fInitialized; 95 }; 96 97 /*! Stores the interpolated on screen view values. This is done so that the 98 interpolation is fixed, and does not change when being scrolled. 99 100 We could also just do this by making sure we always ask for the same 101 interval only, but this way we also save the interpolation. 102 */ 103 class ViewHistory { 104 public: 105 ViewHistory(); 106 107 int64 ValueAt(int32 x); 108 109 int32 Start() const 110 { return fValues.Size() 111 - fValues.CountItems(); } 112 113 void Update(DataHistory* history, int32 width, 114 int32 resolution, bigtime_t toTime, 115 bigtime_t step, bigtime_t refresh); 116 117 private: 118 CircularBuffer<int64> fValues; 119 int32 fResolution; 120 bigtime_t fRefresh; 121 bigtime_t fLastTime; 122 }; 123 124 struct data_item { 125 bigtime_t time; 126 int64 value; 127 }; 128 129 #ifdef __HAIKU__ 130 class ActivityView::HistoryLayoutItem : public BAbstractLayoutItem { 131 public: 132 HistoryLayoutItem(ActivityView* parent); 133 134 virtual bool IsVisible(); 135 virtual void SetVisible(bool visible); 136 137 virtual BRect Frame(); 138 virtual void SetFrame(BRect frame); 139 140 virtual BView* View(); 141 142 virtual BSize BasePreferredSize(); 143 144 private: 145 ActivityView* fParent; 146 BRect fFrame; 147 }; 148 149 class ActivityView::LegendLayoutItem : public BAbstractLayoutItem { 150 public: 151 LegendLayoutItem(ActivityView* parent); 152 153 virtual bool IsVisible(); 154 virtual void SetVisible(bool visible); 155 156 virtual BRect Frame(); 157 virtual void SetFrame(BRect frame); 158 159 virtual BView* View(); 160 161 virtual BSize BaseMinSize(); 162 virtual BSize BaseMaxSize(); 163 virtual BSize BasePreferredSize(); 164 virtual BAlignment BaseAlignment(); 165 166 private: 167 ActivityView* fParent; 168 BRect fFrame; 169 }; 170 #endif 171 172 const bigtime_t kInitialRefreshInterval = 250000LL; 173 174 const uint32 kMsgToggleDataSource = 'tgds'; 175 const uint32 kMsgToggleLegend = 'tglg'; 176 const uint32 kMsgUpdateResolution = 'ures'; 177 178 extern const char* kSignature; 179 180 181 Scale::Scale(scale_type type) 182 : 183 fType(type), 184 fMinimumValue(0), 185 fMaximumValue(0), 186 fInitialized(false) 187 { 188 } 189 190 191 void 192 Scale::Update(int64 value) 193 { 194 if (!fInitialized || fMinimumValue > value) 195 fMinimumValue = value; 196 if (!fInitialized || fMaximumValue < value) 197 fMaximumValue = value; 198 199 fInitialized = true; 200 } 201 202 203 // #pragma mark - 204 205 206 ViewHistory::ViewHistory() 207 : 208 fValues(1), 209 fResolution(-1), 210 fRefresh(-1), 211 fLastTime(0) 212 { 213 } 214 215 216 int64 217 ViewHistory::ValueAt(int32 x) 218 { 219 int64* value = fValues.ItemAt(x - Start()); 220 if (value != NULL) 221 return *value; 222 223 return 0; 224 } 225 226 227 void 228 ViewHistory::Update(DataHistory* history, int32 width, int32 resolution, 229 bigtime_t toTime, bigtime_t step, bigtime_t refresh) 230 { 231 if (width > 16384) { 232 // ignore this - it seems the view hasn't been layouted yet 233 return; 234 } 235 236 // Check if we need to invalidate the existing values 237 if ((int32)fValues.Size() != width 238 || fResolution != resolution 239 || fRefresh != refresh) { 240 fValues.SetSize(width); 241 fResolution = resolution; 242 fRefresh = refresh; 243 fLastTime = 0; 244 } 245 246 // Compute how many new values we need to retrieve 247 if (fLastTime < history->Start()) 248 fLastTime = history->Start(); 249 if (fLastTime > history->End()) 250 return; 251 252 int32 updateWidth = int32((toTime - fLastTime) / step); 253 if (updateWidth < 1) 254 return; 255 256 if (updateWidth > (int32)fValues.Size()) { 257 updateWidth = fValues.Size(); 258 fLastTime = toTime - updateWidth * step; 259 } 260 261 for (int32 i = 0; i < updateWidth; i++) { 262 int64 value = history->ValueAt(fLastTime += step); 263 264 if (step > refresh) { 265 uint32 count = 1; 266 for (bigtime_t offset = refresh; offset < step; offset += refresh) { 267 // TODO: handle int64 overflow correctly! 268 value += history->ValueAt(fLastTime + offset); 269 count++; 270 } 271 value /= count; 272 } 273 274 fValues.AddItem(value); 275 } 276 } 277 278 279 // #pragma mark - 280 281 282 DataHistory::DataHistory(bigtime_t memorize, bigtime_t interval) 283 : 284 fBuffer(10000), 285 fMinimumValue(0), 286 fMaximumValue(0), 287 fRefreshInterval(interval), 288 fLastIndex(-1), 289 fScale(NULL) 290 { 291 } 292 293 294 DataHistory::~DataHistory() 295 { 296 } 297 298 299 void 300 DataHistory::AddValue(bigtime_t time, int64 value) 301 { 302 if (fBuffer.IsEmpty() || fMaximumValue < value) 303 fMaximumValue = value; 304 if (fBuffer.IsEmpty() || fMinimumValue > value) 305 fMinimumValue = value; 306 if (fScale != NULL) 307 fScale->Update(value); 308 309 data_item item = {time, value}; 310 fBuffer.AddItem(item); 311 } 312 313 314 int64 315 DataHistory::ValueAt(bigtime_t time) 316 { 317 int32 left = 0; 318 int32 right = fBuffer.CountItems() - 1; 319 data_item* item = NULL; 320 321 while (left <= right) { 322 int32 index = (left + right) / 2; 323 item = fBuffer.ItemAt(index); 324 325 if (item->time > time) { 326 // search in left part 327 right = index - 1; 328 } else { 329 data_item* nextItem = fBuffer.ItemAt(index + 1); 330 if (nextItem == NULL) 331 return item->value; 332 if (nextItem->time > time) { 333 // found item 334 int64 value = item->value; 335 value += int64(double(nextItem->value - value) 336 / (nextItem->time - item->time) * (time - item->time)); 337 return value; 338 } 339 340 // search in right part 341 left = index + 1; 342 } 343 } 344 345 return 0; 346 } 347 348 349 int64 350 DataHistory::MaximumValue() const 351 { 352 if (fScale != NULL) 353 return fScale->MaximumValue(); 354 355 return fMaximumValue; 356 } 357 358 359 int64 360 DataHistory::MinimumValue() const 361 { 362 if (fScale != NULL) 363 return fScale->MinimumValue(); 364 365 return fMinimumValue; 366 } 367 368 369 bigtime_t 370 DataHistory::Start() const 371 { 372 if (fBuffer.CountItems() == 0) 373 return 0; 374 375 return fBuffer.ItemAt(0)->time; 376 } 377 378 379 bigtime_t 380 DataHistory::End() const 381 { 382 if (fBuffer.CountItems() == 0) 383 return 0; 384 385 return fBuffer.ItemAt(fBuffer.CountItems() - 1)->time; 386 } 387 388 389 void 390 DataHistory::SetRefreshInterval(bigtime_t interval) 391 { 392 // TODO: adjust buffer size 393 } 394 395 396 void 397 DataHistory::SetScale(Scale* scale) 398 { 399 fScale = scale; 400 } 401 402 403 // #pragma mark - 404 405 406 #ifdef __HAIKU__ 407 ActivityView::HistoryLayoutItem::HistoryLayoutItem(ActivityView* parent) 408 : 409 fParent(parent), 410 fFrame() 411 { 412 } 413 414 415 bool 416 ActivityView::HistoryLayoutItem::IsVisible() 417 { 418 return !fParent->IsHidden(fParent); 419 } 420 421 422 void 423 ActivityView::HistoryLayoutItem::SetVisible(bool visible) 424 { 425 // not allowed 426 } 427 428 429 BRect 430 ActivityView::HistoryLayoutItem::Frame() 431 { 432 return fFrame; 433 } 434 435 436 void 437 ActivityView::HistoryLayoutItem::SetFrame(BRect frame) 438 { 439 fFrame = frame; 440 fParent->_UpdateFrame(); 441 } 442 443 444 BView* 445 ActivityView::HistoryLayoutItem::View() 446 { 447 return fParent; 448 } 449 450 451 BSize 452 ActivityView::HistoryLayoutItem::BasePreferredSize() 453 { 454 BSize size(BaseMaxSize()); 455 return size; 456 } 457 458 459 // #pragma mark - 460 461 462 ActivityView::LegendLayoutItem::LegendLayoutItem(ActivityView* parent) 463 : 464 fParent(parent), 465 fFrame() 466 { 467 } 468 469 470 bool 471 ActivityView::LegendLayoutItem::IsVisible() 472 { 473 return !fParent->IsHidden(fParent); 474 } 475 476 477 void 478 ActivityView::LegendLayoutItem::SetVisible(bool visible) 479 { 480 // not allowed 481 } 482 483 484 BRect 485 ActivityView::LegendLayoutItem::Frame() 486 { 487 return fFrame; 488 } 489 490 491 void 492 ActivityView::LegendLayoutItem::SetFrame(BRect frame) 493 { 494 fFrame = frame; 495 fParent->_UpdateFrame(); 496 } 497 498 499 BView* 500 ActivityView::LegendLayoutItem::View() 501 { 502 return fParent; 503 } 504 505 506 BSize 507 ActivityView::LegendLayoutItem::BaseMinSize() 508 { 509 // TODO: Cache the info. Might be too expensive for this call. 510 BSize size; 511 size.width = 80; 512 size.height = fParent->_LegendHeight(); 513 514 return size; 515 } 516 517 518 BSize 519 ActivityView::LegendLayoutItem::BaseMaxSize() 520 { 521 BSize size(BaseMinSize()); 522 size.width = B_SIZE_UNLIMITED; 523 return size; 524 } 525 526 527 BSize 528 ActivityView::LegendLayoutItem::BasePreferredSize() 529 { 530 BSize size(BaseMinSize()); 531 return size; 532 } 533 534 535 BAlignment 536 ActivityView::LegendLayoutItem::BaseAlignment() 537 { 538 return BAlignment(B_ALIGN_USE_FULL_WIDTH, B_ALIGN_USE_FULL_HEIGHT); 539 } 540 #endif 541 542 543 // #pragma mark - 544 545 546 const rgb_color kWhite = (rgb_color){255, 255, 255, 255}; 547 const rgb_color kBlack = (rgb_color){0, 0, 0, 255}; 548 const float kDraggerSize = 7; 549 550 551 ActivityView::ActivityView(BRect frame, const char* name, 552 const BMessage* settings, uint32 resizingMode) 553 : BView(frame, name, resizingMode, 554 B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE | B_FRAME_EVENTS), 555 fSourcesLock("data sources") 556 { 557 _Init(settings); 558 559 BRect rect(Bounds()); 560 rect.top = rect.bottom - kDraggerSize; 561 rect.left = rect.right - kDraggerSize; 562 BDragger* dragger = new BDragger(rect, this, 563 B_FOLLOW_RIGHT | B_FOLLOW_BOTTOM); 564 AddChild(dragger); 565 } 566 567 568 ActivityView::ActivityView(const char* name, const BMessage* settings) 569 #ifdef __HAIKU__ 570 : BView(name, B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE | B_FRAME_EVENTS), 571 #else 572 : BView(BRect(0, 0, 300, 200), name, B_FOLLOW_NONE, 573 B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE | B_FRAME_EVENTS), 574 #endif 575 fSourcesLock("data sources") 576 { 577 SetLowColor(ui_color(B_PANEL_BACKGROUND_COLOR)); 578 579 _Init(settings); 580 581 BRect rect(Bounds()); 582 rect.top = rect.bottom - kDraggerSize; 583 rect.left = rect.right - kDraggerSize; 584 BDragger* dragger = new BDragger(rect, this, 585 B_FOLLOW_RIGHT | B_FOLLOW_BOTTOM); 586 AddChild(dragger); 587 } 588 589 590 ActivityView::ActivityView(BMessage* archive) 591 : BView(archive) 592 { 593 _Init(archive); 594 } 595 596 597 ActivityView::~ActivityView() 598 { 599 delete fOffscreen; 600 delete fSystemInfoHandler; 601 } 602 603 604 void 605 ActivityView::_Init(const BMessage* settings) 606 { 607 fHistoryBackgroundColor = (rgb_color){255, 255, 240}; 608 fLegendBackgroundColor = LowColor(); 609 // the low color is restored by the BView unarchiving 610 fOffscreen = NULL; 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::FrameResized(float /*width*/, float /*height*/) 928 { 929 _UpdateOffscreenBitmap(); 930 } 931 932 933 void 934 ActivityView::_UpdateOffscreenBitmap() 935 { 936 BRect frame = _HistoryFrame(); 937 frame.OffsetTo(B_ORIGIN); 938 939 if (fOffscreen != NULL && frame == fOffscreen->Bounds()) 940 return; 941 942 delete fOffscreen; 943 944 // create offscreen bitmap 945 946 fOffscreen = new(std::nothrow) BBitmap(frame, B_BITMAP_ACCEPTS_VIEWS, 947 B_RGB32); 948 if (fOffscreen == NULL || fOffscreen->InitCheck() != B_OK) { 949 delete fOffscreen; 950 fOffscreen = NULL; 951 return; 952 } 953 954 BView* view = new BView(frame, NULL, B_FOLLOW_NONE, B_SUBPIXEL_PRECISE); 955 view->SetViewColor(fHistoryBackgroundColor); 956 view->SetLowColor(view->ViewColor()); 957 fOffscreen->AddChild(view); 958 } 959 960 961 BView* 962 ActivityView::_OffscreenView() 963 { 964 if (fOffscreen == NULL) 965 return NULL; 966 967 return fOffscreen->ChildAt(0); 968 } 969 970 971 void 972 ActivityView::MouseDown(BPoint where) 973 { 974 int32 buttons = B_SECONDARY_MOUSE_BUTTON; 975 if (Looper() != NULL && Looper()->CurrentMessage() != NULL) 976 Looper()->CurrentMessage()->FindInt32("buttons", &buttons); 977 978 if (buttons == B_PRIMARY_MOUSE_BUTTON) { 979 fZoomPoint = where; 980 fOriginalResolution = fDrawResolution; 981 fZooming = true; 982 SetMouseEventMask(B_POINTER_EVENTS); 983 return; 984 } 985 986 BPopUpMenu *menu = new BPopUpMenu(B_EMPTY_STRING, false, false); 987 menu->SetFont(be_plain_font); 988 989 BMenu* additionalMenu = new BMenu(B_TRANSLATE("Additional items")); 990 additionalMenu->SetFont(be_plain_font); 991 992 SystemInfo info; 993 BMenuItem* item; 994 995 for (int32 i = 0; i < DataSource::CountSources(); i++) { 996 const DataSource* source = DataSource::SourceAt(i); 997 998 if (source->MultiCPUOnly() && info.CPUCount() == 1) 999 continue; 1000 1001 BMessage* message = new BMessage(kMsgToggleDataSource); 1002 message->AddInt32("index", i); 1003 1004 item = new BMenuItem(source->Name(), message); 1005 if (FindDataSource(source)) 1006 item->SetMarked(true); 1007 1008 if (source->Primary()) 1009 menu->AddItem(item); 1010 else 1011 additionalMenu->AddItem(item); 1012 } 1013 1014 menu->AddItem(new BMenuItem(additionalMenu)); 1015 menu->AddSeparatorItem(); 1016 menu->AddItem(new BMenuItem(fShowLegend ? 1017 B_TRANSLATE("Hide legend") : B_TRANSLATE("Show legend"), 1018 new BMessage(kMsgToggleLegend))); 1019 1020 menu->SetTargetForItems(this); 1021 additionalMenu->SetTargetForItems(this); 1022 1023 ActivityWindow* window = dynamic_cast<ActivityWindow*>(Window()); 1024 if (window != NULL && window->ActivityViewCount() > 1) { 1025 menu->AddSeparatorItem(); 1026 BMessage* message = new BMessage(kMsgRemoveView); 1027 message->AddPointer("view", this); 1028 menu->AddItem(item = new BMenuItem(B_TRANSLATE("Remove graph"), 1029 message)); 1030 item->SetTarget(window); 1031 } 1032 1033 ConvertToScreen(&where); 1034 menu->Go(where, true, false, true); 1035 } 1036 1037 1038 void 1039 ActivityView::MouseUp(BPoint where) 1040 { 1041 fZooming = false; 1042 } 1043 1044 1045 void 1046 ActivityView::MouseMoved(BPoint where, uint32 transit, 1047 const BMessage* dragMessage) 1048 { 1049 if (!fZooming) 1050 return; 1051 1052 int32 shift = int32(where.x - fZoomPoint.x) / 25; 1053 int32 resolution; 1054 if (shift > 0) 1055 resolution = fOriginalResolution << shift; 1056 else 1057 resolution = fOriginalResolution >> -shift; 1058 1059 _UpdateResolution(resolution); 1060 } 1061 1062 1063 void 1064 ActivityView::MessageReceived(BMessage* message) 1065 { 1066 // if a color is dropped, use it as background 1067 if (message->WasDropped()) { 1068 rgb_color* color; 1069 ssize_t size; 1070 if (message->FindData("RGBColor", B_RGB_COLOR_TYPE, 0, 1071 (const void**)&color, &size) == B_OK 1072 && size == sizeof(rgb_color)) { 1073 BPoint dropPoint = message->DropPoint(); 1074 ConvertFromScreen(&dropPoint); 1075 1076 if (_HistoryFrame().Contains(dropPoint)) { 1077 fHistoryBackgroundColor = *color; 1078 Invalidate(_HistoryFrame()); 1079 } else { 1080 // check each legend color box 1081 BAutolock _(fSourcesLock); 1082 1083 BRect legendFrame = _LegendFrame(); 1084 for (int32 i = 0; i < fSources.CountItems(); i++) { 1085 BRect frame = _LegendColorFrameAt(legendFrame, i); 1086 if (frame.Contains(dropPoint)) { 1087 fSources.ItemAt(i)->SetColor(*color); 1088 Invalidate(_HistoryFrame()); 1089 Invalidate(frame); 1090 return; 1091 } 1092 } 1093 1094 if (dynamic_cast<ActivityMonitor*>(be_app) == NULL) { 1095 // allow background color change in the replicant only 1096 fLegendBackgroundColor = *color; 1097 SetLowColor(fLegendBackgroundColor); 1098 Invalidate(legendFrame); 1099 } 1100 } 1101 return; 1102 } 1103 } 1104 1105 switch (message->what) { 1106 case B_ABOUT_REQUESTED: 1107 ActivityMonitor::ShowAbout(); 1108 break; 1109 1110 case kMsgUpdateResolution: 1111 { 1112 int32 resolution; 1113 if (message->FindInt32("resolution", &resolution) != B_OK) 1114 break; 1115 1116 _UpdateResolution(resolution, false); 1117 break; 1118 } 1119 1120 case kMsgTimeIntervalUpdated: 1121 bigtime_t interval; 1122 if (message->FindInt64("interval", &interval) != B_OK) 1123 break; 1124 1125 if (interval < 10000) 1126 interval = 10000; 1127 1128 atomic_set64(&fRefreshInterval, interval); 1129 break; 1130 1131 case kMsgToggleDataSource: 1132 { 1133 int32 index; 1134 if (message->FindInt32("index", &index) != B_OK) 1135 break; 1136 1137 const DataSource* baseSource = DataSource::SourceAt(index); 1138 if (baseSource == NULL) 1139 break; 1140 1141 DataSource* source = FindDataSource(baseSource); 1142 if (source == NULL) 1143 AddDataSource(baseSource); 1144 else 1145 RemoveDataSource(baseSource); 1146 1147 Invalidate(); 1148 break; 1149 } 1150 1151 case kMsgToggleLegend: 1152 fShowLegend = !fShowLegend; 1153 Invalidate(); 1154 break; 1155 1156 case B_MOUSE_WHEEL_CHANGED: 1157 { 1158 float deltaY = 0.0f; 1159 if (message->FindFloat("be:wheel_delta_y", &deltaY) != B_OK 1160 || deltaY == 0.0f) 1161 break; 1162 1163 int32 resolution = fDrawResolution; 1164 if (deltaY > 0) 1165 resolution *= 2; 1166 else 1167 resolution /= 2; 1168 1169 _UpdateResolution(resolution); 1170 break; 1171 } 1172 1173 default: 1174 BView::MessageReceived(message); 1175 break; 1176 } 1177 } 1178 1179 1180 void 1181 ActivityView::_UpdateFrame() 1182 { 1183 #ifdef __HAIKU__ 1184 if (fLegendLayoutItem == NULL || fHistoryLayoutItem == NULL) 1185 return; 1186 1187 BRect historyFrame = fHistoryLayoutItem->Frame(); 1188 BRect legendFrame = fLegendLayoutItem->Frame(); 1189 #else 1190 BRect historyFrame = Bounds(); 1191 BRect legendFrame = Bounds(); 1192 historyFrame.bottom -= 2 * Bounds().Height() / 3; 1193 legendFrame.top += Bounds().Height() / 3; 1194 #endif 1195 MoveTo(historyFrame.left, historyFrame.top); 1196 ResizeTo(legendFrame.left + legendFrame.Width() - historyFrame.left, 1197 legendFrame.top + legendFrame.Height() - historyFrame.top); 1198 } 1199 1200 1201 BRect 1202 ActivityView::_HistoryFrame() const 1203 { 1204 BRect frame = Bounds(); 1205 1206 if (fShowLegend) { 1207 BRect legendFrame = _LegendFrame(); 1208 frame.bottom = legendFrame.top - 1; 1209 } 1210 1211 frame.InsetBy(2, 2); 1212 1213 return frame; 1214 } 1215 1216 1217 float 1218 ActivityView::_LegendHeight() const 1219 { 1220 font_height fontHeight; 1221 GetFontHeight(&fontHeight); 1222 1223 BAutolock _(fSourcesLock); 1224 1225 int32 rows = (fSources.CountItems() + 1) / 2; 1226 1227 int32 boldMargin = Parent() 1228 && (Parent()->Flags() & B_DRAW_ON_CHILDREN) != 0 ? 2 : 0; 1229 1230 return rows * (4 + ceilf(fontHeight.ascent) 1231 + ceilf(fontHeight.descent) + ceilf(fontHeight.leading)) + boldMargin; 1232 } 1233 1234 1235 BRect 1236 ActivityView::_LegendFrame() const 1237 { 1238 float height; 1239 #ifdef __HAIKU__ 1240 if (fLegendLayoutItem != NULL) 1241 height = fLegendLayoutItem->Frame().Height(); 1242 else 1243 #endif 1244 height = _LegendHeight(); 1245 1246 BRect frame = Bounds(); 1247 frame.bottom -= kDraggerSize; 1248 frame.top = frame.bottom - height; 1249 1250 return frame; 1251 } 1252 1253 1254 BRect 1255 ActivityView::_LegendFrameAt(BRect frame, int32 index) const 1256 { 1257 int32 column = index & 1; 1258 int32 row = index / 2; 1259 if (column == 0) 1260 frame.right = frame.left + floorf(frame.Width() / 2) - 5; 1261 else 1262 frame.left = frame.right - floorf(frame.Width() / 2) + 5; 1263 1264 BAutolock _(fSourcesLock); 1265 1266 int32 rows = (fSources.CountItems() + 1) / 2; 1267 float height = floorf((frame.Height() - 5) / rows); 1268 1269 frame.top = frame.top + 5 + row * height; 1270 frame.bottom = frame.top + height - 1; 1271 1272 return frame; 1273 } 1274 1275 1276 BRect 1277 ActivityView::_LegendColorFrameAt(BRect frame, int32 index) const 1278 { 1279 frame = _LegendFrameAt(frame, index); 1280 frame.InsetBy(1, 1); 1281 frame.right = frame.left + frame.Height(); 1282 1283 return frame; 1284 } 1285 1286 1287 float 1288 ActivityView::_PositionForValue(DataSource* source, DataHistory* values, 1289 int64 value) 1290 { 1291 int64 min = source->Minimum(); 1292 int64 max = source->Maximum(); 1293 if (source->AdaptiveScale()) { 1294 int64 adaptiveMax = int64(values->MaximumValue() * 1.2); 1295 if (adaptiveMax < max) 1296 max = adaptiveMax; 1297 } 1298 1299 if (value > max) 1300 value = max; 1301 if (value < min) 1302 value = min; 1303 1304 float height = _HistoryFrame().Height(); 1305 return height - (value - min) * height / (max - min); 1306 } 1307 1308 1309 void 1310 ActivityView::_DrawHistory(bool drawBackground) 1311 { 1312 _UpdateOffscreenBitmap(); 1313 1314 BView* view = this; 1315 if (fOffscreen != NULL) { 1316 fOffscreen->Lock(); 1317 view = _OffscreenView(); 1318 } 1319 1320 BRect frame = _HistoryFrame(); 1321 BRect outerFrame = frame.InsetByCopy(-2, -2); 1322 1323 // draw the outer frame 1324 uint32 flags = 0; 1325 if (!drawBackground) 1326 flags |= BControlLook::B_BLEND_FRAME; 1327 be_control_look->DrawTextControlBorder(this, outerFrame, 1328 outerFrame, fLegendBackgroundColor, flags); 1329 1330 // convert to offscreen view if necessary 1331 if (view != this) 1332 frame.OffsetTo(B_ORIGIN); 1333 1334 view->SetLowColor(fHistoryBackgroundColor); 1335 view->FillRect(frame, B_SOLID_LOW); 1336 1337 uint32 step = 2; 1338 uint32 resolution = fDrawResolution; 1339 if (fDrawResolution > 1) { 1340 step = 1; 1341 resolution--; 1342 } 1343 1344 uint32 width = frame.IntegerWidth() - 10; 1345 uint32 steps = width / step; 1346 bigtime_t timeStep = RefreshInterval() * resolution; 1347 bigtime_t now = system_time(); 1348 1349 // Draw scale 1350 // TODO: add second markers? 1351 1352 view->SetPenSize(1); 1353 1354 rgb_color scaleColor = view->LowColor(); 1355 uint32 average = (scaleColor.red + scaleColor.green + scaleColor.blue) / 3; 1356 if (average < 96) 1357 scaleColor = tint_color(scaleColor, B_LIGHTEN_2_TINT); 1358 else 1359 scaleColor = tint_color(scaleColor, B_DARKEN_2_TINT); 1360 1361 view->SetHighColor(scaleColor); 1362 view->StrokeLine(BPoint(frame.left, frame.top + frame.Height() / 2), 1363 BPoint(frame.right, frame.top + frame.Height() / 2)); 1364 1365 // Draw values 1366 1367 view->SetPenSize(1.5); 1368 BAutolock _(fSourcesLock); 1369 1370 for (uint32 i = fSources.CountItems(); i-- > 0;) { 1371 ViewHistory* viewValues = fViewValues.ItemAt(i); 1372 DataSource* source = fSources.ItemAt(i); 1373 DataHistory* values = fValues.ItemAt(i); 1374 1375 viewValues->Update(values, steps, fDrawResolution, now, timeStep, 1376 RefreshInterval()); 1377 1378 uint32 x = viewValues->Start() * step; 1379 BShape shape; 1380 bool first = true; 1381 1382 for (uint32 i = viewValues->Start(); i < steps; x += step, i++) { 1383 float y = _PositionForValue(source, values, 1384 viewValues->ValueAt(i)); 1385 1386 if (first) { 1387 shape.MoveTo(BPoint(x, y)); 1388 first = false; 1389 } else 1390 shape.LineTo(BPoint(x, y)); 1391 } 1392 1393 view->SetHighColor(source->Color()); 1394 view->SetLineMode(B_BUTT_CAP, B_ROUND_JOIN); 1395 view->MovePenTo(B_ORIGIN); 1396 view->StrokeShape(&shape); 1397 } 1398 1399 // TODO: add marks when an app started or quit 1400 view->Sync(); 1401 if (fOffscreen != NULL) { 1402 fOffscreen->Unlock(); 1403 DrawBitmap(fOffscreen, outerFrame.LeftTop()); 1404 } 1405 } 1406 1407 1408 void 1409 ActivityView::_UpdateResolution(int32 resolution, bool broadcast) 1410 { 1411 if (resolution < 1) 1412 resolution = 1; 1413 if (resolution > 128) 1414 resolution = 128; 1415 1416 if (resolution == fDrawResolution) 1417 return; 1418 1419 ActivityWindow* window = dynamic_cast<ActivityWindow*>(Window()); 1420 if (broadcast && window != NULL) { 1421 BMessage update(kMsgUpdateResolution); 1422 update.AddInt32("resolution", resolution); 1423 window->BroadcastToActivityViews(&update, this); 1424 } 1425 1426 fDrawResolution = resolution; 1427 Invalidate(); 1428 } 1429 1430 1431 void 1432 ActivityView::Draw(BRect updateRect) 1433 { 1434 bool drawBackground = true; 1435 if (Parent() && (Parent()->Flags() & B_DRAW_ON_CHILDREN) != 0) 1436 drawBackground = false; 1437 1438 _DrawHistory(drawBackground); 1439 1440 if (!fShowLegend) 1441 return; 1442 1443 // draw legend 1444 BRect legendFrame = _LegendFrame(); 1445 SetLowColor(fLegendBackgroundColor); 1446 if (drawBackground) { 1447 BRect backgroundFrame(legendFrame); 1448 backgroundFrame.bottom += kDraggerSize; 1449 FillRect(backgroundFrame, B_SOLID_LOW); 1450 } 1451 1452 BAutolock _(fSourcesLock); 1453 1454 font_height fontHeight; 1455 GetFontHeight(&fontHeight); 1456 1457 for (int32 i = 0; i < fSources.CountItems(); i++) { 1458 DataSource* source = fSources.ItemAt(i); 1459 DataHistory* values = fValues.ItemAt(i); 1460 BRect frame = _LegendFrameAt(legendFrame, i); 1461 1462 // draw color box 1463 BRect colorBox = _LegendColorFrameAt(legendFrame, i); 1464 BRect rect = colorBox; 1465 uint32 flags = BControlLook::B_BLEND_FRAME; 1466 be_control_look->DrawTextControlBorder(this, rect, 1467 rect, fLegendBackgroundColor, flags); 1468 SetHighColor(source->Color()); 1469 FillRect(rect); 1470 1471 // show current value and label 1472 float y = frame.top + ceilf(fontHeight.ascent); 1473 int64 value = values->ValueAt(values->End()); 1474 BString text; 1475 source->Print(text, value); 1476 float width = StringWidth(text.String()); 1477 1478 BString label = source->Label(); 1479 float possibleLabelWidth = frame.right - colorBox.right - 12 - width; 1480 // TODO: TruncateString() is broken... remove + 5 when fixed! 1481 if (ceilf(StringWidth(label.String()) + 5) > possibleLabelWidth) 1482 label = source->ShortLabel(); 1483 TruncateString(&label, B_TRUNCATE_MIDDLE, possibleLabelWidth); 1484 1485 if (drawBackground) 1486 SetHighColor(ui_color(B_CONTROL_TEXT_COLOR)); 1487 1488 if (be_control_look == NULL) { 1489 DrawString(label.String(), BPoint(6 + colorBox.right, y)); 1490 DrawString(text.String(), BPoint(frame.right - width, y)); 1491 } else { 1492 be_control_look->DrawLabel(this, label.String(), 1493 Parent()->ViewColor(), 0, BPoint(6 + colorBox.right, y)); 1494 be_control_look->DrawLabel(this, text.String(), 1495 Parent()->ViewColor(), 0, BPoint(frame.right - width, y)); 1496 } 1497 } 1498 } 1499 1500 1501 void 1502 ActivityView::_Refresh() 1503 { 1504 bigtime_t lastTimeout = system_time() - RefreshInterval(); 1505 BMessenger target(this); 1506 1507 while (true) { 1508 status_t status = acquire_sem_etc(fRefreshSem, 1, B_ABSOLUTE_TIMEOUT, 1509 lastTimeout + RefreshInterval()); 1510 if (status == B_OK || status == B_BAD_SEM_ID) 1511 break; 1512 if (status == B_INTERRUPTED) 1513 continue; 1514 1515 SystemInfo info(fSystemInfoHandler); 1516 lastTimeout += RefreshInterval(); 1517 1518 fSourcesLock.Lock(); 1519 1520 for (uint32 i = fSources.CountItems(); i-- > 0;) { 1521 DataSource* source = fSources.ItemAt(i); 1522 DataHistory* values = fValues.ItemAt(i); 1523 1524 int64 value = source->NextValue(info); 1525 values->AddValue(info.Time(), value); 1526 } 1527 1528 fSourcesLock.Unlock(); 1529 1530 target.SendMessage(B_INVALIDATE); 1531 } 1532 } 1533 1534 1535 /*static*/ status_t 1536 ActivityView::_RefreshThread(void* self) 1537 { 1538 ((ActivityView*)self)->_Refresh(); 1539 return B_OK; 1540 } 1541