1 /* 2 * Copyright 2006, 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 */ 8 9 10 #include "PowerStatusView.h" 11 #include "PowerStatus.h" 12 13 #include <Alert.h> 14 #include <Application.h> 15 #include <Deskbar.h> 16 #include <Dragger.h> 17 #include <Drivers.h> 18 #include <MenuItem.h> 19 #include <MessageRunner.h> 20 #include <PopUpMenu.h> 21 #include <TextView.h> 22 23 #include <stdio.h> 24 #include <stdlib.h> 25 #include <string.h> 26 #include <unistd.h> 27 28 29 extern "C" _EXPORT BView *instantiate_deskbar_item(void); 30 31 32 const uint32 kMsgUpdate = 'updt'; 33 const uint32 kMsgToggleLabel = 'tglb'; 34 const uint32 kMsgToggleTime = 'tgtm'; 35 const uint32 kMsgToggleStatusIcon = 'tgsi'; 36 37 const uint32 kMinIconWidth = 16; 38 const uint32 kMinIconHeight = 16; 39 40 const bigtime_t kUpdateInterval = 2000000; 41 // every two seconds 42 43 44 #ifndef HAIKU_TARGET_PLATFORM_HAIKU 45 // definitions for the APM driver available for BeOS 46 enum { 47 APM_CONTROL = B_DEVICE_OP_CODES_END + 1, 48 APM_DUMP_POWER_STATUS, 49 APM_BIOS_CALL, 50 APM_SET_SAFETY 51 }; 52 53 #define BIOS_APM_GET_POWER_STATUS 0x530a 54 #endif 55 56 57 PowerStatusView::PowerStatusView(BRect frame, int32 resizingMode, bool inDeskbar) 58 : BView(frame, kDeskbarItemName, resizingMode, 59 B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE), 60 fInDeskbar(inDeskbar) 61 { 62 _Init(); 63 64 if (!inDeskbar) { 65 // we were obviously added to a standard window - let's add a dragger 66 frame.OffsetTo(B_ORIGIN); 67 frame.top = frame.bottom - 7; 68 frame.left = frame.right - 7; 69 BDragger* dragger = new BDragger(frame, this, 70 B_FOLLOW_RIGHT | B_FOLLOW_BOTTOM); 71 AddChild(dragger); 72 } else 73 _Update(); 74 } 75 76 77 PowerStatusView::PowerStatusView(BMessage* archive) 78 : BView(archive) 79 { 80 _Init(); 81 82 bool value; 83 if (archive->FindBool("show label", &value) == B_OK) 84 fShowLabel = value; 85 if (archive->FindBool("show icon", &value) == B_OK) 86 fShowStatusIcon = value; 87 if (archive->FindBool("show time", &value) == B_OK) 88 fShowTime = value; 89 } 90 91 92 PowerStatusView::~PowerStatusView() 93 { 94 #ifndef HAIKU_TARGET_PLATFORM_HAIKU 95 close(fDevice); 96 #endif 97 } 98 99 100 void 101 PowerStatusView::_Init() 102 { 103 fShowLabel = true; 104 fShowTime = false; 105 fShowStatusIcon = true; 106 107 fMessageRunner = NULL; 108 fPercent = -1; 109 fOnline = true; 110 fTimeLeft = 0; 111 112 #ifdef HAIKU_TARGET_PLATFORM_HAIKU 113 // TODO: implement me 114 #else 115 fDevice = open("/dev/misc/apm", O_RDONLY); 116 if (fDevice < 0) { 117 fprintf(stderr, "No power interface found.\n"); 118 _Quit(); 119 } 120 #endif 121 } 122 123 124 void 125 PowerStatusView::_Quit() 126 { 127 if (fInDeskbar) { 128 BDeskbar deskbar; 129 deskbar.RemoveItem(kDeskbarItemName); 130 } else 131 be_app->PostMessage(B_QUIT_REQUESTED); 132 } 133 134 135 PowerStatusView * 136 PowerStatusView::Instantiate(BMessage* archive) 137 { 138 if (!validate_instantiation(archive, "PowerStatusView")) 139 return NULL; 140 141 return new PowerStatusView(archive); 142 } 143 144 145 status_t 146 PowerStatusView::Archive(BMessage* archive, bool deep) const 147 { 148 status_t status = BView::Archive(archive, deep); 149 if (status == B_OK) 150 status = archive->AddString("add_on", kSignature); 151 if (status == B_OK) 152 status = archive->AddString("class", "PowerStatusView"); 153 if (status == B_OK) 154 status = archive->AddBool("show label", fShowLabel); 155 if (status == B_OK) 156 status = archive->AddBool("show icon", fShowStatusIcon); 157 if (status == B_OK) 158 status = archive->AddBool("show time", fShowTime); 159 160 return status; 161 } 162 163 164 void 165 PowerStatusView::AttachedToWindow() 166 { 167 BView::AttachedToWindow(); 168 if (Parent()) 169 SetViewColor(Parent()->ViewColor()); 170 else 171 SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); 172 173 SetLowColor(ViewColor()); 174 175 BMessage update(kMsgUpdate); 176 fMessageRunner = new BMessageRunner(this, &update, kUpdateInterval); 177 178 _Update(); 179 } 180 181 182 void 183 PowerStatusView::DetachedFromWindow() 184 { 185 delete fMessageRunner; 186 } 187 188 189 void 190 PowerStatusView::MessageReceived(BMessage *message) 191 { 192 switch (message->what) { 193 case kMsgUpdate: 194 _Update(); 195 break; 196 197 case kMsgToggleLabel: 198 fShowLabel = !fShowLabel; 199 _Update(true); 200 break; 201 202 case kMsgToggleTime: 203 fShowTime = !fShowTime; 204 _Update(true); 205 break; 206 207 case kMsgToggleStatusIcon: 208 fShowStatusIcon = !fShowStatusIcon; 209 _Update(true); 210 break; 211 212 case B_ABOUT_REQUESTED: 213 _AboutRequested(); 214 break; 215 216 case B_QUIT_REQUESTED: 217 _Quit(); 218 break; 219 220 default: 221 BView::MessageReceived(message); 222 } 223 } 224 225 226 void 227 PowerStatusView::_DrawBattery(BRect rect) 228 { 229 float quarter = floorf((rect.Height() + 1) / 4); 230 rect.top += quarter; 231 rect.bottom -= quarter; 232 233 rect.InsetBy(2, 0); 234 235 float left = rect.left; 236 rect.left += rect.Width() / 11; 237 238 float gap = 1; 239 if (rect.Height() > 8) { 240 rect.InsetBy(1, 1); 241 SetPenSize(2); 242 gap = 2; 243 } 244 245 if (fOnline) 246 SetHighColor(92, 92, 92); 247 248 StrokeRect(rect); 249 250 SetPenSize(1); 251 FillRect(BRect(left, floorf(rect.top + rect.Height() / 4) + 1, 252 rect.left, floorf(rect.bottom - rect.Height() / 4))); 253 254 int32 percent = fPercent; 255 if (percent > 100 || percent < 0) 256 percent = 100; 257 258 if (percent > 0) { 259 if (percent < 16) 260 SetHighColor(180, 0, 0); 261 262 rect.InsetBy(gap + 1, gap + 1); 263 if (gap > 1) { 264 rect.right++; 265 rect.bottom++; 266 } 267 268 rect.right = rect.left + rect.Width() * min_c(percent, 100) / 100.0; 269 FillRect(rect); 270 } 271 272 SetHighColor(0, 0, 0); 273 } 274 275 276 void 277 PowerStatusView::Draw(BRect updateRect) 278 { 279 float aspect = Bounds().Width() / Bounds().Height(); 280 bool below = aspect <= 1.0f; 281 282 font_height fontHeight; 283 GetFontHeight(&fontHeight); 284 float baseLine = ceilf(fontHeight.ascent); 285 286 char text[64]; 287 _SetLabel(text, sizeof(text)); 288 289 float textHeight = ceilf(fontHeight.descent + fontHeight.ascent); 290 float textWidth = StringWidth(text); 291 bool showLabel = fShowLabel && text[0]; 292 293 BRect iconRect; 294 295 if (fShowStatusIcon) { 296 iconRect = Bounds(); 297 if (showLabel) { 298 if (below) 299 iconRect.bottom -= textHeight + 4; 300 else 301 iconRect.right -= textWidth + 4; 302 } 303 304 // make a square 305 iconRect.bottom = min_c(iconRect.bottom, iconRect.right); 306 iconRect.right = iconRect.bottom; 307 308 if (iconRect.Width() + 1 >= kMinIconWidth 309 && iconRect.Height() + 1 >= kMinIconHeight) { 310 // TODO: have real icons 311 //if (!fOnline) 312 _DrawBattery(iconRect); 313 //else 314 // FillRect(iconRect); 315 } else { 316 // there is not enough space for the icon 317 iconRect.Set(0, 0, -1, -1); 318 } 319 } 320 321 if (showLabel) { 322 BPoint point(0, baseLine); 323 324 if (iconRect.IsValid()) { 325 if (below) { 326 point.x = (iconRect.Width() - textWidth) / 2; 327 point.y += iconRect.Height() + 2; 328 } else { 329 point.x = iconRect.Width() + 2; 330 point.y += (iconRect.Height() - textHeight) / 2; 331 } 332 } else { 333 point.x = (Bounds().Width() - textWidth) / 2; 334 point.y += (Bounds().Height() - textHeight) / 2; 335 } 336 337 DrawString(text, point); 338 } 339 } 340 341 342 void 343 PowerStatusView::MouseDown(BPoint point) 344 { 345 BPopUpMenu *menu = new BPopUpMenu(B_EMPTY_STRING, false, false); 346 menu->SetFont(be_plain_font); 347 348 BMenuItem* item; 349 menu->AddItem(item = new BMenuItem("Show Text Label", new BMessage(kMsgToggleLabel))); 350 if (fShowLabel) 351 item->SetMarked(true); 352 menu->AddItem(item = new BMenuItem("Show Status Icon", 353 new BMessage(kMsgToggleStatusIcon))); 354 if (fShowStatusIcon) 355 item->SetMarked(true); 356 menu->AddItem(new BMenuItem(!fShowTime ? "Show Time" : "Show Percent", 357 new BMessage(kMsgToggleTime))); 358 359 menu->AddSeparatorItem(); 360 menu->AddItem(new BMenuItem("About" B_UTF8_ELLIPSIS, new BMessage(B_ABOUT_REQUESTED))); 361 menu->AddItem(new BMenuItem("Quit", new BMessage(B_QUIT_REQUESTED))); 362 menu->SetTargetForItems(this); 363 364 ConvertToScreen(&point); 365 menu->Go(point, true, false, true); 366 } 367 368 369 void 370 PowerStatusView::_AboutRequested() 371 { 372 BAlert *alert = new BAlert("about", "PowerStatus\n" 373 "\twritten by Axel Dörfler\n" 374 "\tCopyright 2006, Haiku, Inc.\n", "Ok"); 375 BTextView *view = alert->TextView(); 376 BFont font; 377 378 view->SetStylable(true); 379 380 view->GetFont(&font); 381 font.SetSize(18); 382 font.SetFace(B_BOLD_FACE); 383 view->SetFontAndColor(0, 11, &font); 384 385 alert->Go(); 386 } 387 388 389 void 390 PowerStatusView::_SetLabel(char* buffer, size_t bufferLength) 391 { 392 if (bufferLength < 1) 393 return; 394 395 buffer[0] = '\0'; 396 397 if (!fShowLabel) 398 return; 399 400 const char* open = ""; 401 const char* close = ""; 402 if (fOnline) { 403 open = "("; 404 close = ")"; 405 } 406 407 if (!fShowTime && fPercent >= 0) 408 snprintf(buffer, bufferLength, "%s%ld%%%s", open, fPercent, close); 409 else if (fShowTime && fTimeLeft >= 0) { 410 snprintf(buffer, bufferLength, "%s%ld:%ld%s", 411 open, fTimeLeft / 3600, (fTimeLeft / 60) % 60, close); 412 } 413 } 414 415 416 void 417 PowerStatusView::_Update(bool force) 418 { 419 int32 previousPercent = fPercent; 420 bool previousTimeLeft = fTimeLeft; 421 bool wasOnline = fOnline; 422 423 #ifdef HAIKU_TARGET_PLATFORM_HAIKU 424 // TODO: retrieve data from APM/ACPI kernel interface 425 fPercent = 42; 426 fTimeLeft = 1500; 427 fOnline = true; 428 #else 429 if (fDevice < 0) 430 return; 431 432 uint16 regs[6] = {0, 0, 0, 0, 0, 0}; 433 regs[0] = BIOS_APM_GET_POWER_STATUS; 434 regs[1] = 0x1; 435 if (ioctl(fDevice, APM_BIOS_CALL, regs) == 0) { 436 fOnline = (regs[1] >> 8) != 0 && (regs[1] >> 8) != 2; 437 fPercent = regs[2] & 255; 438 if (fPercent > 100) 439 fPercent = -1; 440 fTimeLeft = fPercent >= 0 ? regs[3] : -1; 441 if (fTimeLeft > 0xffff) 442 fTimeLeft = -1; 443 else if (fTimeLeft & 0x8000) 444 fTimeLeft = (fTimeLeft & 0x7fff) * 60; 445 } 446 #endif 447 448 if (fInDeskbar) { 449 // make sure the tray icon is large enough 450 float width = fShowStatusIcon ? kMinIconWidth + 2 : 0; 451 452 if (fShowLabel) { 453 char text[64]; 454 _SetLabel(text, sizeof(text)); 455 456 if (text[0]) 457 width += ceilf(StringWidth(text)) + 4; 458 } 459 if (width == 0) { 460 // make sure we're not going away completely 461 width = 8; 462 } 463 464 if (width != Bounds().Width()) 465 ResizeTo(width, Bounds().Height()); 466 } 467 468 if (force || wasOnline != fOnline 469 || (fShowTime && fTimeLeft != previousTimeLeft) 470 || (!fShowTime && fPercent != previousPercent)) 471 Invalidate(); 472 } 473 474 475 // #pragma mark - 476 477 478 extern "C" _EXPORT BView * 479 instantiate_deskbar_item(void) 480 { 481 return new PowerStatusView(BRect(0, 0, 15, 15), B_FOLLOW_NONE, true); 482 } 483 484