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