1 // For license of this file, see <project-root-folder>/LICENSE.md.
2 
3 #include "gui/tabwidget.h"
4 
5 #include "definitions/definitions.h"
6 #include "gui/dialogs/formmain.h"
7 #include "gui/feedmessageviewer.h"
8 #include "gui/feedsview.h"
9 #include "gui/messagesview.h"
10 #include "gui/newspaperpreviewer.h"
11 #include "gui/reusable/plaintoolbutton.h"
12 #include "gui/tabbar.h"
13 #include "miscellaneous/application.h"
14 #include "miscellaneous/iconfactory.h"
15 #include "miscellaneous/settings.h"
16 #include "miscellaneous/textfactory.h"
17 #include "network-web/webfactory.h"
18 
19 #if defined(USE_WEBENGINE)
20 #include "gui/webbrowser.h"
21 #endif
22 
23 #include <QMenu>
24 #include <QToolButton>
25 
TabWidget(QWidget * parent)26 TabWidget::TabWidget(QWidget* parent) : QTabWidget(parent), m_menuMain(nullptr) {
27   setTabBar(new TabBar(this));
28   setupMainMenuButton();
29   initializeTabs();
30   createConnections();
31 }
32 
~TabWidget()33 TabWidget::~TabWidget() {
34   qDebugNN << LOGSEC_GUI << "Destroying TabWidget instance.";
35 }
36 
setupMainMenuButton()37 void TabWidget::setupMainMenuButton() {
38   m_btnMainMenu = new PlainToolButton(this);
39   m_btnMainMenu->setAutoRaise(true);
40   m_btnMainMenu->setPadding(3);
41   m_btnMainMenu->setToolTip(tr("Displays main menu."));
42   m_btnMainMenu->setIcon(qApp->icons()->fromTheme(QSL("go-home")));
43   m_btnMainMenu->setPopupMode(QToolButton::ToolButtonPopupMode::InstantPopup);
44   connect(m_btnMainMenu, &PlainToolButton::clicked, this, &TabWidget::openMainMenu);
45 }
46 
openMainMenu()47 void TabWidget::openMainMenu() {
48   if (m_menuMain == nullptr) {
49     m_menuMain = new QMenu(tr("Main menu"), this);
50     m_menuMain->addMenu(qApp->mainForm()->m_ui->m_menuFile);
51     m_menuMain->addMenu(qApp->mainForm()->m_ui->m_menuView);
52     m_menuMain->addMenu(qApp->mainForm()->m_ui->m_menuAccounts);
53     m_menuMain->addMenu(qApp->mainForm()->m_ui->m_menuFeeds);
54     m_menuMain->addMenu(qApp->mainForm()->m_ui->m_menuMessages);
55     m_menuMain->addMenu(qApp->mainForm()->m_ui->m_menuWebBrowserTabs);
56     m_menuMain->addMenu(qApp->mainForm()->m_ui->m_menuTools);
57     m_menuMain->addMenu(qApp->mainForm()->m_ui->m_menuHelp);
58   }
59 
60   QPoint button_position = m_btnMainMenu->pos();
61   const QSize target_size = m_btnMainMenu->size() / 2.0;
62 
63   button_position.setX(button_position.x() + target_size.width());
64   button_position.setY(button_position.y() + target_size.height());
65   m_menuMain->exec(mapToGlobal(button_position));
66 }
67 
showDownloadManager()68 void TabWidget::showDownloadManager() {
69   for (int i = 0; i < count(); i++) {
70     if (widget(i)->metaObject()->className() == QSL("DownloadManager")) {
71       setCurrentIndex(i);
72       return;
73     }
74   }
75 
76   // Download manager is not opened. Create tab with it.
77   qApp->downloadManager()->setParent(this);
78   addTab(qApp->downloadManager(),
79          qApp->icons()->fromTheme(QSL("emblem-downloads")),
80          tr("Downloads"),
81          TabBar::TabType::DownloadManager);
82   setCurrentIndex(count() - 1);
83 }
84 
checkTabBarVisibility()85 void TabWidget::checkTabBarVisibility() {
86   const bool should_be_visible = count() > 1 ||
87                                  !qApp->settings()->value(GROUP(GUI), SETTING(GUI::HideTabBarIfOnlyOneTab)).toBool();
88 
89   if (should_be_visible) {
90     setCornerWidget(m_btnMainMenu, Qt::Corner::TopLeftCorner);
91     m_btnMainMenu->setVisible(true);
92   }
93   else {
94     setCornerWidget(nullptr, Qt::Corner::TopLeftCorner);
95     setCornerWidget(nullptr, Qt::Corner::TopRightCorner);
96     m_btnMainMenu->setVisible(false);
97   }
98 
99   tabBar()->setVisible(should_be_visible);
100 }
101 
tabInserted(int index)102 void TabWidget::tabInserted(int index) {
103   QTabWidget::tabInserted(index);
104   checkTabBarVisibility();
105   const int count_of_tabs = count();
106 
107   if (index < count_of_tabs - 1 && count_of_tabs > 1) {
108     // New tab was inserted and the tab is not the last one.
109     fixContentsAfterMove(index, count_of_tabs - 1);
110   }
111 }
112 
tabRemoved(int index)113 void TabWidget::tabRemoved(int index) {
114   QTabWidget::tabRemoved(index);
115   checkTabBarVisibility();
116   const int count_of_tabs = count();
117 
118   if (index < count_of_tabs && count_of_tabs > 1) {
119     // Some tab was removed and the tab was not the last one.
120     fixContentsAfterMove(index, count_of_tabs - 1);
121   }
122 }
123 
createConnections()124 void TabWidget::createConnections() {
125   connect(tabBar(), &TabBar::tabCloseRequested, this, &TabWidget::closeTab);
126   connect(tabBar(), &TabBar::emptySpaceDoubleClicked, this, &TabWidget::addEmptyBrowser);
127   connect(tabBar(), &TabBar::tabMoved, this, &TabWidget::fixContentsAfterMove);
128   connect(feedMessageViewer()->messagesView(), &MessagesView::openMessagesInNewspaperView, this, &TabWidget::addNewspaperView);
129   connect(feedMessageViewer()->feedsView(), &FeedsView::openMessagesInNewspaperView, this, &TabWidget::addNewspaperView);
130 }
131 
initializeTabs()132 void TabWidget::initializeTabs() {
133   // Create widget for "Feeds" page and add it.
134   m_feedMessageViewer = new FeedMessageViewer(this);
135   const int index_of_browser = addTab(m_feedMessageViewer, QIcon(), tr("Feeds"), TabBar::TabType::FeedReader);
136 
137   setTabToolTip(index_of_browser, tr("Browse your feeds and articles"));
138 }
139 
setupIcons()140 void TabWidget::setupIcons() {
141   // Iterate through all tabs and update icons
142   // accordingly.
143   for (int index = 0; index < count(); index++) {
144     // Index 0 usually contains widget which displays feeds & messages.
145     if (tabBar()->tabType(index) == TabBar::TabType::FeedReader) {
146       setTabIcon(index, qApp->icons()->fromTheme(QSL("application-rss+xml")));
147     }
148   }
149 }
150 
closeTab(int index)151 bool TabWidget::closeTab(int index) {
152   if (tabBar()->tabType(index) == TabBar::TabType::Closable) {
153     removeTab(index, true);
154     return true;
155   }
156   else if (tabBar()->tabType(index) == TabBar::TabType::DownloadManager) {
157     removeTab(index, false);
158     return true;
159   }
160   else {
161     return false;
162   }
163 }
164 
closeAllTabsExceptCurrent()165 void TabWidget::closeAllTabsExceptCurrent() {
166   // Close tabs after active tab.
167   int index_of_active = currentIndex();
168 
169   for (int i = count() - 1; i >= 0; i--) {
170     if (i != index_of_active) {
171       if (i < index_of_active) {
172         index_of_active--;
173       }
174 
175       closeTab(i);
176     }
177   }
178 }
179 
closeAllTabs()180 void TabWidget::closeAllTabs() {
181   for (int i = count() - 1; i >= 0; i--) {
182     closeTab(i);
183   }
184 }
185 
addNewspaperView(RootItem * root,const QList<Message> & messages)186 int TabWidget::addNewspaperView(RootItem* root, const QList<Message>& messages) {
187   int msg_height = height() - tabBar()->height() - 50;
188   NewspaperPreviewer* prev = new NewspaperPreviewer(msg_height, root, messages, this);
189 
190   connect(prev, &NewspaperPreviewer::markMessageRead,
191           m_feedMessageViewer->messagesView()->sourceModel(), &MessagesModel::setMessageReadById);
192   connect(prev, &NewspaperPreviewer::markMessageImportant,
193           m_feedMessageViewer->messagesView()->sourceModel(), &MessagesModel::setMessageImportantById);
194 
195   int index = addTab(prev,
196                      qApp->icons()->fromTheme(QSL("format-justify-fill")),
197                      tr("Newspaper view"),
198                      TabBar::TabType::Closable);
199 
200   // NOTE: Do not bring "newspaper" tabs to front anymore.
201   //setCurrentIndex(index);
202 
203   return index;
204 }
205 
addEmptyBrowser()206 int TabWidget::addEmptyBrowser() {
207   return addBrowser(false, true);
208 }
209 
addLinkedBrowser(const QUrl & initial_url)210 int TabWidget::addLinkedBrowser(const QUrl& initial_url) {
211   return addBrowser(false, false, initial_url);
212 }
213 
addLinkedBrowser(const QString & initial_url)214 int TabWidget::addLinkedBrowser(const QString& initial_url) {
215   return addLinkedBrowser(QUrl(initial_url));
216 }
217 
addBrowser(bool move_after_current,bool make_active,const QUrl & initial_url)218 int TabWidget::addBrowser(bool move_after_current, bool make_active, const QUrl& initial_url) {
219 #if defined(USE_WEBENGINE)
220   // Create new WebBrowser.
221   WebBrowser* browser = new WebBrowser(this);
222   int final_index;
223   QString browser_tab_name = tr("Web browser");
224 
225 #if defined(Q_OS_MACOSOS)
226   browser_tab_name = browser_tab_name.prepend(QSL("  "));
227 #endif
228 
229   if (move_after_current) {
230     // Insert web browser after current tab.
231     final_index = insertTab(currentIndex() + 1, browser, qApp->icons()->fromTheme(QSL("text-html")),
232                             browser_tab_name, TabBar::TabType::Closable);
233   }
234   else {
235     // Add new browser as the last tab.
236     final_index = addTab(browser, qApp->icons()->fromTheme(QSL("text-html")),
237                          browser_tab_name,
238                          TabBar::TabType::Closable);
239   }
240 
241   // Make connections.
242   connect(browser, &WebBrowser::titleChanged, this, &TabWidget::changeTitle);
243   connect(browser, &WebBrowser::iconChanged, this, &TabWidget::changeIcon);
244 
245   // Setup the tab index.
246   browser->setIndex(final_index);
247 
248   // Load initial web page if desired.
249   if (initial_url.isValid()) {
250     browser->loadUrl(initial_url);
251   }
252 
253   // Make new web browser active if desired.
254   if (make_active) {
255     setCurrentIndex(final_index);
256     browser->setFocus(Qt::FocusReason::OtherFocusReason);
257   }
258 
259   return final_index;
260 #else
261   Q_UNUSED(move_after_current)
262   Q_UNUSED(make_active)
263   qApp->web()->openUrlInExternalBrowser(initial_url.toString());
264   return -1;
265 #endif
266 }
267 
gotoNextTab()268 void TabWidget::gotoNextTab() {
269   if (currentIndex() == count() - 1) {
270     setCurrentIndex(0);
271   }
272   else {
273     setCurrentIndex(currentIndex() + 1);
274   }
275 }
276 
gotoPreviousTab()277 void TabWidget::gotoPreviousTab() {
278   if (currentIndex() == 0) {
279     setCurrentIndex(count() - 1);
280   }
281   else {
282     setCurrentIndex(currentIndex() - 1);
283   }
284 }
285 
indentTabText(int index)286 void TabWidget::indentTabText(int index) {
287 #if defined(Q_OS_MACOSOS)
288   if (tabBar()->tabType(index) != TabBar::FeedReader && !tabIcon(index).isNull()) {
289     // We have closable tab with some icon, fix the title.
290     const QString text = tabText(index);
291 
292     if (!text.startsWith(QSL("  "))) {
293       setTabText(index, QSL("  ") + text);
294     }
295   }
296 #else
297   Q_UNUSED(index)
298 #endif
299 }
300 
removeTab(int index,bool clear_from_memory)301 void TabWidget::removeTab(int index, bool clear_from_memory) {
302   if (clear_from_memory) {
303     widget(index)->deleteLater();
304   }
305 
306   QTabWidget::removeTab(index);
307 }
308 
addTab(TabContent * widget,const QIcon & icon,const QString & label,TabBar::TabType type)309 int TabWidget::addTab(TabContent* widget, const QIcon& icon, const QString& label, TabBar::TabType type) {
310   const int index = QTabWidget::addTab(widget, icon, label);
311 
312   tabBar()->setTabType(index, type);
313   indentTabText(index);
314   return index;
315 }
316 
addTab(TabContent * widget,const QString & label,TabBar::TabType type)317 int TabWidget::addTab(TabContent* widget, const QString& label, TabBar::TabType type) {
318   const int index = QTabWidget::addTab(widget, label);
319 
320   tabBar()->setTabType(index, type);
321   indentTabText(index);
322   return index;
323 }
324 
insertTab(int index,QWidget * widget,const QIcon & icon,const QString & label,TabBar::TabType type)325 int TabWidget::insertTab(int index, QWidget* widget, const QIcon& icon, const QString& label, TabBar::TabType type) {
326   const int tab_index = QTabWidget::insertTab(index, widget, icon, label);
327 
328   tabBar()->setTabType(tab_index, type);
329   indentTabText(index);
330   return tab_index;
331 }
332 
insertTab(int index,QWidget * widget,const QString & label,TabBar::TabType type)333 int TabWidget::insertTab(int index, QWidget* widget, const QString& label, TabBar::TabType type) {
334   const int tab_index = QTabWidget::insertTab(index, widget, label);
335 
336   tabBar()->setTabType(tab_index, type);
337   indentTabText(index);
338   return tab_index;
339 }
340 
changeIcon(int index,const QIcon & new_icon)341 void TabWidget::changeIcon(int index, const QIcon& new_icon) {
342   setTabIcon(index, new_icon);
343   indentTabText(index);
344 }
345 
changeTitle(int index,const QString & new_title)346 void TabWidget::changeTitle(int index, const QString& new_title) {
347   setTabText(index, TextFactory::shorten(new_title));
348   setTabToolTip(index, new_title);
349   indentTabText(index);
350 }
351 
fixContentsAfterMove(int from,int to)352 void TabWidget::fixContentsAfterMove(int from, int to) {
353   from = qMin(from, to);
354   to = qMax(from, to);
355 
356   for (; from <= to; from++) {
357     auto* content = widget(from);
358 
359     content->setIndex(from);
360   }
361 }
362