xref: /haiku/src/apps/powerstatus/PowerStatusView.cpp (revision 344ded80d400028c8f561b4b876257b94c12db4a)
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 	} else if ((fBatteryInfo.state & BATTERY_CRITICAL_STATE) != 0) {
322 		// When a battery is damaged or missing, draw an X over it
323 		view->SetHighColor(200, 0, 0, 96);
324 		view->SetDrawingMode(B_OP_ALPHA);
325 
326 		static const BPoint points[] = {
327 			BPoint(1, 1),
328 			BPoint(1, 2),
329 			BPoint(20, 6),
330 			BPoint(22, 6),
331 			BPoint(22, 5),
332 			BPoint(3, 1),
333 
334 			BPoint(20, 1),
335 			BPoint(1, 5),
336 			BPoint(1, 6),
337 			BPoint(3, 6),
338 			BPoint(22, 2),
339 			BPoint(22, 1)
340 		};
341 		view->FillPolygon(points, 12, lightningRect);
342 
343 		view->SetDrawingMode(B_OP_OVER);
344 	}
345 
346 	view->SetHighColor(0, 0, 0);
347 }
348 
349 
350 void
351 PowerStatusView::Draw(BRect updateRect)
352 {
353 	DrawTo(this, Bounds());
354 }
355 
356 
357 void
358 PowerStatusView::DrawTo(BView* view, BRect rect)
359 {
360 	bool inside = rect.Width() >= 40.0f && rect.Height() >= 40.0f;
361 
362 	font_height fontHeight;
363 	view->GetFontHeight(&fontHeight);
364 	float baseLine = ceilf(fontHeight.ascent);
365 
366 	char text[64];
367 	_SetLabel(text, sizeof(text));
368 
369 	float textHeight = ceilf(fontHeight.descent + fontHeight.ascent);
370 	float textWidth = view->StringWidth(text);
371 	bool showLabel = fShowLabel && text[0];
372 
373 	BRect iconRect;
374 
375 	if (fShowStatusIcon) {
376 		iconRect = rect;
377 		if (showLabel && inside == false)
378 			iconRect.right -= textWidth + 2;
379 
380 		_DrawBattery(view, iconRect);
381 	}
382 
383 	if (showLabel) {
384 		BPoint point(0, baseLine + rect.top);
385 
386 		if (iconRect.IsValid()) {
387 			if (inside == true) {
388 				point.x = rect.left + (iconRect.Width() - textWidth) / 2 +
389 					iconRect.Width() / 20;
390 				point.y += (iconRect.Height() - textHeight) / 2;
391 			} else {
392 				point.x = rect.left + iconRect.Width() + 2;
393 				point.y += (iconRect.Height() - textHeight) / 2;
394 			}
395 		} else {
396 			point.x = rect.left + (Bounds().Width() - textWidth) / 2;
397 			point.y += (Bounds().Height() - textHeight) / 2;
398 		}
399 
400 		view->SetDrawingMode(B_OP_OVER);
401 		if (fInDeskbar == false || inside == true) {
402 			view->SetHighUIColor(B_CONTROL_BACKGROUND_COLOR);
403 			view->DrawString(text, BPoint(point.x + 1, point.y + 1));
404 		}
405 		view->SetHighUIColor(B_CONTROL_TEXT_COLOR);
406 
407 		view->DrawString(text, point);
408 	}
409 }
410 
411 
412 void
413 PowerStatusView::_SetLabel(char* buffer, size_t bufferLength)
414 {
415 	if (bufferLength < 1)
416 		return;
417 
418 	buffer[0] = '\0';
419 
420 	if (!fShowLabel)
421 		return;
422 
423 	const char* open = "";
424 	const char* close = "";
425 	if ((fBatteryInfo.state & BATTERY_DISCHARGING) == 0) {
426 		// surround the percentage with () if the battery is not discharging
427 		open = "(";
428 		close = ")";
429 	}
430 
431 	if (!fShowTime && fPercent >= 0) {
432 		BNumberFormat numberFormat;
433 		BString data;
434 
435 		if (numberFormat.FormatPercent(data, fPercent) != B_OK) {
436 			data.SetToFormat("%" B_PRId32 "%%", int32(fPercent * 100));
437 		}
438 
439 		snprintf(buffer, bufferLength, "%s%s%s", open, data.String(), close);
440 	} else if (fShowTime && fTimeLeft >= 0) {
441 		snprintf(buffer, bufferLength, "%s%" B_PRIdTIME ":%02" B_PRIdTIME "%s",
442 			open, fTimeLeft / 3600, (fTimeLeft / 60) % 60, close);
443 	}
444 }
445 
446 
447 void
448 PowerStatusView::Update(bool force, bool notify)
449 {
450 	double previousPercent = fPercent;
451 	time_t previousTimeLeft = fTimeLeft;
452 	bool wasCharging = (fBatteryInfo.state & BATTERY_CHARGING);
453 	bool hadBattery = fHasBattery;
454 	_GetBatteryInfo(fBatteryID, &fBatteryInfo);
455 	fHasBattery = fBatteryInfo.full_capacity > 0 && fBatteryInfo.state != BATTERY_CRITICAL_STATE;
456 
457 	if (fBatteryInfo.full_capacity > 0 && fHasBattery) {
458 		fPercent = (double)fBatteryInfo.capacity / fBatteryInfo.full_capacity;
459 		fTimeLeft = fBatteryInfo.time_left;
460 	} else {
461 		fPercent = 0.0;
462 		fTimeLeft = -1;
463 	}
464 
465 	if (fHasBattery && (fPercent <= 0 || fPercent > 1.0)) {
466 		// Just ignore this probe -- it obviously returned invalid values
467 		fPercent = previousPercent;
468 		fTimeLeft = previousTimeLeft;
469 		fHasBattery = hadBattery;
470 		return;
471 	}
472 
473 	if (fInDeskbar) {
474 		// make sure the tray icon is (just) large enough
475 		float width = fShowStatusIcon ? Bounds().Height() : 0;
476 
477 		if (fShowLabel) {
478 			char text[64];
479 			_SetLabel(text, sizeof(text));
480 
481 			if (text[0])
482 				width += ceilf(StringWidth(text)) + 2;
483 		} else {
484 			char text[256];
485 			const char* open = "";
486 			const char* close = "";
487 			if ((fBatteryInfo.state & BATTERY_DISCHARGING) == 0) {
488 				// surround the percentage with () if the battery is not discharging
489 				open = "(";
490 				close = ")";
491 			}
492 			if (fHasBattery) {
493 				BNumberFormat numberFormat;
494 				BString data;
495 				size_t length;
496 
497 				if (numberFormat.FormatPercent(data, fPercent) != B_OK) {
498 					data.SetToFormat("%" B_PRId32 "%%", int32(fPercent * 100));
499 				}
500 
501 				length = snprintf(text, sizeof(text), "%s%s%s", open, data.String(), close);
502 
503 				if (fTimeLeft >= 0) {
504 					length += snprintf(text + length, sizeof(text) - length, "\n%" B_PRIdTIME
505 						":%02" B_PRIdTIME, fTimeLeft / 3600, (fTimeLeft / 60) % 60);
506 				}
507 
508 				const char* state = NULL;
509 				if ((fBatteryInfo.state & BATTERY_CHARGING) != 0)
510 					state = B_TRANSLATE("charging");
511 				else if ((fBatteryInfo.state & BATTERY_DISCHARGING) != 0)
512 					state = B_TRANSLATE("discharging");
513 				else if ((fBatteryInfo.state & BATTERY_NOT_CHARGING) != 0)
514 					state = B_TRANSLATE("not charging");
515 
516 				if (state != NULL) {
517 					snprintf(text + length, sizeof(text) - length, "\n%s",
518 						state);
519 				}
520 			} else
521 				strcpy(text, B_TRANSLATE("no battery"));
522 			SetToolTip(text);
523 		}
524 		if (width < 8) {
525 			// make sure we're not going away completely
526 			width = 8;
527 		}
528 
529 		if (width != Bounds().Width()) {
530 			ResizeTo(width, Bounds().Height());
531 
532 			// inform Deskbar that it needs to realign its replicants
533 			BWindow* window = Window();
534 			if (window != NULL) {
535 				BView* view = window->FindView("Status");
536 				if (view != NULL) {
537 					BMessenger target((BHandler*)view);
538 					BMessage realignReplicants('Algn');
539 					target.SendMessage(&realignReplicants);
540 				}
541 			}
542 		}
543 	}
544 
545 	if (force || wasCharging != (fBatteryInfo.state & BATTERY_CHARGING)
546 		|| (fShowTime && fTimeLeft != previousTimeLeft)
547 		|| (!fShowTime && fPercent != previousPercent)) {
548 		Invalidate();
549 	}
550 
551 	// only do low battery notices based on the aggregate virtual battery, not single batteries
552 	if (fBatteryID >= 0)
553 		return;
554 
555 	if (fPercent > kLowBatteryPercentage && fTimeLeft > kLowBatteryTimeLeft)
556 		fHasNotifiedLowBattery = false;
557 
558 	bool justTurnedLowBattery = (previousPercent > kLowBatteryPercentage
559 			&& fPercent <= kLowBatteryPercentage)
560 		|| (fTimeLeft <= kLowBatteryTimeLeft
561 			&& previousTimeLeft > kLowBatteryTimeLeft);
562 
563 	if ((fBatteryInfo.state & BATTERY_DISCHARGING) != 0 && notify && fHasBattery
564 		&& !fHasNotifiedLowBattery && justTurnedLowBattery) {
565 		_NotifyLowBattery();
566 		fHasNotifiedLowBattery = true;
567 	}
568 
569 	if ((fBatteryInfo.state & BATTERY_CHARGING) != 0 && fPercent >= kFullBatteryPercentage
570 		&& previousPercent < kFullBatteryPercentage) {
571 		system_beep("Battery charged");
572 	}
573 }
574 
575 
576 void
577 PowerStatusView::FromMessage(const BMessage* archive)
578 {
579 	bool value;
580 	if (archive->FindBool("show label", &value) == B_OK)
581 		fShowLabel = value;
582 	if (archive->FindBool("show icon", &value) == B_OK)
583 		fShowStatusIcon = value;
584 	if (archive->FindBool("show time", &value) == B_OK)
585 		fShowTime = value;
586 
587 	//Incase we have a bad saving and none are showed..
588 	if (!fShowLabel && !fShowStatusIcon)
589 		fShowLabel = true;
590 
591 	int32 intValue;
592 	if (archive->FindInt32("battery id", &intValue) == B_OK)
593 		fBatteryID = intValue;
594 }
595 
596 
597 status_t
598 PowerStatusView::ToMessage(BMessage* archive) const
599 {
600 	status_t status = archive->AddBool("show label", fShowLabel);
601 	if (status == B_OK)
602 		status = archive->AddBool("show icon", fShowStatusIcon);
603 	if (status == B_OK)
604 		status = archive->AddBool("show time", fShowTime);
605 	if (status == B_OK)
606 		status = archive->AddInt32("battery id", fBatteryID);
607 
608 	return status;
609 }
610 
611 
612 void
613 PowerStatusView::_GetBatteryInfo(int batteryID, battery_info* batteryInfo)
614 {
615 	if (batteryID >= 0) {
616 		fDriverInterface->GetBatteryInfo(batteryID, batteryInfo);
617 	} else {
618 		bool first = true;
619 		memset(batteryInfo, 0, sizeof(battery_info));
620 
621 		for (int i = 0; i < fDriverInterface->GetBatteryCount(); i++) {
622 			battery_info info;
623 			fDriverInterface->GetBatteryInfo(i, &info);
624 			if (info.full_capacity <= 0)
625 				continue;
626 
627 			if (first) {
628 				*batteryInfo = info;
629 				first = false;
630 			} else {
631 				if ((batteryInfo->state & BATTERY_CRITICAL_STATE) == 0) {
632 					// don't propagate CRITICAL_STATE to the aggregate battery.
633 					// one battery charging means "the system is charging" but one battery having
634 					// been removed does not mean "the system has no battery"
635 					batteryInfo->state |= info.state;
636 				}
637 				batteryInfo->capacity += info.capacity;
638 				batteryInfo->full_capacity += info.full_capacity;
639 				batteryInfo->current_rate += info.current_rate;
640 			}
641 		}
642 
643 		// we can't rely on just adding the individual batteries' time_lefts together:
644 		// not-in-use batteries show -1 time_left as they will last infinitely long with their
645 		// current (zero) level of draw, despite them being in the queue to use after the current
646 		// battery is out of energy. therefore to calculate an accurate time, we have to use the
647 		// current total rate of (dis)charge compared to the total remaining capacity of all
648 		// batteries.
649 		if (batteryInfo->current_rate == 0) {
650 			// some systems briefly return current_rate of 0 as the charger is plugged/unplugged
651 			batteryInfo->time_left = 0;
652 		} else if ((batteryInfo->state & BATTERY_CHARGING) != 0) {
653 			batteryInfo->time_left = 3600 * (batteryInfo->full_capacity - batteryInfo->capacity)
654 				/ batteryInfo->current_rate;
655 		} else {
656 			batteryInfo->time_left = 3600 * batteryInfo->capacity / batteryInfo->current_rate;
657 		}
658 	}
659 }
660 
661 
662 void
663 PowerStatusView::_NotifyLowBattery()
664 {
665 	BBitmap* bitmap = NULL;
666 	BResources resources;
667 	resources.SetToImage((void*)&instantiate_deskbar_item);
668 
669 	if (resources.InitCheck() == B_OK) {
670 		size_t resourceSize = 0;
671 		const void* resourceData = resources.LoadResource(
672 			B_VECTOR_ICON_TYPE, fHasBattery
673 				? "battery_low" : "battery_critical", &resourceSize);
674 		if (resourceData != NULL) {
675 			BMemoryIO memoryIO(resourceData, resourceSize);
676 			bitmap = BTranslationUtils::GetBitmap(&memoryIO);
677 		}
678 	}
679 
680 	BNotification notification(
681 		fHasBattery ? B_INFORMATION_NOTIFICATION : B_ERROR_NOTIFICATION);
682 
683 	if (fHasBattery) {
684 		system_beep("Battery low");
685 		notification.SetTitle(B_TRANSLATE("Battery low"));
686 		notification.SetContent(B_TRANSLATE(
687 			"The battery level is getting low, please plug in the device."));
688 	} else {
689 		system_beep("Battery critical");
690 		notification.SetTitle(B_TRANSLATE("Battery critical"));
691 		notification.SetContent(B_TRANSLATE(
692 			"The battery level is critical, please plug in the device "
693 			"immediately."));
694 	}
695 
696 	notification.SetIcon(bitmap);
697 	notification.Send();
698 	delete bitmap;
699 }
700 
701 
702 // #pragma mark - Replicant view
703 
704 
705 PowerStatusReplicant::PowerStatusReplicant(BRect frame, int32 resizingMode,
706 	bool inDeskbar)
707 	:
708 	PowerStatusView(NULL, frame, resizingMode, -1, inDeskbar),
709 	fReplicated(false)
710 {
711 	_Init();
712 	_LoadSettings();
713 
714 	if (!inDeskbar) {
715 		// we were obviously added to a standard window - let's add a dragger
716 		frame.OffsetTo(B_ORIGIN);
717 		frame.top = frame.bottom - 7;
718 		frame.left = frame.right - 7;
719 		BDragger* dragger = new BDragger(frame, this,
720 			B_FOLLOW_RIGHT | B_FOLLOW_BOTTOM);
721 		AddChild(dragger);
722 	} else
723 		Update(false,false);
724 }
725 
726 
727 PowerStatusReplicant::PowerStatusReplicant(BMessage* archive)
728 	:
729 	PowerStatusView(archive),
730 	fReplicated(true)
731 {
732 	_Init();
733 	_LoadSettings();
734 }
735 
736 
737 PowerStatusReplicant::~PowerStatusReplicant()
738 {
739 	if (fMessengerExist)
740 		delete fExtWindowMessenger;
741 
742 	if (fExtendedWindow != NULL && fExtendedWindow->Lock()) {
743 			fExtendedWindow->Quit();
744 			fExtendedWindow = NULL;
745 	}
746 
747 	fDriverInterface->StopWatching(this);
748 	fDriverInterface->Disconnect();
749 	fDriverInterface->ReleaseReference();
750 
751 	_SaveSettings();
752 }
753 
754 
755 PowerStatusReplicant*
756 PowerStatusReplicant::Instantiate(BMessage* archive)
757 {
758 	if (!validate_instantiation(archive, "PowerStatusReplicant"))
759 		return NULL;
760 
761 	return new PowerStatusReplicant(archive);
762 }
763 
764 
765 status_t
766 PowerStatusReplicant::Archive(BMessage* archive, bool deep) const
767 {
768 	status_t status = PowerStatusView::Archive(archive, deep);
769 	if (status == B_OK)
770 		status = archive->AddString("add_on", kSignature);
771 	if (status == B_OK)
772 		status = archive->AddString("class", "PowerStatusReplicant");
773 
774 	return status;
775 }
776 
777 
778 void
779 PowerStatusReplicant::MessageReceived(BMessage *message)
780 {
781 	switch (message->what) {
782 		case kMsgToggleLabel:
783 			if (fShowStatusIcon)
784 				fShowLabel = !fShowLabel;
785 			else
786 				fShowLabel = true;
787 
788 			Update(true);
789 			break;
790 
791 		case kMsgToggleTime:
792 			fShowTime = !fShowTime;
793 			Update(true);
794 			break;
795 
796 		case kMsgToggleStatusIcon:
797 			if (fShowLabel)
798 				fShowStatusIcon = !fShowStatusIcon;
799 			else
800 				fShowStatusIcon = true;
801 
802 			Update(true);
803 			break;
804 
805 		case kMsgToggleExtInfo:
806 			_OpenExtendedWindow();
807 			break;
808 
809 		case B_ABOUT_REQUESTED:
810 			_AboutRequested();
811 			break;
812 
813 		case B_QUIT_REQUESTED:
814 			_Quit();
815 			break;
816 
817 		default:
818 			PowerStatusView::MessageReceived(message);
819 			break;
820 	}
821 }
822 
823 
824 void
825 PowerStatusReplicant::MouseDown(BPoint point)
826 {
827 	BMessage* msg = Window()->CurrentMessage();
828 	int32 buttons = msg->GetInt32("buttons", 0);
829 	if ((buttons & B_TERTIARY_MOUSE_BUTTON) != 0) {
830 		BMessenger messenger(this);
831 		messenger.SendMessage(kMsgToggleExtInfo);
832 	} else {
833 		BPopUpMenu* menu = new BPopUpMenu(B_EMPTY_STRING, false, false);
834 		menu->SetFont(be_plain_font);
835 
836 		BMenuItem* item;
837 		menu->AddItem(item = new BMenuItem(B_TRANSLATE("Show text label"),
838 			new BMessage(kMsgToggleLabel)));
839 		if (fShowLabel)
840 			item->SetMarked(true);
841 		menu->AddItem(item = new BMenuItem(B_TRANSLATE("Show status icon"),
842 			new BMessage(kMsgToggleStatusIcon)));
843 		if (fShowStatusIcon)
844 			item->SetMarked(true);
845 		menu->AddItem(new BMenuItem(!fShowTime ? B_TRANSLATE("Show time") :
846 			B_TRANSLATE("Show percent"), new BMessage(kMsgToggleTime)));
847 
848 		menu->AddSeparatorItem();
849 		menu->AddItem(new BMenuItem(B_TRANSLATE("Battery info" B_UTF8_ELLIPSIS),
850 			new BMessage(kMsgToggleExtInfo)));
851 
852 		menu->AddSeparatorItem();
853 		menu->AddItem(new BMenuItem(B_TRANSLATE("About" B_UTF8_ELLIPSIS),
854 			new BMessage(B_ABOUT_REQUESTED)));
855 		menu->AddItem(new BMenuItem(B_TRANSLATE("Quit"),
856 			new BMessage(B_QUIT_REQUESTED)));
857 		menu->SetTargetForItems(this);
858 
859 		ConvertToScreen(&point);
860 		menu->Go(point, true, false, true);
861 	}
862 }
863 
864 
865 void
866 PowerStatusReplicant::_AboutRequested()
867 {
868 	BAboutWindow* window = new BAboutWindow(
869 		B_TRANSLATE_SYSTEM_NAME("PowerStatus"), kSignature);
870 
871 	const char* authors[] = {
872 		"Axel Dörfler",
873 		"Alexander von Gluck",
874 		"Clemens Zeidler",
875 		NULL
876 	};
877 
878 	window->AddCopyright(2006, "Haiku, Inc.");
879 	window->AddAuthors(authors);
880 
881 	window->Show();
882 }
883 
884 
885 void
886 PowerStatusReplicant::_Init()
887 {
888 	fDriverInterface = new ACPIDriverInterface;
889 	if (fDriverInterface->Connect() != B_OK) {
890 		delete fDriverInterface;
891 		fDriverInterface = new APMDriverInterface;
892 		if (fDriverInterface->Connect() != B_OK) {
893 			fprintf(stderr, "No power interface found.\n");
894 			_Quit();
895 		}
896 	}
897 
898 	fExtendedWindow = NULL;
899 	fMessengerExist = false;
900 	fExtWindowMessenger = NULL;
901 
902 	fDriverInterface->StartWatching(this);
903 }
904 
905 
906 void
907 PowerStatusReplicant::_Quit()
908 {
909 	if (fInDeskbar) {
910 		BDeskbar deskbar;
911 		deskbar.RemoveItem(kDeskbarItemName);
912 	} else if (fReplicated) {
913 		BDragger *dragger = dynamic_cast<BDragger*>(ChildAt(0));
914 		if (dragger != NULL) {
915 			BMessenger messenger(dragger);
916 			messenger.SendMessage(new BMessage(B_TRASH_TARGET));
917 		}
918 	} else
919 		be_app->PostMessage(B_QUIT_REQUESTED);
920 }
921 
922 
923 status_t
924 PowerStatusReplicant::_GetSettings(BFile& file, int mode)
925 {
926 	BPath path;
927 	status_t status = find_directory(B_USER_SETTINGS_DIRECTORY, &path,
928 		(mode & O_ACCMODE) != O_RDONLY);
929 	if (status != B_OK)
930 		return status;
931 
932 	path.Append("PowerStatus settings");
933 
934 	return file.SetTo(path.Path(), mode);
935 }
936 
937 
938 void
939 PowerStatusReplicant::_LoadSettings()
940 {
941 	fShowLabel = false;
942 
943 	BFile file;
944 	if (_GetSettings(file, B_READ_ONLY) != B_OK)
945 		return;
946 
947 	BMessage settings;
948 	if (settings.Unflatten(&file) < B_OK)
949 		return;
950 
951 	FromMessage(&settings);
952 }
953 
954 
955 void
956 PowerStatusReplicant::_SaveSettings()
957 {
958 	BFile file;
959 	if (_GetSettings(file, B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE) != B_OK)
960 		return;
961 
962 	BMessage settings('pwst');
963 	ToMessage(&settings);
964 
965 	ssize_t size = 0;
966 	settings.Flatten(&file, &size);
967 }
968 
969 
970 void
971 PowerStatusReplicant::_OpenExtendedWindow()
972 {
973 	if (!fExtendedWindow) {
974 		fExtendedWindow = new ExtendedInfoWindow(fDriverInterface);
975 		fExtWindowMessenger = new BMessenger(NULL, fExtendedWindow);
976 		fExtendedWindow->Show();
977 		return;
978 	}
979 
980 	BMessage msg(B_SET_PROPERTY);
981 	msg.AddSpecifier("Hidden", int32(0));
982 	if (fExtWindowMessenger->SendMessage(&msg) == B_BAD_PORT_ID) {
983 		fExtendedWindow = new ExtendedInfoWindow(fDriverInterface);
984 		if (fMessengerExist)
985 			delete fExtWindowMessenger;
986 		fExtWindowMessenger = new BMessenger(NULL, fExtendedWindow);
987 		fMessengerExist = true;
988 		fExtendedWindow->Show();
989 	} else
990 		fExtendedWindow->Activate();
991 
992 }
993 
994 
995 //	#pragma mark -
996 
997 
998 extern "C" _EXPORT BView*
999 instantiate_deskbar_item(float maxWidth, float maxHeight)
1000 {
1001 	return new PowerStatusReplicant(BRect(0, 0, maxHeight - 1, maxHeight - 1),
1002 		B_FOLLOW_NONE, true);
1003 }
1004