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