Patch by plfiorini: Integration of InfoPopper as a system service. See ticket

#1245. There are some TODOs outlined in the ticket, but they will be much
easier to review as individual patches against trunk, versus as a new version
of the huge patch.

I've messed a lot with src/servers/notification/NotificationsView.cpp in order
to resolve a crash I was getting when testing this thing (rewrote line
wrapping). I've also replaced the icons with the one that zuMi did long ago.

Thanks, plfiorini, for working on this code as much as you did!


git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@36949 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
Stephan Aßmus
2010-05-27 14:48:27 +00:00
parent 279d26af87
commit de9dcd41f8
50 changed files with 5460 additions and 4 deletions
@@ -0,0 +1,720 @@
/*
* Copyright 2010, Haiku, Inc. All Rights Reserved.
* Copyright 2008-2009, Pier Luigi Fiorini. All Rights Reserved.
* Copyright 2004-2008, Michael Davidson. All Rights Reserved.
* Copyright 2004-2007, Mikael Eiman. All Rights Reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Michael Davidson, [email protected]
* Mikael Eiman, [email protected]
* Pier Luigi Fiorini, [email protected]
*/
#include <stdlib.h>
#include <Font.h>
#include <IconUtils.h>
#include <Messenger.h>
#include <Picture.h>
#include <PropertyInfo.h>
#include <Region.h>
#include <Resources.h>
#include <Roster.h>
#include <StringView.h>
#include <TranslationUtils.h>
#include "NotificationView.h"
#include "NotificationWindow.h"
const char* kSmallIconAttribute = "BEOS:M:STD_ICON";
const char* kLargeIconAttribute = "BEOS:L:STD_ICON";
const char* kIconAttribute = "BEOS:ICON";
property_info message_prop_list[] = {
{ "type", {B_GET_PROPERTY, B_SET_PROPERTY, 0},
{B_DIRECT_SPECIFIER, 0}, "get the notification type"},
{ "app", {B_GET_PROPERTY, B_SET_PROPERTY, 0},
{B_DIRECT_SPECIFIER, 0}, "get notification's app"},
{ "title", {B_GET_PROPERTY, B_SET_PROPERTY, 0},
{B_DIRECT_SPECIFIER, 0}, "get notification's title"},
{ "content", {B_GET_PROPERTY, B_SET_PROPERTY, 0},
{B_DIRECT_SPECIFIER, 0}, "get notification's contents"},
{ "icon", {B_GET_PROPERTY, 0},
{B_DIRECT_SPECIFIER, 0}, "get icon as an archived bitmap"},
{ "progress", {B_GET_PROPERTY, B_SET_PROPERTY, 0},
{B_DIRECT_SPECIFIER, 0}, "get the progress (between 0.0 and 1.0)"},
{ NULL }
};
NotificationView::NotificationView(NotificationWindow* win,
notification_type type, const char* app, const char* title, const char* text,
BMessage* details)
:
BView(BRect(0, 0, win->ViewWidth(), 1), "NotificationView",
B_FOLLOW_LEFT_RIGHT, B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE
| B_FRAME_EVENTS),
fParent(win),
fType(type),
fRunner(NULL),
fProgress(0.0f),
fMessageID(""),
fDetails(details),
fBitmap(NULL),
fIsFirst(false),
fIsLast(false)
{
BMessage iconMsg;
if (fDetails->FindMessage("icon", &iconMsg) == B_OK)
fBitmap = new BBitmap(&iconMsg);
if (!fBitmap)
_LoadIcon();
const char* messageID = NULL;
if (fDetails->FindString("messageID", &messageID) == B_OK)
fMessageID = messageID;
if (fDetails->FindFloat("progress", &fProgress) != B_OK)
fProgress = 0.0f;
// Progress is between 0 and 1
if (fProgress < 0.0f)
fProgress = 0.0f;
if (fProgress > 1.0f)
fProgress = 1.0f;
SetText(app, title, text);
ResizeToPreferred();
switch (type) {
case B_IMPORTANT_NOTIFICATION:
SetViewColor(255, 255, 255);
SetLowColor(255, 255, 255);
break;
case B_ERROR_NOTIFICATION:
SetViewColor(ui_color(B_FAILURE_COLOR));
SetLowColor(ui_color(B_FAILURE_COLOR));
break;
default:
SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));
SetLowColor(ui_color(B_PANEL_BACKGROUND_COLOR));
break;
}
}
NotificationView::~NotificationView()
{
delete fRunner;
delete fDetails;
delete fBitmap;
LineInfoList::iterator lIt;
for (lIt = fLines.begin(); lIt != fLines.end(); lIt++)
delete (*lIt);
}
void
NotificationView::AttachedToWindow()
{
BMessage msg(kRemoveView);
msg.AddPointer("view", this);
int32 timeout = -1;
if (fDetails->FindInt32("timeout", &timeout) != B_OK)
timeout = fParent->Timeout();
bigtime_t delay = timeout * 1000 * 1000;
if (delay > 0)
fRunner = new BMessageRunner(BMessenger(Parent()), &msg, delay, 1);
}
void
NotificationView::MessageReceived(BMessage* msg)
{
switch (msg->what) {
case B_GET_PROPERTY:
{
BMessage specifier;
const char* property;
BMessage reply(B_REPLY);
bool msgOkay = true;
if (msg->FindMessage("specifiers", 0, &specifier) != B_OK)
msgOkay = false;
if (specifier.FindString("property", &property) != B_OK)
msgOkay = false;
if (msgOkay) {
if (strcmp(property, "type") == 0)
reply.AddInt32("result", fType);
if (strcmp(property, "app") == 0)
reply.AddString("result", fApp);
if (strcmp(property, "title") == 0)
reply.AddString("result", fTitle);
if (strcmp(property, "content") == 0)
reply.AddString("result", fText);
if (strcmp(property, "progress") == 0)
reply.AddFloat("result", fProgress);
if ((strcmp(property, "icon") == 0) && fBitmap) {
BMessage archive;
if (fBitmap->Archive(&archive) == B_OK)
reply.AddMessage("result", &archive);
}
reply.AddInt32("error", B_OK);
} else {
reply.what = B_MESSAGE_NOT_UNDERSTOOD;
reply.AddInt32("error", B_ERROR);
}
msg->SendReply(&reply);
break;
}
case B_SET_PROPERTY:
{
BMessage specifier;
const char* property;
BMessage reply(B_REPLY);
bool msgOkay = true;
if (msg->FindMessage("specifiers", 0, &specifier) != B_OK)
msgOkay = false;
if (specifier.FindString("property", &property) != B_OK)
msgOkay = false;
if (msgOkay) {
if (strcmp(property, "app") == 0)
msg->FindString("data", &fApp);
if (strcmp(property, "title") == 0)
msg->FindString("data", &fTitle);
if (strcmp(property, "content") == 0)
msg->FindString("data", &fText);
if (strcmp(property, "icon") == 0) {
BMessage archive;
if (msg->FindMessage("data", &archive) == B_OK) {
delete fBitmap;
fBitmap = new BBitmap(&archive);
}
}
SetText(Application(), Title(), Text());
Invalidate();
reply.AddInt32("error", B_OK);
} else {
reply.what = B_MESSAGE_NOT_UNDERSTOOD;
reply.AddInt32("error", B_ERROR);
}
msg->SendReply(&reply);
break;
}
case kRemoveView:
{
BMessage remove(kRemoveView);
remove.AddPointer("view", this);
BMessenger msgr(Window());
msgr.SendMessage( &remove );
break;
}
default:
BView::MessageReceived(msg);
}
}
void
NotificationView::GetPreferredSize(float* w, float* h)
{
// Parent width, minus the edge padding, minus the pensize
*w = fParent->ViewWidth() - (kEdgePadding * 2) - (kPenSize * 2);
*h = fHeight;
if (fType == B_PROGRESS_NOTIFICATION) {
font_height fh;
be_plain_font->GetHeight(&fh);
float fontHeight = fh.ascent + fh.descent + fh.leading;
*h += (kSmallPadding * 2) + (kEdgePadding * 1) + fontHeight;
}
}
void
NotificationView::Draw(BRect updateRect)
{
BRect progRect;
// Draw progress background
if (fType == B_PROGRESS_NOTIFICATION) {
PushState();
font_height fh;
be_plain_font->GetHeight(&fh);
float fontHeight = fh.ascent + fh.descent + fh.leading;
progRect = Bounds();
progRect.InsetBy(kEdgePadding, kEdgePadding);
progRect.top = progRect.bottom - (kSmallPadding * 2) - fontHeight;
StrokeRect(progRect);
BRect barRect = progRect;
barRect.InsetBy(1.0, 1.0);
barRect.right *= fProgress;
SetHighColor(ui_color(B_CONTROL_HIGHLIGHT_COLOR));
FillRect(barRect);
SetHighColor(ui_color(B_PANEL_TEXT_COLOR));
BString label = "";
label << (int)(fProgress * 100) << " %";
float labelWidth = be_plain_font->StringWidth(label.String());
float labelX = progRect.left + (progRect.IntegerWidth() / 2) - (labelWidth / 2);
SetLowColor(B_TRANSPARENT_COLOR);
SetDrawingMode(B_OP_ALPHA);
DrawString(label.String(), label.Length(),
BPoint(labelX, progRect.top + fh.ascent + fh.leading + kSmallPadding));
PopState();
}
SetDrawingMode(B_OP_ALPHA);
SetBlendingMode(B_PIXEL_ALPHA, B_ALPHA_OVERLAY);
// Icon size
float iconSize = (float)fParent->IconSize();
// Rectangle for icon and overlay icon
BRect iconRect(0, 0, 0, 0);
// Draw icon
if (fBitmap) {
LineInfo* appLine = fLines.back();
font_height fh;
appLine->font.GetHeight(&fh);
float title_bottom = appLine->location.y + fh.descent;
float ix = kEdgePadding;
float iy = 0;
if (fParent->Layout() == TitleAboveIcon)
iy = title_bottom + kEdgePadding + (Bounds().Height() - title_bottom
- kEdgePadding * 2 - iconSize) / 2;
else
iy = (Bounds().Height() - iconSize) / 2.0;
if (fType == B_PROGRESS_NOTIFICATION)
// Move icon up by half progress bar height if it's present
iy -= (progRect.Height() + kEdgePadding) / 2.0;
iconRect.left = ix;
iconRect.top = iy;
iconRect.right = ix + iconSize;
iconRect.bottom = iy + iconSize;
DrawBitmapAsync(fBitmap, fBitmap->Bounds(),
iconRect, B_FILTER_BITMAP_BILINEAR);
}
// Draw content
LineInfoList::iterator lIt;
for (lIt = fLines.begin(); lIt != fLines.end(); lIt++) {
LineInfo *l = (*lIt);
SetFont(&l->font);
DrawString(l->text.String(), l->text.Length(), l->location);
}
rgb_color detailCol = ui_color(B_CONTROL_BORDER_COLOR);
detailCol = tint_color(detailCol, B_LIGHTEN_2_TINT);
// Draw the close widget
BRect closeRect = Bounds();
closeRect.InsetBy(kEdgePadding, kEdgePadding);
closeRect.left = closeRect.right - kCloseSize;
closeRect.bottom = closeRect.top + kCloseSize;
PushState();
SetHighColor(detailCol);
StrokeRoundRect(closeRect, kSmallPadding, kSmallPadding);
BRect closeCross = closeRect.InsetByCopy(kSmallPadding, kSmallPadding);
StrokeLine(closeCross.LeftTop(), closeCross.RightBottom());
StrokeLine(closeCross.LeftBottom(), closeCross.RightTop());
PopState();
Sync();
}
void
NotificationView::MouseDown(BPoint point)
{
int32 buttons;
Window()->CurrentMessage()->FindInt32("buttons", &buttons);
switch (buttons) {
case B_PRIMARY_MOUSE_BUTTON:
{
BRect closeRect = Bounds().InsetByCopy(2,2);
closeRect.left = closeRect.right - kCloseSize;
closeRect.bottom = closeRect.top + kCloseSize;
if (!closeRect.Contains(point)) {
entry_ref launchRef;
BString launchString;
BMessage argMsg(B_ARGV_RECEIVED);
BMessage refMsg(B_REFS_RECEIVED);
entry_ref appRef;
bool useArgv = false;
BList messages;
entry_ref ref;
if (fDetails->FindString("onClickApp", &launchString) == B_OK)
if (be_roster->FindApp(launchString.String(), &appRef) == B_OK)
useArgv = true;
if (fDetails->FindRef("onClickFile", &launchRef) == B_OK) {
if (be_roster->FindApp(&launchRef, &appRef) == B_OK)
useArgv = true;
}
if (fDetails->FindRef("onClickRef", &ref) == B_OK) {
for (int32 i = 0; fDetails->FindRef("onClickRef", i, &ref) == B_OK; i++)
refMsg.AddRef("refs", &ref);
messages.AddItem((void*)&refMsg);
}
if (useArgv) {
type_code type;
int32 argc = 0;
BString arg;
BPath p(&appRef);
argMsg.AddString("argv", p.Path());
fDetails->GetInfo("onClickArgv", &type, &argc);
argMsg.AddInt32("argc", argc + 1);
for (int32 i = 0; fDetails->FindString("onClickArgv", i, &arg) == B_OK; i++)
argMsg.AddString("argv", arg);
messages.AddItem((void*)&argMsg);
}
BMessage tmp;
for (int32 i = 0; fDetails->FindMessage("onClickMsg", i, &tmp) == B_OK; i++)
messages.AddItem((void*)&tmp);
if (fDetails->FindString("onClickApp", &launchString) == B_OK)
be_roster->Launch(launchString.String(), &messages);
else
be_roster->Launch(&launchRef, &messages);
}
// Remove the info view after a click
BMessage remove_msg(kRemoveView);
remove_msg.AddPointer("view", this);
BMessenger msgr(Parent());
msgr.SendMessage(&remove_msg);
break;
}
}
}
void
NotificationView::FrameResized( float w, float /*h*/)
{
SetText(Application(), Title(), Text());
}
BHandler*
NotificationView::ResolveSpecifier(BMessage* msg, int32 index, BMessage* spec, int32 form, const char* prop)
{
BPropertyInfo prop_info(message_prop_list);
if (prop_info.FindMatch(msg, index, spec, form, prop) >= 0) {
msg->PopSpecifier();
return this;
}
return BView::ResolveSpecifier(msg, index, spec, form, prop);
}
status_t
NotificationView::GetSupportedSuites(BMessage* msg)
{
msg->AddString("suites", "suite/x-vnd.Haiku-notification_server");
BPropertyInfo prop_info(message_prop_list);
msg->AddFlat("messages", &prop_info);
return BView::GetSupportedSuites(msg);
}
const char*
NotificationView::Application() const
{
return fApp.Length() > 0 ? fApp.String() : NULL;
}
const char*
NotificationView::Title() const
{
return fTitle.Length() > 0 ? fTitle.String() : NULL;
}
const char*
NotificationView::Text() const
{
return fText.Length() > 0 ? fText.String() : NULL;
}
void
NotificationView::SetText(const char* app, const char* title, const char* text,
float newMaxWidth)
{
if (newMaxWidth < 0)
newMaxWidth = Bounds().Width() - (kEdgePadding * 2);
// Delete old lines
LineInfoList::iterator lIt;
for (lIt = fLines.begin(); lIt != fLines.end(); lIt++)
delete (*lIt);
fLines.clear();
fApp = app;
fTitle = title;
fText = text;
float iconRight = kEdgePadding + kEdgePadding;
if (fBitmap != NULL)
iconRight += fParent->IconSize();
font_height fh;
be_bold_font->GetHeight(&fh);
float fontHeight = ceilf(fh.leading) + ceilf(fh.descent)
+ ceilf(fh.ascent);
float y = fontHeight;
// Title
LineInfo* titleLine = new LineInfo;
titleLine->text = fTitle;
titleLine->font = *be_bold_font;
if (fParent->Layout() == AllTextRightOfIcon)
titleLine->location = BPoint(iconRight, y);
else
titleLine->location = BPoint(kEdgePadding, y);
fLines.push_front(titleLine);
y += fontHeight;
// Rest of text is rendered with be_plain_font.
be_plain_font->GetHeight(&fh);
fontHeight = ceilf(fh.leading) + ceilf(fh.descent)
+ ceilf(fh.ascent);
// Split text into chunks between certain characters and compose the lines.
const char kSeparatorCharacters[] = " \n-\\/";
BString textBuffer = fText;
textBuffer.ReplaceAll("\t", " ");
const char* chunkStart = textBuffer.String();
float maxWidth = newMaxWidth - kEdgePadding - iconRight;
LineInfo* line = NULL;
ssize_t length = textBuffer.Length();
while (chunkStart - textBuffer.String() < length) {
size_t chunkLength = strcspn(chunkStart, kSeparatorCharacters) + 1;
// Start a new line if either we didn't start one before,
// the current offset
BString tempText(line != NULL ? line->text : "");
tempText.Append(chunkStart, chunkLength);
if (line == NULL || chunkStart[0] == '\n'
|| StringWidth(tempText) > maxWidth) {
line = new LineInfo;
line->font = *be_plain_font;
line->location = BPoint(iconRight + kEdgePadding, y);
fLines.push_front(line);
y += fontHeight;
// Skip the eventual new-line character at the beginning of this
// chunk.
if (chunkStart[0] == '\n') {
chunkStart++;
chunkLength--;
}
// Skip more new-line characters and move the line further down.
while (chunkStart[0] == '\n') {
chunkStart++;
chunkLength--;
line->location.y += fontHeight;
y += fontHeight;
}
// Strip space at beginning of a new line.
while (chunkStart[0] == ' ') {
chunkLength--;
chunkStart++;
}
}
if (chunkStart[0] == '\0')
break;
// Append the chunk to the current line, which was either a new
// line or the one from the previous iteration.
line->text.Append(chunkStart, chunkLength);
chunkStart += chunkLength;
}
fHeight = y + (kEdgePadding * 2);
// Make sure icon fits
if (fBitmap != NULL) {
float minHeight = 0;
if (fParent->Layout() == TitleAboveIcon) {
LineInfo* appLine = fLines.back();
font_height fh;
appLine->font.GetHeight(&fh);
minHeight = appLine->location.y + fh.descent;
}
minHeight += fBitmap->Bounds().Height() + 2 * kEdgePadding;
if (fHeight < minHeight)
fHeight = minHeight;
}
BMessenger(Parent()).SendMessage(kResizeToFit);
}
bool
NotificationView::HasMessageID(const char* id)
{
return fMessageID == id;
}
const char*
NotificationView::MessageID()
{
return fMessageID.String();
}
void
NotificationView::SetPosition(bool first, bool last)
{
fIsFirst = first;
fIsLast = last;
}
BBitmap*
NotificationView::_ReadNodeIcon(const char* fileName, icon_size size)
{
BEntry entry(fileName, true);
entry_ref ref;
entry.GetRef(&ref);
BNode node(BPath(&ref).Path());
BBitmap* ret = new BBitmap(BRect(0, 0, (float)size - 1, (float)size - 1), B_RGBA32);
if (BIconUtils::GetIcon(&node, kIconAttribute, kSmallIconAttribute,
kLargeIconAttribute, size, ret) != B_OK) {
delete ret;
ret = NULL;
}
return ret;
}
void
NotificationView::_LoadIcon()
{
// First try to get the icon from the caller application
app_info info;
BMessenger msgr = fDetails->ReturnAddress();
if (msgr.IsValid())
be_roster->GetRunningAppInfo(msgr.Team(), &info);
else if (fType == B_PROGRESS_NOTIFICATION)
be_roster->GetAppInfo("application/x-vnd.Haiku-notification_server",
&info);
BPath path;
path.SetTo(&info.ref);
fBitmap = _ReadNodeIcon(path.Path(), fParent->IconSize());
if (fBitmap)
return;
// If that failed get icons from app_server
if (find_directory(B_BEOS_SERVERS_DIRECTORY, &path) != B_OK)
return;
path.Append("app_server");
BFile file(path.Path(), B_READ_ONLY);
if (file.InitCheck() != B_OK)
return;
BResources res(&file);
if (res.InitCheck() != B_OK)
return;
// Which one should we choose?
const char* iconName = "";
switch (fType) {
case B_INFORMATION_NOTIFICATION:
iconName = "info";
break;
case B_ERROR_NOTIFICATION:
iconName = "stop";
break;
case B_IMPORTANT_NOTIFICATION:
iconName = "warn";
break;
default:
return;
}
// Allocate the bitmap
fBitmap = new BBitmap(BRect(0, 0, (float)B_LARGE_ICON - 1,
(float)B_LARGE_ICON - 1), B_RGBA32);
if (!fBitmap || fBitmap->InitCheck() != B_OK) {
fBitmap = NULL;
return;
}
// Load raw icon data
size_t size = 0;
const uint8* data = (const uint8*)res.LoadResource(B_VECTOR_ICON_TYPE,
iconName, &size);
if ((data == NULL
|| BIconUtils::GetVectorIcon(data, size, fBitmap) != B_OK))
fBitmap = NULL;
}