// This file is part of BOINC.
// http://boinc.berkeley.edu
// Copyright (C) 2023 University of California
//
// BOINC is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License
// as published by the Free Software Foundation,
// either version 3 of the License, or (at your option) any later version.
//
// BOINC is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
// See the GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with BOINC. If not, see .
//
#if defined(__GNUG__) && !defined(__APPLE__)
#pragma implementation "NoticeListCtrl.h"
#endif
#include "stdwx.h"
#include "diagnostics.h"
#include "util.h"
#include "mfile.h"
#include "miofile.h"
#include "parse.h"
#include "error_numbers.h"
#include "wizardex.h"
#include "error_numbers.h"
#include "Events.h"
#include "BOINCGUIApp.h"
#include "SkinManager.h"
#include "MainDocument.h"
#include "NoticeListCtrl.h"
////@begin XPM images
////@end XPM images
/* CNoticeListCtrl type definition
*/
IMPLEMENT_DYNAMIC_CLASS( CNoticeListCtrl, wxWindow )
/*!
* CNoticeListCtrl event table definition
*/
BEGIN_EVENT_TABLE( CNoticeListCtrl, wxWindow )
////@begin CNoticeListCtrl event table entries
#if wxUSE_WEBVIEW
EVT_WEBVIEW_NAVIGATING(ID_LIST_NOTIFICATIONSVIEW, CNoticeListCtrl::OnLinkClicked)
EVT_WEBVIEW_ERROR(ID_LIST_NOTIFICATIONSVIEW, CNoticeListCtrl::OnWebViewError)
#else
EVT_HTML_LINK_CLICKED(ID_LIST_NOTIFICATIONSVIEW, CNoticeListCtrl::OnLinkClicked)
#endif
////@end CNoticeListCtrl event table entries
END_EVENT_TABLE()
/*!
* CNoticeListCtrl constructors
*/
CNoticeListCtrl::CNoticeListCtrl( ) {
}
CNoticeListCtrl::CNoticeListCtrl( wxWindow* parent ) {
Create( parent );
}
CNoticeListCtrl::~CNoticeListCtrl( ) {
}
/*!
* CNoticeListCtrl creator
*/
bool CNoticeListCtrl::Create( wxWindow* parent ) {
////@begin CNoticeListCtrl member initialisation
////@end CNoticeListCtrl member initialisation
////@begin CNoticeListCtrl creation
wxWindow::Create( parent, ID_LIST_NOTIFICATIONSVIEW, wxDefaultPosition, wxDefaultSize,
wxSUNKEN_BORDER | wxTAB_TRAVERSAL );
#if wxUSE_WEBVIEW
m_browser = wxWebView::New( this, ID_LIST_NOTIFICATIONSVIEW );
#else
m_browser = new wxHtmlWindow( this, ID_LIST_NOTIFICATIONSVIEW );
#endif
////@end CNoticeListCtrl creation
wxBoxSizer *topsizer;
topsizer = new wxBoxSizer(wxVERTICAL);
topsizer->Add(m_browser, 1, wxEXPAND);
SetAutoLayout(true);
SetSizer(topsizer);
m_itemCount = 0;
if (wxGetApp().GetIsDarkMode()){
#if wxUSE_WEBVIEW
m_noticesBody = wxT("
");
#else
m_noticesBody = wxT("");
#endif
} else {
m_noticesBody = wxT("");
}
// In Dark Mode, paint the window black immediately
#if wxUSE_WEBVIEW
m_browser->SetPage(m_noticesBody, wxEmptyString);
#else
m_browser->SetPage(m_noticesBody);
#endif
// Display the fetching notices message until we have notices
// to display or have determined that there are no notices.
m_bDisplayFetchingNotices = false;
m_bDisplayEmptyNotice = true;
m_bNeedsReloading = false;
return TRUE;
}
int CNoticeListCtrl::GetItemCount() {
return m_itemCount;
}
void CNoticeListCtrl::SetItemCount(int newCount) {
int i;
CMainDocument* pDoc = wxGetApp().GetDocument();
CSkinAdvanced* pSkinAdvanced = wxGetApp().GetSkinManager()->GetAdvanced();
wxString strTitle = wxEmptyString;
wxString strDescription = wxEmptyString;
wxString strProjectName = wxEmptyString;
wxString strURL = wxEmptyString;
wxString strCreateTime = wxEmptyString;
wxString strCategory = wxEmptyString;
wxString strBuffer = wxEmptyString;
wxString strTemp = wxEmptyString;
wxDateTime dtBuffer;
wxASSERT(pDoc);
wxASSERT(wxDynamicCast(pDoc, CMainDocument));
wxASSERT(pSkinAdvanced);
wxASSERT(wxDynamicCast(pSkinAdvanced, CSkinAdvanced));
m_itemCount = newCount;
if (wxGetApp().GetIsDarkMode()){
#if wxUSE_WEBVIEW
m_noticesBody = wxT("");
#else
m_noticesBody = wxT("");
#endif
} else {
m_noticesBody = wxT("");
}
for (i=0; iIsConnected()) {
NOTICE* np = pDoc->notice((unsigned int)i);
strCategory = wxString(np->category, wxConvUTF8);
strProjectName = wxString(np->project_name, wxConvUTF8);
strURL = wxString(np->link, wxConvUTF8);
strTitle = wxString(np->title, wxConvUTF8);
// Fix-up title
if (strCategory == wxT("client")) {
strBuffer.Printf(
wxT("_(\"Notice from %s\")"),
pSkinAdvanced->GetApplicationShortName().c_str()
);
if (strProjectName.size()) {
strTemp.Printf(wxT("%s: %s"), strProjectName.c_str(), strBuffer.c_str());
} else {
strTemp.Printf(wxT("%s"), strBuffer.c_str());
}
} else if (strCategory == wxT("scheduler")) {
strTemp.Printf(wxT("%s: %s"), strProjectName.c_str(), wxT("_(\"Notice from server\")"));
} else {
if (strProjectName.size()) {
strTemp.Printf(wxT("%s: %s"), strProjectName.c_str(), strTitle.c_str());
} else {
strTemp = strTitle;
}
}
strTitle = strTemp;
eol_to_br(strTitle);
localize(strTitle);
strDescription = wxString(np->description.c_str(), wxConvUTF8);
eol_to_br(strDescription);
localize(strDescription);
// RSS feeds and web pages may use protocol-relative (scheme-relative)
// URLs, such as
// Since the html comes from a web server via http, the scheme is
// assumed to also be http. But we have cached the html in a local
// file, so it is no longer associated with the http protocol / scheme.
// Therefore all our URLs must explicitly specify the http protocol.
//
// The second argument to wxWebView::SetPage is supposed to take care
// of this automatically, but fails to do so under Windows, so we do
// it here explicitly.
strDescription.Replace(wxT("\"//"), wxT("\"http://"));
strDescription.Replace(wxT(""), wxT(" "));
// Apparently attempting to follow links with other targets specified
// fails to fire our event handler. For now we will just strip out
// the special _blank/_new target which is supposed to open a new
// browser window anyways.
strDescription.Replace(wxT("target=\"_blank\""), wxT(""));
strDescription.Replace(wxT("target=\"_new\""), wxT(""));
dtBuffer.Set((time_t)np->create_time);
strCreateTime = dtBuffer.Format();
// Put dividers between notices, but not before first or after last
if (i == 0) {
strBuffer = wxEmptyString;
} else {
strBuffer = wxT("
");
}
strBuffer += wxT("");
if (!strTitle.IsEmpty()) {
strTemp.Printf(
wxT("%s "),
strTitle.c_str()
);
strBuffer += strTemp;
}
strBuffer += strDescription;
strBuffer += wxT(" ");
strBuffer += strCreateTime;
if (!strURL.IsEmpty()) {
strTemp.Printf(
wxT(" · %s "),
strURL.c_str(),
_("more...")
);
strBuffer += strTemp;
}
strBuffer += wxT(" |
");
}
m_noticesBody += strBuffer;
}
m_noticesBody += wxT("");
// baseURL is not needed here (see comments above) and it
// must be an empty string for this to work under OS 10.12.4
#if wxUSE_WEBVIEW
m_browser->SetPage(m_noticesBody, wxEmptyString);
#else
m_browser->SetPage(m_noticesBody);
#endif
}
void CNoticeListCtrl::Clear() {
m_bNeedsReloading = true;
UpdateUI();
}
#if wxUSE_WEBVIEW
void CNoticeListCtrl::OnLinkClicked( wxWebViewEvent& event ) {
if (event.GetURL().StartsWith(wxT("http://")) || event.GetURL().StartsWith(wxT("https://"))) {
event.Veto(); // Tell wxWebView not to follow link
wxLaunchDefaultBrowser(event.GetURL());
} else {
event.Skip();
}
}
void CNoticeListCtrl::OnWebViewError( wxWebViewEvent& event ) {
fprintf(stderr, "wxWebView error: target=%s, URL=%s\n",
(event.GetTarget().ToStdString()).c_str(), (event.GetURL().ToStdString()).c_str());
event.Skip();
}
#else
void CNoticeListCtrl::OnLinkClicked( wxHtmlLinkEvent& event ) {
wxString url = event.GetLinkInfo().GetHref();
if (url.StartsWith(wxT("http://")) || url.StartsWith(wxT("https://"))) {
// wxHtmlLinkEvent doesn't have Veto(), but only loads the page if you
// call Skip().
wxLaunchDefaultBrowser(url);
} else {
event.Skip();
}
}
#endif
/*!
* Update the UI.
*/
bool CNoticeListCtrl::UpdateUI() {
static bool bAlreadyRunning = false;
CMainDocument* pDoc = wxGetApp().GetDocument();
wxASSERT(pDoc);
wxASSERT(wxDynamicCast(pDoc, CMainDocument));
// Call Freeze() / Thaw() only when actually needed;
// otherwise it causes unnecessary redraws
int noticeCount = pDoc->GetNoticeCount();
if ((noticeCount < 0) || (!pDoc->IsConnected()) || m_bNeedsReloading) {
if (GetItemCount()) {
SetItemCount(0);
Refresh();
}
// Display "Fetching Notices" text only when connected
m_bDisplayFetchingNotices = pDoc->IsConnected();
m_bDisplayEmptyNotice = false;
m_bNeedsReloading = false;
return true;
}
if (noticeCount == 0) {
if (GetItemCount()) {
SetItemCount(0);
Refresh();
}
m_bDisplayFetchingNotices = false;
m_bDisplayEmptyNotice = true;
m_bNeedsReloading = false;
return true;
}
if (!bAlreadyRunning) {
bAlreadyRunning = true;
if (
pDoc->IsConnected() &&
(pDoc->notices.complete ||
((int)GetItemCount() != noticeCount))
) {
pDoc->notices.complete = false;
Freeze();
SetItemCount(noticeCount);
m_bDisplayFetchingNotices = false;
m_bDisplayEmptyNotice = false;
Thaw();
}
bAlreadyRunning = false;
}
return true;
}