1 /* 2 * Copyright 2008, Axel Dörfler, axeld@pinc-software.de. All rights reserved. 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 13 #ifdef __HAIKU__ 14 #include <AbstractLayoutItem.h> 15 #endif 16 #include <Application.h> 17 #include <Bitmap.h> 18 #include <Dragger.h> 19 #include <MenuItem.h> 20 #include <MessageRunner.h> 21 #include <PopUpMenu.h> 22 #include <String.h> 23 24 #include "ActivityMonitor.h" 25 #include "ActivityWindow.h" 26 #include "DataSource.h" 27 #include "SystemInfo.h" 28 #include "SystemInfoHandler.h" 29 30 31 struct data_item { 32 bigtime_t time; 33 int64 value; 34 }; 35 36 #ifdef __HAIKU__ 37 class ActivityView::HistoryLayoutItem : public BAbstractLayoutItem { 38 public: 39 HistoryLayoutItem(ActivityView* parent); 40 41 virtual bool IsVisible(); 42 virtual void SetVisible(bool visible); 43 44 virtual BRect Frame(); 45 virtual void SetFrame(BRect frame); 46 47 virtual BView* View(); 48 49 virtual BSize BasePreferredSize(); 50 51 private: 52 ActivityView* fParent; 53 BRect fFrame; 54 }; 55 56 class ActivityView::LegendLayoutItem : public BAbstractLayoutItem { 57 public: 58 LegendLayoutItem(ActivityView* parent); 59 60 virtual bool IsVisible(); 61 virtual void SetVisible(bool visible); 62 63 virtual BRect Frame(); 64 virtual void SetFrame(BRect frame); 65 66 virtual BView* View(); 67 68 virtual BSize BaseMinSize(); 69 virtual BSize BaseMaxSize(); 70 virtual BSize BasePreferredSize(); 71 virtual BAlignment BaseAlignment(); 72 73 private: 74 ActivityView* fParent; 75 BRect fFrame; 76 }; 77 #endif 78 79 const bigtime_t kInitialRefreshInterval = 500000LL; 80 81 const uint32 kMsgRefresh = 'refr'; 82 const uint32 kMsgToggleDataSource = 'tgds'; 83 const uint32 kMsgToggleLegend = 'tglg'; 84 85 extern const char* kSignature; 86 87 88 DataHistory::DataHistory(bigtime_t memorize, bigtime_t interval) 89 : 90 fBuffer(10000), 91 fMinimumValue(0), 92 fMaximumValue(0), 93 fRefreshInterval(interval), 94 fLastIndex(-1) 95 { 96 } 97 98 99 DataHistory::~DataHistory() 100 { 101 } 102 103 104 void 105 DataHistory::AddValue(bigtime_t time, int64 value) 106 { 107 if (fBuffer.IsEmpty() || fMaximumValue < value) 108 fMaximumValue = value; 109 if (fBuffer.IsEmpty() || fMinimumValue > value) 110 fMinimumValue = value; 111 112 data_item item = {time, value}; 113 fBuffer.AddItem(item); 114 } 115 116 117 int64 118 DataHistory::ValueAt(bigtime_t time) 119 { 120 // TODO: if the refresh rate changes, this computation won't work anymore! 121 int32 index = (time - Start()) / fRefreshInterval; 122 data_item* item = fBuffer.ItemAt(index); 123 if (item != NULL) 124 return item->value; 125 126 return 0; 127 } 128 129 130 int64 131 DataHistory::MaximumValue() const 132 { 133 return fMaximumValue; 134 } 135 136 137 int64 138 DataHistory::MinimumValue() const 139 { 140 return fMinimumValue; 141 } 142 143 144 bigtime_t 145 DataHistory::Start() const 146 { 147 if (fBuffer.CountItems() == 0) 148 return 0; 149 150 return fBuffer.ItemAt(0)->time; 151 } 152 153 154 bigtime_t 155 DataHistory::End() const 156 { 157 if (fBuffer.CountItems() == 0) 158 return 0; 159 160 return fBuffer.ItemAt(fBuffer.CountItems() - 1)->time; 161 } 162 163 164 void 165 DataHistory::SetRefreshInterval(bigtime_t interval) 166 { 167 // TODO: adjust buffer size 168 } 169 170 171 // #pragma mark - 172 173 174 #ifdef __HAIKU__ 175 ActivityView::HistoryLayoutItem::HistoryLayoutItem(ActivityView* parent) 176 : 177 fParent(parent), 178 fFrame() 179 { 180 } 181 182 183 bool 184 ActivityView::HistoryLayoutItem::IsVisible() 185 { 186 return !fParent->IsHidden(fParent); 187 } 188 189 190 void 191 ActivityView::HistoryLayoutItem::SetVisible(bool visible) 192 { 193 // not allowed 194 } 195 196 197 BRect 198 ActivityView::HistoryLayoutItem::Frame() 199 { 200 return fFrame; 201 } 202 203 204 void 205 ActivityView::HistoryLayoutItem::SetFrame(BRect frame) 206 { 207 fFrame = frame; 208 fParent->_UpdateFrame(); 209 } 210 211 212 BView* 213 ActivityView::HistoryLayoutItem::View() 214 { 215 return fParent; 216 } 217 218 219 BSize 220 ActivityView::HistoryLayoutItem::BasePreferredSize() 221 { 222 BSize size(BaseMaxSize()); 223 return size; 224 } 225 226 227 // #pragma mark - 228 229 230 ActivityView::LegendLayoutItem::LegendLayoutItem(ActivityView* parent) 231 : 232 fParent(parent), 233 fFrame() 234 { 235 } 236 237 238 bool 239 ActivityView::LegendLayoutItem::IsVisible() 240 { 241 return !fParent->IsHidden(fParent); 242 } 243 244 245 void 246 ActivityView::LegendLayoutItem::SetVisible(bool visible) 247 { 248 // not allowed 249 } 250 251 252 BRect 253 ActivityView::LegendLayoutItem::Frame() 254 { 255 return fFrame; 256 } 257 258 259 void 260 ActivityView::LegendLayoutItem::SetFrame(BRect frame) 261 { 262 fFrame = frame; 263 fParent->_UpdateFrame(); 264 } 265 266 267 BView* 268 ActivityView::LegendLayoutItem::View() 269 { 270 return fParent; 271 } 272 273 274 BSize 275 ActivityView::LegendLayoutItem::BaseMinSize() 276 { 277 // TODO: Cache the info. Might be too expensive for this call. 278 BSize size; 279 size.width = 80; 280 size.height = fParent->_LegendHeight(); 281 282 return size; 283 } 284 285 286 BSize 287 ActivityView::LegendLayoutItem::BaseMaxSize() 288 { 289 BSize size(BaseMinSize()); 290 size.width = B_SIZE_UNLIMITED; 291 return size; 292 } 293 294 295 BSize 296 ActivityView::LegendLayoutItem::BasePreferredSize() 297 { 298 BSize size(BaseMinSize()); 299 return size; 300 } 301 302 303 BAlignment 304 ActivityView::LegendLayoutItem::BaseAlignment() 305 { 306 return BAlignment(B_ALIGN_USE_FULL_WIDTH, B_ALIGN_USE_FULL_HEIGHT); 307 } 308 #endif 309 310 311 // #pragma mark - 312 313 314 ActivityView::ActivityView(BRect frame, const char* name, 315 const BMessage* settings, uint32 resizingMode) 316 : BView(frame, name, resizingMode, 317 B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE | B_FRAME_EVENTS) 318 { 319 _Init(settings); 320 321 BRect rect(Bounds()); 322 rect.top = rect.bottom - 7; 323 rect.left = rect.right - 7; 324 BDragger* dragger = new BDragger(rect, this, 325 B_FOLLOW_RIGHT | B_FOLLOW_BOTTOM); 326 AddChild(dragger); 327 } 328 329 330 ActivityView::ActivityView(const char* name, const BMessage* settings) 331 #ifdef __HAIKU__ 332 : BView(name, B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE | B_FRAME_EVENTS) 333 #else 334 : BView(BRect(0,0,300,200), name, B_FOLLOW_NONE, B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE | B_FRAME_EVENTS) 335 #endif 336 { 337 SetLowColor(ui_color(B_PANEL_BACKGROUND_COLOR)); 338 339 _Init(settings); 340 341 BRect rect(Bounds()); 342 rect.top = rect.bottom - 7; 343 rect.left = rect.right - 7; 344 BDragger* dragger = new BDragger(rect, this, 345 B_FOLLOW_RIGHT | B_FOLLOW_BOTTOM); 346 AddChild(dragger); 347 } 348 349 350 ActivityView::ActivityView(BMessage* archive) 351 : BView(archive) 352 { 353 _Init(archive); 354 } 355 356 357 ActivityView::~ActivityView() 358 { 359 delete fOffscreen; 360 delete fSystemInfoHandler; 361 } 362 363 364 void 365 ActivityView::_Init(const BMessage* settings) 366 { 367 fHistoryBackgroundColor = (rgb_color){255, 255, 240}; 368 fLegendBackgroundColor = LowColor(); 369 // the low color is restored by the BView unarchiving 370 fOffscreen = NULL; 371 #ifdef __HAIKU__ 372 fHistoryLayoutItem = NULL; 373 fLegendLayoutItem = NULL; 374 #endif 375 SetViewColor(B_TRANSPARENT_COLOR); 376 377 fRefreshInterval = kInitialRefreshInterval; 378 fDrawInterval = kInitialRefreshInterval * 2; 379 fLastRefresh = 0; 380 fDrawResolution = 1; 381 382 fSystemInfoHandler = new SystemInfoHandler; 383 384 if (settings == NULL 385 || settings->FindBool("show legend", &fShowLegend) != B_OK) 386 fShowLegend = true; 387 388 if (settings == NULL) { 389 AddDataSource(new UsedMemoryDataSource()); 390 AddDataSource(new CachedMemoryDataSource()); 391 return; 392 } 393 394 ssize_t colorLength; 395 rgb_color *color; 396 if (settings->FindData("history background color", B_RGB_COLOR_TYPE, 397 (const void **)&color, &colorLength) == B_OK 398 && colorLength == sizeof(rgb_color)) 399 fHistoryBackgroundColor = *color; 400 401 const char* name; 402 for (int32 i = 0; settings->FindString("source", i, &name) == B_OK; i++) { 403 AddDataSource(DataSource::FindSource(name), settings); 404 } 405 } 406 407 408 status_t 409 ActivityView::Archive(BMessage* into, bool deep) const 410 { 411 status_t status; 412 413 status = BView::Archive(into, deep); 414 if (status < B_OK) 415 return status; 416 417 status = into->AddString("add_on", kSignature); 418 if (status < B_OK) 419 return status; 420 421 status = SaveState(*into); 422 if (status < B_OK) 423 return status; 424 425 return B_OK; 426 } 427 428 429 BArchivable* 430 ActivityView::Instantiate(BMessage* archive) 431 { 432 if (!validate_instantiation(archive, "ActivityView")) 433 return NULL; 434 435 return new ActivityView(archive); 436 } 437 438 439 status_t 440 ActivityView::SaveState(BMessage& state) const 441 { 442 status_t status = state.AddBool("show legend", fShowLegend); 443 if (status != B_OK) 444 return status; 445 446 status = state.AddData("history background color", B_RGB_COLOR_TYPE, 447 &fHistoryBackgroundColor, sizeof(rgb_color)); 448 if (status != B_OK) 449 return status; 450 451 for (int32 i = 0; i < fSources.CountItems(); i++) { 452 DataSource* source = fSources.ItemAt(i); 453 454 if (!source->PerCPU() || source->CPU() == 0) 455 status = state.AddString("source", source->Name()); 456 if (status != B_OK) 457 return status; 458 459 BString name = source->Name(); 460 name << " color"; 461 rgb_color color = source->Color(); 462 state.AddData(name.String(), B_RGB_COLOR_TYPE, &color, 463 sizeof(rgb_color)); 464 } 465 return B_OK; 466 } 467 468 469 #ifdef __HAIKU__ 470 BLayoutItem* 471 ActivityView::CreateHistoryLayoutItem() 472 { 473 if (fHistoryLayoutItem == NULL) 474 fHistoryLayoutItem = new HistoryLayoutItem(this); 475 476 return fHistoryLayoutItem; 477 } 478 479 480 BLayoutItem* 481 ActivityView::CreateLegendLayoutItem() 482 { 483 if (fLegendLayoutItem == NULL) 484 fLegendLayoutItem = new LegendLayoutItem(this); 485 486 return fLegendLayoutItem; 487 } 488 #endif 489 490 491 DataSource* 492 ActivityView::FindDataSource(const DataSource* search) 493 { 494 for (int32 i = fSources.CountItems(); i-- > 0;) { 495 DataSource* source = fSources.ItemAt(i); 496 if (!strcmp(source->Name(), search->Name())) 497 return source; 498 } 499 500 return NULL; 501 } 502 503 504 status_t 505 ActivityView::AddDataSource(const DataSource* source, const BMessage* state) 506 { 507 if (source == NULL) 508 return B_BAD_VALUE; 509 510 int32 insert = DataSource::IndexOf(source); 511 for (int32 i = 0; i < fSources.CountItems() && i < insert; i++) { 512 DataSource* before = fSources.ItemAt(i); 513 if (DataSource::IndexOf(before) > insert) { 514 insert = i; 515 break; 516 } 517 } 518 if (insert > fSources.CountItems()) 519 insert = fSources.CountItems(); 520 521 uint32 count = 1; 522 if (source->PerCPU()) { 523 SystemInfo info; 524 count = info.CPUCount(); 525 } 526 527 for (uint32 i = 0; i < count; i++) { 528 DataHistory* values = new(std::nothrow) DataHistory(10 * 60000000LL, 529 fRefreshInterval); 530 if (values == NULL) 531 return B_NO_MEMORY; 532 533 if (!fValues.AddItem(values, insert + i)) { 534 delete values; 535 return B_NO_MEMORY; 536 } 537 538 DataSource* copy; 539 if (source->PerCPU()) 540 copy = source->CopyForCPU(i); 541 else 542 copy = source->Copy(); 543 544 BString colorName = source->Name(); 545 colorName << " color"; 546 if (state != NULL) { 547 const rgb_color* color = NULL; 548 ssize_t colorLength; 549 if (state->FindData(colorName.String(), B_RGB_COLOR_TYPE, i, 550 (const void**)&color, &colorLength) == B_OK 551 && colorLength == sizeof(rgb_color)) 552 copy->SetColor(*color); 553 } 554 555 if (!fSources.AddItem(copy, insert + i)) { 556 fValues.RemoveItem(values); 557 delete values; 558 return B_NO_MEMORY; 559 } 560 } 561 562 #ifdef __HAIKU__ 563 InvalidateLayout(); 564 #endif 565 return B_OK; 566 } 567 568 569 status_t 570 ActivityView::RemoveDataSource(const DataSource* remove) 571 { 572 bool removed = false; 573 574 while (true) { 575 DataSource* source = FindDataSource(remove); 576 debug_printf("SEARCH %s, found %p\n", remove->Name(), source); 577 if (source == NULL) { 578 if (removed) 579 break; 580 return B_ENTRY_NOT_FOUND; 581 } 582 583 int32 index = fSources.IndexOf(source); 584 if (index < 0) 585 return B_ENTRY_NOT_FOUND; 586 587 fSources.RemoveItemAt(index); 588 delete source; 589 DataHistory* values = fValues.RemoveItemAt(index); 590 delete values; 591 removed = true; 592 } 593 594 #ifdef __HAIKU__ 595 InvalidateLayout(); 596 #endif 597 return B_OK; 598 } 599 600 601 void 602 ActivityView::RemoveAllDataSources() 603 { 604 fSources.MakeEmpty(); 605 fValues.MakeEmpty(); 606 } 607 608 609 void 610 ActivityView::AttachedToWindow() 611 { 612 Looper()->AddHandler(fSystemInfoHandler); 613 fSystemInfoHandler->StartWatching(); 614 615 BMessage refresh(kMsgRefresh); 616 fRunner = new BMessageRunner(this, &refresh, fRefreshInterval); 617 618 FrameResized(Bounds().Width(), Bounds().Height()); 619 } 620 621 622 void 623 ActivityView::DetachedFromWindow() 624 { 625 fSystemInfoHandler->StopWatching(); 626 Looper()->RemoveHandler(fSystemInfoHandler); 627 628 delete fRunner; 629 } 630 631 632 #ifdef __HAIKU__ 633 BSize 634 ActivityView::MinSize() 635 { 636 BSize size(32, 32); 637 if (fShowLegend) 638 size.height = _LegendHeight(); 639 640 return size; 641 } 642 #endif 643 644 645 void 646 ActivityView::FrameResized(float /*width*/, float /*height*/) 647 { 648 _UpdateOffscreenBitmap(); 649 } 650 651 652 void 653 ActivityView::_UpdateOffscreenBitmap() 654 { 655 BRect frame = _HistoryFrame(); 656 if (fOffscreen != NULL && frame == fOffscreen->Bounds()) 657 return; 658 659 delete fOffscreen; 660 661 // create offscreen bitmap 662 663 fOffscreen = new(std::nothrow) BBitmap(frame, B_BITMAP_ACCEPTS_VIEWS, 664 B_RGB32); 665 if (fOffscreen == NULL || fOffscreen->InitCheck() != B_OK) { 666 delete fOffscreen; 667 fOffscreen = NULL; 668 return; 669 } 670 671 BView* view = new BView(frame, NULL, B_FOLLOW_NONE, B_SUBPIXEL_PRECISE); 672 view->SetViewColor(fHistoryBackgroundColor); 673 view->SetLowColor(view->ViewColor()); 674 fOffscreen->AddChild(view); 675 } 676 677 678 BView* 679 ActivityView::_OffscreenView() 680 { 681 if (fOffscreen == NULL) 682 return NULL; 683 684 return fOffscreen->ChildAt(0); 685 } 686 687 688 void 689 ActivityView::MouseDown(BPoint where) 690 { 691 #if 0 692 int32 buttons = B_PRIMARY_MOUSE_BUTTON; 693 int32 clicks = 1; 694 if (Looper() != NULL && Looper()->CurrentMessage() != NULL) { 695 Looper()->CurrentMessage()->FindInt32("buttons", &buttons); 696 Looper()->CurrentMessage()->FindInt32("clicks", &clicks); 697 } 698 #endif 699 700 BPopUpMenu *menu = new BPopUpMenu(B_EMPTY_STRING, false, false); 701 menu->SetFont(be_plain_font); 702 703 BMenu* additionalMenu = new BMenu("Additional Items"); 704 additionalMenu->SetFont(be_plain_font); 705 706 SystemInfo info; 707 BMenuItem* item; 708 709 for (int32 i = 0; i < DataSource::CountSources(); i++) { 710 const DataSource* source = DataSource::SourceAt(i); 711 712 if (source->MultiCPUOnly() && info.CPUCount() == 1) 713 continue; 714 715 BMessage* message = new BMessage(kMsgToggleDataSource); 716 message->AddInt32("index", i); 717 718 item = new BMenuItem(source->Name(), message); 719 if (FindDataSource(source)) 720 item->SetMarked(true); 721 722 if (source->Primary()) 723 menu->AddItem(item); 724 else 725 additionalMenu->AddItem(item); 726 } 727 728 menu->AddItem(new BMenuItem(additionalMenu)); 729 menu->AddSeparatorItem(); 730 menu->AddItem(new BMenuItem(fShowLegend ? "Hide Legend" : "Show Legend", 731 new BMessage(kMsgToggleLegend))); 732 733 menu->SetTargetForItems(this); 734 additionalMenu->SetTargetForItems(this); 735 736 ActivityWindow* window = dynamic_cast<ActivityWindow*>(Window()); 737 if (window != NULL && window->ActivityViewCount() > 1) { 738 menu->AddSeparatorItem(); 739 BMessage* message = new BMessage(kMsgRemoveView); 740 message->AddPointer("view", this); 741 menu->AddItem(item = new BMenuItem("Remove View", message)); 742 item->SetTarget(window); 743 } 744 745 ConvertToScreen(&where); 746 menu->Go(where, true, false, true); 747 748 } 749 750 751 void 752 ActivityView::MouseMoved(BPoint where, uint32 transit, 753 const BMessage* dragMessage) 754 { 755 } 756 757 758 void 759 ActivityView::MessageReceived(BMessage* message) 760 { 761 // if a color is dropped, use it as background 762 if (message->WasDropped()) { 763 rgb_color* color; 764 ssize_t size; 765 if (message->FindData("RGBColor", B_RGB_COLOR_TYPE, 0, 766 (const void**)&color, &size) == B_OK 767 && size == sizeof(rgb_color)) { 768 BPoint dropPoint = message->DropPoint(); 769 ConvertFromScreen(&dropPoint); 770 771 if (_HistoryFrame().Contains(dropPoint)) { 772 fHistoryBackgroundColor = *color; 773 Invalidate(_HistoryFrame()); 774 } else { 775 // check each legend color box 776 BRect legendFrame = _LegendFrame(); 777 for (int32 i = 0; i < fSources.CountItems(); i++) { 778 BRect frame = _LegendColorFrameAt(legendFrame, i); 779 if (frame.Contains(dropPoint)) { 780 fSources.ItemAt(i)->SetColor(*color); 781 Invalidate(_HistoryFrame()); 782 Invalidate(frame); 783 return; 784 } 785 } 786 787 if (dynamic_cast<ActivityMonitor*>(be_app) == NULL) { 788 // allow background color change in the replicant only 789 fLegendBackgroundColor = *color; 790 SetLowColor(fLegendBackgroundColor); 791 Invalidate(legendFrame); 792 } 793 } 794 return; 795 } 796 } 797 798 switch (message->what) { 799 case B_ABOUT_REQUESTED: 800 ActivityMonitor::ShowAbout(); 801 break; 802 803 case kMsgRefresh: 804 _Refresh(); 805 break; 806 807 case kMsgToggleDataSource: 808 { 809 int32 index; 810 if (message->FindInt32("index", &index) != B_OK) 811 break; 812 813 const DataSource* baseSource = DataSource::SourceAt(index); 814 if (baseSource == NULL) 815 break; 816 817 DataSource* source = FindDataSource(baseSource); 818 if (source == NULL) 819 AddDataSource(baseSource); 820 else 821 RemoveDataSource(source); 822 823 Invalidate(); 824 break; 825 } 826 827 case kMsgToggleLegend: 828 fShowLegend = !fShowLegend; 829 Invalidate(); 830 break; 831 832 case B_MOUSE_WHEEL_CHANGED: 833 { 834 float deltaY = 0.0f; 835 if (message->FindFloat("be:wheel_delta_y", &deltaY) != B_OK 836 || deltaY == 0.0f) 837 break; 838 839 if (deltaY > 0) 840 fDrawResolution *= 2; 841 else 842 fDrawResolution /= 2; 843 844 if (fDrawResolution < 1) 845 fDrawResolution = 1; 846 if (fDrawResolution > 128) 847 fDrawResolution = 128; 848 849 Invalidate(); 850 break; 851 } 852 853 default: 854 BView::MessageReceived(message); 855 break; 856 } 857 } 858 859 860 void 861 ActivityView::_UpdateFrame() 862 { 863 #ifdef __HAIKU__ 864 if (fLegendLayoutItem == NULL || fHistoryLayoutItem == NULL) 865 return; 866 867 BRect historyFrame = fHistoryLayoutItem->Frame(); 868 BRect legendFrame = fLegendLayoutItem->Frame(); 869 #else 870 BRect historyFrame = Bounds(); 871 BRect legendFrame = Bounds(); 872 historyFrame.bottom -= 2 * Bounds().Height() / 3; 873 legendFrame.top += Bounds().Height() / 3; 874 #endif 875 MoveTo(historyFrame.left, historyFrame.top); 876 ResizeTo(legendFrame.left + legendFrame.Width() - historyFrame.left, 877 legendFrame.top + legendFrame.Height() - historyFrame.top); 878 } 879 880 881 BRect 882 ActivityView::_HistoryFrame() const 883 { 884 if (!fShowLegend) 885 return Bounds(); 886 887 BRect frame = Bounds(); 888 BRect legendFrame = _LegendFrame(); 889 890 frame.bottom = legendFrame.top - 1; 891 892 return frame; 893 } 894 895 896 float 897 ActivityView::_LegendHeight() const 898 { 899 font_height fontHeight; 900 GetFontHeight(&fontHeight); 901 902 int32 rows = (fSources.CountItems() + 1) / 2; 903 return rows * (4 + ceilf(fontHeight.ascent) 904 + ceilf(fontHeight.descent) + ceilf(fontHeight.leading)); 905 } 906 907 908 BRect 909 ActivityView::_LegendFrame() const 910 { 911 float height; 912 #ifdef __HAIKU__ 913 if (fLegendLayoutItem != NULL) 914 height = fLegendLayoutItem->Frame().Height(); 915 else 916 #endif 917 height = _LegendHeight(); 918 919 BRect frame = Bounds(); 920 frame.top = frame.bottom - height; 921 922 return frame; 923 } 924 925 926 BRect 927 ActivityView::_LegendFrameAt(BRect frame, int32 index) const 928 { 929 int32 column = index & 1; 930 int32 row = index / 2; 931 if (column == 0) 932 frame.right = frame.left + floorf(frame.Width() / 2) - 5; 933 else 934 frame.left = frame.right - floorf(frame.Width() / 2) + 5; 935 936 int32 rows = (fSources.CountItems() + 1) / 2; 937 float height = floorf((frame.Height() - 5) / rows); 938 939 frame.top = frame.top + 5 + row * height; 940 frame.bottom = frame.top + height - 1; 941 942 return frame; 943 } 944 945 946 BRect 947 ActivityView::_LegendColorFrameAt(BRect frame, int32 index) const 948 { 949 frame = _LegendFrameAt(frame, index); 950 frame.InsetBy(1, 1); 951 frame.right = frame.left + frame.Height(); 952 953 return frame; 954 } 955 956 957 float 958 ActivityView::_PositionForValue(DataSource* source, DataHistory* values, 959 int64 value) 960 { 961 int64 min = source->Minimum(); 962 int64 max = source->Maximum(); 963 if (source->AdaptiveScale()) { 964 int64 adaptiveMax = int64(values->MaximumValue() * 1.2); 965 if (adaptiveMax < max) 966 max = adaptiveMax; 967 } 968 969 if (value > max) 970 value = max; 971 if (value < min) 972 value = min; 973 974 float height = _HistoryFrame().Height(); 975 return height - (value - min) * height / (max - min); 976 } 977 978 979 void 980 ActivityView::_DrawHistory() 981 { 982 _UpdateOffscreenBitmap(); 983 984 BView* view = this; 985 if (fOffscreen != NULL) { 986 fOffscreen->Lock(); 987 view = _OffscreenView(); 988 } 989 990 BRect frame = _HistoryFrame(); 991 view->SetLowColor(fHistoryBackgroundColor); 992 view->FillRect(frame, B_SOLID_LOW); 993 994 uint32 step = 2; 995 uint32 resolution = fDrawResolution; 996 if (fDrawResolution > 1) { 997 step = 1; 998 resolution--; 999 } 1000 1001 uint32 width = frame.IntegerWidth() - 10; 1002 uint32 steps = width / step; 1003 bigtime_t timeStep = fRefreshInterval * resolution; 1004 bigtime_t now = system_time(); 1005 1006 // Draw scale 1007 // TODO: add second markers? 1008 1009 view->SetPenSize(1); 1010 1011 rgb_color scaleColor = view->LowColor(); 1012 uint32 average = (scaleColor.red + scaleColor.green + scaleColor.blue) / 3; 1013 if (average < 96) 1014 scaleColor = tint_color(scaleColor, B_LIGHTEN_2_TINT); 1015 else 1016 scaleColor = tint_color(scaleColor, B_DARKEN_2_TINT); 1017 1018 view->SetHighColor(scaleColor); 1019 view->StrokeRect(frame); 1020 view->StrokeLine(BPoint(frame.left, frame.top + frame.Height() / 2), 1021 BPoint(frame.right, frame.top + frame.Height() / 2)); 1022 1023 // Draw values 1024 1025 view->SetPenSize(2); 1026 1027 for (uint32 i = fSources.CountItems(); i-- > 0;) { 1028 DataSource* source = fSources.ItemAt(i); 1029 DataHistory* values = fValues.ItemAt(i); 1030 bigtime_t time = now - steps * timeStep; 1031 // for now steps pixels per second 1032 1033 view->BeginLineArray(steps); 1034 view->SetHighColor(source->Color()); 1035 1036 float lastY = FLT_MIN; 1037 uint32 lastX = 0; 1038 1039 for (uint32 x = 0; x < width; x += step, time += timeStep) { 1040 // TODO: compute starting point instead! 1041 if (values->Start() > time || values->End() < time) 1042 continue; 1043 1044 int64 value = values->ValueAt(time); 1045 if (timeStep > fRefreshInterval) { 1046 // TODO: always start with the same index, so that it always 1047 // uses the same values for computation (currently it jumps) 1048 uint32 count = 1; 1049 for (bigtime_t offset = fRefreshInterval; offset < timeStep; 1050 offset += fRefreshInterval) { 1051 // TODO: handle int64 overflow correctly! 1052 value += values->ValueAt(time + offset); 1053 count++; 1054 } 1055 value /= count; 1056 } 1057 1058 float y = _PositionForValue(source, values, value); 1059 if (lastY != FLT_MIN) { 1060 view->AddLine(BPoint(lastX, lastY), BPoint(x, y), 1061 source->Color()); 1062 } 1063 1064 lastX = x; 1065 lastY = y; 1066 } 1067 1068 view->EndLineArray(); 1069 } 1070 1071 // TODO: add marks when an app started or quit 1072 view->Sync(); 1073 if (fOffscreen != NULL) { 1074 fOffscreen->Unlock(); 1075 DrawBitmap(fOffscreen); 1076 } 1077 } 1078 1079 1080 void 1081 ActivityView::Draw(BRect /*updateRect*/) 1082 { 1083 _DrawHistory(); 1084 1085 if (!fShowLegend) 1086 return; 1087 1088 // draw legend 1089 1090 BRect legendFrame = _LegendFrame(); 1091 1092 SetLowColor(fLegendBackgroundColor); 1093 FillRect(legendFrame, B_SOLID_LOW); 1094 1095 font_height fontHeight; 1096 GetFontHeight(&fontHeight); 1097 1098 for (int32 i = 0; i < fSources.CountItems(); i++) { 1099 DataSource* source = fSources.ItemAt(i); 1100 DataHistory* values = fValues.ItemAt(i); 1101 BRect frame = _LegendFrameAt(legendFrame, i); 1102 1103 // draw color box 1104 BRect colorBox = _LegendColorFrameAt(legendFrame, i); 1105 SetHighColor(tint_color(source->Color(), B_DARKEN_1_TINT)); 1106 StrokeRect(colorBox); 1107 SetHighColor(source->Color()); 1108 colorBox.InsetBy(1, 1); 1109 FillRect(colorBox); 1110 1111 // show current value and label 1112 float y = frame.top + ceilf(fontHeight.ascent); 1113 int64 value = values->ValueAt(values->End()); 1114 BString text; 1115 source->Print(text, value); 1116 float width = StringWidth(text.String()); 1117 1118 BString label = source->Label(); 1119 TruncateString(&label, B_TRUNCATE_MIDDLE, 1120 frame.right - colorBox.right - 12 - width); 1121 1122 SetHighColor(ui_color(B_CONTROL_TEXT_COLOR)); 1123 DrawString(label.String(), BPoint(6 + colorBox.right, y)); 1124 DrawString(text.String(), BPoint(frame.right - width, y)); 1125 } 1126 } 1127 1128 1129 void 1130 ActivityView::_Refresh() 1131 { 1132 SystemInfo info(fSystemInfoHandler); 1133 1134 // TODO: run refresh in another thread to decouple it from the UI! 1135 1136 for (uint32 i = fSources.CountItems(); i-- > 0;) { 1137 DataSource* source = fSources.ItemAt(i); 1138 DataHistory* values = fValues.ItemAt(i); 1139 1140 int64 value = source->NextValue(info); 1141 values->AddValue(info.Time(), value); 1142 } 1143 1144 bigtime_t now = info.Time(); 1145 if (fLastRefresh + fDrawInterval <= now) { 1146 Invalidate(); 1147 fLastRefresh = now; 1148 } 1149 } 1150 1151