1 // For license of this file, see <project-root-folder>/LICENSE.md.
2
3 #include "gui/feedmessageviewer.h"
4
5 #include "3rd-party/boolinq/boolinq.h"
6 #include "core/feeddownloader.h"
7 #include "core/feedsproxymodel.h"
8 #include "core/messagesproxymodel.h"
9 #include "database/databasecleaner.h"
10 #include "database/databasefactory.h"
11 #include "exceptions/applicationexception.h"
12 #include "gui/dialogs/formdatabasecleanup.h"
13 #include "gui/dialogs/formmain.h"
14 #include "gui/feedsview.h"
15 #include "gui/messagebox.h"
16 #include "gui/messagepreviewer.h"
17 #include "gui/messagesview.h"
18 #include "gui/systemtrayicon.h"
19 #include "gui/toolbars/feedstoolbar.h"
20 #include "gui/toolbars/messagestoolbar.h"
21 #include "gui/toolbars/statusbar.h"
22 #include "miscellaneous/feedreader.h"
23 #include "miscellaneous/iconfactory.h"
24 #include "miscellaneous/mutex.h"
25 #include "miscellaneous/settings.h"
26 #include "miscellaneous/systemfactory.h"
27 #include "miscellaneous/templates.h"
28 #include "services/standard/standardfeed.h"
29 #include "services/standard/standardfeedsimportexportmodel.h"
30 #include "services/standard/standardserviceroot.h"
31
32 #if defined(USE_WEBENGINE)
33 #include "gui/webbrowser.h"
34 #endif
35
36 #include <QAction>
37 #include <QDebug>
38 #include <QLineEdit>
39 #include <QMenu>
40 #include <QPointer>
41 #include <QProgressBar>
42 #include <QSplitter>
43 #include <QStatusBar>
44 #include <QToolBar>
45 #include <QToolButton>
46 #include <QVBoxLayout>
47 #include <QWidgetAction>
48
FeedMessageViewer(QWidget * parent)49 FeedMessageViewer::FeedMessageViewer(QWidget* parent) : TabContent(parent), m_toolBarsEnabled(true), m_listHeadersEnabled(true),
50 m_toolBarFeeds(new FeedsToolBar(tr("Toolbar for feeds"), this)), m_toolBarMessages(new MessagesToolBar(tr("Toolbar for articles"), this)),
51 m_messagesView(new MessagesView(this)), m_feedsView(new FeedsView(this)),
52 m_messagesBrowser(new MessagePreviewer(false, this)) {
53 initialize();
54 initializeViews();
55 createConnections();
56 }
57
~FeedMessageViewer()58 FeedMessageViewer::~FeedMessageViewer() {
59 qDebugNN << LOGSEC_GUI << "Destroying FeedMessageViewer instance.";
60 }
61
62 #if defined(USE_WEBENGINE)
63
webBrowser() const64 WebBrowser* FeedMessageViewer::webBrowser() const {
65 return m_messagesBrowser->webBrowser();
66 }
67
68 #endif
69
feedsView() const70 FeedsView* FeedMessageViewer::feedsView() const {
71 return m_feedsView;
72 }
73
messagesView() const74 MessagesView* FeedMessageViewer::messagesView() const {
75 return m_messagesView;
76 }
77
messagesToolBar() const78 MessagesToolBar* FeedMessageViewer::messagesToolBar() const {
79 return m_toolBarMessages;
80 }
81
feedsToolBar() const82 FeedsToolBar* FeedMessageViewer::feedsToolBar() const {
83 return m_toolBarFeeds;
84 }
85
saveSize()86 void FeedMessageViewer::saveSize() {
87 Settings* settings = qApp->settings();
88
89 m_feedsView->saveAllExpandStates();
90
91 // Store offsets of splitters.
92 settings->setValue(GROUP(GUI), GUI::SplitterFeeds, toVariant(m_feedSplitter->sizes()));
93
94 // We need to display message previewer so that it "has" some dimensions
95 // so that they can be saved.
96 m_messagesBrowser->show();
97 qApp->processEvents();
98
99 if (!settings->value(GROUP(GUI), SETTING(GUI::SplitterMessagesIsVertical)).toBool()) {
100 settings->setValue(GROUP(GUI),
101 GUI::SplitterMessagesHorizontal,
102 toVariant(m_messageSplitter->sizes()));
103 }
104 else {
105 settings->setValue(GROUP(GUI),
106 GUI::SplitterMessagesVertical,
107 toVariant(m_messageSplitter->sizes()));
108 }
109
110 settings->setValue(GROUP(GUI), GUI::MessageViewState, QString(m_messagesView->saveHeaderState().toBase64()));
111
112 // Store "visibility" of toolbars and list headers.
113 settings->setValue(GROUP(GUI), GUI::ToolbarsVisible, m_toolBarsEnabled);
114 settings->setValue(GROUP(GUI), GUI::ListHeadersVisible, m_listHeadersEnabled);
115 }
116
loadSize()117 void FeedMessageViewer::loadSize() {
118 const Settings* settings = qApp->settings();
119
120 // Restore offsets of splitters.
121 m_feedSplitter->setSizes(toList<int>(settings->value(GROUP(GUI),
122 SETTING(GUI::SplitterFeeds))));
123
124 if (settings->value(GROUP(GUI), SETTING(GUI::SplitterMessagesIsVertical)).toBool()) {
125 m_messageSplitter->setSizes(toList<int>(settings->value(GROUP(GUI),
126 SETTING(GUI::SplitterMessagesVertical))));
127 }
128 else {
129 switchMessageSplitterOrientation(false);
130 }
131
132 QString settings_msg_header = settings->value(GROUP(GUI), SETTING(GUI::MessageViewState)).toString();
133
134 if (!settings_msg_header.isEmpty()) {
135 m_messagesView->restoreHeaderState(QByteArray::fromBase64(settings_msg_header.toLocal8Bit()));
136 }
137 }
138
loadMessageViewerFonts()139 void FeedMessageViewer::loadMessageViewerFonts() {
140 m_messagesBrowser->reloadFontSettings();
141 m_messagesView->reloadFontSettings();
142 m_feedsView->reloadFontSettings();
143 }
144
areToolBarsEnabled() const145 bool FeedMessageViewer::areToolBarsEnabled() const {
146 return m_toolBarsEnabled;
147 }
148
areListHeadersEnabled() const149 bool FeedMessageViewer::areListHeadersEnabled() const {
150 return m_listHeadersEnabled;
151 }
152
switchMessageSplitterOrientation(bool save_settings)153 void FeedMessageViewer::switchMessageSplitterOrientation(bool save_settings) {
154 bool preview_visible = m_messagesBrowser->isVisible();
155
156 if (!preview_visible && save_settings) {
157 // Must be visible to get correct dimensions to be saved.
158 m_messagesBrowser->show();
159 qApp->processEvents();
160 }
161
162 if (m_messageSplitter->orientation() == Qt::Orientation::Vertical) {
163 if (save_settings) {
164 qApp->settings()->setValue(GROUP(GUI),
165 GUI::SplitterMessagesVertical,
166 toVariant(m_messageSplitter->sizes()));
167 }
168
169 m_messageSplitter->setOrientation(Qt::Orientation::Horizontal);
170 m_messageSplitter->setSizes(toList<int>(qApp->settings()->value(GROUP(GUI),
171 SETTING(GUI::SplitterMessagesHorizontal))));
172 }
173 else {
174 if (save_settings) {
175 qApp->settings()->setValue(GROUP(GUI),
176 GUI::SplitterMessagesHorizontal,
177 toVariant(m_messageSplitter->sizes()));
178 }
179
180 m_messageSplitter->setOrientation(Qt::Orientation::Vertical);
181 m_messageSplitter->setSizes(toList<int>(qApp->settings()->value(GROUP(GUI),
182 SETTING(GUI::SplitterMessagesVertical))));
183 }
184
185 if (!preview_visible && save_settings) {
186 // Must be visible to get correct dimensions to be saved.
187 m_messagesBrowser->hide();
188 qApp->processEvents();
189 }
190
191 qApp->settings()->setValue(GROUP(GUI),
192 GUI::SplitterMessagesIsVertical,
193 m_messageSplitter->orientation() == Qt::Orientation::Vertical);
194 }
195
setToolBarsEnabled(bool enable)196 void FeedMessageViewer::setToolBarsEnabled(bool enable) {
197 m_toolBarsEnabled = enable;
198 m_toolBarFeeds->setVisible(enable);
199 m_toolBarMessages->setVisible(enable);
200 }
201
setListHeadersEnabled(bool enable)202 void FeedMessageViewer::setListHeadersEnabled(bool enable) {
203 m_listHeadersEnabled = enable;
204 m_feedsView->header()->setVisible(enable);
205 m_messagesView->header()->setVisible(enable);
206 }
207
switchFeedComponentVisibility()208 void FeedMessageViewer::switchFeedComponentVisibility() {
209 auto* sen = qobject_cast<QAction*>(sender());
210
211 if (sen != nullptr) {
212 m_feedsWidget->setVisible(sen->isChecked());
213 }
214 else {
215 m_feedsWidget->setVisible(!m_feedsWidget->isVisible());
216 }
217 }
218
toggleShowOnlyUnreadMessages()219 void FeedMessageViewer::toggleShowOnlyUnreadMessages() {
220 const QAction* origin = qobject_cast<QAction*>(sender());
221
222 if (origin == nullptr) {
223 m_messagesView->switchShowUnreadOnly(true, false);
224 }
225 else {
226 m_messagesView->switchShowUnreadOnly(true, origin->isChecked());
227 }
228 }
229
toggleShowOnlyUnreadFeeds()230 void FeedMessageViewer::toggleShowOnlyUnreadFeeds() {
231 const QAction* origin = qobject_cast<QAction*>(sender());
232
233 if (origin == nullptr) {
234 m_feedsView->model()->invalidateReadFeedsFilter(true, false);
235 }
236 else {
237 m_feedsView->model()->invalidateReadFeedsFilter(true, origin->isChecked());
238 }
239 }
240
toggleShowFeedTreeBranches()241 void FeedMessageViewer::toggleShowFeedTreeBranches() {
242 const QAction* origin = qobject_cast<QAction*>(sender());
243
244 m_feedsView->setRootIsDecorated(origin->isChecked());
245 qApp->settings()->setValue(GROUP(Feeds), Feeds::ShowTreeBranches, origin->isChecked());
246 }
247
toggleItemsAutoExpandingOnSelection()248 void FeedMessageViewer::toggleItemsAutoExpandingOnSelection() {
249 const QAction* origin = qobject_cast<QAction*>(sender());
250
251 qApp->settings()->setValue(GROUP(Feeds), Feeds::AutoExpandOnSelection, origin->isChecked());
252 }
253
alternateRowColorsInLists()254 void FeedMessageViewer::alternateRowColorsInLists() {
255 const QAction* origin = qobject_cast<QAction*>(sender());
256
257 m_feedsView->setAlternatingRowColors(origin->isChecked());
258 m_messagesView->setAlternatingRowColors(origin->isChecked());
259
260 qApp->settings()->setValue(GROUP(GUI), GUI::AlternateRowColorsInLists, origin->isChecked());
261 }
262
displayMessage(const Message & message,RootItem * root)263 void FeedMessageViewer::displayMessage(const Message& message, RootItem* root) {
264 if (qApp->settings()->value(GROUP(Messages), SETTING(Messages::EnableMessagePreview)).toBool()) {
265 m_messagesBrowser->loadMessage(message, root);
266 }
267 else {
268 m_messagesBrowser->hide();
269 }
270 }
271
createConnections()272 void FeedMessageViewer::createConnections() {
273 // Filtering & searching.
274 connect(m_toolBarMessages, &MessagesToolBar::messageSearchPatternChanged, m_messagesView, &MessagesView::searchMessages);
275 connect(m_toolBarFeeds, &FeedsToolBar::feedsFilterPatternChanged, m_feedsView, &FeedsView::filterItems);
276 connect(m_toolBarMessages, &MessagesToolBar::messageFilterChanged, m_messagesView, &MessagesView::filterMessages);
277
278 connect(m_messagesView, &MessagesView::currentMessageRemoved, m_messagesBrowser, &MessagePreviewer::clear);
279 connect(m_messagesBrowser, &MessagePreviewer::markMessageRead, m_messagesView->sourceModel(), &MessagesModel::setMessageReadById);
280 connect(m_messagesBrowser, &MessagePreviewer::markMessageImportant,
281 m_messagesView->sourceModel(), &MessagesModel::setMessageImportantById);
282
283 connect(m_messagesView, &MessagesView::currentMessageChanged, this, &FeedMessageViewer::displayMessage);
284
285 // If user selects feeds, load their messages.
286 connect(m_feedsView, &FeedsView::itemSelected, m_messagesView, &MessagesView::loadItem);
287 connect(m_feedsView, &FeedsView::requestViewNextUnreadMessage, m_messagesView, &MessagesView::selectNextUnreadItem);
288
289 // State of many messages is changed, then we need
290 // to reload selections.
291 connect(m_feedsView->sourceModel(), &FeedsModel::reloadMessageListRequested, m_messagesView, &MessagesView::reloadSelections);
292 }
293
messagesBrowser() const294 MessagePreviewer* FeedMessageViewer::messagesBrowser() const {
295 return m_messagesBrowser;
296 }
297
initialize()298 void FeedMessageViewer::initialize() {
299 // Initialize/populate toolbars.
300 m_toolBarFeeds->setFloatable(false);
301 m_toolBarFeeds->setMovable(false);
302 m_toolBarFeeds->setAllowedAreas(Qt::ToolBarArea::TopToolBarArea);
303 m_toolBarMessages->setFloatable(false);
304 m_toolBarMessages->setMovable(false);
305 m_toolBarMessages->setAllowedAreas(Qt::ToolBarArea::TopToolBarArea);
306 m_messagesBrowser->clear();
307
308 // Now refresh visual setup.
309 refreshVisualProperties();
310 }
311
initializeViews()312 void FeedMessageViewer::initializeViews() {
313 m_feedsWidget = new QWidget(this);
314 m_messagesWidget = new QWidget(this);
315 m_feedSplitter = new QSplitter(Qt::Orientation::Horizontal, this);
316 m_messageSplitter = new QSplitter(Qt::Orientation::Vertical, this);
317
318 // Instantiate needed components.
319 auto* central_layout = new QVBoxLayout(this);
320 auto* feed_layout = new QVBoxLayout(m_feedsWidget);
321 auto* message_layout = new QVBoxLayout(m_messagesWidget);
322
323 // Set layout properties.
324 central_layout->setContentsMargins({});
325 feed_layout->setContentsMargins({});
326 message_layout->setContentsMargins({});
327
328 central_layout->setSpacing(0);
329 feed_layout->setSpacing(0);
330 message_layout->setSpacing(0);
331
332 // Set views.
333 m_feedsView->setFrameStyle(QFrame::Shape::NoFrame);
334 m_messagesView->setFrameStyle(QFrame::Shape::NoFrame);
335
336 // Setup message splitter.
337 m_messageSplitter->setObjectName(QSL("MessageSplitter"));
338 m_messageSplitter->setHandleWidth(1);
339 m_messageSplitter->setOpaqueResize(false);
340 m_messageSplitter->setChildrenCollapsible(false);
341 m_messageSplitter->addWidget(m_messagesView);
342 m_messageSplitter->addWidget(m_messagesBrowser);
343
344 // Assemble message-related components to single widget.
345 message_layout->addWidget(m_toolBarMessages);
346 message_layout->addWidget(m_messageSplitter);
347
348 // Assemble feed-related components to another widget.
349 feed_layout->addWidget(m_toolBarFeeds);
350 feed_layout->addWidget(m_feedsView);
351
352 // Assembler everything together.
353 m_feedSplitter->setHandleWidth(1);
354 m_feedSplitter->setOpaqueResize(false);
355 m_feedSplitter->setChildrenCollapsible(false);
356 m_feedSplitter->addWidget(m_feedsWidget);
357 m_feedSplitter->addWidget(m_messagesWidget);
358
359 // Add toolbar and main feeds/messages widget to main layout.
360 central_layout->addWidget(m_feedSplitter);
361 setTabOrder(m_feedsView, m_messagesView);
362 setTabOrder(m_messagesView, m_toolBarFeeds);
363 setTabOrder(m_toolBarFeeds, m_toolBarMessages);
364 setTabOrder(m_toolBarMessages, m_messagesBrowser);
365
366 // Set initial ratio of sizes.
367 m_feedSplitter->setStretchFactor(0, 1);
368 m_feedSplitter->setStretchFactor(1, 3);
369 }
370
refreshVisualProperties()371 void FeedMessageViewer::refreshVisualProperties() {
372 const Qt::ToolButtonStyle button_style =
373 static_cast<Qt::ToolButtonStyle>(qApp->settings()->value(GROUP(GUI), SETTING(GUI::ToolbarStyle)).toInt());
374
375 m_toolBarFeeds->setToolButtonStyle(button_style);
376 m_toolBarMessages->setToolButtonStyle(button_style);
377 }
378