bin/pc: Mark non-returning function as void
[haiku.git] / src / servers / notification / NotificationView.cpp
blobbb11ef4f673b3e44aa944a9a191067c06b1e6bb7
1 /*
2 * Copyright 2010-2017, Haiku, Inc. All Rights Reserved.
3 * Copyright 2008-2009, Pier Luigi Fiorini. All Rights Reserved.
4 * Copyright 2004-2008, Michael Davidson. All Rights Reserved.
5 * Copyright 2004-2007, Mikael Eiman. All Rights Reserved.
6 * Distributed under the terms of the MIT License.
8 * Authors:
9 * Michael Davidson, slaad@bong.com.au
10 * Mikael Eiman, mikael@eiman.tv
11 * Pier Luigi Fiorini, pierluigi.fiorini@gmail.com
12 * Stephan Aßmus <superstippi@gmx.de>
13 * Adrien Destugues <pulkomandy@pulkomandy.ath.cx>
14 * Brian Hill, supernova@tycho.email
18 #include "NotificationView.h"
21 #include <Bitmap.h>
22 #include <ControlLook.h>
23 #include <GroupLayout.h>
24 #include <LayoutUtils.h>
25 #include <MessageRunner.h>
26 #include <Messenger.h>
27 #include <Notification.h>
28 #include <Path.h>
29 #include <PropertyInfo.h>
30 #include <Roster.h>
31 #include <StatusBar.h>
33 #include <Notifications.h>
35 #include "AppGroupView.h"
36 #include "NotificationWindow.h"
39 const int kIconStripeWidth = 32;
40 const float kCloseSize = 6;
41 const float kEdgePadding = 2;
42 const float kSmallPadding = 2;
44 property_info message_prop_list[] = {
45 { "type", {B_GET_PROPERTY, B_SET_PROPERTY, 0},
46 {B_DIRECT_SPECIFIER, 0}, "get the notification type"},
47 { "app", {B_GET_PROPERTY, B_SET_PROPERTY, 0},
48 {B_DIRECT_SPECIFIER, 0}, "get notification's app"},
49 { "title", {B_GET_PROPERTY, B_SET_PROPERTY, 0},
50 {B_DIRECT_SPECIFIER, 0}, "get notification's title"},
51 { "content", {B_GET_PROPERTY, B_SET_PROPERTY, 0},
52 {B_DIRECT_SPECIFIER, 0}, "get notification's contents"},
53 { "icon", {B_GET_PROPERTY, 0},
54 {B_DIRECT_SPECIFIER, 0}, "get icon as an archived bitmap"},
55 { "progress", {B_GET_PROPERTY, B_SET_PROPERTY, 0},
56 {B_DIRECT_SPECIFIER, 0}, "get the progress (between 0.0 and 1.0)"},
58 { 0 }
62 NotificationView::NotificationView(BNotification* notification, bigtime_t timeout,
63 float iconSize, bool disableTimeout)
65 BView("NotificationView", B_WILL_DRAW),
66 fNotification(notification),
67 fTimeout(timeout),
68 fIconSize(iconSize),
69 fDisableTimeout(disableTimeout),
70 fRunner(NULL),
71 fBitmap(NULL),
72 fCloseClicked(false),
73 fPreviewModeOn(false)
75 if (fNotification->Icon() != NULL)
76 fBitmap = new BBitmap(fNotification->Icon());
78 BGroupLayout* layout = new BGroupLayout(B_VERTICAL);
79 SetLayout(layout);
81 SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
82 SetLowColor(ui_color(B_PANEL_BACKGROUND_COLOR));
84 switch (fNotification->Type()) {
85 case B_IMPORTANT_NOTIFICATION:
86 fStripeColor = ui_color(B_CONTROL_HIGHLIGHT_COLOR);
87 break;
88 case B_ERROR_NOTIFICATION:
89 fStripeColor = ui_color(B_FAILURE_COLOR);
90 break;
91 case B_PROGRESS_NOTIFICATION:
93 BStatusBar* progress = new BStatusBar("progress");
94 progress->SetBarHeight(12.0f);
95 progress->SetMaxValue(1.0f);
96 progress->Update(fNotification->Progress());
98 BString label = "";
99 label << (int)(fNotification->Progress() * 100) << " %";
100 progress->SetTrailingText(label);
102 layout->AddView(progress);
104 // fall through.
105 case B_INFORMATION_NOTIFICATION:
106 fStripeColor = tint_color(ui_color(B_PANEL_BACKGROUND_COLOR),
107 B_DARKEN_1_TINT);
108 break;
113 NotificationView::~NotificationView()
115 delete fRunner;
116 delete fBitmap;
117 delete fNotification;
119 LineInfoList::iterator lIt;
120 for (lIt = fLines.begin(); lIt != fLines.end(); lIt++)
121 delete (*lIt);
125 void
126 NotificationView::AttachedToWindow()
128 SetText();
130 if (!fDisableTimeout) {
131 BMessage msg(kRemoveView);
132 msg.AddPointer("view", this);
133 fRunner = new BMessageRunner(BMessenger(Parent()), &msg, fTimeout, 1);
138 void
139 NotificationView::MessageReceived(BMessage* msg)
141 switch (msg->what) {
142 case B_GET_PROPERTY:
144 BMessage specifier;
145 const char* property;
146 BMessage reply(B_REPLY);
147 bool msgOkay = true;
149 if (msg->FindMessage("specifiers", 0, &specifier) != B_OK)
150 msgOkay = false;
151 if (specifier.FindString("property", &property) != B_OK)
152 msgOkay = false;
154 if (msgOkay) {
155 if (strcmp(property, "type") == 0)
156 reply.AddInt32("result", fNotification->Type());
158 if (strcmp(property, "group") == 0)
159 reply.AddString("result", fNotification->Group());
161 if (strcmp(property, "title") == 0)
162 reply.AddString("result", fNotification->Title());
164 if (strcmp(property, "content") == 0)
165 reply.AddString("result", fNotification->Content());
167 if (strcmp(property, "progress") == 0)
168 reply.AddFloat("result", fNotification->Progress());
170 if ((strcmp(property, "icon") == 0) && fBitmap) {
171 BMessage archive;
172 if (fBitmap->Archive(&archive) == B_OK)
173 reply.AddMessage("result", &archive);
176 reply.AddInt32("error", B_OK);
177 } else {
178 reply.what = B_MESSAGE_NOT_UNDERSTOOD;
179 reply.AddInt32("error", B_ERROR);
182 msg->SendReply(&reply);
183 break;
185 case B_SET_PROPERTY:
187 BMessage specifier;
188 const char* property;
189 BMessage reply(B_REPLY);
190 bool msgOkay = true;
192 if (msg->FindMessage("specifiers", 0, &specifier) != B_OK)
193 msgOkay = false;
194 if (specifier.FindString("property", &property) != B_OK)
195 msgOkay = false;
197 if (msgOkay) {
198 const char* value = NULL;
200 if (strcmp(property, "group") == 0)
201 if (msg->FindString("data", &value) == B_OK)
202 fNotification->SetGroup(value);
204 if (strcmp(property, "title") == 0)
205 if (msg->FindString("data", &value) == B_OK)
206 fNotification->SetTitle(value);
208 if (strcmp(property, "content") == 0)
209 if (msg->FindString("data", &value) == B_OK)
210 fNotification->SetContent(value);
212 if (strcmp(property, "icon") == 0) {
213 BMessage archive;
214 if (msg->FindMessage("data", &archive) == B_OK) {
215 delete fBitmap;
216 fBitmap = new BBitmap(&archive);
220 SetText();
221 Invalidate();
223 reply.AddInt32("error", B_OK);
224 } else {
225 reply.what = B_MESSAGE_NOT_UNDERSTOOD;
226 reply.AddInt32("error", B_ERROR);
229 msg->SendReply(&reply);
230 break;
232 default:
233 BView::MessageReceived(msg);
238 void
239 NotificationView::Draw(BRect updateRect)
241 BRect progRect;
243 SetDrawingMode(B_OP_ALPHA);
244 SetBlendingMode(B_PIXEL_ALPHA, B_ALPHA_OVERLAY);
246 BRect stripeRect = Bounds();
247 stripeRect.right = kIconStripeWidth;
248 SetHighColor(tint_color(ui_color(B_PANEL_BACKGROUND_COLOR),
249 B_DARKEN_1_TINT));
250 FillRect(stripeRect);
252 SetHighColor(fStripeColor);
253 stripeRect.right = 2;
254 FillRect(stripeRect);
256 SetHighColor(ui_color(B_PANEL_TEXT_COLOR));
257 // Rectangle for icon and overlay icon
258 BRect iconRect(0, 0, 0, 0);
260 // Draw icon
261 if (fBitmap) {
262 float ix = 18;
263 float iy = (Bounds().Height() - fIconSize) / 4.0;
264 // Icon is vertically centered in view
266 if (fNotification->Type() == B_PROGRESS_NOTIFICATION)
268 // Move icon up by half progress bar height if it's present
269 iy -= (progRect.Height() + kEdgePadding);
272 iconRect.Set(ix, iy, ix + fIconSize - 1.0, iy + fIconSize - 1.0);
273 DrawBitmapAsync(fBitmap, fBitmap->Bounds(), iconRect);
276 // Draw content
277 LineInfoList::iterator lIt;
278 for (lIt = fLines.begin(); lIt != fLines.end(); lIt++) {
279 LineInfo *l = (*lIt);
281 SetFont(&l->font);
282 // Truncate the string. We have already line-wrapped the text but if
283 // there is a very long 'word' we can only truncate it.
284 BString text(l->text);
285 TruncateString(&text, B_TRUNCATE_END,
286 Bounds().Width() - l->location.x);
287 DrawString(text.String(), text.Length(), l->location);
290 AppGroupView* groupView = dynamic_cast<AppGroupView*>(Parent());
291 if (groupView != NULL && groupView->ChildrenCount() > 1)
292 _DrawCloseButton(updateRect);
294 SetHighColor(tint_color(ViewColor(), B_DARKEN_1_TINT));
295 BPoint left(Bounds().left, Bounds().top);
296 BPoint right(Bounds().right, Bounds().top);
297 StrokeLine(left, right);
299 Sync();
303 void
304 NotificationView::_DrawCloseButton(const BRect& updateRect)
306 PushState();
307 BRect closeRect = Bounds();
309 closeRect.InsetBy(3 * kEdgePadding, 3 * kEdgePadding);
310 closeRect.left = closeRect.right - kCloseSize;
311 closeRect.bottom = closeRect.top + kCloseSize;
313 rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR);
314 float tint = B_DARKEN_2_TINT;
316 if (fCloseClicked) {
317 BRect buttonRect(closeRect.InsetByCopy(-4, -4));
318 be_control_look->DrawButtonFrame(this, buttonRect, updateRect,
319 base, base,
320 BControlLook::B_ACTIVATED | BControlLook::B_BLEND_FRAME);
321 be_control_look->DrawButtonBackground(this, buttonRect, updateRect,
322 base, BControlLook::B_ACTIVATED);
323 tint *= 1.2;
324 closeRect.OffsetBy(1, 1);
327 base = tint_color(base, tint);
328 SetHighColor(base);
329 SetPenSize(2);
330 StrokeLine(closeRect.LeftTop(), closeRect.RightBottom());
331 StrokeLine(closeRect.LeftBottom(), closeRect.RightTop());
332 PopState();
336 void
337 NotificationView::MouseDown(BPoint point)
339 // Preview Mode ignores any mouse clicks
340 if (fPreviewModeOn)
341 return;
343 int32 buttons;
344 Window()->CurrentMessage()->FindInt32("buttons", &buttons);
346 switch (buttons) {
347 case B_PRIMARY_MOUSE_BUTTON:
349 BRect closeRect = Bounds().InsetByCopy(2,2);
350 closeRect.left = closeRect.right - kCloseSize;
351 closeRect.bottom = closeRect.top + kCloseSize;
353 if (!closeRect.Contains(point)) {
354 entry_ref launchRef;
355 BString launchString;
356 BMessage argMsg(B_ARGV_RECEIVED);
357 BMessage refMsg(B_REFS_RECEIVED);
358 entry_ref appRef;
359 bool useArgv = false;
360 BList messages;
361 entry_ref ref;
363 if (fNotification->OnClickApp() != NULL
364 && be_roster->FindApp(fNotification->OnClickApp(), &appRef)
365 == B_OK) {
366 useArgv = true;
369 if (fNotification->OnClickFile() != NULL
370 && be_roster->FindApp(
371 (entry_ref*)fNotification->OnClickFile(), &appRef)
372 == B_OK) {
373 useArgv = true;
376 for (int32 i = 0; i < fNotification->CountOnClickRefs(); i++)
377 refMsg.AddRef("refs", fNotification->OnClickRefAt(i));
378 messages.AddItem((void*)&refMsg);
380 if (useArgv) {
381 int32 argc = fNotification->CountOnClickArgs() + 1;
382 BString arg;
384 BPath p(&appRef);
385 argMsg.AddString("argv", p.Path());
387 argMsg.AddInt32("argc", argc);
389 for (int32 i = 0; i < argc - 1; i++) {
390 argMsg.AddString("argv",
391 fNotification->OnClickArgAt(i));
394 messages.AddItem((void*)&argMsg);
397 if (fNotification->OnClickApp() != NULL)
398 be_roster->Launch(fNotification->OnClickApp(), &messages);
399 else
400 be_roster->Launch(fNotification->OnClickFile(), &messages);
401 } else {
402 fCloseClicked = true;
405 // Remove the info view after a click
406 BMessage remove_msg(kRemoveView);
407 remove_msg.AddPointer("view", this);
409 BMessenger msgr(Parent());
410 msgr.SendMessage(&remove_msg);
411 break;
417 BHandler*
418 NotificationView::ResolveSpecifier(BMessage* msg, int32 index, BMessage* spec,
419 int32 form, const char* prop)
421 BPropertyInfo prop_info(message_prop_list);
422 if (prop_info.FindMatch(msg, index, spec, form, prop) >= 0) {
423 msg->PopSpecifier();
424 return this;
427 return BView::ResolveSpecifier(msg, index, spec, form, prop);
431 status_t
432 NotificationView::GetSupportedSuites(BMessage* msg)
434 msg->AddString("suites", "suite/x-vnd.Haiku-notification_server");
435 BPropertyInfo prop_info(message_prop_list);
436 msg->AddFlat("messages", &prop_info);
437 return BView::GetSupportedSuites(msg);
441 void
442 NotificationView::SetText(float newMaxWidth)
444 if (newMaxWidth < 0 && Parent())
445 newMaxWidth = Parent()->Bounds().IntegerWidth();
446 if (newMaxWidth <= 0)
447 newMaxWidth = kDefaultWidth;
449 // Delete old lines
450 LineInfoList::iterator lIt;
451 for (lIt = fLines.begin(); lIt != fLines.end(); lIt++)
452 delete (*lIt);
453 fLines.clear();
455 float iconRight = kIconStripeWidth;
456 if (fBitmap != NULL)
457 iconRight += fIconSize;
458 else
459 iconRight += 32;
461 font_height fh;
462 be_bold_font->GetHeight(&fh);
463 float fontHeight = ceilf(fh.leading) + ceilf(fh.descent)
464 + ceilf(fh.ascent);
465 float y = fontHeight + kEdgePadding * 2;
467 // Title
468 LineInfo* titleLine = new LineInfo;
469 titleLine->text = fNotification->Title();
470 titleLine->font = *be_bold_font;
472 titleLine->location = BPoint(iconRight + kEdgePadding, y);
474 fLines.push_front(titleLine);
475 y += fontHeight;
477 // Rest of text is rendered with be_plain_font.
478 be_plain_font->GetHeight(&fh);
479 fontHeight = ceilf(fh.leading) + ceilf(fh.descent)
480 + ceilf(fh.ascent);
482 // Split text into chunks between certain characters and compose the lines.
483 const char kSeparatorCharacters[] = " \n-\\";
484 BString textBuffer = fNotification->Content();
485 textBuffer.ReplaceAll("\t", " ");
486 const char* chunkStart = textBuffer.String();
487 float maxWidth = newMaxWidth - kEdgePadding - iconRight;
488 LineInfo* line = NULL;
489 ssize_t length = textBuffer.Length();
490 while (chunkStart - textBuffer.String() < length) {
491 size_t chunkLength = strcspn(chunkStart, kSeparatorCharacters) + 1;
493 // Start a new line if we didn't start one before
494 BString tempText;
495 if (line != NULL)
496 tempText.SetTo(line->text);
497 tempText.Append(chunkStart, chunkLength);
499 if (line == NULL || chunkStart[0] == '\n'
500 || StringWidth(tempText) > maxWidth) {
501 line = new LineInfo;
502 line->font = *be_plain_font;
503 line->location = BPoint(iconRight + kEdgePadding, y);
505 fLines.push_front(line);
506 y += fontHeight;
508 // Skip the eventual new-line character at the beginning of this chunk
509 if (chunkStart[0] == '\n') {
510 chunkStart++;
511 chunkLength--;
514 // Skip more new-line characters and move the line further down
515 while (chunkStart[0] == '\n') {
516 chunkStart++;
517 chunkLength--;
518 line->location.y += fontHeight;
519 y += fontHeight;
522 // Strip space at beginning of a new line
523 while (chunkStart[0] == ' ') {
524 chunkLength--;
525 chunkStart++;
529 if (chunkStart[0] == '\0')
530 break;
532 // Append the chunk to the current line, which was either a new
533 // line or the one from the previous iteration
534 line->text.Append(chunkStart, chunkLength);
536 chunkStart += chunkLength;
539 fHeight = y + (kEdgePadding * 2);
541 // Make sure icon fits
542 if (fBitmap != NULL) {
543 float minHeight = fBitmap->Bounds().Height() + 2 * kEdgePadding;
545 if (fHeight < minHeight)
546 fHeight = minHeight;
549 // Make sure the progress bar is below the text, and the window is big
550 // enough.
551 static_cast<BGroupLayout*>(GetLayout())->SetInsets(kIconStripeWidth + 8,
552 fHeight, 8, 8);
554 _CalculateSize();
558 void
559 NotificationView::SetPreviewModeOn(bool enabled)
561 fPreviewModeOn = enabled;
565 const char*
566 NotificationView::MessageID() const
568 return fNotification->MessageID();
572 void
573 NotificationView::_CalculateSize()
575 float height = fHeight;
577 if (fNotification->Type() == B_PROGRESS_NOTIFICATION) {
578 font_height fh;
579 be_plain_font->GetHeight(&fh);
580 float fontHeight = fh.ascent + fh.descent + fh.leading;
581 height += 9 + (kSmallPadding * 2) + (kEdgePadding * 1)
582 + fontHeight * 2;
585 SetExplicitMinSize(BSize(0, height));
586 SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, height));