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