477 lines
11 KiB
C++
477 lines
11 KiB
C++
/*
|
|
* Copyright 2010-2017, 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, slaad@bong.com.au
|
|
* Mikael Eiman, mikael@eiman.tv
|
|
* Pier Luigi Fiorini, pierluigi.fiorini@gmail.com
|
|
* Brian Hill, supernova@tycho.email
|
|
*/
|
|
#include "NotificationWindow.h"
|
|
|
|
#include <algorithm>
|
|
|
|
#include <Alert.h>
|
|
#include <Application.h>
|
|
#include <Catalog.h>
|
|
#include <Deskbar.h>
|
|
#include <Directory.h>
|
|
#include <File.h>
|
|
#include <FindDirectory.h>
|
|
#include <GroupLayout.h>
|
|
#include <NodeMonitor.h>
|
|
#include <Notifications.h>
|
|
#include <Path.h>
|
|
#include <Point.h>
|
|
#include <PropertyInfo.h>
|
|
#include <Screen.h>
|
|
|
|
#include "AppGroupView.h"
|
|
#include "AppUsage.h"
|
|
|
|
|
|
#undef B_TRANSLATION_CONTEXT
|
|
#define B_TRANSLATION_CONTEXT "NotificationWindow"
|
|
|
|
|
|
property_info main_prop_list[] = {
|
|
{"message", {B_GET_PROPERTY, 0}, {B_INDEX_SPECIFIER, 0},
|
|
"get a message"},
|
|
{"message", {B_COUNT_PROPERTIES, 0}, {B_DIRECT_SPECIFIER, 0},
|
|
"count messages"},
|
|
{"message", {B_CREATE_PROPERTY, 0}, {B_DIRECT_SPECIFIER, 0},
|
|
"create a message"},
|
|
{"message", {B_SET_PROPERTY, 0}, {B_INDEX_SPECIFIER, 0},
|
|
"modify a message"},
|
|
|
|
{ 0 }
|
|
};
|
|
|
|
|
|
/**
|
|
* Checks if notification position overlaps with
|
|
* deskbar position
|
|
*/
|
|
static bool
|
|
is_overlapping(deskbar_location deskbar,
|
|
uint32 notification) {
|
|
if (deskbar == B_DESKBAR_RIGHT_TOP
|
|
&& notification == (B_FOLLOW_RIGHT | B_FOLLOW_TOP))
|
|
return true;
|
|
if (deskbar == B_DESKBAR_RIGHT_BOTTOM
|
|
&& notification == (B_FOLLOW_RIGHT | B_FOLLOW_BOTTOM))
|
|
return true;
|
|
if (deskbar == B_DESKBAR_LEFT_TOP
|
|
&& notification == (B_FOLLOW_LEFT | B_FOLLOW_TOP))
|
|
return true;
|
|
if (deskbar == B_DESKBAR_LEFT_BOTTOM
|
|
&& notification == (B_FOLLOW_LEFT | B_FOLLOW_BOTTOM))
|
|
return true;
|
|
if (deskbar == B_DESKBAR_TOP
|
|
&& (notification == (B_FOLLOW_LEFT | B_FOLLOW_TOP)
|
|
|| notification == (B_FOLLOW_RIGHT | B_FOLLOW_TOP)))
|
|
return true;
|
|
if (deskbar == B_DESKBAR_BOTTOM
|
|
&& (notification == (B_FOLLOW_LEFT | B_FOLLOW_BOTTOM)
|
|
|| notification == (B_FOLLOW_RIGHT | B_FOLLOW_BOTTOM)))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
|
|
NotificationWindow::NotificationWindow()
|
|
:
|
|
BWindow(BRect(0, 0, -1, -1), B_TRANSLATE_MARK("Notification"),
|
|
B_BORDERED_WINDOW_LOOK, B_FLOATING_ALL_WINDOW_FEEL, B_AVOID_FRONT
|
|
| B_AVOID_FOCUS | B_NOT_CLOSABLE | B_NOT_ZOOMABLE | B_NOT_MINIMIZABLE
|
|
| B_NOT_RESIZABLE | B_NOT_MOVABLE | B_AUTO_UPDATE_SIZE_LIMITS,
|
|
B_ALL_WORKSPACES),
|
|
fShouldRun(true)
|
|
{
|
|
status_t result = find_directory(B_USER_CACHE_DIRECTORY, &fCachePath);
|
|
fCachePath.Append("Notifications");
|
|
BDirectory cacheDir;
|
|
result = cacheDir.SetTo(fCachePath.Path());
|
|
if (result == B_ENTRY_NOT_FOUND)
|
|
cacheDir.CreateDirectory(fCachePath.Path(), NULL);
|
|
|
|
SetLayout(new BGroupLayout(B_VERTICAL, 0));
|
|
|
|
_LoadSettings(true);
|
|
|
|
// Start the message loop
|
|
Hide();
|
|
Show();
|
|
}
|
|
|
|
|
|
NotificationWindow::~NotificationWindow()
|
|
{
|
|
appfilter_t::iterator aIt;
|
|
for (aIt = fAppFilters.begin(); aIt != fAppFilters.end(); aIt++)
|
|
delete aIt->second;
|
|
}
|
|
|
|
|
|
bool
|
|
NotificationWindow::QuitRequested()
|
|
{
|
|
appview_t::iterator aIt;
|
|
for (aIt = fAppViews.begin(); aIt != fAppViews.end(); aIt++) {
|
|
aIt->second->RemoveSelf();
|
|
delete aIt->second;
|
|
}
|
|
|
|
BMessenger(be_app).SendMessage(B_QUIT_REQUESTED);
|
|
return BWindow::QuitRequested();
|
|
}
|
|
|
|
|
|
void
|
|
NotificationWindow::WorkspaceActivated(int32 /*workspace*/, bool active)
|
|
{
|
|
// Ensure window is in the correct position
|
|
if (active)
|
|
SetPosition();
|
|
}
|
|
|
|
|
|
void
|
|
NotificationWindow::FrameResized(float width, float height)
|
|
{
|
|
SetPosition();
|
|
}
|
|
|
|
|
|
void
|
|
NotificationWindow::ScreenChanged(BRect frame, color_space mode)
|
|
{
|
|
SetPosition();
|
|
}
|
|
|
|
|
|
void
|
|
NotificationWindow::MessageReceived(BMessage* message)
|
|
{
|
|
switch (message->what) {
|
|
case B_NODE_MONITOR:
|
|
{
|
|
_LoadSettings();
|
|
break;
|
|
}
|
|
case kNotificationMessage:
|
|
{
|
|
if (!fShouldRun)
|
|
break;
|
|
|
|
BMessage reply(B_REPLY);
|
|
BNotification* notification = new BNotification(message);
|
|
|
|
if (notification->InitCheck() == B_OK) {
|
|
bigtime_t timeout;
|
|
if (message->FindInt64("timeout", &timeout) != B_OK)
|
|
timeout = fTimeout;
|
|
BString sourceSignature(notification->SourceSignature());
|
|
BString sourceName(notification->SourceName());
|
|
|
|
bool allow = false;
|
|
appfilter_t::iterator it = fAppFilters
|
|
.find(sourceSignature.String());
|
|
|
|
AppUsage* appUsage = NULL;
|
|
if (it == fAppFilters.end()) {
|
|
if (sourceSignature.Length() > 0
|
|
&& sourceName.Length() > 0) {
|
|
appUsage = new AppUsage(sourceName.String(),
|
|
sourceSignature.String(), true);
|
|
fAppFilters[sourceSignature.String()] = appUsage;
|
|
// TODO save back to settings file
|
|
}
|
|
allow = true;
|
|
} else {
|
|
appUsage = it->second;
|
|
allow = appUsage->Allowed();
|
|
}
|
|
|
|
if (allow) {
|
|
BString groupName(notification->Group());
|
|
appview_t::iterator aIt = fAppViews.find(groupName);
|
|
AppGroupView* group = NULL;
|
|
if (aIt == fAppViews.end()) {
|
|
group = new AppGroupView(this,
|
|
groupName == "" ? NULL : groupName.String());
|
|
fAppViews[groupName] = group;
|
|
GetLayout()->AddView(group);
|
|
} else
|
|
group = aIt->second;
|
|
|
|
NotificationView* view = new NotificationView(notification,
|
|
timeout, fIconSize);
|
|
|
|
group->AddInfo(view);
|
|
|
|
_ShowHide();
|
|
|
|
reply.AddInt32("error", B_OK);
|
|
} else
|
|
reply.AddInt32("error", B_NOT_ALLOWED);
|
|
} else {
|
|
reply.what = B_MESSAGE_NOT_UNDERSTOOD;
|
|
reply.AddInt32("error", B_ERROR);
|
|
}
|
|
|
|
message->SendReply(&reply);
|
|
break;
|
|
}
|
|
case kRemoveGroupView:
|
|
{
|
|
AppGroupView* view = NULL;
|
|
if (message->FindPointer("view", (void**)&view) != B_OK)
|
|
return;
|
|
|
|
// It's possible that between sending this message, and us receiving
|
|
// it, the view has become used again, in which case we shouldn't
|
|
// delete it.
|
|
if (view->HasChildren())
|
|
return;
|
|
|
|
// this shouldn't happen
|
|
if (fAppViews.erase(view->Group()) < 1)
|
|
break;
|
|
|
|
view->RemoveSelf();
|
|
delete view;
|
|
|
|
_ShowHide();
|
|
break;
|
|
}
|
|
default:
|
|
BWindow::MessageReceived(message);
|
|
}
|
|
}
|
|
|
|
|
|
icon_size
|
|
NotificationWindow::IconSize()
|
|
{
|
|
return fIconSize;
|
|
}
|
|
|
|
|
|
int32
|
|
NotificationWindow::Timeout()
|
|
{
|
|
return fTimeout;
|
|
}
|
|
|
|
|
|
float
|
|
NotificationWindow::Width()
|
|
{
|
|
return fWidth;
|
|
}
|
|
|
|
|
|
void
|
|
NotificationWindow::_ShowHide()
|
|
{
|
|
if (fAppViews.empty() && !IsHidden()) {
|
|
Hide();
|
|
return;
|
|
}
|
|
|
|
if (IsHidden()) {
|
|
SetPosition();
|
|
Show();
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
NotificationWindow::SetPosition()
|
|
{
|
|
Layout(true);
|
|
|
|
BRect bounds = DecoratorFrame();
|
|
float width = Bounds().Width() + 1;
|
|
float height = Bounds().Height() + 1;
|
|
|
|
float leftOffset = Frame().left - bounds.left;
|
|
float topOffset = Frame().top - bounds.top + 1;
|
|
float rightOffset = bounds.right - Frame().right;
|
|
float bottomOffset = bounds.bottom - Frame().bottom;
|
|
// Size of the borders around the window
|
|
|
|
float x = Frame().left;
|
|
float y = Frame().top;
|
|
// If we cant guess, don't move...
|
|
BPoint location(x, y);
|
|
|
|
BDeskbar deskbar;
|
|
|
|
// If notification and deskbar position are same
|
|
// then follow deskbar position
|
|
uint32 position = (is_overlapping(deskbar.Location(), fPosition))
|
|
? B_FOLLOW_DESKBAR
|
|
: fPosition;
|
|
|
|
|
|
if (position == B_FOLLOW_DESKBAR) {
|
|
BRect frame = deskbar.Frame();
|
|
switch (deskbar.Location()) {
|
|
case B_DESKBAR_TOP:
|
|
// In case of overlapping here or for bottom
|
|
// use user's notification position
|
|
y = frame.bottom + topOffset;
|
|
x = (fPosition == (B_FOLLOW_LEFT | B_FOLLOW_TOP))
|
|
? frame.left + rightOffset
|
|
: frame.right - width + rightOffset;
|
|
break;
|
|
case B_DESKBAR_BOTTOM:
|
|
y = frame.top - height - bottomOffset;
|
|
x = (fPosition == (B_FOLLOW_LEFT | B_FOLLOW_BOTTOM))
|
|
? frame.left + rightOffset
|
|
: frame.right - width + rightOffset;
|
|
break;
|
|
case B_DESKBAR_RIGHT_TOP:
|
|
y = frame.top - topOffset + 1;
|
|
x = frame.left - width - rightOffset;
|
|
break;
|
|
case B_DESKBAR_LEFT_TOP:
|
|
y = frame.top - topOffset + 1;
|
|
x = frame.right + leftOffset;
|
|
break;
|
|
case B_DESKBAR_RIGHT_BOTTOM:
|
|
y = frame.bottom - height + bottomOffset;
|
|
x = frame.left - width - rightOffset;
|
|
break;
|
|
case B_DESKBAR_LEFT_BOTTOM:
|
|
y = frame.bottom - height + bottomOffset;
|
|
x = frame.right + leftOffset;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
location = BPoint(x, y);
|
|
} else if (position == (B_FOLLOW_RIGHT | B_FOLLOW_BOTTOM)) {
|
|
location = BScreen().Frame().RightBottom();
|
|
location -= BPoint(width, height);
|
|
} else if (position == (B_FOLLOW_LEFT | B_FOLLOW_BOTTOM)) {
|
|
location = BScreen().Frame().LeftBottom();
|
|
location -= BPoint(0, height);
|
|
} else if (position == (B_FOLLOW_RIGHT | B_FOLLOW_TOP)) {
|
|
location = BScreen().Frame().RightTop();
|
|
location -= BPoint(width, 0);
|
|
} else if (position == (B_FOLLOW_LEFT | B_FOLLOW_TOP)) {
|
|
location = BScreen().Frame().LeftTop();
|
|
}
|
|
|
|
MoveTo(location);
|
|
}
|
|
|
|
|
|
void
|
|
NotificationWindow::_LoadSettings(bool startMonitor)
|
|
{
|
|
BPath path;
|
|
BMessage settings;
|
|
|
|
if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK)
|
|
return;
|
|
|
|
path.Append(kSettingsFile);
|
|
|
|
BFile file(path.Path(), B_READ_ONLY | B_CREATE_FILE);
|
|
settings.Unflatten(&file);
|
|
|
|
_LoadGeneralSettings(settings);
|
|
_LoadDisplaySettings(settings);
|
|
_LoadAppFilters(settings);
|
|
|
|
if (startMonitor) {
|
|
node_ref nref;
|
|
BEntry entry(path.Path());
|
|
entry.GetNodeRef(&nref);
|
|
|
|
if (watch_node(&nref, B_WATCH_ALL, BMessenger(this)) != B_OK) {
|
|
BAlert* alert = new BAlert(B_TRANSLATE("Warning"),
|
|
B_TRANSLATE("Couldn't start general settings monitor.\n"
|
|
"Live filter changes disabled."), B_TRANSLATE("OK"));
|
|
alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
|
|
alert->Go(NULL);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
NotificationWindow::_LoadAppFilters(BMessage& settings)
|
|
{
|
|
type_code type;
|
|
int32 count = 0;
|
|
|
|
if (settings.GetInfo("app_usage", &type, &count) != B_OK)
|
|
return;
|
|
|
|
for (int32 i = 0; i < count; i++) {
|
|
AppUsage* app = new AppUsage();
|
|
if (settings.FindFlat("app_usage", i, app) == B_OK)
|
|
fAppFilters[app->Signature()] = app;
|
|
else
|
|
delete app;
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
NotificationWindow::_LoadGeneralSettings(BMessage& settings)
|
|
{
|
|
if (settings.FindBool(kAutoStartName, &fShouldRun) == B_OK) {
|
|
if (fShouldRun == false) {
|
|
// We should not start. Quit the app!
|
|
be_app_messenger.SendMessage(B_QUIT_REQUESTED);
|
|
}
|
|
} else
|
|
fShouldRun = true;
|
|
|
|
if (settings.FindInt32(kTimeoutName, &fTimeout) != B_OK)
|
|
fTimeout = kDefaultTimeout;
|
|
fTimeout *= 1000000;
|
|
// Convert from seconds to microseconds
|
|
}
|
|
|
|
|
|
void
|
|
NotificationWindow::_LoadDisplaySettings(BMessage& settings)
|
|
{
|
|
int32 setting;
|
|
float originalWidth = fWidth;
|
|
|
|
if (settings.FindFloat(kWidthName, &fWidth) != B_OK)
|
|
fWidth = kDefaultWidth;
|
|
if (originalWidth != fWidth)
|
|
GetLayout()->SetExplicitSize(BSize(fWidth, B_SIZE_UNSET));
|
|
|
|
if (settings.FindInt32(kIconSizeName, &setting) != B_OK)
|
|
fIconSize = kDefaultIconSize;
|
|
else
|
|
fIconSize = (icon_size)setting;
|
|
|
|
int32 position;
|
|
if (settings.FindInt32(kNotificationPositionName, &position) != B_OK)
|
|
fPosition = kDefaultNotificationPosition;
|
|
else
|
|
fPosition = position;
|
|
|
|
// Notify the views about the change
|
|
appview_t::iterator aIt;
|
|
for (aIt = fAppViews.begin(); aIt != fAppViews.end(); ++aIt) {
|
|
AppGroupView* view = aIt->second;
|
|
view->Invalidate();
|
|
}
|
|
}
|