diff --git a/checkin_notes b/checkin_notes index 0a820aa6f9..546b46cb70 100644 --- a/checkin_notes +++ b/checkin_notes @@ -2337,3 +2337,21 @@ David 31 Mar 2010 client_types.cpp cs_notices.cpp *.h + +Charlie 1 Apr 2010 + - MGR: Show number of unread notices in title of Notices tab (we consider + all notices as having been read when Notices tab is showing and BOINC + Manager is front process). Notify (balloon on Window or Linux, bounce + Dock icon on Mac for 15 seconds) repeatedly when there are unread + messages, with notification frequency set by Options dialog reminder + interval slider. + Note: Should there be 2 separate reminder frequency sliders for network + connection and Unread notices? + + clientgui/ + AdvancedFrame.cpp, .h + BOINCBaseFrame.cpp, .h + BOINCTaskBar.cpp, .h + DlgOptions.cpp + MainDocument.cpp, .h + MacSysMenu.cpp diff --git a/clientgui/AdvancedFrame.cpp b/clientgui/AdvancedFrame.cpp index 1b2d65966c..fe4c52e646 100644 --- a/clientgui/AdvancedFrame.cpp +++ b/clientgui/AdvancedFrame.cpp @@ -736,6 +736,8 @@ bool CAdvancedFrame::RepopulateNotebook() { CreateNotebookPage(new CViewStatistics(m_pNotebook)); CreateNotebookPage(new CViewResources(m_pNotebook)); + UpdateNoticesTabText(); + wxLogTrace(wxT("Function Start/End"), wxT("CAdvancedFrame::RepopulateNotebook - Function End")); return true; } @@ -767,6 +769,32 @@ bool CAdvancedFrame::CreateNotebookPage( CBOINCBaseView* pwndNewNotebookPage) { } +void CAdvancedFrame::UpdateNoticesTabText() { + wxWindow* pwndNotebookPage = NULL; + CBOINCBaseView* pView = NULL; + wxString strTabText; + CMainDocument* pDoc = wxGetApp().GetDocument(); + + wxASSERT(pDoc); + wxASSERT(wxDynamicCast(pDoc, CMainDocument)); + + wxASSERT(m_pNotebook); + pwndNotebookPage = m_pNotebook->GetPage(ID_ADVNOTICESVIEW - ID_ADVVIEWBASE); + wxASSERT(pwndNotebookPage); + pView = wxDynamicCast(pwndNotebookPage, CBOINCBaseView); + wxASSERT(pView); + + int count = pDoc->GetUnreadNoticeCount(); + if (count) { + strTabText.Printf(wxT("%s (%d)"), pView->GetViewDisplayName().c_str(), count); + } else { + strTabText = pView->GetViewDisplayName(); + } + + m_pNotebook->SetPageText(ID_ADVNOTICESVIEW - ID_ADVVIEWBASE, strTabText); +} + + bool CAdvancedFrame::CreateStatusbar() { wxLogTrace(wxT("Function Start/End"), wxT("CAdvancedFrame::CreateStatusbar - Function Begin")); diff --git a/clientgui/AdvancedFrame.h b/clientgui/AdvancedFrame.h index ccdbc9c85e..b2bc99d85d 100644 --- a/clientgui/AdvancedFrame.h +++ b/clientgui/AdvancedFrame.h @@ -124,7 +124,7 @@ private: bool RepopulateNotebook(); bool CreateNotebookPage( CBOINCBaseView* pwndNewNotebookPage ); bool DeleteNotebook(); - + void UpdateNoticesTabText(); bool CreateStatusbar(); bool DeleteStatusbar(); diff --git a/clientgui/BOINCBaseFrame.cpp b/clientgui/BOINCBaseFrame.cpp index 3c1b1f3c17..a81f92d1a1 100644 --- a/clientgui/BOINCBaseFrame.cpp +++ b/clientgui/BOINCBaseFrame.cpp @@ -370,6 +370,10 @@ int CBOINCBaseFrame::GetCurrentViewPage() { } +void CBOINCBaseFrame::UpdateNoticesTabText() { +} + + void CBOINCBaseFrame::FireInitialize() { CFrameEvent event(wxEVT_FRAME_INITIALIZED, this); AddPendingEvent(event); diff --git a/clientgui/BOINCBaseFrame.h b/clientgui/BOINCBaseFrame.h index 261a9add08..8fab785dcc 100644 --- a/clientgui/BOINCBaseFrame.h +++ b/clientgui/BOINCBaseFrame.h @@ -63,7 +63,7 @@ public: virtual void OnExit( wxCommandEvent& event ); int GetCurrentViewPage(); - + virtual void UpdateNoticesTabText(); int GetReminderFrequency() { return m_iReminderFrequency; } wxString GetDialupConnectionName() { return m_strNetworkDialupConnectionName; } @@ -82,7 +82,7 @@ public: virtual void StopTimers(); virtual void UpdateRefreshTimerInterval(); - inline void UpdateStatusText( const wxChar* ){} + inline void UpdateStatusText( const wxChar* ){} void ShowAlert( const wxString title, @@ -117,6 +117,7 @@ protected: virtual int _GetCurrentViewPage(); + DECLARE_EVENT_TABLE() }; diff --git a/clientgui/BOINCTaskBar.cpp b/clientgui/BOINCTaskBar.cpp index 208cca91fc..04c6dd6af4 100644 --- a/clientgui/BOINCTaskBar.cpp +++ b/clientgui/BOINCTaskBar.cpp @@ -41,10 +41,11 @@ #include "res/macbadgemask.xpm" #endif +// How long to bounce Dock icon on Mac +#define MAX_NOTIFICATION_DURATION 15 DEFINE_EVENT_TYPE(wxEVT_TASKBAR_RELOADSKIN) DEFINE_EVENT_TYPE(wxEVT_TASKBAR_REFRESH) -DEFINE_EVENT_TYPE(wxEVT_TASKBAR_NOTIFICATION_ALERT) BEGIN_EVENT_TABLE(CTaskBarIcon, wxTaskBarIconEx) @@ -52,7 +53,6 @@ BEGIN_EVENT_TABLE(CTaskBarIcon, wxTaskBarIconEx) EVT_CLOSE(CTaskBarIcon::OnClose) EVT_TASKBAR_REFRESH(CTaskBarIcon::OnRefresh) EVT_TASKBAR_RELOADSKIN(CTaskBarIcon::OnReloadSkin) - EVT_TASKBAR_NOTIFICATION_ALERT(CTaskBarIcon::OnNotificationAlert) EVT_TASKBAR_LEFT_DCLICK(CTaskBarIcon::OnLButtonDClick) #ifndef __WXMAC__ EVT_TASKBAR_RIGHT_DOWN(CTaskBarIcon::OnRButtonDown) @@ -80,7 +80,7 @@ END_EVENT_TABLE() CTaskBarIcon::CTaskBarIcon(wxString title, wxIcon* icon, wxIcon* iconDisconnected, wxIcon* iconSnooze) : -#if defined(__WXMAC__) +#ifdef __WXMAC__ wxTaskBarIcon(DOCK) #else wxTaskBarIconEx(wxT("BOINCManagerSystray"), 1) @@ -95,7 +95,9 @@ CTaskBarIcon::CTaskBarIcon(wxString title, wxIcon* icon, wxIcon* iconDisconnecte m_bMouseButtonPressed = false; m_dtLastNotificationAlertExecuted = wxDateTime((time_t)0); - m_iLastNotificationCount = 0; +#ifdef __WXMAC__ + m_pNotificationRequest = NULL; +#endif } @@ -318,30 +320,6 @@ void CTaskBarIcon::OnReloadSkin(CTaskbarEvent& WXUNUSED(event)) { } -void CTaskBarIcon::OnNotificationAlert(CTaskbarEvent& WXUNUSED(event)) { - CSkinAdvanced* pSkinAdvanced = wxGetApp().GetSkinManager()->GetAdvanced(); - wxString strTitle; - - strTitle.Printf( - _("%s Notices"), - pSkinAdvanced->GetApplicationName().c_str() - ); - - // Do not use SafeMessageBox here because we want to continue - // doing periodic RPCs to get messages, get notices, etc. - wxMessageDialog* pDlg = new wxMessageDialog( - NULL, - _("One or more notices are now available for viewing."), - strTitle, - wxOK - ); - pDlg->ShowModal(); - if (pDlg) { - pDlg->Destroy(); - } -} - - void CTaskBarIcon::FireReloadSkin() { CTaskbarEvent event(wxEVT_TASKBAR_RELOADSKIN, this); AddPendingEvent(event); @@ -437,6 +415,27 @@ bool CTaskBarIcon::SetIcon(const wxIcon& icon, const wxString& ) { return result; } + +// wxTopLevel::RequestUserAttention() doesn't have an API to cancel +// after a timeout, so we must call Notification Manager directly on Mac +void CTaskBarIcon::MacRequestUserAttention() +{ + m_pNotificationRequest = (NMRecPtr) NewPtrClear( sizeof( NMRec) ) ; + m_pNotificationRequest->qType = nmType ; + m_pNotificationRequest->nmMark = 1; + + NMInstall(m_pNotificationRequest); +} + +void CTaskBarIcon::MacCancelUserAttentionRequest() +{ + if (m_pNotificationRequest) { + NMRemove(m_pNotificationRequest); + DisposePtr((Ptr)m_pNotificationRequest); + m_pNotificationRequest = NULL; + } +} + #endif // ! __WXMAC__ @@ -703,7 +702,6 @@ void CTaskBarIcon::UpdateNoticeStatus() { CBOINCBaseFrame* pFrame = wxGetApp().GetFrame(); CSkinAdvanced* pSkinAdvanced = wxGetApp().GetSkinManager()->GetAdvanced(); wxString strTitle; - int iNoticeCount = 0; wxASSERT(pDoc); @@ -713,15 +711,18 @@ void CTaskBarIcon::UpdateNoticeStatus() { wxASSERT(wxDynamicCast(pFrame, CBOINCBaseFrame)); wxASSERT(wxDynamicCast(pSkinAdvanced, CSkinAdvanced)); - + if (!pFrame) return; + + // Repeat notification for unread notices at user-selected reminder frequency wxTimeSpan tsLastNotificationDisplayed = wxDateTime::Now() - m_dtLastNotificationAlertExecuted; - if (tsLastNotificationDisplayed.GetMinutes() >= 60) { + if ( + (tsLastNotificationDisplayed.GetMinutes() >= pFrame->GetReminderFrequency()) + && (pFrame->GetReminderFrequency() != 0) + ) { - iNoticeCount = pDoc->GetNoticeCount(); - if (iNoticeCount > m_iLastNotificationCount) { + if (pDoc->GetUnreadNoticeCount()) { // Update cached info - m_iLastNotificationCount = iNoticeCount; m_dtLastNotificationAlertExecuted = wxDateTime::Now(); if (IsBalloonsSupported()) { @@ -738,25 +739,28 @@ void CTaskBarIcon::UpdateNoticeStatus() { ); } else { // For platforms that do not support balloons - if (pFrame) { - // If Manager is hidden, request user attention. - if (! (pFrame->IsShown())) { - pFrame->RequestUserAttention(); - } - // If Manager is open to a tab other than Notices, display an alert. - // If Manager is now hidden, alert will appear when Manager is shown. - int currentTabView = pFrame->GetCurrentViewPage(); - if (! (currentTabView & VW_NOTIF)) { - // Don't run the alert from within the taskbar Refresh event to - // allow updates to continue behind the notification alert - CTaskbarEvent event(wxEVT_TASKBAR_NOTIFICATION_ALERT, this); - AddPendingEvent(event); - } + // If Manager is hidden or in backgroound, request user attention. + if (! (wxGetApp().IsActive())) { +#ifdef __WXMAC__ + MacRequestUserAttention(); // Bounce BOINC Dock icon +#else + pFrame->RequestUserAttention(); +#endif } } } +#ifdef __WXMAC__ + } else { + // Stop bouncing BOINC Dock icon after MAX_NOTIFICATION_DURATION seconds + if (m_pNotificationRequest) { + if (wxGetApp().IsActive() || + (tsLastNotificationDisplayed.GetSeconds() >= MAX_NOTIFICATION_DURATION) + ) { + MacCancelUserAttentionRequest(); + } + } +#endif } wxLogTrace(wxT("Function Start/End"), wxT("CTaskBarIcon::UpdateNoticeStatus - Function End")); } - diff --git a/clientgui/BOINCTaskBar.h b/clientgui/BOINCTaskBar.h index 39a52064d5..6a70bce5e6 100644 --- a/clientgui/BOINCTaskBar.h +++ b/clientgui/BOINCTaskBar.h @@ -50,7 +50,6 @@ public: void OnClose(wxCloseEvent& event); void OnRefresh(CTaskbarEvent& event); void OnReloadSkin(CTaskbarEvent& event); - void OnNotificationAlert(CTaskbarEvent& event); void OnNotificationClick(wxTaskBarIconExEvent& event); void OnShutdown(wxTaskBarIconExEvent& event); @@ -64,6 +63,13 @@ public: void AdjustMenuItems(wxMenu* menu); #ifdef __WXMAC__ +private: + NMRecPtr m_pNotificationRequest; + + void MacRequestUserAttention(); + void MacCancelUserAttentionRequest(); + +public: wxMenu *CreatePopupMenu(); bool SetIcon(const wxIcon& icon, const wxString& message = wxEmptyString); @@ -104,7 +110,6 @@ private: bool m_bMouseButtonPressed; wxDateTime m_dtLastNotificationAlertExecuted; - int m_iLastNotificationCount; void ResetTaskBar(); void DisplayContextMenu(); @@ -140,12 +145,10 @@ public: BEGIN_DECLARE_EVENT_TYPES() DECLARE_EVENT_TYPE( wxEVT_TASKBAR_RELOADSKIN, 10100 ) DECLARE_EVENT_TYPE( wxEVT_TASKBAR_REFRESH, 10101 ) -DECLARE_EVENT_TYPE( wxEVT_TASKBAR_NOTIFICATION_ALERT, 10102 ) END_DECLARE_EVENT_TYPES() #define EVT_TASKBAR_RELOADSKIN(fn) DECLARE_EVENT_TABLE_ENTRY(wxEVT_TASKBAR_RELOADSKIN, -1, -1, (wxObjectEventFunction) (wxEventFunction) &fn, NULL), #define EVT_TASKBAR_REFRESH(fn) DECLARE_EVENT_TABLE_ENTRY(wxEVT_TASKBAR_REFRESH, -1, -1, (wxObjectEventFunction) (wxEventFunction) &fn, NULL), -#define EVT_TASKBAR_NOTIFICATION_ALERT(fn) DECLARE_EVENT_TABLE_ENTRY(wxEVT_TASKBAR_NOTIFICATION_ALERT, -1, -1, (wxObjectEventFunction) (wxEventFunction) &fn, NULL), #endif diff --git a/clientgui/DlgOptions.cpp b/clientgui/DlgOptions.cpp index 4271fa76c4..031ab49f6b 100644 --- a/clientgui/DlgOptions.cpp +++ b/clientgui/DlgOptions.cpp @@ -165,7 +165,7 @@ void CDlgOptions::CreateControls() itemFlexGridSizer6->Add(m_LanguageSelectionCtrl, 0, wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL|wxALL, 5); wxStaticText* itemStaticText9 = new wxStaticText; - itemStaticText9->Create( itemPanel4, wxID_STATIC, _("Network reminder interval:\n(minutes)"), wxDefaultPosition, wxDefaultSize, wxALIGN_RIGHT ); + itemStaticText9->Create( itemPanel4, wxID_STATIC, _("Network or notices reminder interval:\n(minutes)"), wxDefaultPosition, wxDefaultSize, wxALIGN_RIGHT ); itemFlexGridSizer6->Add(itemStaticText9, 0, wxALIGN_RIGHT|wxALIGN_CENTER_VERTICAL|wxALL, 5); m_ReminderFrequencyCtrl = new wxSlider; @@ -177,7 +177,7 @@ void CDlgOptions::CreateControls() #endif wxSL_HORIZONTAL|wxSL_LABELS); if (ShowToolTips()) - m_ReminderFrequencyCtrl->SetToolTip(_("How often should the Manager remind you when a network connection is needed?")); + m_ReminderFrequencyCtrl->SetToolTip(_("How often should the Manager remind you when you have new notices or when a network connection is needed?")); itemFlexGridSizer6->Add(m_ReminderFrequencyCtrl, 0, wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL|wxALL, 5); #ifdef __WXMSW__ diff --git a/clientgui/MainDocument.cpp b/clientgui/MainDocument.cpp index b33acf6384..bc5345c687 100644 --- a/clientgui/MainDocument.cpp +++ b/clientgui/MainDocument.cpp @@ -392,6 +392,8 @@ CMainDocument::CMainDocument() : rpc(this) { m_iMessageSequenceNumber = 0; m_iNoticeSequenceNumber = 0; + m_iLastReadNoticeSequenceNumber = 0; + m_iNumberUnreadNotices = 0; m_dtCachedStateTimestamp = wxDateTime((time_t)0); m_iGet_state_rpc_result = 0; @@ -1873,6 +1875,21 @@ int CMainDocument::CachedNoticeUpdate() { if (notices.notices.size() != 0) { m_iNoticeSequenceNumber = notices.notices[0]->seqno; } + + // Consider all notices as having been read if Notices tab is open + CBOINCBaseFrame* pFrame = wxGetApp().GetFrame(); + if (!pFrame) goto done; + wxASSERT(wxDynamicCast(pFrame, CBOINCBaseFrame)); + + int currentTabView = pFrame->GetCurrentViewPage(); + if ((currentTabView & VW_NOTIF) && wxGetApp().IsActive()) { + m_iLastReadNoticeSequenceNumber = m_iNoticeSequenceNumber; + } + int unread = m_iNoticeSequenceNumber - m_iLastReadNoticeSequenceNumber; + if (m_iNumberUnreadNotices != unread) { + m_iNumberUnreadNotices = unread; + pFrame->UpdateNoticesTabText(); + } } done: in_this_func = false; diff --git a/clientgui/MainDocument.h b/clientgui/MainDocument.h index d9cc4fbf66..556604a3d5 100644 --- a/clientgui/MainDocument.h +++ b/clientgui/MainDocument.h @@ -291,6 +291,10 @@ public: private: wxDateTime m_dtNoticesTimeStamp; + int m_iNoticeSequenceNumber; + int m_iLastReadNoticeSequenceNumber; + int m_iNumberUnreadNotices; + public: NOTICES notices; NOTICES async_notices_buf; @@ -300,11 +304,10 @@ public: int CachedNoticeUpdate(); int GetNoticeCount(); + int GetUnreadNoticeCount() { return m_iNumberUnreadNotices; }; int ResetNoticeState(); - int m_iNoticeSequenceNumber; - // // Messages Tab diff --git a/clientgui/mac/MacSysMenu.cpp b/clientgui/mac/MacSysMenu.cpp index e9d4e4bfde..21fb768223 100644 --- a/clientgui/mac/MacSysMenu.cpp +++ b/clientgui/mac/MacSysMenu.cpp @@ -37,8 +37,6 @@ pascal OSStatus SysMenuEventHandler( EventHandlerCallRef inHandlerCallRef, { kEventClassApplication, kEventAppShown}, {kEventClassMenu, kEventMenuOpening} }; -static const wxIcon* currentIcon = NULL; - #if wxCHECK_VERSION(2,8,0) @@ -215,8 +213,6 @@ void CMacSystemMenu::BuildMenu() { themenu->SetEventHandler(this); SetUpSystemMenu((MenuRef)(themenu->GetHMenu()), imageRef); - - currentIcon = NULL; } if(imageRef != NULL) CGImageRelease( imageRef ); }