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