xref: /haiku/src/apps/powerstatus/PowerStatusView.cpp (revision 25a7b01d15612846f332751841da3579db313082)
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