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