xref: /haiku/src/apps/powerstatus/PowerStatusView.cpp (revision dd2a1e350b303b855a50fd64e6cb55618be1ae6a)
1 /*
2  * Copyright 2006-2018, 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  *		Kacper Kasper, kacperkasper@gmail.com
10  */
11 
12 
13 #include "PowerStatusView.h"
14 
15 #include <algorithm>
16 #include <stdio.h>
17 #include <stdlib.h>
18 #include <string.h>
19 #include <unistd.h>
20 
21 #include <AboutWindow.h>
22 #include <Application.h>
23 #include <Bitmap.h>
24 #include <Beep.h>
25 #include <Catalog.h>
26 #include <DataIO.h>
27 #include <Deskbar.h>
28 #include <Dragger.h>
29 #include <Drivers.h>
30 #include <File.h>
31 #include <FindDirectory.h>
32 #include <GradientLinear.h>
33 #include <MenuItem.h>
34 #include <MessageRunner.h>
35 #include <Notification.h>
36 #include <NumberFormat.h>
37 #include <Path.h>
38 #include <PopUpMenu.h>
39 #include <Resources.h>
40 #include <Roster.h>
41 #include <TextView.h>
42 #include <TranslationUtils.h>
43 
44 #include "ACPIDriverInterface.h"
45 #include "APMDriverInterface.h"
46 #include "ExtendedInfoWindow.h"
47 #include "PowerStatus.h"
48 
49 
50 #undef B_TRANSLATION_CONTEXT
51 #define B_TRANSLATION_CONTEXT "PowerStatus"
52 
53 
54 extern "C" _EXPORT BView *instantiate_deskbar_item(float maxWidth,
55 	float maxHeight);
56 extern const char* kDeskbarItemName;
57 
58 const uint32 kMsgToggleLabel = 'tglb';
59 const uint32 kMsgToggleTime = 'tgtm';
60 const uint32 kMsgToggleStatusIcon = 'tgsi';
61 const uint32 kMsgToggleExtInfo = 'texi';
62 
63 const double kLowBatteryPercentage = 0.15;
64 const double kNoteBatteryPercentage = 0.3;
65 const double kFullBatteryPercentage = 1.0;
66 
67 const time_t kLowBatteryTimeLeft = 30 * 60;
68 
69 
70 PowerStatusView::PowerStatusView(PowerStatusDriverInterface* interface,
71 	BRect frame, int32 resizingMode,  int batteryID, bool inDeskbar)
72 	:
73 	BView(frame, kDeskbarItemName, resizingMode,
74 		B_WILL_DRAW | B_TRANSPARENT_BACKGROUND | B_FULL_UPDATE_ON_RESIZE),
75 	fDriverInterface(interface),
76 	fBatteryID(batteryID),
77 	fInDeskbar(inDeskbar)
78 {
79 	_Init();
80 }
81 
82 
83 PowerStatusView::PowerStatusView(BMessage* archive)
84 	:
85 	BView(archive),
86 	fInDeskbar(false)
87 {
88 	app_info info;
89 	if (be_app->GetAppInfo(&info) == B_OK
90 		&& !strcasecmp(info.signature, kDeskbarSignature))
91 		fInDeskbar = true;
92 	_Init();
93 	FromMessage(archive);
94 }
95 
96 
97 PowerStatusView::~PowerStatusView()
98 {
99 }
100 
101 
102 status_t
103 PowerStatusView::Archive(BMessage* archive, bool deep) const
104 {
105 	status_t status = BView::Archive(archive, deep);
106 	if (status == B_OK)
107 		status = ToMessage(archive);
108 
109 	return status;
110 }
111 
112 
113 void
114 PowerStatusView::_Init()
115 {
116 	fShowLabel = true;
117 	fShowTime = false;
118 	fShowStatusIcon = true;
119 
120 	fPercent = 1.0;
121 	fOnline = true;
122 	fTimeLeft = 0;
123 
124 	fHasNotifiedLowBattery = false;
125 
126 	add_system_beep_event("Battery critical");
127 	add_system_beep_event("Battery low");
128 	add_system_beep_event("Battery charged");
129 }
130 
131 
132 void
133 PowerStatusView::AttachedToWindow()
134 {
135 	BView::AttachedToWindow();
136 
137 	SetViewColor(B_TRANSPARENT_COLOR);
138 
139 	if (ViewUIColor() != B_NO_COLOR)
140 		SetLowUIColor(ViewUIColor());
141 	else
142 		SetLowColor(ViewColor());
143 
144 	Update();
145 }
146 
147 
148 void
149 PowerStatusView::DetachedFromWindow()
150 {
151 }
152 
153 
154 void
155 PowerStatusView::MessageReceived(BMessage *message)
156 {
157 	switch (message->what) {
158 		case kMsgUpdate:
159 			Update();
160 			break;
161 
162 		default:
163 			BView::MessageReceived(message);
164 			break;
165 	}
166 }
167 
168 
169 void
170 PowerStatusView::_DrawBattery(BView* view, BRect rect)
171 {
172 	BRect lightningRect = rect;
173 	float quarter = floorf((rect.Height() + 1) / 4);
174 	rect.top += quarter;
175 	rect.bottom -= quarter;
176 
177 	rect.InsetBy(2, 0);
178 
179 	float left = rect.left;
180 	rect.left += rect.Width() / 11;
181 	lightningRect.left = rect.left;
182 	lightningRect.InsetBy(0.0f, 5.0f * rect.Height() / 16);
183 
184 	if (view->LowColor().IsLight())
185 		view->SetHighColor(0, 0, 0);
186 	else
187 		view->SetHighColor(128, 128, 128);
188 
189 	float gap = 1;
190 	if (rect.Height() > 8) {
191 		gap = ceilf((rect.left - left) / 2);
192 
193 		// left
194 		view->FillRect(BRect(rect.left, rect.top, rect.left + gap - 1,
195 			rect.bottom));
196 		// right
197 		view->FillRect(BRect(rect.right - gap + 1, rect.top, rect.right,
198 			rect.bottom));
199 		// top
200 		view->FillRect(BRect(rect.left + gap, rect.top, rect.right - gap,
201 			rect.top + gap - 1));
202 		// bottom
203 		view->FillRect(BRect(rect.left + gap, rect.bottom + 1 - gap,
204 			rect.right - gap, rect.bottom));
205 	} else
206 		view->StrokeRect(rect);
207 
208 	view->FillRect(BRect(left, floorf(rect.top + rect.Height() / 4) + 1,
209 		rect.left - 1, floorf(rect.bottom - rect.Height() / 4)));
210 
211 	double percent = fPercent;
212 	if (percent > 1.0)
213 		percent = 1.0;
214 	else if (percent < 0.0 || !fHasBattery)
215 		percent = 0.0;
216 
217 	rect.InsetBy(gap, gap);
218 
219 	if (fHasBattery) {
220 		// draw unfilled area
221 		rgb_color unfilledColor = make_color(0x4c, 0x4c, 0x4c);
222 		if (view->LowColor().IsDark()) {
223 			unfilledColor.red = 256 - unfilledColor.red;
224 			unfilledColor.green = 256 - unfilledColor.green;
225 			unfilledColor.blue = 256 - unfilledColor.blue;
226 		}
227 
228 		BRect unfilled = rect;
229 		if (percent > 0.0)
230 			unfilled.left += unfilled.Width() * percent;
231 
232 		view->SetHighColor(unfilledColor);
233 		view->FillRect(unfilled);
234 
235 		if (percent > 0.0) {
236 			// draw filled area
237 			rgb_color fillColor;
238 			if (percent <= kLowBatteryPercentage)
239 				fillColor.set_to(180, 0, 0);
240 			else if (percent <= kNoteBatteryPercentage)
241 				fillColor.set_to(200, 140, 0);
242 			else
243 				fillColor.set_to(20, 180, 0);
244 
245 			BRect fill = rect;
246 			fill.right = fill.left + fill.Width() * percent;
247 
248 			// draw bevel
249 			rgb_color bevelLightColor  = tint_color(fillColor, 0.2);
250 			rgb_color bevelShadowColor = tint_color(fillColor, 1.08);
251 
252 			view->BeginLineArray(4);
253 			view->AddLine(BPoint(fill.left, fill.bottom),
254 				BPoint(fill.left, fill.top), bevelLightColor);
255 			view->AddLine(BPoint(fill.left, fill.top),
256 				BPoint(fill.right, fill.top), bevelLightColor);
257 			view->AddLine(BPoint(fill.right, fill.top),
258 				BPoint(fill.right, fill.bottom), bevelShadowColor);
259 			view->AddLine(BPoint(fill.left, fill.bottom),
260 				BPoint(fill.right, fill.bottom), bevelShadowColor);
261 			view->EndLineArray();
262 
263 			fill.InsetBy(1, 1);
264 
265 			// draw gradient
266 			float topTint = 0.49;
267 			float middleTint1 = 0.62;
268 			float middleTint2 = 0.76;
269 			float bottomTint = 0.90;
270 
271 			BGradientLinear gradient;
272 			gradient.AddColor(tint_color(fillColor, topTint), 0);
273 			gradient.AddColor(tint_color(fillColor, middleTint1), 132);
274 			gradient.AddColor(tint_color(fillColor, middleTint2), 136);
275 			gradient.AddColor(tint_color(fillColor, bottomTint), 255);
276 			gradient.SetStart(fill.LeftTop());
277 			gradient.SetEnd(fill.LeftBottom());
278 
279 			view->FillRect(fill, gradient);
280 		}
281 	}
282 
283 	if (fOnline) {
284 		// When charging, draw a lightning symbol over the battery.
285 		view->SetHighColor(255, 255, 0, 180);
286 		view->SetDrawingMode(B_OP_ALPHA);
287 
288 		static const BPoint points[] = {
289 			BPoint(3, 14),
290 			BPoint(10, 6),
291 			BPoint(10, 8),
292 			BPoint(17, 3),
293 			BPoint(9, 12),
294 			BPoint(9, 10)
295 		};
296 		view->FillPolygon(points, 6, lightningRect);
297 
298 		view->SetDrawingMode(B_OP_OVER);
299 	}
300 
301 	view->SetHighColor(0, 0, 0);
302 }
303 
304 
305 void
306 PowerStatusView::Draw(BRect updateRect)
307 {
308 	DrawTo(this, Bounds());
309 }
310 
311 
312 void
313 PowerStatusView::DrawTo(BView* view, BRect rect)
314 {
315 	bool inside = rect.Width() >= 40.0f && rect.Height() >= 40.0f;
316 
317 	font_height fontHeight;
318 	view->GetFontHeight(&fontHeight);
319 	float baseLine = ceilf(fontHeight.ascent);
320 
321 	char text[64];
322 	_SetLabel(text, sizeof(text));
323 
324 	float textHeight = ceilf(fontHeight.descent + fontHeight.ascent);
325 	float textWidth = view->StringWidth(text);
326 	bool showLabel = fShowLabel && text[0];
327 
328 	BRect iconRect;
329 
330 	if (fShowStatusIcon) {
331 		iconRect = rect;
332 		if (showLabel && inside == false)
333 			iconRect.right -= textWidth + 2;
334 
335 		_DrawBattery(view, iconRect);
336 	}
337 
338 	if (showLabel) {
339 		BPoint point(0, baseLine + rect.top);
340 
341 		if (iconRect.IsValid()) {
342 			if (inside == true) {
343 				point.x = rect.left + (iconRect.Width() - textWidth) / 2 +
344 					iconRect.Width() / 20;
345 				point.y += (iconRect.Height() - textHeight) / 2;
346 			} else {
347 				point.x = rect.left + iconRect.Width() + 2;
348 				point.y += (iconRect.Height() - textHeight) / 2;
349 			}
350 		} else {
351 			point.x = rect.left + (Bounds().Width() - textWidth) / 2;
352 			point.y += (Bounds().Height() - textHeight) / 2;
353 		}
354 
355 		view->SetDrawingMode(B_OP_OVER);
356 		if (fInDeskbar == false || inside == true) {
357 			view->SetHighUIColor(B_CONTROL_BACKGROUND_COLOR);
358 			view->DrawString(text, BPoint(point.x + 1, point.y + 1));
359 		}
360 		view->SetHighUIColor(B_CONTROL_TEXT_COLOR);
361 
362 		view->DrawString(text, point);
363 	}
364 }
365 
366 
367 void
368 PowerStatusView::_SetLabel(char* buffer, size_t bufferLength)
369 {
370 	if (bufferLength < 1)
371 		return;
372 
373 	buffer[0] = '\0';
374 
375 	if (!fShowLabel)
376 		return;
377 
378 	const char* open = "";
379 	const char* close = "";
380 	if (fOnline) {
381 		open = "(";
382 		close = ")";
383 	}
384 
385 	if (!fShowTime && fPercent >= 0) {
386 		BNumberFormat numberFormat;
387 		BString data;
388 
389 		if (numberFormat.FormatPercent(data, fPercent) != B_OK) {
390 			data.SetToFormat("%" B_PRId32 "%%", int32(fPercent * 100));
391 		}
392 
393 		snprintf(buffer, bufferLength, "%s%s%s", open, data.String(), close);
394 	} else if (fShowTime && fTimeLeft >= 0) {
395 		snprintf(buffer, bufferLength, "%s%" B_PRIdTIME ":%02" B_PRIdTIME "%s",
396 			open, fTimeLeft / 3600, (fTimeLeft / 60) % 60, close);
397 	}
398 }
399 
400 
401 void
402 PowerStatusView::Update(bool force, bool notify)
403 {
404 	double previousPercent = fPercent;
405 	time_t previousTimeLeft = fTimeLeft;
406 	bool wasOnline = fOnline;
407 	bool hadBattery = fHasBattery;
408 	_GetBatteryInfo(fBatteryID, &fBatteryInfo);
409 	fHasBattery = fBatteryInfo.full_capacity > 0;
410 
411 	if (fBatteryInfo.full_capacity > 0 && fHasBattery) {
412 		fPercent = (double)fBatteryInfo.capacity / fBatteryInfo.full_capacity;
413 		fOnline = (fBatteryInfo.state & BATTERY_DISCHARGING) == 0;
414 		fTimeLeft = fBatteryInfo.time_left;
415 	} else {
416 		fPercent = 0.0;
417 		fOnline = false;
418 		fTimeLeft = -1;
419 	}
420 
421 	if (fHasBattery && (fPercent <= 0 || fPercent > 1.0)) {
422 		// Just ignore this probe -- it obviously returned invalid values
423 		fPercent = previousPercent;
424 		fTimeLeft = previousTimeLeft;
425 		fOnline = wasOnline;
426 		fHasBattery = hadBattery;
427 		return;
428 	}
429 
430 	if (fInDeskbar) {
431 		// make sure the tray icon is (just) large enough
432 		float width = fShowStatusIcon ? Bounds().Height() : 0;
433 
434 		if (fShowLabel) {
435 			char text[64];
436 			_SetLabel(text, sizeof(text));
437 
438 			if (text[0])
439 				width += ceilf(StringWidth(text)) + 2;
440 		} else {
441 			char text[256];
442 			const char* open = "";
443 			const char* close = "";
444 			if (fOnline) {
445 				open = "(";
446 				close = ")";
447 			}
448 			if (fHasBattery) {
449 				BNumberFormat numberFormat;
450 				BString data;
451 				size_t length;
452 
453 				if (numberFormat.FormatPercent(data, fPercent) != B_OK) {
454 					data.SetToFormat("%" B_PRId32 "%%", int32(fPercent * 100));
455 				}
456 
457 				length = snprintf(text, sizeof(text), "%s%s%s", open, data.String(), close);
458 
459 				if (fTimeLeft >= 0) {
460 					length += snprintf(text + length, sizeof(text) - length, "\n%" B_PRIdTIME
461 						":%02" B_PRIdTIME, fTimeLeft / 3600, (fTimeLeft / 60) % 60);
462 				}
463 
464 				const char* state = NULL;
465 				if ((fBatteryInfo.state & BATTERY_CHARGING) != 0)
466 					state = B_TRANSLATE("charging");
467 				else if ((fBatteryInfo.state & BATTERY_DISCHARGING) != 0)
468 					state = B_TRANSLATE("discharging");
469 				else if ((fBatteryInfo.state & BATTERY_NOT_CHARGING) != 0)
470 					state = B_TRANSLATE("not charging");
471 
472 				if (state != NULL) {
473 					snprintf(text + length, sizeof(text) - length, "\n%s",
474 						state);
475 				}
476 			} else
477 				strcpy(text, B_TRANSLATE("no battery"));
478 			SetToolTip(text);
479 		}
480 		if (width < 8) {
481 			// make sure we're not going away completely
482 			width = 8;
483 		}
484 
485 		if (width != Bounds().Width()) {
486 			ResizeTo(width, Bounds().Height());
487 
488 			// inform Deskbar that it needs to realign its replicants
489 			BWindow* window = Window();
490 			if (window != NULL) {
491 				BView* view = window->FindView("Status");
492 				if (view != NULL) {
493 					BMessenger target((BHandler*)view);
494 					BMessage realignReplicants('Algn');
495 					target.SendMessage(&realignReplicants);
496 				}
497 			}
498 		}
499 	}
500 
501 	if (force || wasOnline != fOnline
502 		|| (fShowTime && fTimeLeft != previousTimeLeft)
503 		|| (!fShowTime && fPercent != previousPercent)) {
504 		Invalidate();
505 	}
506 
507 	if (fPercent > kLowBatteryPercentage && fTimeLeft > kLowBatteryTimeLeft)
508 		fHasNotifiedLowBattery = false;
509 
510 	bool justTurnedLowBattery = (previousPercent > kLowBatteryPercentage
511 			&& fPercent <= kLowBatteryPercentage)
512 		|| (fTimeLeft <= kLowBatteryTimeLeft
513 			&& previousTimeLeft > kLowBatteryTimeLeft);
514 
515 	if (!fOnline && notify && fHasBattery
516 		&& !fHasNotifiedLowBattery && justTurnedLowBattery) {
517 		_NotifyLowBattery();
518 		fHasNotifiedLowBattery = true;
519 	}
520 
521 	if (fOnline && fPercent >= kFullBatteryPercentage
522 		&& previousPercent < kFullBatteryPercentage) {
523 		system_beep("Battery charged");
524 	}
525 }
526 
527 
528 void
529 PowerStatusView::FromMessage(const BMessage* archive)
530 {
531 	bool value;
532 	if (archive->FindBool("show label", &value) == B_OK)
533 		fShowLabel = value;
534 	if (archive->FindBool("show icon", &value) == B_OK)
535 		fShowStatusIcon = value;
536 	if (archive->FindBool("show time", &value) == B_OK)
537 		fShowTime = value;
538 
539 	//Incase we have a bad saving and none are showed..
540 	if (!fShowLabel && !fShowStatusIcon)
541 		fShowLabel = true;
542 
543 	int32 intValue;
544 	if (archive->FindInt32("battery id", &intValue) == B_OK)
545 		fBatteryID = intValue;
546 }
547 
548 
549 status_t
550 PowerStatusView::ToMessage(BMessage* archive) const
551 {
552 	status_t status = archive->AddBool("show label", fShowLabel);
553 	if (status == B_OK)
554 		status = archive->AddBool("show icon", fShowStatusIcon);
555 	if (status == B_OK)
556 		status = archive->AddBool("show time", fShowTime);
557 	if (status == B_OK)
558 		status = archive->AddInt32("battery id", fBatteryID);
559 
560 	return status;
561 }
562 
563 
564 void
565 PowerStatusView::_GetBatteryInfo(int batteryID, battery_info* batteryInfo)
566 {
567 	if (batteryID >= 0) {
568 		fDriverInterface->GetBatteryInfo(batteryID, batteryInfo);
569 	} else {
570 		bool first = true;
571 		memset(batteryInfo, 0, sizeof(battery_info));
572 
573 		for (int i = 0; i < fDriverInterface->GetBatteryCount(); i++) {
574 			battery_info info;
575 			fDriverInterface->GetBatteryInfo(i, &info);
576 
577 			if (info.full_capacity <= 0)
578 				continue;
579 
580 			if (first) {
581 				*batteryInfo = info;
582 				first = false;
583 			} else {
584 				batteryInfo->state |= info.state;
585 				batteryInfo->capacity += info.capacity;
586 				batteryInfo->full_capacity += info.full_capacity;
587 				batteryInfo->time_left += info.time_left;
588 			}
589 		}
590 	}
591 }
592 
593 
594 void
595 PowerStatusView::_NotifyLowBattery()
596 {
597 	BBitmap* bitmap = NULL;
598 	BResources resources;
599 	resources.SetToImage((void*)&instantiate_deskbar_item);
600 
601 	if (resources.InitCheck() == B_OK) {
602 		size_t resourceSize = 0;
603 		const void* resourceData = resources.LoadResource(
604 			B_VECTOR_ICON_TYPE, fHasBattery
605 				? "battery_low" : "battery_critical", &resourceSize);
606 		if (resourceData != NULL) {
607 			BMemoryIO memoryIO(resourceData, resourceSize);
608 			bitmap = BTranslationUtils::GetBitmap(&memoryIO);
609 		}
610 	}
611 
612 	BNotification notification(
613 		fHasBattery ? B_INFORMATION_NOTIFICATION : B_ERROR_NOTIFICATION);
614 
615 	if (fHasBattery) {
616 		system_beep("Battery low");
617 		notification.SetTitle(B_TRANSLATE("Battery low"));
618 		notification.SetContent(B_TRANSLATE(
619 			"The battery level is getting low, please plug in the device."));
620 	} else {
621 		system_beep("Battery critical");
622 		notification.SetTitle(B_TRANSLATE("Battery critical"));
623 		notification.SetContent(B_TRANSLATE(
624 			"The battery level is critical, please plug in the device "
625 			"immediately."));
626 	}
627 
628 	notification.SetIcon(bitmap);
629 	notification.Send();
630 	delete bitmap;
631 }
632 
633 
634 // #pragma mark - Replicant view
635 
636 
637 PowerStatusReplicant::PowerStatusReplicant(BRect frame, int32 resizingMode,
638 	bool inDeskbar)
639 	:
640 	PowerStatusView(NULL, frame, resizingMode, -1, inDeskbar),
641 	fReplicated(false)
642 {
643 	_Init();
644 	_LoadSettings();
645 
646 	if (!inDeskbar) {
647 		// we were obviously added to a standard window - let's add a dragger
648 		frame.OffsetTo(B_ORIGIN);
649 		frame.top = frame.bottom - 7;
650 		frame.left = frame.right - 7;
651 		BDragger* dragger = new BDragger(frame, this,
652 			B_FOLLOW_RIGHT | B_FOLLOW_BOTTOM);
653 		AddChild(dragger);
654 	} else
655 		Update(false,false);
656 }
657 
658 
659 PowerStatusReplicant::PowerStatusReplicant(BMessage* archive)
660 	:
661 	PowerStatusView(archive),
662 	fReplicated(true)
663 {
664 	_Init();
665 	_LoadSettings();
666 }
667 
668 
669 PowerStatusReplicant::~PowerStatusReplicant()
670 {
671 	if (fMessengerExist)
672 		delete fExtWindowMessenger;
673 
674 	if (fExtendedWindow != NULL && fExtendedWindow->Lock()) {
675 			fExtendedWindow->Quit();
676 			fExtendedWindow = NULL;
677 	}
678 
679 	fDriverInterface->StopWatching(this);
680 	fDriverInterface->Disconnect();
681 	fDriverInterface->ReleaseReference();
682 
683 	_SaveSettings();
684 }
685 
686 
687 PowerStatusReplicant*
688 PowerStatusReplicant::Instantiate(BMessage* archive)
689 {
690 	if (!validate_instantiation(archive, "PowerStatusReplicant"))
691 		return NULL;
692 
693 	return new PowerStatusReplicant(archive);
694 }
695 
696 
697 status_t
698 PowerStatusReplicant::Archive(BMessage* archive, bool deep) const
699 {
700 	status_t status = PowerStatusView::Archive(archive, deep);
701 	if (status == B_OK)
702 		status = archive->AddString("add_on", kSignature);
703 	if (status == B_OK)
704 		status = archive->AddString("class", "PowerStatusReplicant");
705 
706 	return status;
707 }
708 
709 
710 void
711 PowerStatusReplicant::MessageReceived(BMessage *message)
712 {
713 	switch (message->what) {
714 		case kMsgToggleLabel:
715 			if (fShowStatusIcon)
716 				fShowLabel = !fShowLabel;
717 			else
718 				fShowLabel = true;
719 
720 			Update(true);
721 			break;
722 
723 		case kMsgToggleTime:
724 			fShowTime = !fShowTime;
725 			Update(true);
726 			break;
727 
728 		case kMsgToggleStatusIcon:
729 			if (fShowLabel)
730 				fShowStatusIcon = !fShowStatusIcon;
731 			else
732 				fShowStatusIcon = true;
733 
734 			Update(true);
735 			break;
736 
737 		case kMsgToggleExtInfo:
738 			_OpenExtendedWindow();
739 			break;
740 
741 		case B_ABOUT_REQUESTED:
742 			_AboutRequested();
743 			break;
744 
745 		case B_QUIT_REQUESTED:
746 			_Quit();
747 			break;
748 
749 		default:
750 			PowerStatusView::MessageReceived(message);
751 			break;
752 	}
753 }
754 
755 
756 void
757 PowerStatusReplicant::MouseDown(BPoint point)
758 {
759 	BMessage* msg = Window()->CurrentMessage();
760 	int32 buttons = msg->GetInt32("buttons", 0);
761 	if ((buttons & B_TERTIARY_MOUSE_BUTTON) != 0) {
762 		BMessenger messenger(this);
763 		messenger.SendMessage(kMsgToggleExtInfo);
764 	} else {
765 		BPopUpMenu* menu = new BPopUpMenu(B_EMPTY_STRING, false, false);
766 		menu->SetFont(be_plain_font);
767 
768 		BMenuItem* item;
769 		menu->AddItem(item = new BMenuItem(B_TRANSLATE("Show text label"),
770 			new BMessage(kMsgToggleLabel)));
771 		if (fShowLabel)
772 			item->SetMarked(true);
773 		menu->AddItem(item = new BMenuItem(B_TRANSLATE("Show status icon"),
774 			new BMessage(kMsgToggleStatusIcon)));
775 		if (fShowStatusIcon)
776 			item->SetMarked(true);
777 		menu->AddItem(new BMenuItem(!fShowTime ? B_TRANSLATE("Show time") :
778 			B_TRANSLATE("Show percent"), new BMessage(kMsgToggleTime)));
779 
780 		menu->AddSeparatorItem();
781 		menu->AddItem(new BMenuItem(B_TRANSLATE("Battery info" B_UTF8_ELLIPSIS),
782 			new BMessage(kMsgToggleExtInfo)));
783 
784 		menu->AddSeparatorItem();
785 		menu->AddItem(new BMenuItem(B_TRANSLATE("About" B_UTF8_ELLIPSIS),
786 			new BMessage(B_ABOUT_REQUESTED)));
787 		menu->AddItem(new BMenuItem(B_TRANSLATE("Quit"),
788 			new BMessage(B_QUIT_REQUESTED)));
789 		menu->SetTargetForItems(this);
790 
791 		ConvertToScreen(&point);
792 		menu->Go(point, true, false, true);
793 	}
794 }
795 
796 
797 void
798 PowerStatusReplicant::_AboutRequested()
799 {
800 	BAboutWindow* window = new BAboutWindow(
801 		B_TRANSLATE_SYSTEM_NAME("PowerStatus"), kSignature);
802 
803 	const char* authors[] = {
804 		"Axel Dörfler",
805 		"Alexander von Gluck",
806 		"Clemens Zeidler",
807 		NULL
808 	};
809 
810 	window->AddCopyright(2006, "Haiku, Inc.");
811 	window->AddAuthors(authors);
812 
813 	window->Show();
814 }
815 
816 
817 void
818 PowerStatusReplicant::_Init()
819 {
820 	fDriverInterface = new ACPIDriverInterface;
821 	if (fDriverInterface->Connect() != B_OK) {
822 		delete fDriverInterface;
823 		fDriverInterface = new APMDriverInterface;
824 		if (fDriverInterface->Connect() != B_OK) {
825 			fprintf(stderr, "No power interface found.\n");
826 			_Quit();
827 		}
828 	}
829 
830 	fExtendedWindow = NULL;
831 	fMessengerExist = false;
832 	fExtWindowMessenger = NULL;
833 
834 	fDriverInterface->StartWatching(this);
835 }
836 
837 
838 void
839 PowerStatusReplicant::_Quit()
840 {
841 	if (fInDeskbar) {
842 		BDeskbar deskbar;
843 		deskbar.RemoveItem(kDeskbarItemName);
844 	} else if (fReplicated) {
845 		BDragger *dragger = dynamic_cast<BDragger*>(ChildAt(0));
846 		if (dragger != NULL) {
847 			BMessenger messenger(dragger);
848 			messenger.SendMessage(new BMessage(B_TRASH_TARGET));
849 		}
850 	} else
851 		be_app->PostMessage(B_QUIT_REQUESTED);
852 }
853 
854 
855 status_t
856 PowerStatusReplicant::_GetSettings(BFile& file, int mode)
857 {
858 	BPath path;
859 	status_t status = find_directory(B_USER_SETTINGS_DIRECTORY, &path,
860 		(mode & O_ACCMODE) != O_RDONLY);
861 	if (status != B_OK)
862 		return status;
863 
864 	path.Append("PowerStatus settings");
865 
866 	return file.SetTo(path.Path(), mode);
867 }
868 
869 
870 void
871 PowerStatusReplicant::_LoadSettings()
872 {
873 	fShowLabel = false;
874 
875 	BFile file;
876 	if (_GetSettings(file, B_READ_ONLY) != B_OK)
877 		return;
878 
879 	BMessage settings;
880 	if (settings.Unflatten(&file) < B_OK)
881 		return;
882 
883 	FromMessage(&settings);
884 }
885 
886 
887 void
888 PowerStatusReplicant::_SaveSettings()
889 {
890 	BFile file;
891 	if (_GetSettings(file, B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE) != B_OK)
892 		return;
893 
894 	BMessage settings('pwst');
895 	ToMessage(&settings);
896 
897 	ssize_t size = 0;
898 	settings.Flatten(&file, &size);
899 }
900 
901 
902 void
903 PowerStatusReplicant::_OpenExtendedWindow()
904 {
905 	if (!fExtendedWindow) {
906 		fExtendedWindow = new ExtendedInfoWindow(fDriverInterface);
907 		fExtWindowMessenger = new BMessenger(NULL, fExtendedWindow);
908 		fExtendedWindow->Show();
909 		return;
910 	}
911 
912 	BMessage msg(B_SET_PROPERTY);
913 	msg.AddSpecifier("Hidden", int32(0));
914 	if (fExtWindowMessenger->SendMessage(&msg) == B_BAD_PORT_ID) {
915 		fExtendedWindow = new ExtendedInfoWindow(fDriverInterface);
916 		if (fMessengerExist)
917 			delete fExtWindowMessenger;
918 		fExtWindowMessenger = new BMessenger(NULL, fExtendedWindow);
919 		fMessengerExist = true;
920 		fExtendedWindow->Show();
921 	} else
922 		fExtendedWindow->Activate();
923 
924 }
925 
926 
927 //	#pragma mark -
928 
929 
930 extern "C" _EXPORT BView*
931 instantiate_deskbar_item(float maxWidth, float maxHeight)
932 {
933 	return new PowerStatusReplicant(BRect(0, 0, maxHeight - 1, maxHeight - 1),
934 		B_FOLLOW_NONE, true);
935 }
936