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