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