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 <fs_attr.h> 23 #include <MenuItem.h> 24 #include <MessageRunner.h> 25 #include <PopUpMenu.h> 26 #include <Shape.h> 27 #include <StorageKit.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 37 template<typename ObjectType> 38 class ListAddDeleter { 39 public: 40 ListAddDeleter(BObjectList<ObjectType>& list, ObjectType* object, 41 int32 spot) 42 : 43 fList(list), 44 fObject(object) 45 { 46 if (fObject != NULL && !fList.AddItem(fObject, spot)) { 47 delete fObject; 48 fObject = NULL; 49 } 50 } 51 52 ~ListAddDeleter() 53 { 54 if (fObject != NULL) { 55 fList.RemoveItem(fObject); 56 delete fObject; 57 } 58 } 59 60 bool Failed() const 61 { 62 return fObject == NULL; 63 } 64 65 void Detach() 66 { 67 fObject = NULL; 68 } 69 70 private: 71 BObjectList<ObjectType>& fList; 72 ObjectType* fObject; 73 }; 74 75 76 /*! This class manages the scale of a history with a dynamic scale. 77 Every history value will be input via Update(), and the minimum/maximum 78 is computed from that. 79 */ 80 class Scale { 81 public: 82 Scale(scale_type type); 83 84 int64 MinimumValue() const { return fMinimumValue; } 85 int64 MaximumValue() const { return fMaximumValue; } 86 87 void Update(int64 value); 88 89 private: 90 scale_type fType; 91 int64 fMinimumValue; 92 int64 fMaximumValue; 93 bool fInitialized; 94 }; 95 96 /*! Stores the interpolated on screen view values. This is done so that the 97 interpolation is fixed, and does not change when being scrolled. 98 99 We could also just do this by making sure we always ask for the same 100 interval only, but this way we also save the interpolation. 101 */ 102 class ViewHistory { 103 public: 104 ViewHistory(); 105 106 int64 ValueAt(int32 x); 107 108 int32 Start() const 109 { return fValues.Size() 110 - fValues.CountItems(); } 111 112 void Update(DataHistory* history, int32 width, 113 int32 resolution, bigtime_t toTime, 114 bigtime_t step, bigtime_t refresh); 115 116 private: 117 CircularBuffer<int64> fValues; 118 int32 fResolution; 119 bigtime_t fRefresh; 120 bigtime_t fLastTime; 121 }; 122 123 struct data_item { 124 bigtime_t time; 125 int64 value; 126 }; 127 128 #ifdef __HAIKU__ 129 class ActivityView::HistoryLayoutItem : public BAbstractLayoutItem { 130 public: 131 HistoryLayoutItem(ActivityView* parent); 132 133 virtual bool IsVisible(); 134 virtual void SetVisible(bool visible); 135 136 virtual BRect Frame(); 137 virtual void SetFrame(BRect frame); 138 139 virtual BView* View(); 140 141 virtual BSize BasePreferredSize(); 142 143 private: 144 ActivityView* fParent; 145 BRect fFrame; 146 }; 147 148 class ActivityView::LegendLayoutItem : public BAbstractLayoutItem { 149 public: 150 LegendLayoutItem(ActivityView* parent); 151 152 virtual bool IsVisible(); 153 virtual void SetVisible(bool visible); 154 155 virtual BRect Frame(); 156 virtual void SetFrame(BRect frame); 157 158 virtual BView* View(); 159 160 virtual BSize BaseMinSize(); 161 virtual BSize BaseMaxSize(); 162 virtual BSize BasePreferredSize(); 163 virtual BAlignment BaseAlignment(); 164 165 private: 166 ActivityView* fParent; 167 BRect fFrame; 168 }; 169 #endif 170 171 const bigtime_t kInitialRefreshInterval = 250000LL; 172 173 const uint32 kMsgToggleDataSource = 'tgds'; 174 const uint32 kMsgToggleLegend = 'tglg'; 175 const uint32 kMsgUpdateResolution = 'ures'; 176 177 extern const char* kSignature; 178 179 const char* kDesktopAttrName = "be:bgndimginfo"; 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 stop_watching(this); 600 delete fOffscreen; 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 fOffscreen = NULL; 612 #ifdef __HAIKU__ 613 fHistoryLayoutItem = NULL; 614 fLegendLayoutItem = NULL; 615 #endif 616 SetViewColor(B_TRANSPARENT_COLOR); 617 618 fLastRefresh = 0; 619 fDrawResolution = 1; 620 fZooming = false; 621 622 fSystemInfoHandler = new SystemInfoHandler; 623 624 if (settings == NULL 625 || settings->FindInt64("refresh interval", &fRefreshInterval) != B_OK) 626 fRefreshInterval = kInitialRefreshInterval; 627 628 if (settings == NULL 629 || settings->FindBool("show legend", &fShowLegend) != B_OK) 630 fShowLegend = true; 631 632 if (settings == NULL) 633 return; 634 635 ssize_t colorLength; 636 rgb_color *color; 637 if (settings->FindData("history background color", B_RGB_COLOR_TYPE, 638 (const void **)&color, &colorLength) == B_OK 639 && colorLength == sizeof(rgb_color)) 640 fHistoryBackgroundColor = *color; 641 642 const char* name; 643 for (int32 i = 0; settings->FindString("source", i, &name) == B_OK; i++) { 644 AddDataSource(DataSource::FindSource(name), settings); 645 } 646 } 647 648 649 status_t 650 ActivityView::Archive(BMessage* into, bool deep) const 651 { 652 status_t status; 653 654 status = BView::Archive(into, deep); 655 if (status < B_OK) 656 return status; 657 658 status = into->AddString("add_on", kSignature); 659 if (status < B_OK) 660 return status; 661 662 status = SaveState(*into); 663 if (status < B_OK) 664 return status; 665 666 return B_OK; 667 } 668 669 670 BArchivable* 671 ActivityView::Instantiate(BMessage* archive) 672 { 673 if (!validate_instantiation(archive, "ActivityView")) 674 return NULL; 675 676 return new ActivityView(archive); 677 } 678 679 680 status_t 681 ActivityView::SaveState(BMessage& state) const 682 { 683 status_t status = state.AddBool("show legend", fShowLegend); 684 if (status != B_OK) 685 return status; 686 687 status = state.AddInt64("refresh interval", fRefreshInterval); 688 if (status != B_OK) 689 return status; 690 691 status = state.AddData("history background color", B_RGB_COLOR_TYPE, 692 &fHistoryBackgroundColor, sizeof(rgb_color)); 693 if (status != B_OK) 694 return status; 695 696 for (int32 i = 0; i < fSources.CountItems(); i++) { 697 DataSource* source = fSources.ItemAt(i); 698 699 if (!source->PerCPU() || source->CPU() == 0) 700 status = state.AddString("source", source->Name()); 701 if (status != B_OK) 702 return status; 703 704 BString name = source->Name(); 705 name << " color"; 706 rgb_color color = source->Color(); 707 state.AddData(name.String(), B_RGB_COLOR_TYPE, &color, 708 sizeof(rgb_color)); 709 } 710 return B_OK; 711 } 712 713 714 Scale* 715 ActivityView::_ScaleFor(scale_type type) 716 { 717 if (type == kNoScale) 718 return NULL; 719 720 std::map<scale_type, ::Scale*>::iterator iterator = fScales.find(type); 721 if (iterator != fScales.end()) 722 return iterator->second; 723 724 // add new scale 725 ::Scale* scale = new ::Scale(type); 726 fScales[type] = scale; 727 728 return scale; 729 } 730 731 732 #ifdef __HAIKU__ 733 BLayoutItem* 734 ActivityView::CreateHistoryLayoutItem() 735 { 736 if (fHistoryLayoutItem == NULL) 737 fHistoryLayoutItem = new HistoryLayoutItem(this); 738 739 return fHistoryLayoutItem; 740 } 741 742 743 BLayoutItem* 744 ActivityView::CreateLegendLayoutItem() 745 { 746 if (fLegendLayoutItem == NULL) 747 fLegendLayoutItem = new LegendLayoutItem(this); 748 749 return fLegendLayoutItem; 750 } 751 #endif 752 753 754 DataSource* 755 ActivityView::FindDataSource(const DataSource* search) 756 { 757 BAutolock _(fSourcesLock); 758 759 for (int32 i = fSources.CountItems(); i-- > 0;) { 760 DataSource* source = fSources.ItemAt(i); 761 if (!strcmp(source->Name(), search->Name())) 762 return source; 763 } 764 765 return NULL; 766 } 767 768 769 status_t 770 ActivityView::AddDataSource(const DataSource* source, const BMessage* state) 771 { 772 if (source == NULL) 773 return B_BAD_VALUE; 774 775 BAutolock _(fSourcesLock); 776 777 // Search for the correct insert spot to maintain the order of the sources 778 int32 insert = DataSource::IndexOf(source); 779 for (int32 i = 0; i < fSources.CountItems() && i < insert; i++) { 780 DataSource* before = fSources.ItemAt(i); 781 if (DataSource::IndexOf(before) > insert) { 782 insert = i; 783 break; 784 } 785 } 786 if (insert > fSources.CountItems()) 787 insert = fSources.CountItems(); 788 789 // Generate DataHistory and ViewHistory objects for the source 790 // (one might need one history per CPU) 791 792 uint32 count = 1; 793 if (source->PerCPU()) { 794 SystemInfo info; 795 count = info.CPUCount(); 796 } 797 798 for (uint32 i = 0; i < count; i++) { 799 DataHistory* values = new(std::nothrow) DataHistory(10 * 60000000LL, 800 RefreshInterval()); 801 ListAddDeleter<DataHistory> valuesDeleter(fValues, values, insert); 802 803 ViewHistory* viewValues = new(std::nothrow) ViewHistory; 804 ListAddDeleter<ViewHistory> viewValuesDeleter(fViewValues, viewValues, 805 insert); 806 807 if (valuesDeleter.Failed() || viewValuesDeleter.Failed()) 808 return B_NO_MEMORY; 809 810 values->SetScale(_ScaleFor(source->ScaleType())); 811 812 DataSource* copy; 813 if (source->PerCPU()) 814 copy = source->CopyForCPU(i); 815 else 816 copy = source->Copy(); 817 818 ListAddDeleter<DataSource> sourceDeleter(fSources, copy, insert); 819 if (sourceDeleter.Failed()) 820 return B_NO_MEMORY; 821 822 BString colorName = source->Name(); 823 colorName << " color"; 824 if (state != NULL) { 825 const rgb_color* color = NULL; 826 ssize_t colorLength; 827 if (state->FindData(colorName.String(), B_RGB_COLOR_TYPE, i, 828 (const void**)&color, &colorLength) == B_OK 829 && colorLength == sizeof(rgb_color)) 830 copy->SetColor(*color); 831 } 832 833 valuesDeleter.Detach(); 834 viewValuesDeleter.Detach(); 835 sourceDeleter.Detach(); 836 } 837 838 #ifdef __HAIKU__ 839 InvalidateLayout(); 840 #endif 841 return B_OK; 842 } 843 844 845 status_t 846 ActivityView::RemoveDataSource(const DataSource* remove) 847 { 848 bool removed = false; 849 850 BAutolock _(fSourcesLock); 851 852 while (true) { 853 DataSource* source = FindDataSource(remove); 854 if (source == NULL) { 855 if (removed) 856 break; 857 return B_ENTRY_NOT_FOUND; 858 } 859 860 int32 index = fSources.IndexOf(source); 861 if (index < 0) 862 return B_ENTRY_NOT_FOUND; 863 864 fSources.RemoveItemAt(index); 865 delete source; 866 DataHistory* values = fValues.RemoveItemAt(index); 867 delete values; 868 removed = true; 869 } 870 871 #ifdef __HAIKU__ 872 InvalidateLayout(); 873 #endif 874 return B_OK; 875 } 876 877 878 void 879 ActivityView::RemoveAllDataSources() 880 { 881 BAutolock _(fSourcesLock); 882 883 fSources.MakeEmpty(); 884 fValues.MakeEmpty(); 885 } 886 887 888 void 889 ActivityView::AttachedToWindow() 890 { 891 if (Parent() && (Parent()->Flags() & B_DRAW_ON_CHILDREN) != 0) 892 _LoadBackgroundInfo(true); 893 894 Looper()->AddHandler(fSystemInfoHandler); 895 fSystemInfoHandler->StartWatching(); 896 897 fRefreshSem = create_sem(0, "refresh sem"); 898 fRefreshThread = spawn_thread(&_RefreshThread, "source refresh", 899 B_URGENT_DISPLAY_PRIORITY, this); 900 resume_thread(fRefreshThread); 901 902 FrameResized(Bounds().Width(), Bounds().Height()); 903 } 904 905 906 void 907 ActivityView::DetachedFromWindow() 908 { 909 fSystemInfoHandler->StopWatching(); 910 Looper()->RemoveHandler(fSystemInfoHandler); 911 912 delete_sem(fRefreshSem); 913 wait_for_thread(fRefreshThread, NULL); 914 } 915 916 917 #ifdef __HAIKU__ 918 BSize 919 ActivityView::MinSize() 920 { 921 BSize size(32, 32); 922 if (fShowLegend) 923 size.height = _LegendHeight(); 924 925 return size; 926 } 927 #endif 928 929 930 void 931 ActivityView::FrameResized(float /*width*/, float /*height*/) 932 { 933 _UpdateOffscreenBitmap(); 934 } 935 936 937 void 938 ActivityView::_UpdateOffscreenBitmap() 939 { 940 BRect frame = _HistoryFrame(); 941 frame.OffsetTo(B_ORIGIN); 942 943 if (fOffscreen != NULL && frame == fOffscreen->Bounds()) 944 return; 945 946 delete fOffscreen; 947 948 // create offscreen bitmap 949 950 fOffscreen = new(std::nothrow) BBitmap(frame, B_BITMAP_ACCEPTS_VIEWS, 951 B_RGB32); 952 if (fOffscreen == NULL || fOffscreen->InitCheck() != B_OK) { 953 delete fOffscreen; 954 fOffscreen = NULL; 955 return; 956 } 957 958 BView* view = new BView(frame, NULL, B_FOLLOW_NONE, B_SUBPIXEL_PRECISE); 959 view->SetViewColor(fHistoryBackgroundColor); 960 view->SetLowColor(view->ViewColor()); 961 fOffscreen->AddChild(view); 962 } 963 964 965 BView* 966 ActivityView::_OffscreenView() 967 { 968 if (fOffscreen == NULL) 969 return NULL; 970 971 return fOffscreen->ChildAt(0); 972 } 973 974 975 void 976 ActivityView::MouseDown(BPoint where) 977 { 978 int32 buttons = B_SECONDARY_MOUSE_BUTTON; 979 if (Looper() != NULL && Looper()->CurrentMessage() != NULL) 980 Looper()->CurrentMessage()->FindInt32("buttons", &buttons); 981 982 if (buttons == B_PRIMARY_MOUSE_BUTTON) { 983 fZoomPoint = where; 984 fOriginalResolution = fDrawResolution; 985 fZooming = true; 986 SetMouseEventMask(B_POINTER_EVENTS); 987 return; 988 } 989 990 BPopUpMenu *menu = new BPopUpMenu(B_EMPTY_STRING, false, false); 991 menu->SetFont(be_plain_font); 992 993 BMenu* additionalMenu = new BMenu("Additional items"); 994 additionalMenu->SetFont(be_plain_font); 995 996 SystemInfo info; 997 BMenuItem* item; 998 999 for (int32 i = 0; i < DataSource::CountSources(); i++) { 1000 const DataSource* source = DataSource::SourceAt(i); 1001 1002 if (source->MultiCPUOnly() && info.CPUCount() == 1) 1003 continue; 1004 1005 BMessage* message = new BMessage(kMsgToggleDataSource); 1006 message->AddInt32("index", i); 1007 1008 item = new BMenuItem(source->Name(), message); 1009 if (FindDataSource(source)) 1010 item->SetMarked(true); 1011 1012 if (source->Primary()) 1013 menu->AddItem(item); 1014 else 1015 additionalMenu->AddItem(item); 1016 } 1017 1018 menu->AddItem(new BMenuItem(additionalMenu)); 1019 menu->AddSeparatorItem(); 1020 menu->AddItem(new BMenuItem(fShowLegend ? "Hide legend" : "Show legend", 1021 new BMessage(kMsgToggleLegend))); 1022 1023 menu->SetTargetForItems(this); 1024 additionalMenu->SetTargetForItems(this); 1025 1026 ActivityWindow* window = dynamic_cast<ActivityWindow*>(Window()); 1027 if (window != NULL && window->ActivityViewCount() > 1) { 1028 menu->AddSeparatorItem(); 1029 BMessage* message = new BMessage(kMsgRemoveView); 1030 message->AddPointer("view", this); 1031 menu->AddItem(item = new BMenuItem("Remove graph", message)); 1032 item->SetTarget(window); 1033 } 1034 1035 ConvertToScreen(&where); 1036 menu->Go(where, true, false, true); 1037 } 1038 1039 1040 void 1041 ActivityView::MouseUp(BPoint where) 1042 { 1043 fZooming = false; 1044 } 1045 1046 1047 void 1048 ActivityView::MouseMoved(BPoint where, uint32 transit, 1049 const BMessage* dragMessage) 1050 { 1051 if (!fZooming) 1052 return; 1053 1054 int32 shift = int32(where.x - fZoomPoint.x) / 25; 1055 int32 resolution; 1056 if (shift > 0) 1057 resolution = fOriginalResolution << shift; 1058 else 1059 resolution = fOriginalResolution >> -shift; 1060 1061 _UpdateResolution(resolution); 1062 } 1063 1064 1065 void 1066 ActivityView::MessageReceived(BMessage* message) 1067 { 1068 // if a color is dropped, use it as background 1069 if (message->WasDropped()) { 1070 rgb_color* color; 1071 ssize_t size; 1072 if (message->FindData("RGBColor", B_RGB_COLOR_TYPE, 0, 1073 (const void**)&color, &size) == B_OK 1074 && size == sizeof(rgb_color)) { 1075 BPoint dropPoint = message->DropPoint(); 1076 ConvertFromScreen(&dropPoint); 1077 1078 if (_HistoryFrame().Contains(dropPoint)) { 1079 fHistoryBackgroundColor = *color; 1080 Invalidate(_HistoryFrame()); 1081 } else { 1082 // check each legend color box 1083 BAutolock _(fSourcesLock); 1084 1085 BRect legendFrame = _LegendFrame(); 1086 for (int32 i = 0; i < fSources.CountItems(); i++) { 1087 BRect frame = _LegendColorFrameAt(legendFrame, i); 1088 if (frame.Contains(dropPoint)) { 1089 fSources.ItemAt(i)->SetColor(*color); 1090 Invalidate(_HistoryFrame()); 1091 Invalidate(frame); 1092 return; 1093 } 1094 } 1095 1096 if (dynamic_cast<ActivityMonitor*>(be_app) == NULL) { 1097 // allow background color change in the replicant only 1098 fLegendBackgroundColor = *color; 1099 SetLowColor(fLegendBackgroundColor); 1100 Invalidate(legendFrame); 1101 } 1102 } 1103 return; 1104 } 1105 } 1106 1107 switch (message->what) { 1108 case B_ABOUT_REQUESTED: 1109 ActivityMonitor::ShowAbout(); 1110 break; 1111 1112 case B_NODE_MONITOR: 1113 { 1114 BString attrName; 1115 if (message->FindString("attr", &attrName) == B_OK) { 1116 if (attrName == kDesktopAttrName) 1117 _LoadBackgroundInfo(false); 1118 } else 1119 _LoadBackgroundInfo(false); 1120 break; 1121 } 1122 1123 case kMsgUpdateResolution: 1124 { 1125 int32 resolution; 1126 if (message->FindInt32("resolution", &resolution) != B_OK) 1127 break; 1128 1129 _UpdateResolution(resolution, false); 1130 break; 1131 } 1132 1133 case kMsgTimeIntervalUpdated: 1134 bigtime_t interval; 1135 if (message->FindInt64("interval", &interval) != B_OK) 1136 break; 1137 1138 if (interval < 10000) 1139 interval = 10000; 1140 1141 atomic_set64(&fRefreshInterval, interval); 1142 break; 1143 1144 case kMsgToggleDataSource: 1145 { 1146 int32 index; 1147 if (message->FindInt32("index", &index) != B_OK) 1148 break; 1149 1150 const DataSource* baseSource = DataSource::SourceAt(index); 1151 if (baseSource == NULL) 1152 break; 1153 1154 DataSource* source = FindDataSource(baseSource); 1155 if (source == NULL) 1156 AddDataSource(baseSource); 1157 else 1158 RemoveDataSource(baseSource); 1159 1160 Invalidate(); 1161 break; 1162 } 1163 1164 case kMsgToggleLegend: 1165 fShowLegend = !fShowLegend; 1166 Invalidate(); 1167 break; 1168 1169 case B_MOUSE_WHEEL_CHANGED: 1170 { 1171 float deltaY = 0.0f; 1172 if (message->FindFloat("be:wheel_delta_y", &deltaY) != B_OK 1173 || deltaY == 0.0f) 1174 break; 1175 1176 int32 resolution = fDrawResolution; 1177 if (deltaY > 0) 1178 resolution *= 2; 1179 else 1180 resolution /= 2; 1181 1182 _UpdateResolution(resolution); 1183 break; 1184 } 1185 1186 default: 1187 BView::MessageReceived(message); 1188 break; 1189 } 1190 } 1191 1192 1193 void 1194 ActivityView::_UpdateFrame() 1195 { 1196 #ifdef __HAIKU__ 1197 if (fLegendLayoutItem == NULL || fHistoryLayoutItem == NULL) 1198 return; 1199 1200 BRect historyFrame = fHistoryLayoutItem->Frame(); 1201 BRect legendFrame = fLegendLayoutItem->Frame(); 1202 #else 1203 BRect historyFrame = Bounds(); 1204 BRect legendFrame = Bounds(); 1205 historyFrame.bottom -= 2 * Bounds().Height() / 3; 1206 legendFrame.top += Bounds().Height() / 3; 1207 #endif 1208 MoveTo(historyFrame.left, historyFrame.top); 1209 ResizeTo(legendFrame.left + legendFrame.Width() - historyFrame.left, 1210 legendFrame.top + legendFrame.Height() - historyFrame.top); 1211 } 1212 1213 1214 BRect 1215 ActivityView::_HistoryFrame() const 1216 { 1217 BRect frame = Bounds(); 1218 1219 if (fShowLegend) { 1220 BRect legendFrame = _LegendFrame(); 1221 frame.bottom = legendFrame.top - 1; 1222 } 1223 1224 frame.InsetBy(2, 2); 1225 1226 return frame; 1227 } 1228 1229 1230 float 1231 ActivityView::_LegendHeight() const 1232 { 1233 font_height fontHeight; 1234 GetFontHeight(&fontHeight); 1235 1236 BAutolock _(fSourcesLock); 1237 1238 int32 rows = (fSources.CountItems() + 1) / 2; 1239 1240 int32 boldMargin = Parent() 1241 && (Parent()->Flags() & B_DRAW_ON_CHILDREN) != 0 ? 2 : 0; 1242 1243 return rows * (4 + ceilf(fontHeight.ascent) 1244 + ceilf(fontHeight.descent) + ceilf(fontHeight.leading)) + boldMargin; 1245 } 1246 1247 1248 BRect 1249 ActivityView::_LegendFrame() const 1250 { 1251 float height; 1252 #ifdef __HAIKU__ 1253 if (fLegendLayoutItem != NULL) 1254 height = fLegendLayoutItem->Frame().Height(); 1255 else 1256 #endif 1257 height = _LegendHeight(); 1258 1259 BRect frame = Bounds(); 1260 frame.bottom -= kDraggerSize; 1261 frame.top = frame.bottom - height; 1262 1263 return frame; 1264 } 1265 1266 1267 BRect 1268 ActivityView::_LegendFrameAt(BRect frame, int32 index) const 1269 { 1270 int32 column = index & 1; 1271 int32 row = index / 2; 1272 if (column == 0) 1273 frame.right = frame.left + floorf(frame.Width() / 2) - 5; 1274 else 1275 frame.left = frame.right - floorf(frame.Width() / 2) + 5; 1276 1277 BAutolock _(fSourcesLock); 1278 1279 int32 rows = (fSources.CountItems() + 1) / 2; 1280 float height = floorf((frame.Height() - 5) / rows); 1281 1282 frame.top = frame.top + 5 + row * height; 1283 frame.bottom = frame.top + height - 1; 1284 1285 return frame; 1286 } 1287 1288 1289 BRect 1290 ActivityView::_LegendColorFrameAt(BRect frame, int32 index) const 1291 { 1292 frame = _LegendFrameAt(frame, index); 1293 frame.InsetBy(1, 1); 1294 frame.right = frame.left + frame.Height(); 1295 1296 return frame; 1297 } 1298 1299 1300 float 1301 ActivityView::_PositionForValue(DataSource* source, DataHistory* values, 1302 int64 value) 1303 { 1304 int64 min = source->Minimum(); 1305 int64 max = source->Maximum(); 1306 if (source->AdaptiveScale()) { 1307 int64 adaptiveMax = int64(values->MaximumValue() * 1.2); 1308 if (adaptiveMax < max) 1309 max = adaptiveMax; 1310 } 1311 1312 if (value > max) 1313 value = max; 1314 if (value < min) 1315 value = min; 1316 1317 float height = _HistoryFrame().Height(); 1318 return height - (value - min) * height / (max - min); 1319 } 1320 1321 1322 void 1323 ActivityView::_DrawHistory(bool drawBackground) 1324 { 1325 _UpdateOffscreenBitmap(); 1326 1327 BView* view = this; 1328 if (fOffscreen != NULL) { 1329 fOffscreen->Lock(); 1330 view = _OffscreenView(); 1331 } 1332 1333 BRect frame = _HistoryFrame(); 1334 BRect outerFrame = frame.InsetByCopy(-2, -2); 1335 1336 // draw the outer frame 1337 uint32 flags = 0; 1338 if (!drawBackground) 1339 flags |= BControlLook::B_BLEND_FRAME; 1340 be_control_look->DrawTextControlBorder(this, outerFrame, 1341 outerFrame, fLegendBackgroundColor, flags); 1342 1343 // convert to offscreen view if necessary 1344 if (view != this) 1345 frame.OffsetTo(B_ORIGIN); 1346 1347 view->SetLowColor(fHistoryBackgroundColor); 1348 view->FillRect(frame, B_SOLID_LOW); 1349 1350 uint32 step = 2; 1351 uint32 resolution = fDrawResolution; 1352 if (fDrawResolution > 1) { 1353 step = 1; 1354 resolution--; 1355 } 1356 1357 uint32 width = frame.IntegerWidth() - 10; 1358 uint32 steps = width / step; 1359 bigtime_t timeStep = RefreshInterval() * resolution; 1360 bigtime_t now = system_time(); 1361 1362 // Draw scale 1363 // TODO: add second markers? 1364 1365 view->SetPenSize(1); 1366 1367 rgb_color scaleColor = view->LowColor(); 1368 uint32 average = (scaleColor.red + scaleColor.green + scaleColor.blue) / 3; 1369 if (average < 96) 1370 scaleColor = tint_color(scaleColor, B_LIGHTEN_2_TINT); 1371 else 1372 scaleColor = tint_color(scaleColor, B_DARKEN_2_TINT); 1373 1374 view->SetHighColor(scaleColor); 1375 view->StrokeLine(BPoint(frame.left, frame.top + frame.Height() / 2), 1376 BPoint(frame.right, frame.top + frame.Height() / 2)); 1377 1378 // Draw values 1379 1380 view->SetPenSize(1.5); 1381 BAutolock _(fSourcesLock); 1382 1383 for (uint32 i = fSources.CountItems(); i-- > 0;) { 1384 ViewHistory* viewValues = fViewValues.ItemAt(i); 1385 DataSource* source = fSources.ItemAt(i); 1386 DataHistory* values = fValues.ItemAt(i); 1387 1388 viewValues->Update(values, steps, fDrawResolution, now, timeStep, 1389 RefreshInterval()); 1390 1391 uint32 x = viewValues->Start() * step; 1392 BShape shape; 1393 bool first = true; 1394 1395 for (uint32 i = viewValues->Start(); i < steps; x += step, i++) { 1396 float y = _PositionForValue(source, values, 1397 viewValues->ValueAt(i)); 1398 1399 if (first) { 1400 shape.MoveTo(BPoint(x, y)); 1401 first = false; 1402 } else 1403 shape.LineTo(BPoint(x, y)); 1404 } 1405 1406 view->SetHighColor(source->Color()); 1407 view->SetLineMode(B_BUTT_CAP, B_ROUND_JOIN); 1408 view->MovePenTo(B_ORIGIN); 1409 view->StrokeShape(&shape); 1410 } 1411 1412 // TODO: add marks when an app started or quit 1413 view->Sync(); 1414 if (fOffscreen != NULL) { 1415 fOffscreen->Unlock(); 1416 DrawBitmap(fOffscreen, outerFrame.LeftTop()); 1417 } 1418 } 1419 1420 1421 void 1422 ActivityView::_UpdateResolution(int32 resolution, bool broadcast) 1423 { 1424 if (resolution < 1) 1425 resolution = 1; 1426 if (resolution > 128) 1427 resolution = 128; 1428 1429 if (resolution == fDrawResolution) 1430 return; 1431 1432 ActivityWindow* window = dynamic_cast<ActivityWindow*>(Window()); 1433 if (broadcast && window != NULL) { 1434 BMessage update(kMsgUpdateResolution); 1435 update.AddInt32("resolution", resolution); 1436 window->BroadcastToActivityViews(&update, this); 1437 } 1438 1439 fDrawResolution = resolution; 1440 Invalidate(); 1441 } 1442 1443 1444 void 1445 ActivityView::_LoadBackgroundInfo(bool watch) 1446 { 1447 fCachedOutline = false; 1448 fCachedWorkspace = -1; 1449 BPath path; 1450 if (find_directory(B_DESKTOP_DIRECTORY, &path) == B_OK) { 1451 BNode desktopNode = BNode(path.Path()); 1452 1453 attr_info info; 1454 if (desktopNode.GetAttrInfo(kDesktopAttrName, &info) != B_OK) 1455 return; 1456 1457 char* buffer = new char[info.size]; 1458 if (desktopNode.ReadAttr(kDesktopAttrName, B_MESSAGE_TYPE, 0, 1459 buffer, (size_t)info.size) == info.size) { 1460 BMessage message; 1461 if (message.Unflatten(buffer) == B_OK) 1462 fBackgroundInfo = message; 1463 } 1464 delete[] buffer; 1465 1466 if (watch) { 1467 node_ref nref; 1468 desktopNode.GetNodeRef(&nref); 1469 watch_node(&nref, B_WATCH_ATTR, this); 1470 } 1471 } 1472 } 1473 1474 1475 void 1476 ActivityView::Draw(BRect updateRect) 1477 { 1478 bool drawBackground = true; 1479 if (Parent() && (Parent()->Flags() & B_DRAW_ON_CHILDREN) != 0) 1480 drawBackground = false; 1481 1482 _DrawHistory(drawBackground); 1483 1484 if (!fShowLegend) 1485 return; 1486 1487 // draw legend 1488 BRect legendFrame = _LegendFrame(); 1489 SetLowColor(fLegendBackgroundColor); 1490 if (drawBackground) { 1491 BRect backgroundFrame(legendFrame); 1492 backgroundFrame.bottom += kDraggerSize; 1493 FillRect(backgroundFrame, B_SOLID_LOW); 1494 } 1495 1496 BAutolock _(fSourcesLock); 1497 1498 font_height fontHeight; 1499 GetFontHeight(&fontHeight); 1500 1501 for (int32 i = 0; i < fSources.CountItems(); i++) { 1502 DataSource* source = fSources.ItemAt(i); 1503 DataHistory* values = fValues.ItemAt(i); 1504 BRect frame = _LegendFrameAt(legendFrame, i); 1505 1506 // draw color box 1507 BRect colorBox = _LegendColorFrameAt(legendFrame, i); 1508 BRect rect = colorBox; 1509 uint32 flags = BControlLook::B_BLEND_FRAME; 1510 be_control_look->DrawTextControlBorder(this, rect, 1511 rect, fLegendBackgroundColor, flags); 1512 SetHighColor(source->Color()); 1513 FillRect(rect); 1514 1515 // show current value and label 1516 float y = frame.top + ceilf(fontHeight.ascent); 1517 int64 value = values->ValueAt(values->End()); 1518 BString text; 1519 source->Print(text, value); 1520 float width = StringWidth(text.String()); 1521 1522 BString label = source->Label(); 1523 float possibleLabelWidth = frame.right - colorBox.right - 12 - width; 1524 // TODO: TruncateString() is broken... remove + 5 when fixed! 1525 if (ceilf(StringWidth(label.String()) + 5) > possibleLabelWidth) 1526 label = source->ShortLabel(); 1527 TruncateString(&label, B_TRUNCATE_MIDDLE, possibleLabelWidth); 1528 1529 if (drawBackground) 1530 SetHighColor(ui_color(B_CONTROL_TEXT_COLOR)); 1531 else { 1532 rgb_color c = Parent()->ViewColor(); 1533 rgb_color textColor = c.red + c.green * 1.5f + c.blue * 0.50f 1534 >= 300 ? kBlack : kWhite; 1535 1536 int32 mask; 1537 bool tmpOutline = false; 1538 bool outline = fCachedOutline; 1539 int8 indice = 0; 1540 1541 if (fCachedWorkspace != current_workspace()) { 1542 while (fBackgroundInfo.FindInt32("be:bgndimginfoworkspaces", 1543 indice, &mask) == B_OK 1544 && fBackgroundInfo.FindBool("be:bgndimginfoerasetext", 1545 indice, &tmpOutline) == B_OK) { 1546 if (((1 << current_workspace()) & mask) != 0) { 1547 outline = tmpOutline; 1548 fCachedWorkspace = current_workspace(); 1549 fCachedOutline = outline; 1550 break; 1551 } 1552 indice++; 1553 } 1554 } 1555 1556 if (outline) { 1557 SetDrawingMode(B_OP_ALPHA); 1558 SetBlendingMode(B_CONSTANT_ALPHA, B_ALPHA_OVERLAY); 1559 1560 BFont font; 1561 GetFont(&font); 1562 if (textColor == kBlack) { 1563 // Black text with white halo/glow 1564 rgb_color glowColor = kWhite; 1565 1566 font.SetFalseBoldWidth(2.0); 1567 SetFont(&font, B_FONT_FALSE_BOLD_WIDTH); 1568 1569 glowColor.alpha = 30; 1570 SetHighColor(glowColor); 1571 DrawString(label.String(), BPoint(6 + colorBox.right, y)); 1572 DrawString(text.String(), BPoint(frame.right - width, y)); 1573 1574 font.SetFalseBoldWidth(1.0); 1575 SetFont(&font, B_FONT_FALSE_BOLD_WIDTH); 1576 1577 glowColor.alpha = 65; 1578 SetHighColor(glowColor); 1579 DrawString(label.String(), BPoint(6 + colorBox.right, y)); 1580 DrawString(text.String(), BPoint(frame.right - width, y)); 1581 1582 font.SetFalseBoldWidth(0.0); 1583 SetFont(&font, B_FONT_FALSE_BOLD_WIDTH); 1584 } else { 1585 // white text with black outline 1586 rgb_color outlineColor = kBlack; 1587 1588 font.SetFalseBoldWidth(1.0); 1589 SetFont(&font, B_FONT_FALSE_BOLD_WIDTH); 1590 1591 outlineColor.alpha = 30; 1592 SetHighColor(outlineColor); 1593 DrawString(label.String(), BPoint(6 + colorBox.right, y)); 1594 DrawString(text.String(), BPoint(frame.right - width, y)); 1595 1596 font.SetFalseBoldWidth(0.0); 1597 SetFont(&font, B_FONT_FALSE_BOLD_WIDTH); 1598 1599 outlineColor.alpha = 200; 1600 SetHighColor(outlineColor); 1601 DrawString(label.String(), BPoint(6 + colorBox.right + 1, 1602 y + 1)); 1603 DrawString(text.String(), BPoint(frame.right - width + 1, 1604 y + 1)); 1605 } 1606 } 1607 SetDrawingMode(B_OP_OVER); 1608 SetHighColor(textColor); 1609 } 1610 DrawString(label.String(), BPoint(6 + colorBox.right, y)); 1611 DrawString(text.String(), BPoint(frame.right - width, y)); 1612 } 1613 } 1614 1615 1616 void 1617 ActivityView::_Refresh() 1618 { 1619 bigtime_t lastTimeout = system_time() - RefreshInterval(); 1620 BMessenger target(this); 1621 1622 while (true) { 1623 status_t status = acquire_sem_etc(fRefreshSem, 1, B_ABSOLUTE_TIMEOUT, 1624 lastTimeout + RefreshInterval()); 1625 if (status == B_OK || status == B_BAD_SEM_ID) 1626 break; 1627 if (status == B_INTERRUPTED) 1628 continue; 1629 1630 SystemInfo info(fSystemInfoHandler); 1631 lastTimeout += RefreshInterval(); 1632 1633 fSourcesLock.Lock(); 1634 1635 for (uint32 i = fSources.CountItems(); i-- > 0;) { 1636 DataSource* source = fSources.ItemAt(i); 1637 DataHistory* values = fValues.ItemAt(i); 1638 1639 int64 value = source->NextValue(info); 1640 values->AddValue(info.Time(), value); 1641 } 1642 1643 fSourcesLock.Unlock(); 1644 1645 target.SendMessage(B_INVALIDATE); 1646 } 1647 } 1648 1649 1650 /*static*/ status_t 1651 ActivityView::_RefreshThread(void* self) 1652 { 1653 ((ActivityView*)self)->_Refresh(); 1654 return B_OK; 1655 } 1656