xref: /haiku/src/apps/powerstatus/PowerStatusView.cpp (revision 85fb3e7df81f8d5b6e47a9a64a53873ea906ea6e)
1 /*
2  * Copyright 2006-2010, 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 <Alert.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 	int32 intValue;
424 	if (archive->FindInt32("battery id", &intValue) == B_OK)
425 		fBatteryID = intValue;
426 }
427 
428 
429 status_t
430 PowerStatusView::ToMessage(BMessage* archive) const
431 {
432 	status_t status = archive->AddBool("show label", fShowLabel);
433 	if (status == B_OK)
434 		status = archive->AddBool("show icon", fShowStatusIcon);
435 	if (status == B_OK)
436 		status = archive->AddBool("show time", fShowTime);
437 	if (status == B_OK)
438 		status = archive->AddInt32("battery id", fBatteryID);
439 
440 	return status;
441 }
442 
443 
444 void
445 PowerStatusView::_GetBatteryInfo(battery_info* batteryInfo, int batteryID)
446 {
447 	if (batteryID >= 0) {
448 		fDriverInterface->GetBatteryInfo(batteryInfo, batteryID);
449 	} else {
450 		for (int i = 0; i < fDriverInterface->GetBatteryCount(); i++) {
451 			battery_info info;
452 			fDriverInterface->GetBatteryInfo(&info, i);
453 
454 			if (i == 0)
455 				*batteryInfo = info;
456 			else {
457 				batteryInfo->state &= info.state;
458 				batteryInfo->capacity += info.capacity;
459 				batteryInfo->full_capacity += info.full_capacity;
460 				batteryInfo->time_left += info.time_left;
461 			}
462 		}
463 	}
464 }
465 
466 
467 //	#pragma mark -
468 
469 
470 PowerStatusReplicant::PowerStatusReplicant(BRect frame, int32 resizingMode,
471 		bool inDeskbar)
472 	:
473 	PowerStatusView(NULL, frame, resizingMode, -1, inDeskbar)
474 {
475 	_Init();
476 	_LoadSettings();
477 
478 	if (!inDeskbar) {
479 		// we were obviously added to a standard window - let's add a dragger
480 		frame.OffsetTo(B_ORIGIN);
481 		frame.top = frame.bottom - 7;
482 		frame.left = frame.right - 7;
483 		BDragger* dragger = new BDragger(frame, this,
484 			B_FOLLOW_RIGHT | B_FOLLOW_BOTTOM);
485 		AddChild(dragger);
486 	} else
487 		Update();
488 }
489 
490 
491 PowerStatusReplicant::PowerStatusReplicant(BMessage* archive)
492 	:
493 	PowerStatusView(archive)
494 {
495 	_Init();
496 	_LoadSettings();
497 }
498 
499 
500 PowerStatusReplicant::~PowerStatusReplicant()
501 {
502 	if (fMessengerExist) {
503 		delete fExtWindowMessenger;
504 	}
505 
506 	fDriverInterface->StopWatching(this);
507 	fDriverInterface->Disconnect();
508 	fDriverInterface->ReleaseReference();
509 
510 	_SaveSettings();
511 }
512 
513 
514 PowerStatusReplicant*
515 PowerStatusReplicant::Instantiate(BMessage* archive)
516 {
517 	if (!validate_instantiation(archive, "PowerStatusReplicant"))
518 		return NULL;
519 
520 	return new PowerStatusReplicant(archive);
521 }
522 
523 
524 status_t
525 PowerStatusReplicant::Archive(BMessage* archive, bool deep) const
526 {
527 	status_t status = PowerStatusView::Archive(archive, deep);
528 	if (status == B_OK)
529 		status = archive->AddString("add_on", kSignature);
530 	if (status == B_OK)
531 		status = archive->AddString("class", "PowerStatusReplicant");
532 
533 	return status;
534 }
535 
536 
537 void
538 PowerStatusReplicant::MessageReceived(BMessage *message)
539 {
540 	switch (message->what) {
541 		case kMsgToggleLabel:
542 			fShowLabel = !fShowLabel;
543 			Update(true);
544 			break;
545 
546 		case kMsgToggleTime:
547 			fShowTime = !fShowTime;
548 			Update(true);
549 			break;
550 
551 		case kMsgToggleStatusIcon:
552 			fShowStatusIcon = !fShowStatusIcon;
553 			Update(true);
554 			break;
555 
556 		case kMsgToggleExtInfo:
557 			_OpenExtendedWindow();
558 			break;
559 
560 		case B_ABOUT_REQUESTED:
561 			_AboutRequested();
562 			break;
563 
564 		case B_QUIT_REQUESTED:
565 			_Quit();
566 			break;
567 
568 		default:
569 			PowerStatusView::MessageReceived(message);
570 	}
571 }
572 
573 
574 void
575 PowerStatusReplicant::MouseDown(BPoint point)
576 {
577 	BPopUpMenu *menu = new BPopUpMenu(B_EMPTY_STRING, false, false);
578 	menu->SetFont(be_plain_font);
579 
580 	BMenuItem* item;
581 	menu->AddItem(item = new BMenuItem(B_TRANSLATE("Show text label"),
582 		new BMessage(kMsgToggleLabel)));
583 	if (fShowLabel)
584 		item->SetMarked(true);
585 	menu->AddItem(item = new BMenuItem(B_TRANSLATE("Show status icon"),
586 		new BMessage(kMsgToggleStatusIcon)));
587 	if (fShowStatusIcon)
588 		item->SetMarked(true);
589 	menu->AddItem(new BMenuItem(!fShowTime ? B_TRANSLATE("Show time") :
590 	B_TRANSLATE("Show percent"),
591 		new BMessage(kMsgToggleTime)));
592 
593 	menu->AddSeparatorItem();
594 	menu->AddItem(new BMenuItem(B_TRANSLATE("Battery info" B_UTF8_ELLIPSIS),
595 		new BMessage(kMsgToggleExtInfo)));
596 
597 	menu->AddSeparatorItem();
598 	menu->AddItem(new BMenuItem(B_TRANSLATE("About" B_UTF8_ELLIPSIS),
599 		new BMessage(B_ABOUT_REQUESTED)));
600 	menu->AddItem(new BMenuItem(B_TRANSLATE("Quit"),
601 		new BMessage(B_QUIT_REQUESTED)));
602 	menu->SetTargetForItems(this);
603 
604 	ConvertToScreen(&point);
605 	menu->Go(point, true, false, true);
606 }
607 
608 
609 void
610 PowerStatusReplicant::_AboutRequested()
611 {
612 	BAlert* alert = new BAlert(B_TRANSLATE("About"),
613 		B_TRANSLATE("PowerStatus\n"
614 			"written by Axel Dörfler, Clemens Zeidler\n"
615 			"Copyright 2006, Haiku, Inc.\n"), B_TRANSLATE("OK"));
616 	BTextView *view = alert->TextView();
617 	BFont font;
618 
619 	view->SetStylable(true);
620 
621 	view->GetFont(&font);
622 	font.SetSize(18);
623 	font.SetFace(B_BOLD_FACE);
624 	view->SetFontAndColor(0, strlen(B_TRANSLATE("PowerStatus")), &font);
625 
626 	alert->Go();
627 }
628 
629 
630 void
631 PowerStatusReplicant::_Init()
632 {
633 	fDriverInterface = new ACPIDriverInterface;
634 	if (fDriverInterface->Connect() != B_OK) {
635 		delete fDriverInterface;
636 		fDriverInterface = new APMDriverInterface;
637 		if (fDriverInterface->Connect() != B_OK) {
638 			fprintf(stderr, "No power interface found.\n");
639 			_Quit();
640 		}
641 	}
642 
643 	fExtendedWindow = NULL;
644 	fMessengerExist = false;
645 	fExtWindowMessenger = NULL;
646 
647 	fDriverInterface->StartWatching(this);
648 }
649 
650 
651 void
652 PowerStatusReplicant::_Quit()
653 {
654 	if (fInDeskbar) {
655 		BDeskbar deskbar;
656 		deskbar.RemoveItem(kDeskbarItemName);
657 	} else
658 		be_app->PostMessage(B_QUIT_REQUESTED);
659 }
660 
661 
662 status_t
663 PowerStatusReplicant::_GetSettings(BFile& file, int mode)
664 {
665 	BPath path;
666 	status_t status = find_directory(B_USER_SETTINGS_DIRECTORY, &path,
667 		(mode & O_ACCMODE) != O_RDONLY);
668 	if (status != B_OK)
669 		return status;
670 
671 	path.Append("PowerStatus settings");
672 
673 	return file.SetTo(path.Path(), mode);
674 }
675 
676 
677 void
678 PowerStatusReplicant::_LoadSettings()
679 {
680 	fShowLabel = false;
681 
682 	BFile file;
683 	if (_GetSettings(file, B_READ_ONLY) != B_OK)
684 		return;
685 
686 	BMessage settings;
687 	if (settings.Unflatten(&file) < B_OK)
688 		return;
689 
690 	FromMessage(&settings);
691 }
692 
693 
694 void
695 PowerStatusReplicant::_SaveSettings()
696 {
697 	BFile file;
698 	if (_GetSettings(file, B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE) != B_OK)
699 		return;
700 
701 	BMessage settings('pwst');
702 	ToMessage(&settings);
703 
704 	ssize_t size = 0;
705 	settings.Flatten(&file, &size);
706 }
707 
708 
709 void
710 PowerStatusReplicant::_OpenExtendedWindow()
711 {
712 	if (!fExtendedWindow) {
713 		fExtendedWindow = new ExtendedInfoWindow(fDriverInterface);
714 		fExtWindowMessenger = new BMessenger(NULL, fExtendedWindow);
715 		fExtendedWindow->Show();
716 		return;
717 	}
718 
719 	BMessage msg(B_SET_PROPERTY);
720 	msg.AddSpecifier("Hidden", int32(0));
721 	if (fExtWindowMessenger->SendMessage(&msg) == B_BAD_PORT_ID) {
722 		fExtendedWindow = new ExtendedInfoWindow(fDriverInterface);
723 		if (fMessengerExist)
724 			delete fExtWindowMessenger;
725 		fExtWindowMessenger = new BMessenger(NULL, fExtendedWindow);
726 		fMessengerExist = true;
727 		fExtendedWindow->Show();
728 	} else
729 		fExtendedWindow->Activate();
730 
731 }
732 
733 
734 //	#pragma mark -
735 
736 
737 extern "C" _EXPORT BView*
738 instantiate_deskbar_item(void)
739 {
740 	return new PowerStatusReplicant(BRect(0, 0, 15, 15), B_FOLLOW_NONE, true);
741 }
742 
743