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