1 /* 2 * Copyright 2006-2012, Haiku, Inc. All Rights Reserved. 3 * Distributed under the terms of the MIT License. 4 * 5 * Authors: 6 * Axel Dörfler, axeld@pinc-software.de 7 * Clemens Zeidler, haiku@Clemens-Zeidler.de 8 * Alexander von Gluck, kallisti5@unixzen.com 9 */ 10 11 12 #include "PowerStatusView.h" 13 14 #include <stdio.h> 15 #include <stdlib.h> 16 #include <string.h> 17 #include <unistd.h> 18 19 #include <AboutWindow.h> 20 #include <Application.h> 21 #include <Catalog.h> 22 #include <ControlLook.h> 23 #include <Deskbar.h> 24 #include <Dragger.h> 25 #include <Drivers.h> 26 #include <File.h> 27 #include <FindDirectory.h> 28 #include <MenuItem.h> 29 #include <MessageRunner.h> 30 #include <Path.h> 31 #include <PopUpMenu.h> 32 #include <TextView.h> 33 34 #include "ACPIDriverInterface.h" 35 #include "APMDriverInterface.h" 36 #include "ExtendedInfoWindow.h" 37 #include "PowerStatus.h" 38 39 40 #undef B_TRANSLATION_CONTEXT 41 #define B_TRANSLATION_CONTEXT "PowerStatus" 42 43 44 extern "C" _EXPORT BView *instantiate_deskbar_item(void); 45 extern const char* kDeskbarItemName; 46 47 const uint32 kMsgToggleLabel = 'tglb'; 48 const uint32 kMsgToggleTime = 'tgtm'; 49 const uint32 kMsgToggleStatusIcon = 'tgsi'; 50 const uint32 kMsgToggleExtInfo = 'texi'; 51 52 const uint32 kMinIconWidth = 16; 53 const uint32 kMinIconHeight = 16; 54 55 PowerStatusView::PowerStatusView(PowerStatusDriverInterface* interface, 56 BRect frame, int32 resizingMode, int batteryID, bool inDeskbar) 57 : 58 BView(frame, kDeskbarItemName, resizingMode, 59 B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE), 60 fDriverInterface(interface), 61 fBatteryID(batteryID), 62 fInDeskbar(inDeskbar) 63 { 64 fPreferredSize.width = frame.Width(); 65 fPreferredSize.height = frame.Height(); 66 _Init(); 67 } 68 69 70 PowerStatusView::PowerStatusView(BMessage* archive) 71 : BView(archive) 72 { 73 _Init(); 74 FromMessage(archive); 75 } 76 77 78 PowerStatusView::~PowerStatusView() 79 { 80 } 81 82 83 status_t 84 PowerStatusView::Archive(BMessage* archive, bool deep) const 85 { 86 status_t status = BView::Archive(archive, deep); 87 if (status == B_OK) 88 status = ToMessage(archive); 89 90 return status; 91 } 92 93 94 void 95 PowerStatusView::_Init() 96 { 97 SetViewColor(B_TRANSPARENT_COLOR); 98 99 fShowLabel = true; 100 fShowTime = false; 101 fShowStatusIcon = true; 102 103 fPercent = -1; 104 fOnline = true; 105 fTimeLeft = 0; 106 } 107 108 109 void 110 PowerStatusView::AttachedToWindow() 111 { 112 BView::AttachedToWindow(); 113 if (Parent()) 114 SetLowColor(Parent()->ViewColor()); 115 else 116 SetLowColor(ui_color(B_PANEL_BACKGROUND_COLOR)); 117 118 Update(); 119 } 120 121 122 void 123 PowerStatusView::DetachedFromWindow() 124 { 125 } 126 127 128 void 129 PowerStatusView::MessageReceived(BMessage *message) 130 { 131 switch (message->what) { 132 case kMsgUpdate: 133 Update(); 134 break; 135 136 default: 137 BView::MessageReceived(message); 138 } 139 } 140 141 142 void 143 PowerStatusView::GetPreferredSize(float *width, float *height) 144 { 145 *width = fPreferredSize.width; 146 *height = fPreferredSize.height; 147 } 148 149 150 void 151 PowerStatusView::_DrawBattery(BRect rect) 152 { 153 float quarter = floorf((rect.Height() + 1) / 4); 154 rect.top += quarter; 155 rect.bottom -= quarter; 156 157 rect.InsetBy(2, 0); 158 159 float left = rect.left; 160 rect.left += rect.Width() / 11; 161 162 SetHighColor(0, 0, 0); 163 164 float gap = 1; 165 if (rect.Height() > 8) { 166 gap = ceilf((rect.left - left) / 2); 167 168 // left 169 FillRect(BRect(rect.left, rect.top, rect.left + gap - 1, rect.bottom)); 170 // right 171 FillRect(BRect(rect.right - gap + 1, rect.top, rect.right, 172 rect.bottom)); 173 // top 174 FillRect(BRect(rect.left + gap, rect.top, rect.right - gap, 175 rect.top + gap - 1)); 176 // bottom 177 FillRect(BRect(rect.left + gap, rect.bottom + 1 - gap, 178 rect.right - gap, rect.bottom)); 179 } else 180 StrokeRect(rect); 181 182 FillRect(BRect(left, floorf(rect.top + rect.Height() / 4) + 1, 183 rect.left - 1, floorf(rect.bottom - rect.Height() / 4))); 184 185 int32 percent = fPercent; 186 if (percent > 100) 187 percent = 100; 188 else if (percent < 0 || !fHasBattery) 189 percent = 0; 190 191 if (percent > 0) { 192 rect.InsetBy(gap, gap); 193 rgb_color base = {84, 84, 84, 255}; 194 if (be_control_look != NULL) { 195 BRect empty = rect; 196 if (fHasBattery && percent > 0) 197 empty.left += empty.Width() * percent / 100.0; 198 199 be_control_look->DrawButtonBackground(this, empty, empty, base, 200 fHasBattery 201 ? BControlLook::B_ACTIVATED : BControlLook::B_DISABLED, 202 fHasBattery && percent > 0 203 ? (BControlLook::B_ALL_BORDERS 204 & ~BControlLook::B_LEFT_BORDER) 205 : BControlLook::B_ALL_BORDERS); 206 } 207 208 if (fHasBattery) { 209 if (percent <= 15) 210 base.set_to(180, 0, 0); 211 else 212 base.set_to(20, 180, 0); 213 214 rect.right = rect.left + rect.Width() * percent / 100.0; 215 216 if (be_control_look != NULL) { 217 be_control_look->DrawButtonBackground(this, rect, rect, base, 218 fHasBattery ? 0 : BControlLook::B_DISABLED); 219 } else 220 FillRect(rect); 221 } 222 } 223 224 SetHighColor(0, 0, 0); 225 } 226 227 228 void 229 PowerStatusView::Draw(BRect updateRect) 230 { 231 bool drawBackground = Parent() == NULL 232 || (Parent()->Flags() & B_DRAW_ON_CHILDREN) == 0; 233 if (drawBackground) 234 FillRect(updateRect, B_SOLID_LOW); 235 236 float aspect = Bounds().Width() / Bounds().Height(); 237 bool below = aspect <= 1.0f; 238 239 font_height fontHeight; 240 GetFontHeight(&fontHeight); 241 float baseLine = ceilf(fontHeight.ascent); 242 243 char text[64]; 244 _SetLabel(text, sizeof(text)); 245 246 float textHeight = ceilf(fontHeight.descent + fontHeight.ascent); 247 float textWidth = StringWidth(text); 248 bool showLabel = fShowLabel && text[0]; 249 250 BRect iconRect; 251 252 if (fShowStatusIcon) { 253 iconRect = Bounds(); 254 if (showLabel) { 255 if (below) 256 iconRect.bottom -= textHeight + 4; 257 else 258 iconRect.right -= textWidth + 4; 259 } 260 261 // make a square 262 iconRect.bottom = min_c(iconRect.bottom, iconRect.right); 263 iconRect.right = iconRect.bottom; 264 265 if (iconRect.Width() + 1 >= kMinIconWidth 266 && iconRect.Height() + 1 >= kMinIconHeight) { 267 _DrawBattery(iconRect); 268 } else { 269 // there is not enough space for the icon 270 iconRect.Set(0, 0, -1, -1); 271 } 272 } 273 274 if (showLabel) { 275 BPoint point(0, baseLine); 276 277 if (iconRect.IsValid()) { 278 if (below) { 279 point.x = (iconRect.Width() - textWidth) / 2; 280 point.y += iconRect.Height() + 2; 281 } else { 282 point.x = iconRect.Width() + 2; 283 point.y += (iconRect.Height() - textHeight) / 2; 284 } 285 } else { 286 point.x = (Bounds().Width() - textWidth) / 2; 287 point.y += (Bounds().Height() - textHeight) / 2; 288 } 289 290 if (drawBackground) 291 SetHighColor(ui_color(B_CONTROL_TEXT_COLOR)); 292 else { 293 SetDrawingMode(B_OP_OVER); 294 rgb_color c = Parent()->LowColor(); 295 if (c.red + c.green + c.blue > 128 * 3) 296 SetHighColor(0, 0, 0); 297 else 298 SetHighColor(255, 255, 255); 299 } 300 301 DrawString(text, point); 302 } 303 } 304 305 306 void 307 PowerStatusView::_SetLabel(char* buffer, size_t bufferLength) 308 { 309 if (bufferLength < 1) 310 return; 311 312 buffer[0] = '\0'; 313 314 if (!fShowLabel) 315 return; 316 317 const char* open = ""; 318 const char* close = ""; 319 if (fOnline) { 320 open = "("; 321 close = ")"; 322 } 323 324 if (!fShowTime && fPercent >= 0) 325 snprintf(buffer, bufferLength, "%s%ld%%%s", open, fPercent, close); 326 else if (fShowTime && fTimeLeft >= 0) { 327 snprintf(buffer, bufferLength, "%s%ld:%02ld%s", 328 open, fTimeLeft / 3600, (fTimeLeft / 60) % 60, close); 329 } 330 } 331 332 333 334 void 335 PowerStatusView::Update(bool force) 336 { 337 int32 previousPercent = fPercent; 338 bool previousTimeLeft = fTimeLeft; 339 bool wasOnline = fOnline; 340 341 _GetBatteryInfo(&fBatteryInfo, fBatteryID); 342 343 fHasBattery = (fBatteryInfo.state & BATTERY_CRITICAL_STATE) == 0; 344 345 if (fBatteryInfo.full_capacity > 0 && fHasBattery) { 346 fPercent = (100 * fBatteryInfo.capacity) / fBatteryInfo.full_capacity; 347 fOnline = (fBatteryInfo.state & BATTERY_CHARGING) != 0; 348 fTimeLeft = fBatteryInfo.time_left; 349 } else { 350 fPercent = 0; 351 fOnline = false; 352 fTimeLeft = false; 353 } 354 355 356 if (fInDeskbar) { 357 // make sure the tray icon is large enough 358 float width = fShowStatusIcon ? kMinIconWidth + 2 : 0; 359 360 if (fShowLabel) { 361 char text[64]; 362 _SetLabel(text, sizeof(text)); 363 364 if (text[0]) 365 width += ceilf(StringWidth(text)) + 4; 366 } else { 367 char text[256]; 368 const char* open = ""; 369 const char* close = ""; 370 if (fOnline) { 371 open = "("; 372 close = ")"; 373 } 374 if (fHasBattery) { 375 size_t length = snprintf(text, sizeof(text), "%s%ld%%%s", 376 open, fPercent, close); 377 if (fTimeLeft) { 378 length += snprintf(text + length, sizeof(text) - length, 379 "\n%ld:%02ld", fTimeLeft / 3600, (fTimeLeft / 60) % 60); 380 } 381 382 const char* state = NULL; 383 if ((fBatteryInfo.state & BATTERY_CHARGING) != 0) 384 state = B_TRANSLATE("charging"); 385 else if ((fBatteryInfo.state & BATTERY_DISCHARGING) != 0) 386 state = B_TRANSLATE("discharging"); 387 388 if (state != NULL) { 389 snprintf(text + length, sizeof(text) - length, "\n%s", 390 state); 391 } 392 } else 393 strcpy(text, B_TRANSLATE("no battery")); 394 SetToolTip(text); 395 } 396 if (width == 0) { 397 // make sure we're not going away completely 398 width = 8; 399 } 400 401 if (width != Bounds().Width()) 402 ResizeTo(width, Bounds().Height()); 403 } 404 405 if (force || wasOnline != fOnline 406 || (fShowTime && fTimeLeft != previousTimeLeft) 407 || (!fShowTime && fPercent != previousPercent)) 408 Invalidate(); 409 } 410 411 412 void 413 PowerStatusView::FromMessage(const BMessage* archive) 414 { 415 bool value; 416 if (archive->FindBool("show label", &value) == B_OK) 417 fShowLabel = value; 418 if (archive->FindBool("show icon", &value) == B_OK) 419 fShowStatusIcon = value; 420 if (archive->FindBool("show time", &value) == B_OK) 421 fShowTime = value; 422 423 //Incase we have a bad saving and none are showed.. 424 if (!fShowLabel && !fShowStatusIcon) 425 fShowLabel = true; 426 427 int32 intValue; 428 if (archive->FindInt32("battery id", &intValue) == B_OK) 429 fBatteryID = intValue; 430 } 431 432 433 status_t 434 PowerStatusView::ToMessage(BMessage* archive) const 435 { 436 status_t status = archive->AddBool("show label", fShowLabel); 437 if (status == B_OK) 438 status = archive->AddBool("show icon", fShowStatusIcon); 439 if (status == B_OK) 440 status = archive->AddBool("show time", fShowTime); 441 if (status == B_OK) 442 status = archive->AddInt32("battery id", fBatteryID); 443 444 return status; 445 } 446 447 448 void 449 PowerStatusView::_GetBatteryInfo(battery_info* batteryInfo, int batteryID) 450 { 451 if (batteryID >= 0) { 452 fDriverInterface->GetBatteryInfo(batteryInfo, batteryID); 453 } else { 454 for (int i = 0; i < fDriverInterface->GetBatteryCount(); i++) { 455 battery_info info; 456 fDriverInterface->GetBatteryInfo(&info, i); 457 458 if (i == 0) 459 *batteryInfo = info; 460 else { 461 batteryInfo->state &= info.state; 462 batteryInfo->capacity += info.capacity; 463 batteryInfo->full_capacity += info.full_capacity; 464 batteryInfo->time_left += info.time_left; 465 } 466 } 467 } 468 } 469 470 471 // #pragma mark - 472 473 474 PowerStatusReplicant::PowerStatusReplicant(BRect frame, int32 resizingMode, 475 bool inDeskbar) 476 : 477 PowerStatusView(NULL, frame, resizingMode, -1, inDeskbar) 478 { 479 _Init(); 480 _LoadSettings(); 481 482 if (!inDeskbar) { 483 // we were obviously added to a standard window - let's add a dragger 484 frame.OffsetTo(B_ORIGIN); 485 frame.top = frame.bottom - 7; 486 frame.left = frame.right - 7; 487 BDragger* dragger = new BDragger(frame, this, 488 B_FOLLOW_RIGHT | B_FOLLOW_BOTTOM); 489 AddChild(dragger); 490 } else 491 Update(); 492 } 493 494 495 PowerStatusReplicant::PowerStatusReplicant(BMessage* archive) 496 : 497 PowerStatusView(archive) 498 { 499 _Init(); 500 _LoadSettings(); 501 } 502 503 504 PowerStatusReplicant::~PowerStatusReplicant() 505 { 506 if (fMessengerExist) 507 delete fExtWindowMessenger; 508 509 fDriverInterface->StopWatching(this); 510 fDriverInterface->Disconnect(); 511 fDriverInterface->ReleaseReference(); 512 513 _SaveSettings(); 514 } 515 516 517 PowerStatusReplicant* 518 PowerStatusReplicant::Instantiate(BMessage* archive) 519 { 520 if (!validate_instantiation(archive, "PowerStatusReplicant")) 521 return NULL; 522 523 return new PowerStatusReplicant(archive); 524 } 525 526 527 status_t 528 PowerStatusReplicant::Archive(BMessage* archive, bool deep) const 529 { 530 status_t status = PowerStatusView::Archive(archive, deep); 531 if (status == B_OK) 532 status = archive->AddString("add_on", kSignature); 533 if (status == B_OK) 534 status = archive->AddString("class", "PowerStatusReplicant"); 535 536 return status; 537 } 538 539 540 void 541 PowerStatusReplicant::MessageReceived(BMessage *message) 542 { 543 switch (message->what) { 544 case kMsgToggleLabel: 545 if (fShowStatusIcon) 546 fShowLabel = !fShowLabel; 547 else 548 fShowLabel = true; 549 550 Update(true); 551 break; 552 553 case kMsgToggleTime: 554 fShowTime = !fShowTime; 555 Update(true); 556 break; 557 558 case kMsgToggleStatusIcon: 559 if (fShowLabel) 560 fShowStatusIcon = !fShowStatusIcon; 561 else 562 fShowStatusIcon = true; 563 564 Update(true); 565 break; 566 567 case kMsgToggleExtInfo: 568 _OpenExtendedWindow(); 569 break; 570 571 case B_ABOUT_REQUESTED: 572 _AboutRequested(); 573 break; 574 575 case B_QUIT_REQUESTED: 576 _Quit(); 577 break; 578 579 default: 580 PowerStatusView::MessageReceived(message); 581 } 582 } 583 584 585 void 586 PowerStatusReplicant::MouseDown(BPoint point) 587 { 588 BPopUpMenu *menu = new BPopUpMenu(B_EMPTY_STRING, false, false); 589 menu->SetFont(be_plain_font); 590 591 BMenuItem* item; 592 menu->AddItem(item = new BMenuItem(B_TRANSLATE("Show text label"), 593 new BMessage(kMsgToggleLabel))); 594 if (fShowLabel) 595 item->SetMarked(true); 596 menu->AddItem(item = new BMenuItem(B_TRANSLATE("Show status icon"), 597 new BMessage(kMsgToggleStatusIcon))); 598 if (fShowStatusIcon) 599 item->SetMarked(true); 600 menu->AddItem(new BMenuItem(!fShowTime ? B_TRANSLATE("Show time") : 601 B_TRANSLATE("Show percent"), 602 new BMessage(kMsgToggleTime))); 603 604 menu->AddSeparatorItem(); 605 menu->AddItem(new BMenuItem(B_TRANSLATE("Battery info" B_UTF8_ELLIPSIS), 606 new BMessage(kMsgToggleExtInfo))); 607 608 menu->AddSeparatorItem(); 609 menu->AddItem(new BMenuItem(B_TRANSLATE("About" B_UTF8_ELLIPSIS), 610 new BMessage(B_ABOUT_REQUESTED))); 611 menu->AddItem(new BMenuItem(B_TRANSLATE("Quit"), 612 new BMessage(B_QUIT_REQUESTED))); 613 menu->SetTargetForItems(this); 614 615 ConvertToScreen(&point); 616 menu->Go(point, true, false, true); 617 } 618 619 620 void 621 PowerStatusReplicant::_AboutRequested() 622 { 623 BAboutWindow* window = new BAboutWindow( 624 B_TRANSLATE_SYSTEM_NAME("PowerStatus"), kSignature); 625 626 const char* authors[] = { 627 "Axel Dörfler", 628 "Alexander von Gluck", 629 "Clemens Zeidler", 630 NULL 631 }; 632 633 window->AddCopyright(2006, "Haiku, Inc."); 634 window->AddAuthors(authors); 635 636 window->Show(); 637 } 638 639 640 void 641 PowerStatusReplicant::_Init() 642 { 643 fDriverInterface = new ACPIDriverInterface; 644 if (fDriverInterface->Connect() != B_OK) { 645 delete fDriverInterface; 646 fDriverInterface = new APMDriverInterface; 647 if (fDriverInterface->Connect() != B_OK) { 648 fprintf(stderr, "No power interface found.\n"); 649 _Quit(); 650 } 651 } 652 653 fExtendedWindow = NULL; 654 fMessengerExist = false; 655 fExtWindowMessenger = NULL; 656 657 fDriverInterface->StartWatching(this); 658 } 659 660 661 void 662 PowerStatusReplicant::_Quit() 663 { 664 if (fInDeskbar) { 665 BDeskbar deskbar; 666 deskbar.RemoveItem(kDeskbarItemName); 667 } else 668 be_app->PostMessage(B_QUIT_REQUESTED); 669 } 670 671 672 status_t 673 PowerStatusReplicant::_GetSettings(BFile& file, int mode) 674 { 675 BPath path; 676 status_t status = find_directory(B_USER_SETTINGS_DIRECTORY, &path, 677 (mode & O_ACCMODE) != O_RDONLY); 678 if (status != B_OK) 679 return status; 680 681 path.Append("PowerStatus settings"); 682 683 return file.SetTo(path.Path(), mode); 684 } 685 686 687 void 688 PowerStatusReplicant::_LoadSettings() 689 { 690 fShowLabel = false; 691 692 BFile file; 693 if (_GetSettings(file, B_READ_ONLY) != B_OK) 694 return; 695 696 BMessage settings; 697 if (settings.Unflatten(&file) < B_OK) 698 return; 699 700 FromMessage(&settings); 701 } 702 703 704 void 705 PowerStatusReplicant::_SaveSettings() 706 { 707 BFile file; 708 if (_GetSettings(file, B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE) != B_OK) 709 return; 710 711 BMessage settings('pwst'); 712 ToMessage(&settings); 713 714 ssize_t size = 0; 715 settings.Flatten(&file, &size); 716 } 717 718 719 void 720 PowerStatusReplicant::_OpenExtendedWindow() 721 { 722 if (!fExtendedWindow) { 723 fExtendedWindow = new ExtendedInfoWindow(fDriverInterface); 724 fExtWindowMessenger = new BMessenger(NULL, fExtendedWindow); 725 fExtendedWindow->Show(); 726 return; 727 } 728 729 BMessage msg(B_SET_PROPERTY); 730 msg.AddSpecifier("Hidden", int32(0)); 731 if (fExtWindowMessenger->SendMessage(&msg) == B_BAD_PORT_ID) { 732 fExtendedWindow = new ExtendedInfoWindow(fDriverInterface); 733 if (fMessengerExist) 734 delete fExtWindowMessenger; 735 fExtWindowMessenger = new BMessenger(NULL, fExtendedWindow); 736 fMessengerExist = true; 737 fExtendedWindow->Show(); 738 } else 739 fExtendedWindow->Activate(); 740 741 } 742 743 744 // #pragma mark - 745 746 747 extern "C" _EXPORT BView* 748 instantiate_deskbar_item(void) 749 { 750 return new PowerStatusReplicant(BRect(0, 0, 15, 15), B_FOLLOW_NONE, true); 751 } 752 753