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