xref: /haiku/src/apps/powerstatus/PowerStatusView.cpp (revision e1c4049fed1047bdb957b0529e1921e97ef94770)
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().Brightness() > 100)
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().Brightness() < 128) {
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 
470 				if (state != NULL) {
471 					snprintf(text + length, sizeof(text) - length, "\n%s",
472 						state);
473 				}
474 			} else
475 				strcpy(text, B_TRANSLATE("no battery"));
476 			SetToolTip(text);
477 		}
478 		if (width < 8) {
479 			// make sure we're not going away completely
480 			width = 8;
481 		}
482 
483 		if (width != Bounds().Width()) {
484 			ResizeTo(width, Bounds().Height());
485 
486 			// inform Deskbar that it needs to realign its replicants
487 			BWindow* window = Window();
488 			if (window != NULL) {
489 				BView* view = window->FindView("Status");
490 				if (view != NULL) {
491 					BMessenger target((BHandler*)view);
492 					BMessage realignReplicants('Algn');
493 					target.SendMessage(&realignReplicants);
494 				}
495 			}
496 		}
497 	}
498 
499 	if (force || wasOnline != fOnline
500 		|| (fShowTime && fTimeLeft != previousTimeLeft)
501 		|| (!fShowTime && fPercent != previousPercent)) {
502 		Invalidate();
503 	}
504 
505 	if (fPercent > kLowBatteryPercentage && fTimeLeft > kLowBatteryTimeLeft)
506 		fHasNotifiedLowBattery = false;
507 
508 	bool justTurnedLowBattery = (previousPercent > kLowBatteryPercentage
509 			&& fPercent <= kLowBatteryPercentage)
510 		|| (fTimeLeft <= kLowBatteryTimeLeft
511 			&& previousTimeLeft > kLowBatteryTimeLeft);
512 
513 	if (!fOnline && notify && fHasBattery
514 		&& !fHasNotifiedLowBattery && justTurnedLowBattery) {
515 		_NotifyLowBattery();
516 		fHasNotifiedLowBattery = true;
517 	}
518 
519 	if (fOnline && fPercent >= kFullBatteryPercentage
520 		&& previousPercent < kFullBatteryPercentage) {
521 		system_beep("Battery charged");
522 	}
523 }
524 
525 
526 void
527 PowerStatusView::FromMessage(const BMessage* archive)
528 {
529 	bool value;
530 	if (archive->FindBool("show label", &value) == B_OK)
531 		fShowLabel = value;
532 	if (archive->FindBool("show icon", &value) == B_OK)
533 		fShowStatusIcon = value;
534 	if (archive->FindBool("show time", &value) == B_OK)
535 		fShowTime = value;
536 
537 	//Incase we have a bad saving and none are showed..
538 	if (!fShowLabel && !fShowStatusIcon)
539 		fShowLabel = true;
540 
541 	int32 intValue;
542 	if (archive->FindInt32("battery id", &intValue) == B_OK)
543 		fBatteryID = intValue;
544 }
545 
546 
547 status_t
548 PowerStatusView::ToMessage(BMessage* archive) const
549 {
550 	status_t status = archive->AddBool("show label", fShowLabel);
551 	if (status == B_OK)
552 		status = archive->AddBool("show icon", fShowStatusIcon);
553 	if (status == B_OK)
554 		status = archive->AddBool("show time", fShowTime);
555 	if (status == B_OK)
556 		status = archive->AddInt32("battery id", fBatteryID);
557 
558 	return status;
559 }
560 
561 
562 void
563 PowerStatusView::_GetBatteryInfo(int batteryID, battery_info* batteryInfo)
564 {
565 	if (batteryID >= 0) {
566 		fDriverInterface->GetBatteryInfo(batteryID, batteryInfo);
567 	} else {
568 		bool first = true;
569 		memset(batteryInfo, 0, sizeof(battery_info));
570 
571 		for (int i = 0; i < fDriverInterface->GetBatteryCount(); i++) {
572 			battery_info info;
573 			fDriverInterface->GetBatteryInfo(i, &info);
574 
575 			if (info.full_capacity <= 0)
576 				continue;
577 
578 			if (first) {
579 				*batteryInfo = info;
580 				first = false;
581 			} else {
582 				batteryInfo->state |= info.state;
583 				batteryInfo->capacity += info.capacity;
584 				batteryInfo->full_capacity += info.full_capacity;
585 				batteryInfo->time_left += info.time_left;
586 			}
587 		}
588 	}
589 }
590 
591 
592 void
593 PowerStatusView::_NotifyLowBattery()
594 {
595 	BBitmap* bitmap = NULL;
596 	BResources resources;
597 	resources.SetToImage((void*)&instantiate_deskbar_item);
598 
599 	if (resources.InitCheck() == B_OK) {
600 		size_t resourceSize = 0;
601 		const void* resourceData = resources.LoadResource(
602 			B_VECTOR_ICON_TYPE, fHasBattery
603 				? "battery_low" : "battery_critical", &resourceSize);
604 		if (resourceData != NULL) {
605 			BMemoryIO memoryIO(resourceData, resourceSize);
606 			bitmap = BTranslationUtils::GetBitmap(&memoryIO);
607 		}
608 	}
609 
610 	BNotification notification(
611 		fHasBattery ? B_INFORMATION_NOTIFICATION : B_ERROR_NOTIFICATION);
612 
613 	if (fHasBattery) {
614 		system_beep("Battery low");
615 		notification.SetTitle(B_TRANSLATE("Battery low"));
616 		notification.SetContent(B_TRANSLATE(
617 			"The battery level is getting low, please plug in the device."));
618 	} else {
619 		system_beep("Battery critical");
620 		notification.SetTitle(B_TRANSLATE("Battery critical"));
621 		notification.SetContent(B_TRANSLATE(
622 			"The battery level is critical, please plug in the device "
623 			"immediately."));
624 	}
625 
626 	notification.SetIcon(bitmap);
627 	notification.Send();
628 	delete bitmap;
629 }
630 
631 
632 // #pragma mark - Replicant view
633 
634 
635 PowerStatusReplicant::PowerStatusReplicant(BRect frame, int32 resizingMode,
636 	bool inDeskbar)
637 	:
638 	PowerStatusView(NULL, frame, resizingMode, -1, inDeskbar),
639 	fReplicated(false)
640 {
641 	_Init();
642 	_LoadSettings();
643 
644 	if (!inDeskbar) {
645 		// we were obviously added to a standard window - let's add a dragger
646 		frame.OffsetTo(B_ORIGIN);
647 		frame.top = frame.bottom - 7;
648 		frame.left = frame.right - 7;
649 		BDragger* dragger = new BDragger(frame, this,
650 			B_FOLLOW_RIGHT | B_FOLLOW_BOTTOM);
651 		AddChild(dragger);
652 	} else
653 		Update(false,false);
654 }
655 
656 
657 PowerStatusReplicant::PowerStatusReplicant(BMessage* archive)
658 	:
659 	PowerStatusView(archive),
660 	fReplicated(true)
661 {
662 	_Init();
663 	_LoadSettings();
664 }
665 
666 
667 PowerStatusReplicant::~PowerStatusReplicant()
668 {
669 	if (fMessengerExist)
670 		delete fExtWindowMessenger;
671 
672 	if (fExtendedWindow != NULL && fExtendedWindow->Lock()) {
673 			fExtendedWindow->Quit();
674 			fExtendedWindow = NULL;
675 	}
676 
677 	fDriverInterface->StopWatching(this);
678 	fDriverInterface->Disconnect();
679 	fDriverInterface->ReleaseReference();
680 
681 	_SaveSettings();
682 }
683 
684 
685 PowerStatusReplicant*
686 PowerStatusReplicant::Instantiate(BMessage* archive)
687 {
688 	if (!validate_instantiation(archive, "PowerStatusReplicant"))
689 		return NULL;
690 
691 	return new PowerStatusReplicant(archive);
692 }
693 
694 
695 status_t
696 PowerStatusReplicant::Archive(BMessage* archive, bool deep) const
697 {
698 	status_t status = PowerStatusView::Archive(archive, deep);
699 	if (status == B_OK)
700 		status = archive->AddString("add_on", kSignature);
701 	if (status == B_OK)
702 		status = archive->AddString("class", "PowerStatusReplicant");
703 
704 	return status;
705 }
706 
707 
708 void
709 PowerStatusReplicant::MessageReceived(BMessage *message)
710 {
711 	switch (message->what) {
712 		case kMsgToggleLabel:
713 			if (fShowStatusIcon)
714 				fShowLabel = !fShowLabel;
715 			else
716 				fShowLabel = true;
717 
718 			Update(true);
719 			break;
720 
721 		case kMsgToggleTime:
722 			fShowTime = !fShowTime;
723 			Update(true);
724 			break;
725 
726 		case kMsgToggleStatusIcon:
727 			if (fShowLabel)
728 				fShowStatusIcon = !fShowStatusIcon;
729 			else
730 				fShowStatusIcon = true;
731 
732 			Update(true);
733 			break;
734 
735 		case kMsgToggleExtInfo:
736 			_OpenExtendedWindow();
737 			break;
738 
739 		case B_ABOUT_REQUESTED:
740 			_AboutRequested();
741 			break;
742 
743 		case B_QUIT_REQUESTED:
744 			_Quit();
745 			break;
746 
747 		default:
748 			PowerStatusView::MessageReceived(message);
749 			break;
750 	}
751 }
752 
753 
754 void
755 PowerStatusReplicant::MouseDown(BPoint point)
756 {
757 	BMessage* msg = Window()->CurrentMessage();
758 	int32 buttons = msg->GetInt32("buttons", 0);
759 	if ((buttons & B_TERTIARY_MOUSE_BUTTON) != 0) {
760 		BMessenger messenger(this);
761 		messenger.SendMessage(kMsgToggleExtInfo);
762 	} else {
763 		BPopUpMenu* menu = new BPopUpMenu(B_EMPTY_STRING, false, false);
764 		menu->SetFont(be_plain_font);
765 
766 		BMenuItem* item;
767 		menu->AddItem(item = new BMenuItem(B_TRANSLATE("Show text label"),
768 			new BMessage(kMsgToggleLabel)));
769 		if (fShowLabel)
770 			item->SetMarked(true);
771 		menu->AddItem(item = new BMenuItem(B_TRANSLATE("Show status icon"),
772 			new BMessage(kMsgToggleStatusIcon)));
773 		if (fShowStatusIcon)
774 			item->SetMarked(true);
775 		menu->AddItem(new BMenuItem(!fShowTime ? B_TRANSLATE("Show time") :
776 			B_TRANSLATE("Show percent"), new BMessage(kMsgToggleTime)));
777 
778 		menu->AddSeparatorItem();
779 		menu->AddItem(new BMenuItem(B_TRANSLATE("Battery info" B_UTF8_ELLIPSIS),
780 			new BMessage(kMsgToggleExtInfo)));
781 
782 		menu->AddSeparatorItem();
783 		menu->AddItem(new BMenuItem(B_TRANSLATE("About" B_UTF8_ELLIPSIS),
784 			new BMessage(B_ABOUT_REQUESTED)));
785 		menu->AddItem(new BMenuItem(B_TRANSLATE("Quit"),
786 			new BMessage(B_QUIT_REQUESTED)));
787 		menu->SetTargetForItems(this);
788 
789 		ConvertToScreen(&point);
790 		menu->Go(point, true, false, true);
791 	}
792 }
793 
794 
795 void
796 PowerStatusReplicant::_AboutRequested()
797 {
798 	BAboutWindow* window = new BAboutWindow(
799 		B_TRANSLATE_SYSTEM_NAME("PowerStatus"), kSignature);
800 
801 	const char* authors[] = {
802 		"Axel Dörfler",
803 		"Alexander von Gluck",
804 		"Clemens Zeidler",
805 		NULL
806 	};
807 
808 	window->AddCopyright(2006, "Haiku, Inc.");
809 	window->AddAuthors(authors);
810 
811 	window->Show();
812 }
813 
814 
815 void
816 PowerStatusReplicant::_Init()
817 {
818 	fDriverInterface = new ACPIDriverInterface;
819 	if (fDriverInterface->Connect() != B_OK) {
820 		delete fDriverInterface;
821 		fDriverInterface = new APMDriverInterface;
822 		if (fDriverInterface->Connect() != B_OK) {
823 			fprintf(stderr, "No power interface found.\n");
824 			_Quit();
825 		}
826 	}
827 
828 	fExtendedWindow = NULL;
829 	fMessengerExist = false;
830 	fExtWindowMessenger = NULL;
831 
832 	fDriverInterface->StartWatching(this);
833 }
834 
835 
836 void
837 PowerStatusReplicant::_Quit()
838 {
839 	if (fInDeskbar) {
840 		BDeskbar deskbar;
841 		deskbar.RemoveItem(kDeskbarItemName);
842 	} else if (fReplicated) {
843 		BDragger *dragger = dynamic_cast<BDragger*>(ChildAt(0));
844 		if (dragger != NULL) {
845 			BMessenger messenger(dragger);
846 			messenger.SendMessage(new BMessage(B_TRASH_TARGET));
847 		}
848 	} else
849 		be_app->PostMessage(B_QUIT_REQUESTED);
850 }
851 
852 
853 status_t
854 PowerStatusReplicant::_GetSettings(BFile& file, int mode)
855 {
856 	BPath path;
857 	status_t status = find_directory(B_USER_SETTINGS_DIRECTORY, &path,
858 		(mode & O_ACCMODE) != O_RDONLY);
859 	if (status != B_OK)
860 		return status;
861 
862 	path.Append("PowerStatus settings");
863 
864 	return file.SetTo(path.Path(), mode);
865 }
866 
867 
868 void
869 PowerStatusReplicant::_LoadSettings()
870 {
871 	fShowLabel = false;
872 
873 	BFile file;
874 	if (_GetSettings(file, B_READ_ONLY) != B_OK)
875 		return;
876 
877 	BMessage settings;
878 	if (settings.Unflatten(&file) < B_OK)
879 		return;
880 
881 	FromMessage(&settings);
882 }
883 
884 
885 void
886 PowerStatusReplicant::_SaveSettings()
887 {
888 	BFile file;
889 	if (_GetSettings(file, B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE) != B_OK)
890 		return;
891 
892 	BMessage settings('pwst');
893 	ToMessage(&settings);
894 
895 	ssize_t size = 0;
896 	settings.Flatten(&file, &size);
897 }
898 
899 
900 void
901 PowerStatusReplicant::_OpenExtendedWindow()
902 {
903 	if (!fExtendedWindow) {
904 		fExtendedWindow = new ExtendedInfoWindow(fDriverInterface);
905 		fExtWindowMessenger = new BMessenger(NULL, fExtendedWindow);
906 		fExtendedWindow->Show();
907 		return;
908 	}
909 
910 	BMessage msg(B_SET_PROPERTY);
911 	msg.AddSpecifier("Hidden", int32(0));
912 	if (fExtWindowMessenger->SendMessage(&msg) == B_BAD_PORT_ID) {
913 		fExtendedWindow = new ExtendedInfoWindow(fDriverInterface);
914 		if (fMessengerExist)
915 			delete fExtWindowMessenger;
916 		fExtWindowMessenger = new BMessenger(NULL, fExtendedWindow);
917 		fMessengerExist = true;
918 		fExtendedWindow->Show();
919 	} else
920 		fExtendedWindow->Activate();
921 
922 }
923 
924 
925 //	#pragma mark -
926 
927 
928 extern "C" _EXPORT BView*
929 instantiate_deskbar_item(float maxWidth, float maxHeight)
930 {
931 	return new PowerStatusReplicant(BRect(0, 0, maxHeight - 1, maxHeight - 1),
932 		B_FOLLOW_NONE, true);
933 }
934