xref: /haiku/src/apps/powerstatus/PowerStatusView.cpp (revision 37fedaf8494b34aad811abcc49e79aa32943f880)
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%" B_PRId32 "%%%s", open, fPercent,
326 			close);
327 	} else if (fShowTime && fTimeLeft >= 0) {
328 		snprintf(buffer, bufferLength, "%s%" B_PRId32 ":%02" B_PRId32 "%s",
329 			open, fTimeLeft / 3600, (fTimeLeft / 60) % 60, close);
330 	}
331 }
332 
333 
334 
335 void
336 PowerStatusView::Update(bool force)
337 {
338 	int32 previousPercent = fPercent;
339 	time_t previousTimeLeft = fTimeLeft;
340 	bool wasOnline = fOnline;
341 
342 	_GetBatteryInfo(&fBatteryInfo, fBatteryID);
343 
344 	fHasBattery = (fBatteryInfo.state & BATTERY_CRITICAL_STATE) == 0;
345 
346 	if (fBatteryInfo.full_capacity > 0 && fHasBattery) {
347 		fPercent = (100 * fBatteryInfo.capacity) / fBatteryInfo.full_capacity;
348 		fOnline = (fBatteryInfo.state & BATTERY_CHARGING) != 0;
349 		fTimeLeft = fBatteryInfo.time_left;
350 	} else {
351 		fPercent = 0;
352 		fOnline = false;
353 		fTimeLeft = -1;
354 	}
355 
356 
357 	if (fInDeskbar) {
358 		// make sure the tray icon is large enough
359 		float width = fShowStatusIcon ? kMinIconWidth + 2 : 0;
360 
361 		if (fShowLabel) {
362 			char text[64];
363 			_SetLabel(text, sizeof(text));
364 
365 			if (text[0])
366 				width += ceilf(StringWidth(text)) + 4;
367 		} else {
368 			char text[256];
369 			const char* open = "";
370 			const char* close = "";
371 			if (fOnline) {
372 				open = "(";
373 				close = ")";
374 			}
375 			if (fHasBattery) {
376 				size_t length = snprintf(text, sizeof(text), "%s%" B_PRId32
377 					"%%%s", open, fPercent, close);
378 				if (fTimeLeft >= 0) {
379 					length += snprintf(text + length, sizeof(text) - length,
380 						"\n%" B_PRId32 ":%02" B_PRId32, fTimeLeft / 3600,
381 						(fTimeLeft / 60) % 60);
382 				}
383 
384 				const char* state = NULL;
385 				if ((fBatteryInfo.state & BATTERY_CHARGING) != 0)
386 					state = B_TRANSLATE("charging");
387 				else if ((fBatteryInfo.state & BATTERY_DISCHARGING) != 0)
388 					state = B_TRANSLATE("discharging");
389 
390 				if (state != NULL) {
391 					snprintf(text + length, sizeof(text) - length, "\n%s",
392 						state);
393 				}
394 			} else
395 				strcpy(text, B_TRANSLATE("no battery"));
396 			SetToolTip(text);
397 		}
398 		if (width == 0) {
399 			// make sure we're not going away completely
400 			width = 8;
401 		}
402 
403 		if (width != Bounds().Width())
404 			ResizeTo(width, Bounds().Height());
405 	}
406 
407 	if (force || wasOnline != fOnline
408 		|| (fShowTime && fTimeLeft != previousTimeLeft)
409 		|| (!fShowTime && fPercent != previousPercent))
410 		Invalidate();
411 }
412 
413 
414 void
415 PowerStatusView::FromMessage(const BMessage* archive)
416 {
417 	bool value;
418 	if (archive->FindBool("show label", &value) == B_OK)
419 		fShowLabel = value;
420 	if (archive->FindBool("show icon", &value) == B_OK)
421 		fShowStatusIcon = value;
422 	if (archive->FindBool("show time", &value) == B_OK)
423 		fShowTime = value;
424 
425 	//Incase we have a bad saving and none are showed..
426 	if (!fShowLabel && !fShowStatusIcon)
427 		fShowLabel = true;
428 
429 	int32 intValue;
430 	if (archive->FindInt32("battery id", &intValue) == B_OK)
431 		fBatteryID = intValue;
432 }
433 
434 
435 status_t
436 PowerStatusView::ToMessage(BMessage* archive) const
437 {
438 	status_t status = archive->AddBool("show label", fShowLabel);
439 	if (status == B_OK)
440 		status = archive->AddBool("show icon", fShowStatusIcon);
441 	if (status == B_OK)
442 		status = archive->AddBool("show time", fShowTime);
443 	if (status == B_OK)
444 		status = archive->AddInt32("battery id", fBatteryID);
445 
446 	return status;
447 }
448 
449 
450 void
451 PowerStatusView::_GetBatteryInfo(battery_info* batteryInfo, int batteryID)
452 {
453 	if (batteryID >= 0) {
454 		fDriverInterface->GetBatteryInfo(batteryInfo, batteryID);
455 	} else {
456 		for (int i = 0; i < fDriverInterface->GetBatteryCount(); i++) {
457 			battery_info info;
458 			fDriverInterface->GetBatteryInfo(&info, i);
459 
460 			if (i == 0)
461 				*batteryInfo = info;
462 			else {
463 				batteryInfo->state &= info.state;
464 				batteryInfo->capacity += info.capacity;
465 				batteryInfo->full_capacity += info.full_capacity;
466 				batteryInfo->time_left += info.time_left;
467 			}
468 		}
469 	}
470 }
471 
472 
473 //	#pragma mark -
474 
475 
476 PowerStatusReplicant::PowerStatusReplicant(BRect frame, int32 resizingMode,
477 		bool inDeskbar)
478 	:
479 	PowerStatusView(NULL, frame, resizingMode, -1, inDeskbar)
480 {
481 	_Init();
482 	_LoadSettings();
483 
484 	if (!inDeskbar) {
485 		// we were obviously added to a standard window - let's add a dragger
486 		frame.OffsetTo(B_ORIGIN);
487 		frame.top = frame.bottom - 7;
488 		frame.left = frame.right - 7;
489 		BDragger* dragger = new BDragger(frame, this,
490 			B_FOLLOW_RIGHT | B_FOLLOW_BOTTOM);
491 		AddChild(dragger);
492 	} else
493 		Update();
494 }
495 
496 
497 PowerStatusReplicant::PowerStatusReplicant(BMessage* archive)
498 	:
499 	PowerStatusView(archive)
500 {
501 	_Init();
502 	_LoadSettings();
503 }
504 
505 
506 PowerStatusReplicant::~PowerStatusReplicant()
507 {
508 	if (fMessengerExist)
509 		delete fExtWindowMessenger;
510 
511 	fDriverInterface->StopWatching(this);
512 	fDriverInterface->Disconnect();
513 	fDriverInterface->ReleaseReference();
514 
515 	_SaveSettings();
516 }
517 
518 
519 PowerStatusReplicant*
520 PowerStatusReplicant::Instantiate(BMessage* archive)
521 {
522 	if (!validate_instantiation(archive, "PowerStatusReplicant"))
523 		return NULL;
524 
525 	return new PowerStatusReplicant(archive);
526 }
527 
528 
529 status_t
530 PowerStatusReplicant::Archive(BMessage* archive, bool deep) const
531 {
532 	status_t status = PowerStatusView::Archive(archive, deep);
533 	if (status == B_OK)
534 		status = archive->AddString("add_on", kSignature);
535 	if (status == B_OK)
536 		status = archive->AddString("class", "PowerStatusReplicant");
537 
538 	return status;
539 }
540 
541 
542 void
543 PowerStatusReplicant::MessageReceived(BMessage *message)
544 {
545 	switch (message->what) {
546 		case kMsgToggleLabel:
547 			if (fShowStatusIcon)
548 				fShowLabel = !fShowLabel;
549 			else
550 				fShowLabel = true;
551 
552 			Update(true);
553 			break;
554 
555 		case kMsgToggleTime:
556 			fShowTime = !fShowTime;
557 			Update(true);
558 			break;
559 
560 		case kMsgToggleStatusIcon:
561 			if (fShowLabel)
562 				fShowStatusIcon = !fShowStatusIcon;
563 			else
564 				fShowStatusIcon = true;
565 
566 			Update(true);
567 			break;
568 
569 		case kMsgToggleExtInfo:
570 			_OpenExtendedWindow();
571 			break;
572 
573 		case B_ABOUT_REQUESTED:
574 			_AboutRequested();
575 			break;
576 
577 		case B_QUIT_REQUESTED:
578 			_Quit();
579 			break;
580 
581 		default:
582 			PowerStatusView::MessageReceived(message);
583 	}
584 }
585 
586 
587 void
588 PowerStatusReplicant::MouseDown(BPoint point)
589 {
590 	BPopUpMenu *menu = new BPopUpMenu(B_EMPTY_STRING, false, false);
591 	menu->SetFont(be_plain_font);
592 
593 	BMenuItem* item;
594 	menu->AddItem(item = new BMenuItem(B_TRANSLATE("Show text label"),
595 		new BMessage(kMsgToggleLabel)));
596 	if (fShowLabel)
597 		item->SetMarked(true);
598 	menu->AddItem(item = new BMenuItem(B_TRANSLATE("Show status icon"),
599 		new BMessage(kMsgToggleStatusIcon)));
600 	if (fShowStatusIcon)
601 		item->SetMarked(true);
602 	menu->AddItem(new BMenuItem(!fShowTime ? B_TRANSLATE("Show time") :
603 	B_TRANSLATE("Show percent"),
604 		new BMessage(kMsgToggleTime)));
605 
606 	menu->AddSeparatorItem();
607 	menu->AddItem(new BMenuItem(B_TRANSLATE("Battery info" B_UTF8_ELLIPSIS),
608 		new BMessage(kMsgToggleExtInfo)));
609 
610 	menu->AddSeparatorItem();
611 	menu->AddItem(new BMenuItem(B_TRANSLATE("About" B_UTF8_ELLIPSIS),
612 		new BMessage(B_ABOUT_REQUESTED)));
613 	menu->AddItem(new BMenuItem(B_TRANSLATE("Quit"),
614 		new BMessage(B_QUIT_REQUESTED)));
615 	menu->SetTargetForItems(this);
616 
617 	ConvertToScreen(&point);
618 	menu->Go(point, true, false, true);
619 }
620 
621 
622 void
623 PowerStatusReplicant::_AboutRequested()
624 {
625 	BAboutWindow* window = new BAboutWindow(
626 		B_TRANSLATE_SYSTEM_NAME("PowerStatus"), kSignature);
627 
628 	const char* authors[] = {
629 		"Axel Dörfler",
630 		"Alexander von Gluck",
631 		"Clemens Zeidler",
632 		NULL
633 	};
634 
635 	window->AddCopyright(2006, "Haiku, Inc.");
636 	window->AddAuthors(authors);
637 
638 	window->Show();
639 }
640 
641 
642 void
643 PowerStatusReplicant::_Init()
644 {
645 	fDriverInterface = new ACPIDriverInterface;
646 	if (fDriverInterface->Connect() != B_OK) {
647 		delete fDriverInterface;
648 		fDriverInterface = new APMDriverInterface;
649 		if (fDriverInterface->Connect() != B_OK) {
650 			fprintf(stderr, "No power interface found.\n");
651 			_Quit();
652 		}
653 	}
654 
655 	fExtendedWindow = NULL;
656 	fMessengerExist = false;
657 	fExtWindowMessenger = NULL;
658 
659 	fDriverInterface->StartWatching(this);
660 }
661 
662 
663 void
664 PowerStatusReplicant::_Quit()
665 {
666 	if (fInDeskbar) {
667 		BDeskbar deskbar;
668 		deskbar.RemoveItem(kDeskbarItemName);
669 	} else
670 		be_app->PostMessage(B_QUIT_REQUESTED);
671 }
672 
673 
674 status_t
675 PowerStatusReplicant::_GetSettings(BFile& file, int mode)
676 {
677 	BPath path;
678 	status_t status = find_directory(B_USER_SETTINGS_DIRECTORY, &path,
679 		(mode & O_ACCMODE) != O_RDONLY);
680 	if (status != B_OK)
681 		return status;
682 
683 	path.Append("PowerStatus settings");
684 
685 	return file.SetTo(path.Path(), mode);
686 }
687 
688 
689 void
690 PowerStatusReplicant::_LoadSettings()
691 {
692 	fShowLabel = false;
693 
694 	BFile file;
695 	if (_GetSettings(file, B_READ_ONLY) != B_OK)
696 		return;
697 
698 	BMessage settings;
699 	if (settings.Unflatten(&file) < B_OK)
700 		return;
701 
702 	FromMessage(&settings);
703 }
704 
705 
706 void
707 PowerStatusReplicant::_SaveSettings()
708 {
709 	BFile file;
710 	if (_GetSettings(file, B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE) != B_OK)
711 		return;
712 
713 	BMessage settings('pwst');
714 	ToMessage(&settings);
715 
716 	ssize_t size = 0;
717 	settings.Flatten(&file, &size);
718 }
719 
720 
721 void
722 PowerStatusReplicant::_OpenExtendedWindow()
723 {
724 	if (!fExtendedWindow) {
725 		fExtendedWindow = new ExtendedInfoWindow(fDriverInterface);
726 		fExtWindowMessenger = new BMessenger(NULL, fExtendedWindow);
727 		fExtendedWindow->Show();
728 		return;
729 	}
730 
731 	BMessage msg(B_SET_PROPERTY);
732 	msg.AddSpecifier("Hidden", int32(0));
733 	if (fExtWindowMessenger->SendMessage(&msg) == B_BAD_PORT_ID) {
734 		fExtendedWindow = new ExtendedInfoWindow(fDriverInterface);
735 		if (fMessengerExist)
736 			delete fExtWindowMessenger;
737 		fExtWindowMessenger = new BMessenger(NULL, fExtendedWindow);
738 		fMessengerExist = true;
739 		fExtendedWindow->Show();
740 	} else
741 		fExtendedWindow->Activate();
742 
743 }
744 
745 
746 //	#pragma mark -
747 
748 
749 extern "C" _EXPORT BView*
750 instantiate_deskbar_item(void)
751 {
752 	return new PowerStatusReplicant(BRect(0, 0, 15, 15), B_FOLLOW_NONE, true);
753 }
754