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