diff --git a/src/librssguard/CMakeLists.txt b/src/librssguard/CMakeLists.txt index 203e032f8..9707f49f6 100644 --- a/src/librssguard/CMakeLists.txt +++ b/src/librssguard/CMakeLists.txt @@ -568,9 +568,13 @@ if(USE_WEBENGINE) endif() list(APPEND SOURCES - # Lite non-WebEngine message browser. + # Litehtml. gui/webviewers/litehtml/litehtmlviewer.h gui/webviewers/litehtml/litehtmlviewer.cpp + + # QTextBrowser. + gui/webviewers/qtextbrowser/textbrowserviewer.h + gui/webviewers/qtextbrowser/textbrowserviewer.cpp ) # Deal with .ui files. diff --git a/src/librssguard/gui/webbrowser.h b/src/librssguard/gui/webbrowser.h index 88f22c80d..503274663 100644 --- a/src/librssguard/gui/webbrowser.h +++ b/src/librssguard/gui/webbrowser.h @@ -26,10 +26,11 @@ class DiscoverFeedsButton; class SearchTextWidget; class WebBrowser : public TabContent { - Q_OBJECT + Q_OBJECT - friend class WebEngineViewer; - friend class LiteHtmlViewer; + friend class WebEngineViewer; + friend class LiteHtmlViewer; + friend class TextBrowserViewer; public: explicit WebBrowser(WebViewer* viewer = nullptr, QWidget* parent = nullptr); diff --git a/src/librssguard/gui/webviewers/litehtml/litehtmlviewer.cpp b/src/librssguard/gui/webviewers/litehtml/litehtmlviewer.cpp index 74624a5d3..8ebbfdb91 100644 --- a/src/librssguard/gui/webviewers/litehtml/litehtmlviewer.cpp +++ b/src/librssguard/gui/webviewers/litehtml/litehtmlviewer.cpp @@ -161,7 +161,8 @@ QPair LiteHtmlViewer::prepareHtmlForMessage(const QList& else {*/ QString cnts = message.m_contents; - html += cnts.replace(imgTagRegex, QString()); + html += cnts; + // html += cnts.replace(imgTagRegex, QString()); //} diff --git a/src/librssguard/gui/webviewers/qtextbrowser/textbrowserviewer.cpp b/src/librssguard/gui/webviewers/qtextbrowser/textbrowserviewer.cpp new file mode 100644 index 000000000..cdcd860ad --- /dev/null +++ b/src/librssguard/gui/webviewers/qtextbrowser/textbrowserviewer.cpp @@ -0,0 +1,275 @@ +// For license of this file, see /LICENSE.md. + +#include "gui/webviewers/qtextbrowser/textbrowserviewer.h" + +#include "gui/messagebox.h" +#include "gui/webbrowser.h" +#include "miscellaneous/application.h" +#include "miscellaneous/externaltool.h" +#include "miscellaneous/iconfactory.h" +#include "network-web/webfactory.h" + +#include +#include +#include + +TextBrowserViewer::TextBrowserViewer(QWidget* parent) : QTextBrowser(parent) { + setAutoFillBackground(true); + setFrameShape(QFrame::Shape::StyledPanel); + setFrameShadow(QFrame::Shadow::Plain); + setTabChangesFocus(true); + setOpenLinks(false); + viewport()->setAutoFillBackground(true); + + connect(this, &QTextBrowser::anchorClicked, this, &TextBrowserViewer::onAnchorClicked); + connect(this, QOverload::of(&QTextBrowser::highlighted), this, &TextBrowserViewer::linkMouseHighlighted); +} + +QVariant TextBrowserViewer::loadResource(int type, const QUrl& name) { + return {}; +} + +QSize TextBrowserViewer::sizeHint() const { + auto doc_size = document()->size().toSize(); + + doc_size.setHeight(doc_size.height() + contentsMargins().top() + contentsMargins().bottom()); + return doc_size; +} + +QPair TextBrowserViewer::prepareHtmlForMessage(const QList& messages, + RootItem* selected_item) const { + QString html; + + for (const Message& message : messages) { + html += QString("

%1

").arg(message.m_title); + + if (!message.m_url.isEmpty()) { + html += QString("[url] %1
").arg(message.m_url); + } + + for (const Enclosure& enc : message.m_enclosures) { + html += QString("[%2] %1
").arg(enc.m_url, enc.m_mimeType); + } + + QRegularExpression imgTagRegex("\\]*src\\s*=\\s*[\"\']([^\"\']*)[\"\'][^\\>]*\\>", + QRegularExpression::PatternOption::CaseInsensitiveOption | + QRegularExpression::PatternOption::InvertedGreedinessOption); + QRegularExpressionMatchIterator i = imgTagRegex.globalMatch(message.m_contents); + QString pictures_html; + + while (i.hasNext()) { + QRegularExpressionMatch match = i.next(); + + pictures_html += QString("
[%1] %2").arg(tr("image"), match.captured(1)); + } + + /*if (qApp->settings()->value(GROUP(Messages), SETTING(Messages::DisplayImagePlaceholders)).toBool()) { + html += message.m_contents; + } + else {*/ + QString cnts = message.m_contents; + + html += cnts; + // html += cnts.replace(imgTagRegex, QString()); + + //} + + html += pictures_html; + } + + // TODO: If FgInteresting not defined by the skin + // use current pallette/Highlight color perhaps. + return { + QSL("" + "" + "%1" + "") + .arg(html, + qApp->skins()->currentSkin().colorForModel(SkinEnums::PaletteColors::FgInteresting).value().name()), + QUrl()}; +} + +void TextBrowserViewer::bindToBrowser(WebBrowser* browser) { + installEventFilter(browser); + + browser->m_actionBack = new QAction(this); + browser->m_actionForward = new QAction(this); + browser->m_actionReload = new QAction(this); + browser->m_actionStop = new QAction(this); + + browser->m_actionBack->setEnabled(false); + browser->m_actionForward->setEnabled(false); + browser->m_actionReload->setEnabled(false); + + // TODO: When clicked "Stop", save the "Stop" state and return {} from "handleResource(...)" + // right away. + browser->m_actionStop->setEnabled(false); + + // TODO: add "Open in new tab" to context menu. +} + +void TextBrowserViewer::findText(const QString& text, bool backwards) { + if (!text.isEmpty()) { + QTextBrowser::find(text, backwards ? QTextDocument::FindFlag::FindBackward : QTextDocument::FindFlag(0)); + } + else { + textCursor().clearSelection(); + moveCursor(QTextCursor::MoveOperation::Left); + } +} + +void TextBrowserViewer::setUrl(const QUrl& url) {} + +void TextBrowserViewer::setHtml(const QString& html, const QUrl& base_url) { + m_currentUrl = base_url; + + QTextBrowser::setHtml(html); + + emit pageTitleChanged(documentTitle()); + emit pageUrlChanged(base_url); +} + +QString TextBrowserViewer::html() const { + return QTextBrowser::toHtml(); +} + +QUrl TextBrowserViewer::url() const { + return m_currentUrl; +} + +void TextBrowserViewer::clear() { + setHtml({}); +} + +void TextBrowserViewer::loadMessages(const QList& messages, RootItem* root) { + m_root = root; + + auto html_messages = prepareHtmlForMessage(messages, root); + + setHtml(html_messages.first, html_messages.second); + emit loadingFinished(true); +} + +double TextBrowserViewer::verticalScrollBarPosition() const { + return verticalScrollBar()->value(); +} + +void TextBrowserViewer::setVerticalScrollBarPosition(double pos) { + verticalScrollBar()->setValue(int(pos)); +} + +void TextBrowserViewer::applyFont(const QFont& fon) { + setFont(fon); +} + +qreal TextBrowserViewer::zoomFactor() const { + return font().pointSize() / 12.0; +} + +void TextBrowserViewer::setZoomFactor(qreal zoom_factor) { + auto fon = font(); + fon.setPointSize(fon.pointSize() * zoom_factor); +} + +void TextBrowserViewer::contextMenuEvent(QContextMenuEvent* event) { + event->accept(); + + auto* menu = createStandardContextMenu(); + + if (menu == nullptr) { + return; + } + + auto anchor = anchorAt(event->pos()); + + if (!anchor.isEmpty()) { + QFileIconProvider icon_provider; + QMenu* menu_ext_tools = new QMenu(tr("Open with external tool"), menu); + auto tools = ExternalTool::toolsFromSettings(); + + menu_ext_tools->setIcon(qApp->icons()->fromTheme(QSL("document-open"))); + + for (const ExternalTool& tool : qAsConst(tools)) { + QAction* act_tool = new QAction(QFileInfo(tool.executable()).fileName(), menu_ext_tools); + + act_tool->setIcon(icon_provider.icon(QFileInfo(tool.executable()))); + act_tool->setToolTip(tool.executable()); + act_tool->setData(QVariant::fromValue(tool)); + menu_ext_tools->addAction(act_tool); + + connect(act_tool, &QAction::triggered, this, [act_tool, anchor]() { + act_tool->data().value().run(anchor); + }); + } + + if (menu_ext_tools->actions().isEmpty()) { + QAction* act_not_tools = new QAction("No external tools activated"); + + act_not_tools->setEnabled(false); + menu_ext_tools->addAction(act_not_tools); + } + + menu->addMenu(menu_ext_tools); + } + + menu->popup(event->globalPos()); +} + +void TextBrowserViewer::resizeEvent(QResizeEvent* event) { + // Notify parents about changed geometry. + updateGeometry(); + QTextBrowser::resizeEvent(event); +} + +void TextBrowserViewer::onAnchorClicked(const QUrl& url) { + if (!url.isEmpty()) { + bool open_externally_now = + qApp->settings()->value(GROUP(Browser), SETTING(Browser::OpenLinksInExternalBrowserRightAway)).toBool(); + + if (open_externally_now) { + qApp->web()->openUrlInExternalBrowser(url.toString()); + } + else { + // User clicked some URL. Open it in external browser or download? + MsgBox box(qApp->mainFormWidget()); + + box.setText(tr("You clicked some link. You can download the link contents or open it in external web browser.")); + box.setInformativeText(tr("What action do you want to take?")); + box.setDetailedText(url.toString()); + + QAbstractButton* btn_open = box.addButton(tr("Open in external browser"), QMessageBox::ButtonRole::ActionRole); + QAbstractButton* btn_download = box.addButton(tr("Download"), QMessageBox::ButtonRole::ActionRole); + QAbstractButton* btn_cancel = box.addButton(QMessageBox::StandardButton::Cancel); + bool always; + + MsgBox::setCheckBox(&box, tr("Always open links in external browser."), &always); + + box.setDefaultButton(QMessageBox::StandardButton::Cancel); + box.exec(); + + if (box.clickedButton() != box.button(QMessageBox::StandardButton::Cancel)) { + // Store selected checkbox value. + qApp->settings()->setValue(GROUP(Browser), Browser::OpenLinksInExternalBrowserRightAway, always); + } + + if (box.clickedButton() == btn_open) { + qApp->web()->openUrlInExternalBrowser(url.toString()); + } + else if (box.clickedButton() == btn_download) { + qApp->downloadManager()->download(url); + } + + btn_download->deleteLater(); + btn_open->deleteLater(); + btn_cancel->deleteLater(); + } + } + else { + MsgBox::show(qApp->mainFormWidget(), + QMessageBox::Warning, + tr("Incorrect link"), + tr("Selected hyperlink is invalid.")); + } +} diff --git a/src/librssguard/gui/webviewers/qtextbrowser/textbrowserviewer.h b/src/librssguard/gui/webviewers/qtextbrowser/textbrowserviewer.h new file mode 100644 index 000000000..64b6d9664 --- /dev/null +++ b/src/librssguard/gui/webviewers/qtextbrowser/textbrowserviewer.h @@ -0,0 +1,67 @@ +// For license of this file, see /LICENSE.md. + +#ifndef TEXTBROWSERVIEWER_H +#define TEXTBROWSERVIEWER_H + +#include "gui/webviewers/webviewer.h" + +#include + +#include + +class QContextMenuEvent; +class QResizeEvent; +class WebBrowser; + +class TextBrowserViewer : public QTextBrowser, public WebViewer { + Q_OBJECT + Q_INTERFACES(WebViewer) + + public: + explicit TextBrowserViewer(QWidget* parent = nullptr); + + virtual QVariant loadResource(int type, const QUrl& name); + virtual QSize sizeHint() const; + + public: + virtual void bindToBrowser(WebBrowser* browser); + virtual void findText(const QString& text, bool backwards); + virtual void setUrl(const QUrl& url); + virtual void setHtml(const QString& html, const QUrl& base_url = {}); + virtual QString html() const; + virtual QUrl url() const; + virtual void clear(); + virtual void loadMessages(const QList& messages, RootItem* root); + virtual double verticalScrollBarPosition() const; + virtual void setVerticalScrollBarPosition(double pos); + virtual void applyFont(const QFont& fon); + virtual qreal zoomFactor() const; + virtual void setZoomFactor(qreal zoom_factor); + + protected: + virtual void contextMenuEvent(QContextMenuEvent* event); + virtual void resizeEvent(QResizeEvent* event); + + private slots: + void onAnchorClicked(const QUrl& url); + + signals: + void pageTitleChanged(const QString& new_title); + void pageUrlChanged(const QUrl& url); + void pageIconChanged(const QIcon&); + void linkMouseHighlighted(const QUrl& url); + void loadingStarted(); + void loadingProgress(int progress); + void loadingFinished(bool success); + void newWindowRequested(WebViewer* viewer); + void closeWindowRequested(); + + private: + QPair prepareHtmlForMessage(const QList& messages, RootItem* selected_item) const; + + private: + QUrl m_currentUrl; + QPointer m_root; +}; + +#endif // TEXTBROWSERVIEWER_H diff --git a/src/librssguard/miscellaneous/application.cpp b/src/librssguard/miscellaneous/application.cpp index fc758a97c..047ba8120 100644 --- a/src/librssguard/miscellaneous/application.cpp +++ b/src/librssguard/miscellaneous/application.cpp @@ -11,7 +11,8 @@ #include "gui/feedsview.h" #include "gui/messagebox.h" #include "gui/toolbars/statusbar.h" -#include "gui/webviewers/litehtml/litehtmlviewer.h" // QLiteHtml-based web browsing. +#include "gui/webviewers/litehtml/litehtmlviewer.h" // QLiteHtml-based web browsing. +#include "gui/webviewers/qtextbrowser/textbrowserviewer.h" // QTextBrowser-based web browsing. #include "miscellaneous/feedreader.h" #include "miscellaneous/iconfactory.h" #include "miscellaneous/iofactory.h" @@ -62,7 +63,7 @@ #endif #endif -Application::Application(const QString &id, int &argc, char **argv, const QStringList &raw_cli_args) +Application::Application(const QString& id, int& argc, char** argv, const QStringList& raw_cli_args) : SingleApplication(id, argc, argv), m_updateFeedsLock(new Mutex()) { parseCmdArgumentsFromMyInstance(raw_cli_args); qInstallMessageHandler(performLogging); @@ -87,15 +88,19 @@ Application::Application(const QString &id, int &argc, char **argv, const QStrin m_windowsTaskBar = nullptr; const GUID qIID_ITaskbarList4 = {0xc43dc798, 0x95d1, 0x4bea, {0x90, 0x30, 0xbb, 0x99, 0xe2, 0x98, 0x3a, 0x1a}}; - HRESULT task_result = CoCreateInstance(CLSID_TaskbarList, nullptr, CLSCTX_INPROC_SERVER, qIID_ITaskbarList4, - reinterpret_cast(&m_windowsTaskBar)); + HRESULT task_result = CoCreateInstance(CLSID_TaskbarList, + nullptr, + CLSCTX_INPROC_SERVER, + qIID_ITaskbarList4, + reinterpret_cast(&m_windowsTaskBar)); if (FAILED(task_result)) { qCriticalNN << LOGSEC_CORE << "Taskbar integration for Windows failed to initialize with HRESULT:" << QUOTE_W_SPACE_DOT(task_result); m_windowsTaskBar = nullptr; - } else if (FAILED(m_windowsTaskBar->HrInit())) { + } + else if (FAILED(m_windowsTaskBar->HrInit())) { qCriticalNN << LOGSEC_CORE << "Taskbar integration for Windows failed to initialize with inner HRESULT:" << QUOTE_W_SPACE_DOT(m_windowsTaskBar->HrInit()); @@ -156,7 +161,9 @@ Application::Application(const QString &id, int &argc, char **argv, const QStrin qDebugNN << LOGSEC_NETWORK << "Persistent web data storage path:" << QUOTE_W_SPACE_DOT(QWebEngineProfile::defaultProfile()->persistentStoragePath()); - connect(QWebEngineProfile::defaultProfile(), &QWebEngineProfile::downloadRequested, this, + connect(QWebEngineProfile::defaultProfile(), + &QWebEngineProfile::downloadRequested, + this, &Application::downloadRequested); #endif @@ -164,9 +171,10 @@ Application::Application(const QString &id, int &argc, char **argv, const QStrin QTimer::singleShot(3000, this, [=]() { try { - m_webFactory->adBlock()->setEnabled( - qApp->settings()->value(GROUP(AdBlock), SETTING(AdBlock::AdBlockEnabled)).toBool()); - } catch (...) { + m_webFactory->adBlock() + ->setEnabled(qApp->settings()->value(GROUP(AdBlock), SETTING(AdBlock::AdBlockEnabled)).toBool()); + } + catch (...) { onAdBlockFailure(); } }); @@ -175,14 +183,16 @@ Application::Application(const QString &id, int &argc, char **argv, const QStrin if (isFirstRun()) { m_notifications->save({Notification(Notification::Event::GeneralEvent, true), - Notification(Notification::Event::NewUnreadArticlesFetched, true, + Notification(Notification::Event::NewUnreadArticlesFetched, + true, QSL("%1/notify.wav").arg(SOUNDS_BUILTIN_DIRECTORY)), Notification(Notification::Event::NewAppVersionAvailable, true), Notification(Notification::Event::LoginFailure, true), Notification(Notification::Event::NodePackageUpdated, true), Notification(Notification::Event::NodePackageFailedToUpdate, true)}, settings()); - } else { + } + else { m_notifications->load(settings()); } @@ -206,7 +216,7 @@ Application::~Application() { QString s_customLogFile = QString(); bool s_disableDebug = false; -void Application::performLogging(QtMsgType type, const QMessageLogContext &context, const QString &msg) { +void Application::performLogging(QtMsgType type, const QMessageLogContext& context, const QString& msg) { #ifndef QT_NO_DEBUG_OUTPUT QString console_message = qFormatLogMessage(type, context, msg); @@ -244,13 +254,16 @@ void Application::hideOrShowMainForm() { SystemTrayIcon::isSystemTrayDesired() && SystemTrayIcon::isSystemTrayAreaAvailable()) { qDebugNN << LOGSEC_CORE << "Hiding the main window when the application is starting."; mainForm()->switchVisibility(true); - } else { + } + else { qDebugNN << LOGSEC_CORE << "Showing the main window when the application is starting."; mainForm()->show(); } } -void Application::loadDynamicShortcuts() { DynamicShortcuts::load(userActions()); } +void Application::loadDynamicShortcuts() { + DynamicShortcuts::load(userActions()); +} void Application::showPolls() const { /* @@ -271,7 +284,10 @@ void Application::offerChanges() const { "version by clicking this popup notification.") .arg(QSL(APP_LONG_NAME)), QSystemTrayIcon::MessageIcon::NoIcon}, - {}, {tr("Go to changelog"), [] { FormAbout(qApp->mainForm()).exec(); }}); + {}, + {tr("Go to changelog"), [] { + FormAbout(qApp->mainForm()).exec(); + }}); } } @@ -284,15 +300,21 @@ bool Application::isAlreadyRunning() { QStringList Application::builtinSounds() const { auto builtin_sounds = QDir(QSL(SOUNDS_BUILTIN_DIRECTORY)).entryInfoList(QDir::Filter::Files, QDir::SortFlag::Name); - auto iter = boolinq::from(builtin_sounds).select([](const QFileInfo &i) { return i.absoluteFilePath(); }).toStdList(); + auto iter = boolinq::from(builtin_sounds) + .select([](const QFileInfo& i) { + return i.absoluteFilePath(); + }) + .toStdList(); auto descs = FROM_STD_LIST(QStringList, iter); return descs; } -FeedReader *Application::feedReader() { return m_feedReader; } +FeedReader* Application::feedReader() { + return m_feedReader; +} -QList Application::userActions() { +QList Application::userActions() { if (m_mainForm != nullptr && m_userActions.isEmpty()) { m_userActions = m_mainForm->allActions(); m_userActions.append(m_webFactory->adBlock()->adBlockIcon()); @@ -301,21 +323,37 @@ QList Application::userActions() { return m_userActions; } -bool Application::isFirstRun() const { return m_firstRunEver; } +bool Application::isFirstRun() const { + return m_firstRunEver; +} -bool Application::isFirstRunCurrentVersion() const { return m_firstRunCurrentVersion; } +bool Application::isFirstRunCurrentVersion() const { + return m_firstRunCurrentVersion; +} -QCommandLineParser *Application::cmdParser() { return &m_cmdParser; } +QCommandLineParser* Application::cmdParser() { + return &m_cmdParser; +} -WebFactory *Application::web() const { return m_webFactory; } +WebFactory* Application::web() const { + return m_webFactory; +} -SystemFactory *Application::system() { return m_system; } +SystemFactory* Application::system() { + return m_system; +} -SkinFactory *Application::skins() { return m_skins; } +SkinFactory* Application::skins() { + return m_skins; +} -Localization *Application::localization() { return m_localization; } +Localization* Application::localization() { + return m_localization; +} -DatabaseFactory *Application::database() { return m_database; } +DatabaseFactory* Application::database() { + return m_database; +} void Application::eliminateFirstRuns() { settings()->setValue(GROUP(General), General::FirstRun, false); @@ -323,15 +361,21 @@ void Application::eliminateFirstRuns() { } #if defined(USE_WEBENGINE) -bool Application::forcedNoWebEngine() const { return m_forcedNoWebEngine; } +bool Application::forcedNoWebEngine() const { + return m_forcedNoWebEngine; +} #endif -NodeJs *Application::nodejs() const { return m_nodejs; } +NodeJs* Application::nodejs() const { + return m_nodejs; +} -NotificationFactory *Application::notifications() const { return m_notifications; } +NotificationFactory* Application::notifications() const { + return m_notifications; +} -void Application::setFeedReader(FeedReader *feed_reader) { +void Application::setFeedReader(FeedReader* feed_reader) { m_feedReader = feed_reader; connect(m_feedReader, &FeedReader::feedUpdatesStarted, this, &Application::onFeedUpdatesStarted); @@ -340,29 +384,45 @@ void Application::setFeedReader(FeedReader *feed_reader) { connect(m_feedReader->feedsModel(), &FeedsModel::messageCountsChanged, this, &Application::showMessagesNumber); } -IconFactory *Application::icons() { return m_icons; } +IconFactory* Application::icons() { + return m_icons; +} -DownloadManager *Application::downloadManager() { +DownloadManager* Application::downloadManager() { if (m_downloadManager == nullptr) { m_downloadManager = new DownloadManager(); - connect(m_downloadManager, &DownloadManager::downloadFinished, mainForm()->statusBar(), + connect(m_downloadManager, + &DownloadManager::downloadFinished, + mainForm()->statusBar(), &StatusBar::clearProgressDownload); - connect(m_downloadManager, &DownloadManager::downloadProgressed, mainForm()->statusBar(), + connect(m_downloadManager, + &DownloadManager::downloadProgressed, + mainForm()->statusBar(), &StatusBar::showProgressDownload); } return m_downloadManager; } -Settings *Application::settings() const { return m_settings; } +Settings* Application::settings() const { + return m_settings; +} -Mutex *Application::feedUpdateLock() { return m_updateFeedsLock.data(); } +Mutex* Application::feedUpdateLock() { + return m_updateFeedsLock.data(); +} -FormMain *Application::mainForm() { return m_mainForm; } +FormMain* Application::mainForm() { + return m_mainForm; +} -QWidget *Application::mainFormWidget() { return m_mainForm; } +QWidget* Application::mainFormWidget() { + return m_mainForm; +} -void Application::setMainForm(FormMain *main_form) { m_mainForm = main_form; } +void Application::setMainForm(FormMain* main_form) { + m_mainForm = main_form; +} QString Application::configFolder() const { return IOFactory::getSystemFolder(QStandardPaths::StandardLocation::GenericConfigLocation); @@ -377,9 +437,11 @@ QString Application::userDataAppFolder() const { QString Application::userDataFolder() { if (settings()->type() == SettingsProperties::SettingsType::Custom) { return customDataFolder(); - } else if (settings()->type() == SettingsProperties::SettingsType::Portable) { + } + else if (settings()->type() == SettingsProperties::SettingsType::Portable) { return userDataAppFolder(); - } else { + } + else { return userDataHomeFolder(); } } @@ -421,8 +483,10 @@ QString Application::homeFolder() const { #endif } -void Application::backupDatabaseSettings(bool backup_database, bool backup_settings, const QString &target_path, - const QString &backup_name) { +void Application::backupDatabaseSettings(bool backup_database, + bool backup_settings, + const QString& target_path, + const QString& backup_name) { if (!QFileInfo(target_path).isWritable()) { throw ApplicationException(tr("Output directory is not writable.")); } @@ -443,29 +507,31 @@ void Application::backupDatabaseSettings(bool backup_database, bool backup_setti } } -void Application::restoreDatabaseSettings(bool restore_database, bool restore_settings, - const QString &source_database_file_path, - const QString &source_settings_file_path) { +void Application::restoreDatabaseSettings(bool restore_database, + bool restore_settings, + const QString& source_database_file_path, + const QString& source_settings_file_path) { if (restore_database) { if (!qApp->database()->driver()->initiateRestoration(source_database_file_path)) { - throw ApplicationException( - tr("Database restoration was not initiated. Make sure that output directory is writable.")); + throw ApplicationException(tr("Database restoration was not initiated. Make sure that output directory is " + "writable.")); } } if (restore_settings) { if (!qApp->settings()->initiateRestoration(source_settings_file_path)) { - throw ApplicationException( - tr("Settings restoration was not initiated. Make sure that output directory is writable.")); + throw ApplicationException(tr("Settings restoration was not initiated. Make sure that output directory is " + "writable.")); } } } -SystemTrayIcon *Application::trayIcon() { +SystemTrayIcon* Application::trayIcon() { if (m_trayIcon == nullptr) { if (qApp->settings()->value(GROUP(GUI), SETTING(GUI::MonochromeTrayIcon)).toBool()) { m_trayIcon = new SystemTrayIcon(APP_ICON_MONO_PATH, APP_ICON_MONO_PLAIN_PATH, m_mainForm); - } else { + } + else { m_trayIcon = new SystemTrayIcon(APP_ICON_PATH, APP_ICON_PLAIN_PATH, m_mainForm); } @@ -480,7 +546,8 @@ QIcon Application::desktopAwareIcon() const { if (!from_theme.isNull()) { return from_theme; - } else { + } + else { return QIcon(APP_ICON_PATH); } } @@ -494,7 +561,8 @@ void Application::showTrayIcon() { if (SystemTrayIcon::isSystemTrayAreaAvailable()) { qDebugNN << LOGSEC_GUI << "Tray icon is available, showing now."; trayIcon()->show(); - } else { + } + else { m_feedReader->feedsModel()->notifyWithCounts(); } #else @@ -504,12 +572,14 @@ void Application::showTrayIcon() { if (SystemTrayIcon::isSystemTrayAreaAvailable()) { qWarningNN << LOGSEC_GUI << "Tray icon is available, showing now."; trayIcon()->show(); - } else { + } + else { m_feedReader->feedsModel()->notifyWithCounts(); } }); #endif - } else { + } + else { m_feedReader->feedsModel()->notifyWithCounts(); } } @@ -526,8 +596,11 @@ void Application::deleteTrayIcon() { } } -void Application::showGuiMessage(Notification::Event event, const GuiMessage &msg, const GuiMessageDestination &dest, - const GuiAction &action, QWidget *parent) { +void Application::showGuiMessage(Notification::Event event, + const GuiMessage& msg, + const GuiMessageDestination& dest, + const GuiAction& action, + QWidget* parent) { if (SystemTrayIcon::areNotificationsEnabled()) { auto notification = m_notifications->notificationForEvent(event); @@ -538,36 +611,51 @@ void Application::showGuiMessage(Notification::Event event, const GuiMessage &ms notification.balloonEnabled() && dest.m_tray) { trayIcon()->showMessage(msg.m_title.simplified().isEmpty() ? Notification::nameForEvent(notification.event()) : msg.m_title, - msg.m_message, msg.m_type, TRAY_ICON_BUBBLE_TIMEOUT, std::move(action.m_action)); + msg.m_message, + msg.m_type, + TRAY_ICON_BUBBLE_TIMEOUT, + std::move(action.m_action)); return; } } if (dest.m_messageBox || msg.m_type == QSystemTrayIcon::MessageIcon::Critical) { // Tray icon or OSD is not available, display simple text box. - MsgBox::show(parent == nullptr ? mainFormWidget() : parent, QMessageBox::Icon(msg.m_type), msg.m_title, - msg.m_message, {}, {}, QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok, {}, - action.m_title, action.m_action); - } else if (dest.m_statusBar && mainForm()->statusBar() != nullptr && mainForm()->statusBar()->isVisible()) { + MsgBox::show(parent == nullptr ? mainFormWidget() : parent, + QMessageBox::Icon(msg.m_type), + msg.m_title, + msg.m_message, + {}, + {}, + QMessageBox::StandardButton::Ok, + QMessageBox::StandardButton::Ok, + {}, + action.m_title, + action.m_action); + } + else if (dest.m_statusBar && mainForm()->statusBar() != nullptr && mainForm()->statusBar()->isVisible()) { mainForm()->statusBar()->showMessage(msg.m_message); - } else { + } + else { qDebugNN << LOGSEC_CORE << "Silencing GUI message:" << QUOTE_W_SPACE_DOT(msg.m_message); } } -WebViewer *Application::createWebView() { +WebViewer* Application::createWebView() { #if !defined(USE_WEBENGINE) return new LiteHtmlViewer(); #else if (forcedNoWebEngine()) { - return new LiteHtmlViewer(); - } else { + return new TextBrowserViewer(); + // return new LiteHtmlViewer(); + } + else { return new WebEngineViewer(); } #endif } -void Application::onCommitData(QSessionManager &manager) { +void Application::onCommitData(QSessionManager& manager) { qDebugNN << LOGSEC_CORE << "OS asked application to commit its data."; onAboutToQuit(); @@ -576,7 +664,7 @@ void Application::onCommitData(QSessionManager &manager) { manager.release(); } -void Application::onSaveState(QSessionManager &manager) { +void Application::onSaveState(QSessionManager& manager) { qDebugNN << LOGSEC_CORE << "OS asked application to save its state."; manager.setRestartHint(QSessionManager::RestartHint::RestartNever); @@ -603,7 +691,8 @@ void Application::onAboutToQuit() { // We locked the lock to exit peacefully, unlock it to avoid warnings. feedUpdateLock()->unlock(); - } else { + } + else { // Request for write lock timed-out. This means // that some critical action can be processed right now. qWarningNN << LOGSEC_CORE << "Close lock timed-out."; @@ -623,7 +712,8 @@ void Application::onAboutToQuit() { if (QProcess::startDetached(QDir::toNativeSeparators(applicationFilePath()), arguments().mid(1))) { qDebugNN << LOGSEC_CORE << "New application instance was started."; - } else { + } + else { qCriticalNN << LOGSEC_CORE << "New application instance was not started successfully."; } } @@ -663,9 +753,10 @@ void Application::showMessagesNumber(int unread_messages, bool any_feed_has_new_ HICON overlay_hicon = overlay_icon.toHICON(); #endif - HRESULT overlay_result = m_windowsTaskBar->SetOverlayIcon( - reinterpret_cast(m_mainForm->winId()), - (task_bar_count_enabled && unread_messages > 0) ? overlay_hicon : nullptr, nullptr); + HRESULT overlay_result = + m_windowsTaskBar->SetOverlayIcon(reinterpret_cast(m_mainForm->winId()), + (task_bar_count_enabled && unread_messages > 0) ? overlay_hicon : nullptr, + nullptr); DestroyIcon(overlay_hicon); @@ -690,9 +781,11 @@ QImage Application::generateOverlayIcon(int number) const { if (number < 1000) { num_txt = QString::number(number); - } else if (number < 100000) { + } + else if (number < 100000) { num_txt = QSL("%1k").arg(int(number / 1000)); - } else { + } + else { num_txt = QChar(8734); } @@ -702,9 +795,11 @@ QImage Application::generateOverlayIcon(int number) const { if (num_txt.size() == 3) { fon.setPixelSize(img.width() * 0.52); - } else if (num_txt.size() == 2) { + } + else if (num_txt.size() == 2) { fon.setPixelSize(img.width() * 0.68); - } else { + } + else { fon.setPixelSize(img.width() * 0.79); } @@ -721,7 +816,8 @@ QImage Application::generateOverlayIcon(int number) const { p.setPen(Qt::GlobalColor::black); p.drawPath(rounded_rectangle); - p.drawText(img.rect().marginsRemoved(QMargins(0, 0, 0, img.height() * 0.05)), num_txt, + p.drawText(img.rect().marginsRemoved(QMargins(0, 0, 0, img.height() * 0.05)), + num_txt, QTextOption(Qt::AlignmentFlag::AlignCenter)); p.end(); @@ -738,9 +834,9 @@ void Application::restart() { #if defined(USE_WEBENGINE) #if QT_VERSION_MAJOR == 6 -void Application::downloadRequested(QWebEngineDownloadRequest *download_item) { +void Application::downloadRequested(QWebEngineDownloadRequest* download_item) { #else -void Application::downloadRequested(QWebEngineDownloadItem *download_item) { +void Application::downloadRequested(QWebEngineDownloadItem* download_item) { #endif downloadManager()->download(download_item->url()); download_item->cancel(); @@ -760,7 +856,7 @@ void Application::onFeedUpdatesStarted() { #endif } -void Application::onFeedUpdatesProgress(const Feed *feed, int current, int total) { +void Application::onFeedUpdatesProgress(const Feed* feed, int current, int total) { #if defined(Q_OS_WIN) // Use SetOverlayIcon Windows API method on Windows. bool task_bar_count_enabled = settings()->value(GROUP(GUI), SETTING(GUI::UnreadNumbersOnTaskBar)).toBool(); @@ -771,7 +867,7 @@ void Application::onFeedUpdatesProgress(const Feed *feed, int current, int total #endif } -void Application::onFeedUpdatesFinished(const FeedDownloadResults &results) { +void Application::onFeedUpdatesFinished(const FeedDownloadResults& results) { if (!results.updatedFeeds().isEmpty()) { // Now, inform about results via GUI message/notification. qApp->showGuiMessage(Notification::Event::NewUnreadArticlesFetched, @@ -788,7 +884,7 @@ void Application::onFeedUpdatesFinished(const FeedDownloadResults &results) { #endif } -void Application::setupCustomDataFolder(const QString &data_folder) { +void Application::setupCustomDataFolder(const QString& data_folder) { if (!QDir().mkpath(data_folder)) { qCriticalNN << LOGSEC_CORE << "Failed to create custom data path" << QUOTE_W_SPACE(data_folder) << "thus falling back to standard setup."; @@ -822,7 +918,7 @@ void Application::determineFirstRuns() { eliminateFirstRuns(); } -void Application::parseCmdArgumentsFromOtherInstance(const QString &message) { +void Application::parseCmdArgumentsFromOtherInstance(const QString& message) { if (message.isEmpty()) { qDebugNN << LOGSEC_CORE << "No execution message received from other app instances."; return; @@ -842,9 +938,10 @@ void Application::parseCmdArgumentsFromOtherInstance(const QString &message) { cmd_parser.addOption(QCommandLineOption({QSL(CLI_QUIT_INSTANCE)})); cmd_parser.addOption(QCommandLineOption({QSL(CLI_IS_RUNNING)})); - cmd_parser.addPositionalArgument( - QSL("urls"), QSL("List of URL addresses pointing to individual online feeds which should be added."), - QSL("[url-1 ... url-n]")); + cmd_parser + .addPositionalArgument(QSL("urls"), + QSL("List of URL addresses pointing to individual online feeds which should be added."), + QSL("[url-1 ... url-n]")); if (!cmd_parser.parse(messages)) { qCriticalNN << LOGSEC_CORE << cmd_parser.errorText(); @@ -853,23 +950,27 @@ void Application::parseCmdArgumentsFromOtherInstance(const QString &message) { if (cmd_parser.isSet(QSL(CLI_QUIT_INSTANCE))) { quit(); return; - } else if (cmd_parser.isSet(QSL(CLI_IS_RUNNING))) { - showGuiMessage(Notification::Event::GeneralEvent, {tr("Already running"), tr("Application is already running."), - QSystemTrayIcon::MessageIcon::Information}); + } + else if (cmd_parser.isSet(QSL(CLI_IS_RUNNING))) { + showGuiMessage(Notification::Event::GeneralEvent, + {tr("Already running"), + tr("Application is already running."), + QSystemTrayIcon::MessageIcon::Information}); mainForm()->display(); } messages = cmd_parser.positionalArguments(); - for (const QString &msg : qAsConst(messages)) { + for (const QString& msg : qAsConst(messages)) { // Application was running, and someone wants to add new feed. - ServiceRoot *rt = boolinq::from(feedReader()->feedsModel()->serviceRoots()).firstOrDefault([](ServiceRoot *root) { + ServiceRoot* rt = boolinq::from(feedReader()->feedsModel()->serviceRoots()).firstOrDefault([](ServiceRoot* root) { return root->supportsFeedAdding(); }); if (rt != nullptr) { rt->addNewFeed(nullptr, msg); - } else { + } + else { showGuiMessage(Notification::Event::GeneralEvent, {tr("Cannot add feed"), tr("Feed cannot be added because there is no active account which can add feeds."), @@ -878,15 +979,17 @@ void Application::parseCmdArgumentsFromOtherInstance(const QString &message) { } } -void Application::parseCmdArgumentsFromMyInstance(const QStringList &raw_cli_args) { +void Application::parseCmdArgumentsFromMyInstance(const QStringList& raw_cli_args) { QCommandLineOption help({QSL(CLI_HELP_SHORT), QSL(CLI_HELP_LONG)}, QSL("Displays overview of CLI.")); QCommandLineOption version({QSL(CLI_VER_SHORT), QSL(CLI_VER_LONG)}, QSL("Displays version of the application.")); - QCommandLineOption log_file( - {QSL(CLI_LOG_SHORT), QSL(CLI_LOG_LONG)}, - QSL("Write application debug log to file. Note that logging to file may slow application down."), QSL("log-file")); - QCommandLineOption custom_data_folder( - {QSL(CLI_DAT_SHORT), QSL(CLI_DAT_LONG)}, - QSL("Use custom folder for user data and disable single instance application mode."), QSL("user-data-folder")); + QCommandLineOption + log_file({QSL(CLI_LOG_SHORT), QSL(CLI_LOG_LONG)}, + QSL("Write application debug log to file. Note that logging to file may slow application down."), + QSL("log-file")); + QCommandLineOption + custom_data_folder({QSL(CLI_DAT_SHORT), QSL(CLI_DAT_LONG)}, + QSL("Use custom folder for user data and disable single instance application mode."), + QSL("user-data-folder")); QCommandLineOption disable_singleinstance({QSL(CLI_SIN_SHORT), QSL(CLI_SIN_LONG)}, QSL("Allow running of multiple application instances.")); @@ -899,7 +1002,8 @@ void Application::parseCmdArgumentsFromMyInstance(const QStringList &raw_cli_arg QSL("Disable just \"debug\" output.")); QCommandLineOption disable_debug({QSL(CLI_NSTDOUTERR_SHORT), QSL(CLI_NSTDOUTERR_LONG)}, QSL("Completely disable stdout/stderr outputs.")); - QCommandLineOption forced_style({QSL(CLI_STYLE_SHORT), QSL(CLI_STYLE_LONG)}, QSL("Force some application style."), + QCommandLineOption forced_style({QSL(CLI_STYLE_SHORT), QSL(CLI_STYLE_LONG)}, + QSL("Force some application style."), QSL("style-name")); m_cmdParser.addOptions({ @@ -909,9 +1013,10 @@ void Application::parseCmdArgumentsFromMyInstance(const QStringList &raw_cli_arg #endif forced_style }); - m_cmdParser.addPositionalArgument( - QSL("urls"), QSL("List of URL addresses pointing to individual online feeds which should be added."), - QSL("[url-1 ... url-n]")); + m_cmdParser + .addPositionalArgument(QSL("urls"), + QSL("List of URL addresses pointing to individual online feeds which should be added."), + QSL("[url-1 ... url-n]")); m_cmdParser.setApplicationDescription(QSL(APP_NAME)); m_cmdParser.setSingleDashWordOptionMode(QCommandLineParser::SingleDashWordOptionMode::ParseAsLongOptions); @@ -940,13 +1045,15 @@ void Application::parseCmdArgumentsFromMyInstance(const QStringList &raw_cli_arg << QUOTE_W_SPACE_DOT(data_folder); setupCustomDataFolder(data_folder); - } else { + } + else { m_allowMultipleInstances = false; } if (m_cmdParser.isSet(QSL(CLI_HELP_SHORT))) { m_cmdParser.showHelp(); - } else if (m_cmdParser.isSet(QSL(CLI_VER_SHORT))) { + } + else if (m_cmdParser.isSet(QSL(CLI_VER_SHORT))) { m_cmdParser.showVersion(); } @@ -969,15 +1076,15 @@ void Application::parseCmdArgumentsFromMyInstance(const QStringList &raw_cli_arg } } -void Application::onNodeJsPackageUpdateError(const QList &pkgs, const QString &error) { - qApp->showGuiMessage( - Notification::Event::NodePackageFailedToUpdate, - {{}, - tr("Packages %1 were NOT updated because of error: %2.").arg(NodeJs::packagesToString(pkgs), error), - QSystemTrayIcon::MessageIcon::Critical}); +void Application::onNodeJsPackageUpdateError(const QList& pkgs, const QString& error) { + qApp->showGuiMessage(Notification::Event::NodePackageFailedToUpdate, + {{}, + tr("Packages %1 were NOT updated because of error: %2.") + .arg(NodeJs::packagesToString(pkgs), error), + QSystemTrayIcon::MessageIcon::Critical}); } -void Application::onNodeJsPackageInstalled(const QList &pkgs, bool already_up_to_date) { +void Application::onNodeJsPackageInstalled(const QList& pkgs, bool already_up_to_date) { if (!already_up_to_date) { qApp->showGuiMessage(Notification::Event::NodePackageUpdated, {{}, @@ -986,4 +1093,6 @@ void Application::onNodeJsPackageInstalled(const QList } } -QString Application::customDataFolder() const { return m_customDataFolder; } +QString Application::customDataFolder() const { + return m_customDataFolder; +}