⛏️ index : haiku.git

/*
 * Copyright 2006-2018, Haiku, Inc. All Rights Reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *		Axel DΓΆrfler, axeld@pinc-software.de
 *		Clemens Zeidler, haiku@Clemens-Zeidler.de
 *		Alexander von Gluck, kallisti5@unixzen.com
 *		Kacper Kasper, kacperkasper@gmail.com
 */


#include "PowerStatusView.h"

#include <algorithm>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <AboutWindow.h>
#include <Application.h>
#include <Bitmap.h>
#include <Beep.h>
#include <Catalog.h>
#include <DataIO.h>
#include <Deskbar.h>
#include <Dragger.h>
#include <Drivers.h>
#include <File.h>
#include <FindDirectory.h>
#include <GradientLinear.h>
#include <MenuItem.h>
#include <MessageRunner.h>
#include <Notification.h>
#include <NumberFormat.h>
#include <Path.h>
#include <PopUpMenu.h>
#include <Resources.h>
#include <Roster.h>
#include <TextView.h>
#include <TranslationUtils.h>

#include "ACPIDriverInterface.h"
#include "APMDriverInterface.h"
#include "ExtendedInfoWindow.h"
#include "PowerStatus.h"


#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "PowerStatus"


extern "C" _EXPORT BView *instantiate_deskbar_item(float maxWidth,
	float maxHeight);
extern const char* kDeskbarItemName;

const uint32 kMsgToggleLabel = 'tglb';
const uint32 kMsgToggleTime = 'tgtm';
const uint32 kMsgToggleStatusIcon = 'tgsi';
const uint32 kMsgToggleExtInfo = 'texi';

const double kLowBatteryPercentage = 0.15;
const double kNoteBatteryPercentage = 0.3;
const double kFullBatteryPercentage = 1.0;

const time_t kLowBatteryTimeLeft = 30 * 60;


PowerStatusView::PowerStatusView(PowerStatusDriverInterface* interface,
	BRect frame, int32 resizingMode,  int batteryID, bool inDeskbar)
	:
	BView(frame, kDeskbarItemName, resizingMode,
		B_WILL_DRAW | B_TRANSPARENT_BACKGROUND | B_FULL_UPDATE_ON_RESIZE),
	fDriverInterface(interface),
	fBatteryID(batteryID),
	fInDeskbar(inDeskbar)
{
	_Init();
}


PowerStatusView::PowerStatusView(BMessage* archive)
	:
	BView(archive),
	fInDeskbar(false)
{
	app_info info;
	if (be_app->GetAppInfo(&info) == B_OK
		&& !strcasecmp(info.signature, kDeskbarSignature))
		fInDeskbar = true;
	_Init();
	FromMessage(archive);
}


PowerStatusView::~PowerStatusView()
{
}


status_t
PowerStatusView::Archive(BMessage* archive, bool deep) const
{
	status_t status = BView::Archive(archive, deep);
	if (status == B_OK)
		status = ToMessage(archive);

	return status;
}


void
PowerStatusView::_Init()
{
	fShowLabel = true;
	fShowTime = false;
	fShowStatusIcon = true;

	fPercent = 1.0;
	fTimeLeft = 0;

	fHasNotifiedLowBattery = false;

	add_system_beep_event("Battery critical");
	add_system_beep_event("Battery low");
	add_system_beep_event("Battery charged");
}


void
PowerStatusView::AttachedToWindow()
{
	BView::AttachedToWindow();

	SetViewColor(B_TRANSPARENT_COLOR);

	if (ViewUIColor() != B_NO_COLOR)
		SetLowUIColor(ViewUIColor());
	else
		SetLowColor(ViewColor());

	Update();
}


void
PowerStatusView::DetachedFromWindow()
{
}


void
PowerStatusView::MessageReceived(BMessage *message)
{
	switch (message->what) {
		case kMsgUpdate:
			Update();
			break;

		default:
			BView::MessageReceived(message);
			break;
	}
}


void
PowerStatusView::_DrawBattery(BView* view, BRect rect)
{
	BRect lightningRect = rect;
	BRect pauseRect = rect;
	float margin = floorf((rect.Height() + 1) / 4.5f);
	rect.top += margin;
	rect.bottom -= margin;

	rect.InsetBy(2, 0);

	float left = rect.left;
	rect.left += rect.Width() / 11;
	lightningRect.left = rect.left;
	lightningRect.InsetBy(0.0f, rect.Height() * 0.25f);
	pauseRect.left = rect.left;
	pauseRect.InsetBy(rect.Width() * 0.1f, rect.Height() * 0.4f);

	if (view->LowColor().IsLight())
		view->SetHighColor(0, 0, 0);
	else
		view->SetHighColor(128, 128, 128);

	float gap = 1;
	if (rect.Height() > 8) {
		gap = max_c(1.0f, floorf((rect.left - left) / 2));

		// left
		view->FillRect(BRect(rect.left, rect.top, rect.left + gap - 1,
			rect.bottom));
		// right
		view->FillRect(BRect(rect.right - gap + 1, rect.top, rect.right,
			rect.bottom));
		// top
		view->FillRect(BRect(rect.left + gap, rect.top, rect.right - gap,
			rect.top + gap - 1));
		// bottom
		view->FillRect(BRect(rect.left + gap, rect.bottom + 1 - gap,
			rect.right - gap, rect.bottom));
	} else
		view->StrokeRect(rect);

	view->FillRect(BRect(left, floorf(rect.top + rect.Height() / 4) + 1,
		rect.left - 1, floorf(rect.bottom - rect.Height() / 4)));

	double percent = fPercent;
	if (percent > 1.0)
		percent = 1.0;
	else if (percent < 0.0 || !fHasBattery)
		percent = 0.0;

	rect.InsetBy(gap, gap);

	if (fHasBattery) {
		// draw unfilled area
		rgb_color unfilledColor = make_color(0x4c, 0x4c, 0x4c);
		if (view->LowColor().IsDark())
			unfilledColor = make_color(0xb4, 0xb4, 0xb4);

		BRect unfilled = rect;
		if (percent > 0.0)
			unfilled.right = unfilled.left + unfilled.Width() * (1 - percent);

		view->SetHighColor(unfilledColor);
		view->FillRect(unfilled);

		if (percent > 0.0) {
			// draw filled area
			rgb_color fillColor = make_color(20, 180, 0);
			if (percent <= kLowBatteryPercentage) {
				fillColor.set_to(180, 0, 0);
			} else if (percent <= kNoteBatteryPercentage) {
				fillColor.set_to(200, 140, 0);
			} else if ((fBatteryInfo.state & ~BATTERY_NOT_CHARGING) == 0) {
				// When a battery is not in use at all, draw it in blue.
				fillColor.set_to(50, 150, 255, 255);
			}

			BRect fill = rect;
			fill.left += fill.Width() * (1 - percent);

			// draw bevel
			rgb_color bevelLightColor  = tint_color(fillColor, 0.2);
			rgb_color bevelShadowColor = tint_color(fillColor, 1.08);

			view->BeginLineArray(4);
			view->AddLine(BPoint(fill.left, fill.bottom),
				BPoint(fill.left, fill.top), bevelLightColor);
			view->AddLine(BPoint(fill.left, fill.top),
				BPoint(fill.right, fill.top), bevelLightColor);
			view->AddLine(BPoint(fill.right, fill.top),
				BPoint(fill.right, fill.bottom), bevelShadowColor);
			view->AddLine(BPoint(fill.left, fill.bottom),
				BPoint(fill.right, fill.bottom), bevelShadowColor);
			view->EndLineArray();

			fill.InsetBy(1, 1);

			// draw gradient
			float topTint = 0.49;
			float middleTint1 = 0.62;
			float middleTint2 = 0.76;
			float bottomTint = 0.90;

			BGradientLinear gradient;
			gradient.AddColor(tint_color(fillColor, topTint), 0);
			gradient.AddColor(tint_color(fillColor, middleTint1), 132);
			gradient.AddColor(tint_color(fillColor, middleTint2), 136);
			gradient.AddColor(tint_color(fillColor, bottomTint), 255);
			gradient.SetStart(fill.LeftTop());
			gradient.SetEnd(fill.LeftBottom());

			view->FillRect(fill, gradient);
		}
	}

	if ((fBatteryInfo.state & BATTERY_CHARGING) != 0) {
		// When charging, draw a lightning symbol over the battery.
		view->SetHighColor(255, 255, 0, 180);
		view->SetDrawingMode(B_OP_ALPHA);

		static const BPoint points[] = {
			BPoint(3, 14),
			BPoint(10, 6),
			BPoint(10, 8),
			BPoint(17, 3),
			BPoint(9, 12),
			BPoint(9, 10)
		};
		view->FillPolygon(points, 6, lightningRect);

		view->SetDrawingMode(B_OP_OVER);
	} else if ((fBatteryInfo.state & BATTERY_CRITICAL_STATE) != 0) {
		// When a battery is damaged or missing, draw an X over it
		view->SetHighColor(200, 0, 0, 96);
		view->SetDrawingMode(B_OP_ALPHA);

		static const BPoint points[] = {
			BPoint(1, 1),
			BPoint(1, 2),
			BPoint(20, 6),
			BPoint(22, 6),
			BPoint(22, 5),
			BPoint(3, 1),

			BPoint(20, 1),
			BPoint(1, 5),
			BPoint(1, 6),
			BPoint(3, 6),
			BPoint(22, 2),
			BPoint(22, 1)
		};
		view->FillPolygon(points, 12, lightningRect);

		view->SetDrawingMode(B_OP_OVER);
	}

	view->SetHighColor(0, 0, 0);
}


void
PowerStatusView::Draw(BRect updateRect)
{
	DrawTo(this, Bounds());
}


void
PowerStatusView::DrawTo(BView* view, BRect rect)
{
	bool inside = rect.Width() >= 40.0f && rect.Height() >= 40.0f;

	font_height fontHeight;
	view->GetFontHeight(&fontHeight);
	float baseLine = ceilf(fontHeight.ascent);

	char text[64];
	_SetLabel(text, sizeof(text));

	float textHeight = ceilf(fontHeight.descent + fontHeight.ascent);
	float textWidth = view->StringWidth(text);
	bool showLabel = fShowLabel && text[0];

	BRect iconRect;

	if (fShowStatusIcon) {
		iconRect = rect;
		if (showLabel && inside == false)
			iconRect.right -= textWidth + 2;

		_DrawBattery(view, iconRect);
	}

	if (showLabel) {
		BPoint point(0, baseLine + rect.top);

		if (iconRect.IsValid()) {
			if (inside == true) {
				point.x = rect.left + (iconRect.Width() - textWidth) / 2 +
					iconRect.Width() / 20;
				point.y += (iconRect.Height() - textHeight) / 2;
			} else {
				point.x = rect.left + iconRect.Width() + 2;
				point.y += (iconRect.Height() - textHeight) / 2;
			}
		} else {
			point.x = rect.left + (Bounds().Width() - textWidth) / 2;
			point.y += (Bounds().Height() - textHeight) / 2;
		}

		view->SetDrawingMode(B_OP_OVER);
		if (fInDeskbar == false || inside == true) {
			view->SetHighUIColor(B_CONTROL_BACKGROUND_COLOR);
			view->DrawString(text, BPoint(point.x + 1, point.y + 1));
		}
		view->SetHighUIColor(B_CONTROL_TEXT_COLOR);

		view->DrawString(text, point);
	}
}


void
PowerStatusView::_SetLabel(char* buffer, size_t bufferLength)
{
	if (bufferLength < 1)
		return;

	buffer[0] = '\0';

	if (!fShowLabel)
		return;

	const char* open = "";
	const char* close = "";
	if ((fBatteryInfo.state & BATTERY_DISCHARGING) == 0) {
		// surround the percentage with () if the battery is not discharging
		open = "(";
		close = ")";
	}

	if (!fShowTime && fPercent >= 0) {
		BNumberFormat numberFormat;
		BString data;

		if (numberFormat.FormatPercent(data, fPercent) != B_OK) {
			data.SetToFormat("%" B_PRId32 "%%", int32(fPercent * 100));
		}

		snprintf(buffer, bufferLength, "%s%s%s", open, data.String(), close);
	} else if (fShowTime && fTimeLeft >= 0) {
		snprintf(buffer, bufferLength, "%s%" B_PRIdTIME ":%02" B_PRIdTIME "%s",
			open, fTimeLeft / 3600, (fTimeLeft / 60) % 60, close);
	}
}


void
PowerStatusView::Update(bool force, bool notify)
{
	double previousPercent = fPercent;
	time_t previousTimeLeft = fTimeLeft;
	bool wasCharging = (fBatteryInfo.state & BATTERY_CHARGING);
	bool hadBattery = fHasBattery;
	_GetBatteryInfo(fBatteryID, &fBatteryInfo);
	fHasBattery = fBatteryInfo.full_capacity > 0 && fBatteryInfo.state != BATTERY_CRITICAL_STATE;

	if (fBatteryInfo.full_capacity > 0 && fHasBattery) {
		fPercent = (double)fBatteryInfo.capacity / fBatteryInfo.full_capacity;
		fTimeLeft = fBatteryInfo.time_left;
	} else {
		fPercent = 0.0;
		fTimeLeft = -1;
	}

	if (fHasBattery && (fPercent <= 0 || fPercent > 1.0)) {
		// Just ignore this probe -- it obviously returned invalid values
		fPercent = previousPercent;
		fTimeLeft = previousTimeLeft;
		fHasBattery = hadBattery;
		return;
	}

	if (fInDeskbar) {
		// make sure the tray icon is (just) large enough
		float width = fShowStatusIcon ? floorf(Bounds().Height() * 1.15f) : 0;

		if (fShowLabel) {
			char text[64];
			_SetLabel(text, sizeof(text));

			if (text[0])
				width += ceilf(StringWidth(text)) + 2;
		} else {
			char text[256];
			const char* open = "";
			const char* close = "";
			if ((fBatteryInfo.state & BATTERY_DISCHARGING) == 0) {
				// surround the percentage with () if the battery is not discharging
				open = "(";
				close = ")";
			}
			if (fHasBattery) {
				BNumberFormat numberFormat;
				BString data;
				size_t length;

				if (numberFormat.FormatPercent(data, fPercent) != B_OK) {
					data.SetToFormat("%" B_PRId32 "%%", int32(fPercent * 100));
				}

				length = snprintf(text, sizeof(text), "%s%s%s", open, data.String(), close);

				if (fTimeLeft >= 0) {
					length += snprintf(text + length, sizeof(text) - length, "\n%" B_PRIdTIME
						":%02" B_PRIdTIME, fTimeLeft / 3600, (fTimeLeft / 60) % 60);
				}

				const char* state = NULL;
				if ((fBatteryInfo.state & BATTERY_CHARGING) != 0)
					state = B_TRANSLATE("charging");
				else if ((fBatteryInfo.state & BATTERY_DISCHARGING) != 0)
					state = B_TRANSLATE("discharging");
				else if ((fBatteryInfo.state & BATTERY_NOT_CHARGING) != 0)
					state = B_TRANSLATE("not charging");

				if (state != NULL) {
					snprintf(text + length, sizeof(text) - length, "\n%s",
						state);
				}
			} else
				strcpy(text, B_TRANSLATE("no battery"));
			SetToolTip(text);
		}
		if (width < 8) {
			// make sure we're not going away completely
			width = 8;
		}

		if (width != Bounds().Width()) {
			ResizeTo(width, Bounds().Height());

			// inform Deskbar that it needs to realign its replicants
			BWindow* window = Window();
			if (window != NULL) {
				BView* view = window->FindView("Status");
				if (view != NULL) {
					BMessenger target((BHandler*)view);
					BMessage realignReplicants('Algn');
					target.SendMessage(&realignReplicants);
				}
			}
		}
	}

	if (force || wasCharging != (fBatteryInfo.state & BATTERY_CHARGING)
		|| (fShowTime && fTimeLeft != previousTimeLeft)
		|| (!fShowTime && fPercent != previousPercent)) {
		Invalidate();
	}

	// only do low battery notices based on the aggregate virtual battery, not single batteries
	if (fBatteryID >= 0)
		return;

	if (fPercent > kLowBatteryPercentage && fTimeLeft > kLowBatteryTimeLeft)
		fHasNotifiedLowBattery = false;

	bool justTurnedLowBattery = (previousPercent > kLowBatteryPercentage
			&& fPercent <= kLowBatteryPercentage)
		|| (fTimeLeft <= kLowBatteryTimeLeft
			&& previousTimeLeft > kLowBatteryTimeLeft);

	if ((fBatteryInfo.state & BATTERY_DISCHARGING) != 0 && notify && fHasBattery
		&& !fHasNotifiedLowBattery && justTurnedLowBattery) {
		_NotifyLowBattery();
		fHasNotifiedLowBattery = true;
	}

	if ((fBatteryInfo.state & BATTERY_CHARGING) != 0 && fPercent >= kFullBatteryPercentage
		&& previousPercent < kFullBatteryPercentage) {
		system_beep("Battery charged");
	}
}


void
PowerStatusView::FromMessage(const BMessage* archive)
{
	bool value;
	if (archive->FindBool("show label", &value) == B_OK)
		fShowLabel = value;
	if (archive->FindBool("show icon", &value) == B_OK)
		fShowStatusIcon = value;
	if (archive->FindBool("show time", &value) == B_OK)
		fShowTime = value;

	//Incase we have a bad saving and none are showed..
	if (!fShowLabel && !fShowStatusIcon)
		fShowLabel = true;

	int32 intValue;
	if (archive->FindInt32("battery id", &intValue) == B_OK)
		fBatteryID = intValue;
}


status_t
PowerStatusView::ToMessage(BMessage* archive) const
{
	status_t status = archive->AddBool("show label", fShowLabel);
	if (status == B_OK)
		status = archive->AddBool("show icon", fShowStatusIcon);
	if (status == B_OK)
		status = archive->AddBool("show time", fShowTime);
	if (status == B_OK)
		status = archive->AddInt32("battery id", fBatteryID);

	return status;
}


void
PowerStatusView::_GetBatteryInfo(int batteryID, battery_info* batteryInfo)
{
	if (batteryID >= 0) {
		fDriverInterface->GetBatteryInfo(batteryID, batteryInfo);
	} else {
		bool first = true;
		memset(batteryInfo, 0, sizeof(battery_info));

		for (int i = 0; i < fDriverInterface->GetBatteryCount(); i++) {
			battery_info info;
			fDriverInterface->GetBatteryInfo(i, &info);
			if (info.full_capacity <= 0)
				continue;

			if (first) {
				*batteryInfo = info;
				first = false;
			} else {
				if ((batteryInfo->state & BATTERY_CRITICAL_STATE) == 0) {
					// don't propagate CRITICAL_STATE to the aggregate battery.
					// one battery charging means "the system is charging" but one battery having
					// been removed does not mean "the system has no battery"
					batteryInfo->state |= info.state;
				}
				batteryInfo->capacity += info.capacity;
				batteryInfo->full_capacity += info.full_capacity;
				batteryInfo->current_rate += info.current_rate;
			}
		}

		// we can't rely on just adding the individual batteries' time_lefts together:
		// not-in-use batteries show -1 time_left as they will last infinitely long with their
		// current (zero) level of draw, despite them being in the queue to use after the current
		// battery is out of energy. therefore to calculate an accurate time, we have to use the
		// current total rate of (dis)charge compared to the total remaining capacity of all
		// batteries.
		if (batteryInfo->current_rate == 0) {
			// some systems briefly return current_rate of 0 as the charger is plugged/unplugged
			batteryInfo->time_left = 0;
		} else if ((batteryInfo->state & BATTERY_CHARGING) != 0) {
			batteryInfo->time_left = 3600 * (batteryInfo->full_capacity - batteryInfo->capacity)
				/ batteryInfo->current_rate;
		} else {
			batteryInfo->time_left = 3600 * batteryInfo->capacity / batteryInfo->current_rate;
		}
	}
}


void
PowerStatusView::_NotifyLowBattery()
{
	BBitmap* bitmap = NULL;
	BResources resources;
	resources.SetToImage((void*)&instantiate_deskbar_item);

	if (resources.InitCheck() == B_OK) {
		size_t resourceSize = 0;
		const void* resourceData = resources.LoadResource(
			B_VECTOR_ICON_TYPE, fHasBattery
				? "battery_low" : "battery_critical", &resourceSize);
		if (resourceData != NULL) {
			BMemoryIO memoryIO(resourceData, resourceSize);
			bitmap = BTranslationUtils::GetBitmap(&memoryIO);
		}
	}

	BNotification notification(
		fHasBattery ? B_INFORMATION_NOTIFICATION : B_ERROR_NOTIFICATION);

	if (fHasBattery) {
		system_beep("Battery low");
		notification.SetTitle(B_TRANSLATE("Battery low"));
		notification.SetContent(B_TRANSLATE(
			"The battery level is getting low, please plug in the device."));
	} else {
		system_beep("Battery critical");
		notification.SetTitle(B_TRANSLATE("Battery critical"));
		notification.SetContent(B_TRANSLATE(
			"The battery level is critical, please plug in the device "
			"immediately."));
	}

	notification.SetIcon(bitmap);
	notification.Send();
	delete bitmap;
}


// #pragma mark - Replicant view


PowerStatusReplicant::PowerStatusReplicant(BRect frame, int32 resizingMode,
	bool inDeskbar)
	:
	PowerStatusView(NULL, frame, resizingMode, -1, inDeskbar),
	fReplicated(false)
{
	_Init();
	_LoadSettings();

	if (!inDeskbar) {
		// we were obviously added to a standard window - let's add a dragger
		frame.OffsetTo(B_ORIGIN);
		frame.top = frame.bottom - 7;
		frame.left = frame.right - 7;
		BDragger* dragger = new BDragger(frame, this,
			B_FOLLOW_RIGHT | B_FOLLOW_BOTTOM);
		AddChild(dragger);
	} else
		Update(false, false);
}


PowerStatusReplicant::PowerStatusReplicant(BMessage* archive)
	:
	PowerStatusView(archive),
	fReplicated(true)
{
	_Init();
	_LoadSettings();
}


PowerStatusReplicant::~PowerStatusReplicant()
{
	if (fMessengerExist)
		delete fExtWindowMessenger;

	if (fExtendedWindow != NULL && fExtendedWindow->Lock()) {
			fExtendedWindow->Quit();
			fExtendedWindow = NULL;
	}

	fDriverInterface->StopWatching(this);
	fDriverInterface->Disconnect();
	fDriverInterface->ReleaseReference();

	_SaveSettings();
}


PowerStatusReplicant*
PowerStatusReplicant::Instantiate(BMessage* archive)
{
	if (!validate_instantiation(archive, "PowerStatusReplicant"))
		return NULL;

	return new PowerStatusReplicant(archive);
}


status_t
PowerStatusReplicant::Archive(BMessage* archive, bool deep) const
{
	status_t status = PowerStatusView::Archive(archive, deep);
	if (status == B_OK)
		status = archive->AddString("add_on", kSignature);
	if (status == B_OK)
		status = archive->AddString("class", "PowerStatusReplicant");

	return status;
}


void
PowerStatusReplicant::MessageReceived(BMessage *message)
{
	switch (message->what) {
		case kMsgToggleLabel:
			if (fShowStatusIcon)
				fShowLabel = !fShowLabel;
			else
				fShowLabel = true;

			Update(true);
			break;

		case kMsgToggleTime:
			fShowTime = !fShowTime;
			Update(true);
			break;

		case kMsgToggleStatusIcon:
			if (fShowLabel)
				fShowStatusIcon = !fShowStatusIcon;
			else
				fShowStatusIcon = true;

			Update(true);
			break;

		case kMsgToggleExtInfo:
			_OpenExtendedWindow();
			break;

		case B_ABOUT_REQUESTED:
			_AboutRequested();
			break;

		case B_QUIT_REQUESTED:
			_Quit();
			break;

		default:
			PowerStatusView::MessageReceived(message);
			break;
	}
}


void
PowerStatusReplicant::MouseDown(BPoint point)
{
	BMessage* msg = Window()->CurrentMessage();
	int32 buttons = msg->GetInt32("buttons", 0);
	if ((buttons & B_TERTIARY_MOUSE_BUTTON) != 0) {
		BMessenger messenger(this);
		messenger.SendMessage(kMsgToggleExtInfo);
	} else {
		BPopUpMenu* menu = new BPopUpMenu(B_EMPTY_STRING, false, false);
		menu->SetFont(be_plain_font);

		BMenuItem* item;
		menu->AddItem(item = new BMenuItem(B_TRANSLATE("Show text label"),
			new BMessage(kMsgToggleLabel)));
		if (fShowLabel)
			item->SetMarked(true);
		menu->AddItem(item = new BMenuItem(B_TRANSLATE("Show status icon"),
			new BMessage(kMsgToggleStatusIcon)));
		if (fShowStatusIcon)
			item->SetMarked(true);
		menu->AddItem(new BMenuItem(!fShowTime ? B_TRANSLATE("Show time") :
			B_TRANSLATE("Show percent"), new BMessage(kMsgToggleTime)));

		menu->AddSeparatorItem();
		menu->AddItem(new BMenuItem(B_TRANSLATE("Battery info" B_UTF8_ELLIPSIS),
			new BMessage(kMsgToggleExtInfo)));

		menu->AddSeparatorItem();
		menu->AddItem(new BMenuItem(B_TRANSLATE("About" B_UTF8_ELLIPSIS),
			new BMessage(B_ABOUT_REQUESTED)));
		menu->AddItem(new BMenuItem(B_TRANSLATE("Quit"),
			new BMessage(B_QUIT_REQUESTED)));
		menu->SetTargetForItems(this);

		ConvertToScreen(&point);
		menu->Go(point, true, false, true);
	}
}


void
PowerStatusReplicant::_AboutRequested()
{
	BAboutWindow* window = new BAboutWindow(
		B_TRANSLATE_SYSTEM_NAME("PowerStatus"), kSignature);

	const char* authors[] = {
		"Axel DΓΆrfler",
		"Alexander von Gluck",
		"Clemens Zeidler",
		NULL
	};

	window->AddCopyright(2006, "Haiku, Inc.");
	window->AddAuthors(authors);

	window->Show();
}


void
PowerStatusReplicant::_Init()
{
	fDriverInterface = new ACPIDriverInterface;
	if (fDriverInterface->Connect() != B_OK) {
		delete fDriverInterface;
		fDriverInterface = new APMDriverInterface;
		if (fDriverInterface->Connect() != B_OK) {
			fprintf(stderr, "No power interface found.\n");
			_Quit();
		}
	}

	fExtendedWindow = NULL;
	fMessengerExist = false;
	fExtWindowMessenger = NULL;

	fDriverInterface->StartWatching(this);
}


void
PowerStatusReplicant::_Quit()
{
	if (fInDeskbar) {
		BDeskbar deskbar;
		deskbar.RemoveItem(kDeskbarItemName);
	} else if (fReplicated) {
		BDragger *dragger = dynamic_cast<BDragger*>(ChildAt(0));
		if (dragger != NULL) {
			BMessenger messenger(dragger);
			messenger.SendMessage(new BMessage(B_TRASH_TARGET));
		}
	} else
		be_app->PostMessage(B_QUIT_REQUESTED);
}


status_t
PowerStatusReplicant::_GetSettings(BFile& file, int mode)
{
	BPath path;
	status_t status = find_directory(B_USER_SETTINGS_DIRECTORY, &path,
		(mode & O_ACCMODE) != O_RDONLY);
	if (status != B_OK)
		return status;

	path.Append("PowerStatus settings");

	return file.SetTo(path.Path(), mode);
}


void
PowerStatusReplicant::_LoadSettings()
{
	fShowLabel = false;

	BFile file;
	if (_GetSettings(file, B_READ_ONLY) != B_OK)
		return;

	BMessage settings;
	if (settings.Unflatten(&file) < B_OK)
		return;

	FromMessage(&settings);
}


void
PowerStatusReplicant::_SaveSettings()
{
	BFile file;
	if (_GetSettings(file, B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE) != B_OK)
		return;

	BMessage settings('pwst');
	ToMessage(&settings);

	ssize_t size = 0;
	settings.Flatten(&file, &size);
}


void
PowerStatusReplicant::_OpenExtendedWindow()
{
	if (!fExtendedWindow) {
		fExtendedWindow = new ExtendedInfoWindow(fDriverInterface);
		fExtWindowMessenger = new BMessenger(NULL, fExtendedWindow);
		fExtendedWindow->Show();
		return;
	}

	BMessage msg(B_SET_PROPERTY);
	msg.AddSpecifier("Hidden", int32(0));
	if (fExtWindowMessenger->SendMessage(&msg) == B_BAD_PORT_ID) {
		fExtendedWindow = new ExtendedInfoWindow(fDriverInterface);
		if (fMessengerExist)
			delete fExtWindowMessenger;
		fExtWindowMessenger = new BMessenger(NULL, fExtendedWindow);
		fMessengerExist = true;
		fExtendedWindow->Show();
	} else
		fExtendedWindow->Activate();

}


//	#pragma mark -


extern "C" _EXPORT BView*
instantiate_deskbar_item(float maxWidth, float maxHeight)
{
	return new PowerStatusReplicant(BRect(0, 0, maxWidth - 1, maxHeight - 1),
		B_FOLLOW_TOP, true);
}