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