Restructure wxNotificationMessage.

wxNotificationMessage has been refactored to always use wxNotificationMessageImpl (this was previously already done in the MSW implementation)

This adds various features and fixes to wxNotificationMessage:
- OS X Notification Center implementation
- Generic "toast" notifications
- SetIcon() to specify a custom icon
- AddAction() to add actions to notifications
- Events to get notify of notification clicks, dismiss or actions
This commit is contained in:
Tobias Taschner
2015-09-09 22:59:21 +02:00
parent 39716462e6
commit bf5e403a68
31 changed files with 1999 additions and 817 deletions

View File

@@ -25,13 +25,16 @@
#if wxUSE_NOTIFICATION_MESSAGE
#ifndef WX_PRECOMP
#include "wx/dialog.h"
#include "wx/timer.h"
#include "wx/frame.h"
#include "wx/timer.h"
#include "wx/sizer.h"
#include "wx/statbmp.h"
#include "wx/settings.h"
#include "wx/panel.h"
#endif //WX_PRECOMP
#include "wx/artprov.h"
#include "wx/bmpbuttn.h"
// even if the platform has the native implementation, we still normally want
// to use the generic one (unless it's totally unsuitable for the target UI)
@@ -41,93 +44,220 @@
// uses the generic version, the second inclusion will do no harm)
#include "wx/notifmsg.h"
#include "wx/generic/notifmsg.h"
#include "wx/generic/private/notifmsg.h"
#include "wx/display.h"
#include "wx/textwrapper.h"
// ----------------------------------------------------------------------------
// wxNotificationMessageDialog
// wxNotificationMessageWindow
// ----------------------------------------------------------------------------
class wxNotificationMessageDialog : public wxDialog
class wxNotificationMessageWindow : public wxFrame
{
public:
wxNotificationMessageDialog(wxWindow *parent,
const wxString& text,
int timeout,
int flags);
wxNotificationMessageWindow(wxGenericNotificationMessageImpl* notificationImpl);
void Set(wxWindow *parent,
const wxString& text,
int timeout,
int flags);
virtual ~wxNotificationMessageWindow();
bool IsAutomatic() const { return m_timer.IsRunning(); }
void SetDeleteOnHide() { m_deleteOnHide = true; }
void Set(int timeout);
bool Hide();
void SetMessageTitle(const wxString& title);
void SetMessage(const wxString& message);
void SetMessageIcon(const wxIcon& icon);
bool AddAction(wxWindowID actionid, const wxString &label);
private:
void OnClose(wxCloseEvent& event);
void OnTimer(wxTimerEvent& event);
void OnNotificationClicked(wxMouseEvent& event);
void OnNotificationMouseEnter(wxMouseEvent& event);
void OnNotificationMouseLeave(wxMouseEvent& event);
void OnCloseClicked(wxCommandEvent& event);
void OnActionButtonClicked(wxCommandEvent& event);
// if true, delete the dialog when it should disappear, otherwise just hide
// it (initially false)
bool m_deleteOnHide;
// Dialog elements
wxPanel* m_messagePanel;
wxStaticBitmap* m_messageBmp;
wxStaticText* m_messageText;
wxStaticText* m_messageTitle;
wxBitmapButton* m_closeBtn;
wxBoxSizer* m_buttonSizer;
// timer which will hide this dialog when it expires, if it's not running
// it means we were created without timeout
wxTimer m_timer;
int m_timeout;
long m_timeoutTargetTime;
int m_mouseActiveCount;
wxGenericNotificationMessageImpl* m_notificationImpl;
void PrepareNotificationControl(wxWindow* ctrl, bool handleClick = true);
static wxPoint ms_presentationPos;
static int ms_presentationDirection;
static wxVector<wxNotificationMessageWindow*> ms_visibleNotifications;
static void AddVisibleNotification(wxNotificationMessageWindow* notif);
static void RemoveVisibleNotification(wxNotificationMessageWindow* notif);
static void ResizeAndFitVisibleNotifications();
wxDECLARE_EVENT_TABLE();
wxDECLARE_NO_COPY_CLASS(wxNotificationMessageDialog);
wxDECLARE_NO_COPY_CLASS(wxNotificationMessageWindow);
};
int wxNotificationMessageWindow::ms_presentationDirection = 0;
wxPoint wxNotificationMessageWindow::ms_presentationPos = wxDefaultPosition;
// ============================================================================
// wxNotificationMessageDialog implementation
// wxNotificationMessageWindow implementation
// ============================================================================
wxBEGIN_EVENT_TABLE(wxNotificationMessageDialog, wxDialog)
EVT_CLOSE(wxNotificationMessageDialog::OnClose)
wxBEGIN_EVENT_TABLE(wxNotificationMessageWindow, wxFrame)
EVT_CLOSE(wxNotificationMessageWindow::OnClose)
EVT_TIMER(wxID_ANY, wxNotificationMessageDialog::OnTimer)
EVT_TIMER(wxID_ANY, wxNotificationMessageWindow::OnTimer)
wxEND_EVENT_TABLE()
wxNotificationMessageDialog::wxNotificationMessageDialog(wxWindow *parent,
const wxString& text,
int timeout,
int flags)
: wxDialog(parent, wxID_ANY, _("Notice"),
wxDefaultPosition, wxDefaultSize,
0 /* no caption, no border styles */),
m_timer(this)
{
m_deleteOnHide = false;
wxVector<wxNotificationMessageWindow*> wxNotificationMessageWindow::ms_visibleNotifications;
Set(parent, text, timeout, flags);
wxNotificationMessageWindow::wxNotificationMessageWindow(wxGenericNotificationMessageImpl* notificationImpl)
: wxFrame(NULL, wxID_ANY, _("Notice"),
wxDefaultPosition, wxDefaultSize,
wxBORDER_NONE | wxFRAME_TOOL_WINDOW | wxSTAY_ON_TOP /* no caption, no border styles */),
m_timer(this),
m_mouseActiveCount(0),
m_notificationImpl(notificationImpl)
{
m_buttonSizer = NULL;
SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_BTNSHADOW));
m_messagePanel = new wxPanel(this, wxID_ANY);
wxSizer * const msgSizer = new wxBoxSizer(wxHORIZONTAL);
m_messagePanel->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
m_messagePanel->SetSizer(msgSizer);
PrepareNotificationControl(m_messagePanel);
// Add message icon to layout
m_messageBmp = new wxStaticBitmap
(
m_messagePanel,
wxID_ANY,
wxArtProvider::GetMessageBoxIcon(wxICON_INFORMATION)
);
m_messageBmp->Hide();
PrepareNotificationControl(m_messageBmp);
msgSizer->Add(m_messageBmp, wxSizerFlags().Centre().DoubleBorder());
// Create title and message sizers
wxSizer* textSizer = new wxBoxSizer(wxVERTICAL);
m_messageTitle = new wxStaticText(m_messagePanel, wxID_ANY, wxString());
m_messageTitle->SetFont(m_messageTitle->GetFont().MakeBold());
textSizer->Add(m_messageTitle, wxSizerFlags(0).Border());
m_messageTitle->Hide();
PrepareNotificationControl(m_messageTitle);
m_messageText = new wxStaticText(m_messagePanel, wxID_ANY, wxString());
textSizer->Add(m_messageText, wxSizerFlags(0).Border(wxLEFT | wxRIGHT | wxBOTTOM));
PrepareNotificationControl(m_messageText);
msgSizer->Add(textSizer, wxSizerFlags(1).Center());
// Add a single close button if no actions are specified
m_closeBtn = wxBitmapButton::NewCloseButton(m_messagePanel, wxID_ANY);
msgSizer->Add(m_closeBtn, wxSizerFlags(0).Border(wxALL, 3).Top());
m_closeBtn->Bind(wxEVT_BUTTON, &wxNotificationMessageWindow::OnCloseClicked, this);
PrepareNotificationControl(m_closeBtn, false);
wxSizer * const sizerTop = new wxBoxSizer(wxHORIZONTAL);
sizerTop->Add(m_messagePanel, wxSizerFlags().Border(wxALL, FromDIP(1)));
SetSizer(sizerTop);
}
void
wxNotificationMessageDialog::Set(wxWindow * WXUNUSED(parent),
const wxString& text,
int timeout,
int flags)
wxNotificationMessageWindow::~wxNotificationMessageWindow()
{
wxSizer * const sizerTop = new wxBoxSizer(wxHORIZONTAL);
if ( flags & wxICON_MASK )
RemoveVisibleNotification(this);
}
void wxNotificationMessageWindow::PrepareNotificationControl(wxWindow* ctrl, bool handleClick)
{
ctrl->Bind(wxEVT_ENTER_WINDOW, &wxNotificationMessageWindow::OnNotificationMouseEnter, this);
ctrl->Bind(wxEVT_LEAVE_WINDOW, &wxNotificationMessageWindow::OnNotificationMouseLeave, this);
if ( handleClick )
ctrl->Bind(wxEVT_LEFT_DOWN, &wxNotificationMessageWindow::OnNotificationClicked, this);
}
void wxNotificationMessageWindow::SetMessageTitle(const wxString& title)
{
m_messageTitle->SetLabelText(title);
m_messageTitle->Show(!title.empty());
}
void wxNotificationMessageWindow::SetMessage(const wxString& message)
{
m_messageText->SetLabelText(message);
m_messageText->Show(!message.empty());
}
void wxNotificationMessageWindow::SetMessageIcon(const wxIcon& icon)
{
m_messageBmp->SetBitmap(icon);
m_messageBmp->Show(icon.IsOk());
}
bool wxNotificationMessageWindow::AddAction(wxWindowID actionid, const wxString &label)
{
wxSizer* msgSizer = m_messagePanel->GetSizer();
if ( m_buttonSizer == NULL )
{
sizerTop->Add(new wxStaticBitmap
(
this,
wxID_ANY,
wxArtProvider::GetMessageBoxIcon(flags)
),
wxSizerFlags().Centre().Border());
msgSizer->Detach(m_closeBtn);
m_closeBtn->Hide();
m_buttonSizer = new wxBoxSizer(wxVERTICAL);
msgSizer->Add(m_buttonSizer, wxSizerFlags(0).Center().Border());
}
sizerTop->Add(CreateTextSizer(text), wxSizerFlags(1).Border());
SetSizerAndFit(sizerTop);
wxButton* actionButton = new wxButton(m_messagePanel, actionid, label);
actionButton->Bind(wxEVT_BUTTON, &wxNotificationMessageWindow::OnActionButtonClicked, this);
PrepareNotificationControl(actionButton, false);
int borderDir = (m_buttonSizer->GetChildren().empty()) ? 0 : wxTOP;
m_buttonSizer->Add(actionButton, wxSizerFlags(0).Border(borderDir).Expand());
return true;
}
bool wxNotificationMessageWindow::Hide()
{
if ( m_timer.IsRunning() )
m_timer.Stop();
RemoveVisibleNotification(this);
return wxFrame::HideWithEffect(wxSHOW_EFFECT_BLEND);
}
void wxNotificationMessageWindow::Set(int timeout)
{
Layout();
Fit();
AddVisibleNotification(this);
if ( timeout != wxGenericNotificationMessage::Timeout_Never )
{
// wxTimer uses ms, timeout is in seconds
m_timer.Start(timeout*1000, true /* one shot only */);
m_timer.Start(500);
m_timeout = timeout;
m_timeoutTargetTime = wxGetUTCTime() + timeout;
}
else if ( m_timer.IsRunning() )
{
@@ -135,102 +265,263 @@ wxNotificationMessageDialog::Set(wxWindow * WXUNUSED(parent),
}
}
void wxNotificationMessageDialog::OnClose(wxCloseEvent& event)
void wxNotificationMessageWindow::OnClose(wxCloseEvent& WXUNUSED(event))
{
if ( m_deleteOnHide )
{
// we don't need to keep this dialog alive any more
Destroy();
}
else // don't really close, just hide, as we can be shown again later
{
event.Veto();
wxCommandEvent evt(wxEVT_NOTIFICATION_MESSAGE_DISMISSED);
m_notificationImpl->ProcessNotificationEvent(evt);
Hide();
if ( m_timer.IsRunning() )
m_timer.Stop();
m_notificationImpl->Close();
}
void wxNotificationMessageWindow::OnTimer(wxTimerEvent& WXUNUSED(event))
{
if ( m_mouseActiveCount > 0 )
{
m_timeoutTargetTime = wxGetUTCTime() + m_timeout;
}
else if ( m_timeoutTargetTime != -1 &&
wxGetUTCTime() >= m_timeoutTargetTime )
{
m_notificationImpl->Close();
}
}
void wxNotificationMessageDialog::OnTimer(wxTimerEvent& WXUNUSED(event))
void wxNotificationMessageWindow::OnNotificationClicked(wxMouseEvent& WXUNUSED(event))
{
if ( m_deleteOnHide )
Destroy();
else
Hide();
wxCommandEvent evt(wxEVT_NOTIFICATION_MESSAGE_CLICK);
m_notificationImpl->ProcessNotificationEvent(evt);
m_notificationImpl->Close();
}
void wxNotificationMessageWindow::OnNotificationMouseEnter(wxMouseEvent& WXUNUSED(event))
{
m_mouseActiveCount++;
}
void wxNotificationMessageWindow::OnNotificationMouseLeave(wxMouseEvent& WXUNUSED(event))
{
m_mouseActiveCount--;
}
void wxNotificationMessageWindow::OnCloseClicked(wxCommandEvent& WXUNUSED(event))
{
wxCommandEvent evt(wxEVT_NOTIFICATION_MESSAGE_DISMISSED);
m_notificationImpl->ProcessNotificationEvent(evt);
m_notificationImpl->Close();
}
void wxNotificationMessageWindow::OnActionButtonClicked(wxCommandEvent& event)
{
wxCommandEvent evt(wxEVT_NOTIFICATION_MESSAGE_ACTION, event.GetId());
m_notificationImpl->ProcessNotificationEvent(evt);
m_notificationImpl->Close();
}
void wxNotificationMessageWindow::AddVisibleNotification(wxNotificationMessageWindow* notif)
{
bool found = false;
for ( wxVector<wxNotificationMessageWindow*>::iterator it = ms_visibleNotifications.begin();
it != ms_visibleNotifications.end(); it++ )
{
if ( *it == notif )
{
found = true;
break;
}
}
if ( !found )
ms_visibleNotifications.push_back(notif);
ResizeAndFitVisibleNotifications();
}
void wxNotificationMessageWindow::RemoveVisibleNotification(wxNotificationMessageWindow* notif)
{
for ( wxVector<wxNotificationMessageWindow*>::iterator it = ms_visibleNotifications.begin();
it != ms_visibleNotifications.end(); it++ )
{
if ( *it == notif )
{
ms_visibleNotifications.erase(it);
break;
}
}
ResizeAndFitVisibleNotifications();
}
void wxNotificationMessageWindow::ResizeAndFitVisibleNotifications()
{
if ( ms_presentationDirection == 0 )
{
// Determine presentation position
wxDisplay display;
wxRect clientArea = display.GetClientArea();
wxRect geom = display.GetGeometry();
if ( clientArea.y > 0 ) // Taskbar is at top
{
ms_presentationDirection = 1;
ms_presentationPos = clientArea.GetTopRight();
}
else if ( clientArea.GetHeight() != geom.GetHeight() ) // Taskbar at bottom
{
ms_presentationDirection = -1;
ms_presentationPos = clientArea.GetBottomRight();
}
else // Default to upper right screen corner with some padding
{
ms_presentationDirection = 1;
ms_presentationPos.x = geom.GetWidth() - 30;
ms_presentationPos.y = 30;
}
}
int maxWidth = -1;
// Determine max width
for (wxVector<wxNotificationMessageWindow*>::iterator notif = ms_visibleNotifications.begin();
notif != ms_visibleNotifications.end(); ++notif)
{
wxSize notifSize = (*notif)->GetSize();
if ( notifSize.GetWidth() > maxWidth )
maxWidth = notifSize.GetWidth();
}
int notifPadding = 2;
wxPoint presentPos = ms_presentationPos;
presentPos.x -= notifPadding + maxWidth;
int prevNotifHeight = 0;
for (wxVector<wxNotificationMessageWindow*>::iterator notif = ms_visibleNotifications.begin();
notif != ms_visibleNotifications.end(); ++notif)
{
// Modify existing maxwidth
wxSize notifSize = (*notif)->GetSize();
if ( notifSize.GetWidth() < maxWidth )
{
notifSize.SetWidth(maxWidth);
(*notif)->SetSize(notifSize);
(*notif)->Layout();
}
if ( ms_presentationDirection > 0 )
{
presentPos.y += (notifPadding + prevNotifHeight);
prevNotifHeight = notifSize.GetHeight();
}
else
{
presentPos.y -= (notifPadding + notifSize.GetHeight());
}
(*notif)->SetPosition(presentPos);
}
}
// ============================================================================
// wxGenericNotificationMessage implementation
// ============================================================================
int wxGenericNotificationMessage::ms_timeout = 10;
/* static */ void wxGenericNotificationMessage::SetDefaultTimeout(int timeout)
{
wxASSERT_MSG( timeout > 0,
"negative or zero default timeout doesn't make sense" );
ms_timeout = timeout;
wxGenericNotificationMessageImpl::SetDefaultTimeout(timeout);
}
void wxGenericNotificationMessage::Init()
{
m_dialog = NULL;
m_impl = new wxGenericNotificationMessageImpl(this);
}
wxGenericNotificationMessage::~wxGenericNotificationMessage()
// ----------------------------------------------------------------------------
// wxGenericNotificationMessageImpl
// ----------------------------------------------------------------------------
int wxGenericNotificationMessageImpl::ms_timeout = 3;
wxGenericNotificationMessageImpl::wxGenericNotificationMessageImpl(wxNotificationMessageBase* notification) :
wxNotificationMessageImpl(notification)
{
if ( m_dialog->IsAutomatic() )
{
// we want to allow the user to create an automatically hidden
// notification just by creating a local wxGenericNotificationMessage object
// and so we shouldn't hide the notification when this object goes out
// of scope
m_dialog->SetDeleteOnHide();
}
else // manual dialog, hide it immediately
{
// OTOH for permanently shown dialogs only the code can hide them and
// if the object is deleted, we must do it now as it won't be
// accessible programmatically any more
delete m_dialog;
}
m_window = new wxNotificationMessageWindow(this);
}
bool wxGenericNotificationMessage::Show(int timeout)
wxGenericNotificationMessageImpl::~wxGenericNotificationMessageImpl()
{
if ( timeout == Timeout_Auto )
m_window->Destroy();
}
/* static */ void wxGenericNotificationMessageImpl::SetDefaultTimeout(int timeout)
{
wxASSERT_MSG(timeout > 0,
"negative or zero default timeout doesn't make sense");
ms_timeout = timeout;
}
bool wxGenericNotificationMessageImpl::Show(int timeout)
{
if ( timeout == wxNotificationMessageBase::Timeout_Auto )
{
timeout = GetDefaultTimeout();
}
if ( !m_dialog )
{
m_dialog = new wxNotificationMessageDialog
(
GetParent(),
GetFullMessage(),
timeout,
GetFlags()
);
}
else // update the existing dialog
{
m_dialog->Set(GetParent(), GetFullMessage(), timeout, GetFlags());
}
SetActive(true);
m_window->Set(timeout);
m_dialog->Show();
m_window->ShowWithEffect(wxSHOW_EFFECT_BLEND);
return true;
}
bool wxGenericNotificationMessage::Close()
bool wxGenericNotificationMessageImpl::Close()
{
if ( !m_dialog )
if ( !m_window )
return false;
m_dialog->Hide();
m_window->Hide();
SetActive(false);
return true;
}
void wxGenericNotificationMessageImpl::SetTitle(const wxString& title)
{
m_window->SetMessageTitle(title);
}
void wxGenericNotificationMessageImpl::SetMessage(const wxString& message)
{
m_window->SetMessage(message);
}
void wxGenericNotificationMessageImpl::SetParent(wxWindow *WXUNUSED(parent))
{
}
void wxGenericNotificationMessageImpl::SetFlags(int flags)
{
m_window->SetMessageIcon( wxArtProvider::GetMessageBoxIcon(flags) );
}
void wxGenericNotificationMessageImpl::SetIcon(const wxIcon& icon)
{
m_window->SetMessageIcon(icon);
}
bool wxGenericNotificationMessageImpl::AddAction(wxWindowID actionid, const wxString &label)
{
return m_window->AddAction(actionid, label);
}
#endif // wxUSE_NOTIFICATION_MESSAGE