xref: /haiku/src/apps/powerstatus/PowerStatusView.cpp (revision 1acbe440b8dd798953bec31d18ee589aa3f71b73)
1 /*
2  * Copyright 2006, 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  */
8 
9 
10 #include "PowerStatusView.h"
11 #include "PowerStatus.h"
12 
13 #include <Alert.h>
14 #include <Application.h>
15 #include <Deskbar.h>
16 #include <Dragger.h>
17 #include <Drivers.h>
18 #include <MenuItem.h>
19 #include <MessageRunner.h>
20 #include <PopUpMenu.h>
21 #include <TextView.h>
22 
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <unistd.h>
27 
28 
29 extern "C" _EXPORT BView *instantiate_deskbar_item(void);
30 
31 
32 const uint32 kMsgUpdate = 'updt';
33 const uint32 kMsgToggleLabel = 'tglb';
34 const uint32 kMsgToggleTime = 'tgtm';
35 const uint32 kMsgToggleStatusIcon = 'tgsi';
36 
37 const uint32 kMinIconWidth = 16;
38 const uint32 kMinIconHeight = 16;
39 
40 const bigtime_t kUpdateInterval = 2000000;
41 		// every two seconds
42 
43 
44 #ifndef HAIKU_TARGET_PLATFORM_HAIKU
45 // definitions for the APM driver available for BeOS
46 enum {
47 	APM_CONTROL = B_DEVICE_OP_CODES_END + 1,
48 	APM_DUMP_POWER_STATUS,
49 	APM_BIOS_CALL,
50 	APM_SET_SAFETY
51 };
52 
53 #define BIOS_APM_GET_POWER_STATUS 0x530a
54 #endif
55 
56 
57 PowerStatusView::PowerStatusView(BRect frame, int32 resizingMode, bool inDeskbar)
58 	: BView(frame, kDeskbarItemName, resizingMode,
59 		B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE),
60 	fInDeskbar(inDeskbar)
61 {
62 	_Init();
63 
64 	if (!inDeskbar) {
65 		// we were obviously added to a standard window - let's add a dragger
66 		frame.OffsetTo(B_ORIGIN);
67 		frame.top = frame.bottom - 7;
68 		frame.left = frame.right - 7;
69 		BDragger* dragger = new BDragger(frame, this,
70 			B_FOLLOW_RIGHT | B_FOLLOW_BOTTOM);
71 		AddChild(dragger);
72 	} else
73 		_Update();
74 }
75 
76 
77 PowerStatusView::PowerStatusView(BMessage* archive)
78 	: BView(archive)
79 {
80 	_Init();
81 
82 	bool value;
83 	if (archive->FindBool("show label", &value) == B_OK)
84 		fShowLabel = value;
85 	if (archive->FindBool("show icon", &value) == B_OK)
86 		fShowStatusIcon = value;
87 	if (archive->FindBool("show time", &value) == B_OK)
88 		fShowTime = value;
89 }
90 
91 
92 PowerStatusView::~PowerStatusView()
93 {
94 #ifndef HAIKU_TARGET_PLATFORM_HAIKU
95 	close(fDevice);
96 #endif
97 }
98 
99 
100 void
101 PowerStatusView::_Init()
102 {
103 	fShowLabel = true;
104 	fShowTime = false;
105 	fShowStatusIcon = true;
106 
107 	fMessageRunner = NULL;
108 	fPercent = -1;
109 	fOnline = true;
110 	fTimeLeft = 0;
111 
112 #ifdef HAIKU_TARGET_PLATFORM_HAIKU
113 	// TODO: implement me
114 #else
115 	fDevice = open("/dev/misc/apm", O_RDONLY);
116 	if (fDevice < 0) {
117 		fprintf(stderr, "No power interface found.\n");
118 		_Quit();
119 	}
120 #endif
121 }
122 
123 
124 void
125 PowerStatusView::_Quit()
126 {
127 	if (fInDeskbar) {
128 		BDeskbar deskbar;
129 		deskbar.RemoveItem(kDeskbarItemName);
130 	} else
131 		be_app->PostMessage(B_QUIT_REQUESTED);
132 }
133 
134 
135 PowerStatusView *
136 PowerStatusView::Instantiate(BMessage* archive)
137 {
138 	if (!validate_instantiation(archive, "PowerStatusView"))
139 		return NULL;
140 
141 	return new PowerStatusView(archive);
142 }
143 
144 
145 status_t
146 PowerStatusView::Archive(BMessage* archive, bool deep) const
147 {
148 	status_t status = BView::Archive(archive, deep);
149 	if (status == B_OK)
150 		status = archive->AddString("add_on", kSignature);
151 	if (status == B_OK)
152 		status = archive->AddString("class", "PowerStatusView");
153 	if (status == B_OK)
154 		status = archive->AddBool("show label", fShowLabel);
155 	if (status == B_OK)
156 		status = archive->AddBool("show icon", fShowStatusIcon);
157 	if (status == B_OK)
158 		status = archive->AddBool("show time", fShowTime);
159 
160 	return status;
161 }
162 
163 
164 void
165 PowerStatusView::AttachedToWindow()
166 {
167 	BView::AttachedToWindow();
168 	if (Parent())
169 		SetViewColor(Parent()->ViewColor());
170 	else
171 		SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));
172 
173 	SetLowColor(ViewColor());
174 
175 	BMessage update(kMsgUpdate);
176 	fMessageRunner = new BMessageRunner(this, &update, kUpdateInterval);
177 
178 	_Update();
179 }
180 
181 
182 void
183 PowerStatusView::DetachedFromWindow()
184 {
185 	delete fMessageRunner;
186 }
187 
188 
189 void
190 PowerStatusView::MessageReceived(BMessage *message)
191 {
192 	switch (message->what) {
193 		case kMsgUpdate:
194 			_Update();
195 			break;
196 
197 		case kMsgToggleLabel:
198 			fShowLabel = !fShowLabel;
199 			_Update(true);
200 			break;
201 
202 		case kMsgToggleTime:
203 			fShowTime = !fShowTime;
204 			_Update(true);
205 			break;
206 
207 		case kMsgToggleStatusIcon:
208 			fShowStatusIcon = !fShowStatusIcon;
209 			_Update(true);
210 			break;
211 
212 		case B_ABOUT_REQUESTED:
213 			_AboutRequested();
214 			break;
215 
216 		case B_QUIT_REQUESTED:
217 			_Quit();
218 			break;
219 
220 		default:
221 			BView::MessageReceived(message);
222 	}
223 }
224 
225 
226 void
227 PowerStatusView::_DrawBattery(BRect rect)
228 {
229 	float quarter = floorf((rect.Height() + 1) / 4);
230 	rect.top += quarter;
231 	rect.bottom -= quarter;
232 
233 	rect.InsetBy(2, 0);
234 
235 	float left = rect.left;
236 	rect.left += rect.Width() / 11;
237 
238 	float gap = 1;
239 	if (rect.Height() > 8) {
240 		rect.InsetBy(1, 1);
241 		SetPenSize(2);
242 		gap = 2;
243 	}
244 
245 	if (fOnline)
246 		SetHighColor(92, 92, 92);
247 
248 	StrokeRect(rect);
249 
250 	SetPenSize(1);
251 	FillRect(BRect(left, floorf(rect.top + rect.Height() / 4) + 1,
252 		rect.left, floorf(rect.bottom - rect.Height() / 4)));
253 
254 	int32 percent = fPercent;
255 	if (percent > 100 || percent < 0)
256 		percent = 100;
257 
258 	if (percent > 0) {
259 		if (percent < 16)
260 			SetHighColor(180, 0, 0);
261 
262 		rect.InsetBy(gap + 1, gap + 1);
263 		if (gap > 1) {
264 			rect.right++;
265 			rect.bottom++;
266 		}
267 
268 		rect.right = rect.left + rect.Width() * min_c(percent, 100) / 100.0;
269 		FillRect(rect);
270 	}
271 
272 	SetHighColor(0, 0, 0);
273 }
274 
275 
276 void
277 PowerStatusView::Draw(BRect updateRect)
278 {
279 	float aspect = Bounds().Width() / Bounds().Height();
280 	bool below = aspect <= 1.0f;
281 
282 	font_height fontHeight;
283 	GetFontHeight(&fontHeight);
284 	float baseLine = ceilf(fontHeight.ascent);
285 
286 	char text[64];
287 	_SetLabel(text, sizeof(text));
288 
289 	float textHeight = ceilf(fontHeight.descent + fontHeight.ascent);
290 	float textWidth = StringWidth(text);
291 	bool showLabel = fShowLabel && text[0];
292 
293 	BRect iconRect;
294 
295 	if (fShowStatusIcon) {
296 		iconRect = Bounds();
297 		if (showLabel) {
298 			if (below)
299 				iconRect.bottom -= textHeight + 4;
300 			else
301 				iconRect.right -= textWidth + 4;
302 		}
303 
304 		// make a square
305 		iconRect.bottom = min_c(iconRect.bottom, iconRect.right);
306 		iconRect.right = iconRect.bottom;
307 
308 		if (iconRect.Width() + 1 >= kMinIconWidth
309 			&& iconRect.Height() + 1 >= kMinIconHeight) {
310 			// TODO: have real icons
311 			//if (!fOnline)
312 				_DrawBattery(iconRect);
313 			//else
314 			//	FillRect(iconRect);
315 		} else {
316 			// there is not enough space for the icon
317 			iconRect.Set(0, 0, -1, -1);
318 		}
319 	}
320 
321 	if (showLabel) {
322 		BPoint point(0, baseLine);
323 
324 		if (iconRect.IsValid()) {
325 			if (below) {
326 				point.x = (iconRect.Width() - textWidth) / 2;
327 				point.y += iconRect.Height() + 2;
328 			} else {
329 				point.x = iconRect.Width() + 2;
330 				point.y += (iconRect.Height() - textHeight) / 2;
331 			}
332 		} else {
333 			point.x = (Bounds().Width() - textWidth) / 2;
334 			point.y += (Bounds().Height() - textHeight) / 2;
335 		}
336 
337 		DrawString(text, point);
338 	}
339 }
340 
341 
342 void
343 PowerStatusView::MouseDown(BPoint point)
344 {
345 	BPopUpMenu *menu = new BPopUpMenu(B_EMPTY_STRING, false, false);
346 	menu->SetFont(be_plain_font);
347 
348 	BMenuItem* item;
349 	menu->AddItem(item = new BMenuItem("Show Text Label", new BMessage(kMsgToggleLabel)));
350 	if (fShowLabel)
351 		item->SetMarked(true);
352 	menu->AddItem(item = new BMenuItem("Show Status Icon",
353 		new BMessage(kMsgToggleStatusIcon)));
354 	if (fShowStatusIcon)
355 		item->SetMarked(true);
356 	menu->AddItem(new BMenuItem(!fShowTime ? "Show Time" : "Show Percent",
357 		new BMessage(kMsgToggleTime)));
358 
359 	menu->AddSeparatorItem();
360 	menu->AddItem(new BMenuItem("About" B_UTF8_ELLIPSIS, new BMessage(B_ABOUT_REQUESTED)));
361 	menu->AddItem(new BMenuItem("Quit", new BMessage(B_QUIT_REQUESTED)));
362 	menu->SetTargetForItems(this);
363 
364 	ConvertToScreen(&point);
365 	menu->Go(point, true, false, true);
366 }
367 
368 
369 void
370 PowerStatusView::_AboutRequested()
371 {
372 	BAlert *alert = new BAlert("about", "PowerStatus\n"
373 		"\twritten by Axel Dörfler\n"
374 		"\tCopyright 2006, Haiku, Inc.\n", "Ok");
375 	BTextView *view = alert->TextView();
376 	BFont font;
377 
378 	view->SetStylable(true);
379 
380 	view->GetFont(&font);
381 	font.SetSize(18);
382 	font.SetFace(B_BOLD_FACE);
383 	view->SetFontAndColor(0, 11, &font);
384 
385 	alert->Go();
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 (fOnline) {
403 		open = "(";
404 		close = ")";
405 	}
406 
407 	if (!fShowTime && fPercent >= 0)
408 		snprintf(buffer, bufferLength, "%s%ld%%%s", open, fPercent, close);
409 	else if (fShowTime && fTimeLeft >= 0) {
410 		snprintf(buffer, bufferLength, "%s%ld:%ld%s",
411 			open, fTimeLeft / 3600, (fTimeLeft / 60) % 60, close);
412 	}
413 }
414 
415 
416 void
417 PowerStatusView::_Update(bool force)
418 {
419 	int32 previousPercent = fPercent;
420 	bool previousTimeLeft = fTimeLeft;
421 	bool wasOnline = fOnline;
422 
423 #ifdef HAIKU_TARGET_PLATFORM_HAIKU
424 	// TODO: retrieve data from APM/ACPI kernel interface
425 	fPercent = 42;
426 	fTimeLeft = 1500;
427 	fOnline = true;
428 #else
429 	if (fDevice < 0)
430 		return;
431 
432 	uint16 regs[6] = {0, 0, 0, 0, 0, 0};
433 	regs[0] = BIOS_APM_GET_POWER_STATUS;
434 	regs[1] = 0x1;
435 	if (ioctl(fDevice, APM_BIOS_CALL, regs) == 0) {
436 		fOnline = (regs[1] >> 8) != 0 && (regs[1] >> 8) != 2;
437 		fPercent = regs[2] & 255;
438 		if (fPercent > 100)
439 			fPercent = -1;
440 		fTimeLeft = fPercent >= 0 ? regs[3] : -1;
441 		if (fTimeLeft > 0xffff)
442 			fTimeLeft = -1;
443 		else if (fTimeLeft & 0x8000)
444 			fTimeLeft = (fTimeLeft & 0x7fff) * 60;
445 	}
446 #endif
447 
448 	if (fInDeskbar) {
449 		// make sure the tray icon is large enough
450 		float width = fShowStatusIcon ? kMinIconWidth + 2 : 0;
451 
452 		if (fShowLabel) {
453 			char text[64];
454 			_SetLabel(text, sizeof(text));
455 
456 			if (text[0])
457 				width += ceilf(StringWidth(text)) + 4;
458 		}
459 		if (width == 0) {
460 			// make sure we're not going away completely
461 			width = 8;
462 		}
463 
464 		if (width != Bounds().Width())
465 			ResizeTo(width, Bounds().Height());
466 	}
467 
468 	if (force || wasOnline != fOnline
469 		|| (fShowTime && fTimeLeft != previousTimeLeft)
470 		|| (!fShowTime && fPercent != previousPercent))
471 		Invalidate();
472 }
473 
474 
475 //	#pragma mark -
476 
477 
478 extern "C" _EXPORT BView *
479 instantiate_deskbar_item(void)
480 {
481 	return new PowerStatusView(BRect(0, 0, 15, 15), B_FOLLOW_NONE, true);
482 }
483 
484