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