diff --git a/resources/text/CHANGELOG b/resources/text/CHANGELOG index eb4be886b..d261cf76c 100644 --- a/resources/text/CHANGELOG +++ b/resources/text/CHANGELOG @@ -3,6 +3,8 @@ Added: ▪ Standard account is now automatically added if RSS Guard is started with empty database. +▪ Menu action "Select next unread message" in "Messages" menu now works across all feeds, so user can navigate through all unread messages with a sigle keyboard shortcut. (#132, #6) +▪ Added two bindable menu actions (in menu "Web browser & tabs") which allow to cycle among tabs. (#6) Fixed: ▪ Fixed build on some Unit-like operating systems. diff --git a/src/gui/dialogs/formmain.cpp b/src/gui/dialogs/formmain.cpp index 67429eb3e..95ca65746 100755 --- a/src/gui/dialogs/formmain.cpp +++ b/src/gui/dialogs/formmain.cpp @@ -65,25 +65,31 @@ FormMain::FormMain(QWidget* parent, Qt::WindowFlags f) : QMainWindow(parent, f), m_ui(new Ui::FormMain) { m_ui->setupUi(this); qApp->setMainForm(this); + #if defined (USE_WEBENGINE) m_ui->m_menuWebBrowserTabs->addAction(AdBlockManager::instance()->adBlockIcon()); m_ui->m_menuWebBrowserTabs->addAction(qApp->web()->engineSettingsAction()); #endif + // Add these actions to the list of actions of the main window. // This allows to use actions via shortcuts // even if main menu is not visible. addActions(qApp->userActions()); setStatusBar(m_statusBar = new StatusBar(this)); + // Prepare main window and tabs. prepareMenus(); + // Prepare tabs. tabWidget()->feedMessageViewer()->feedsToolBar()->loadSavedActions(); tabWidget()->feedMessageViewer()->messagesToolBar()->loadSavedActions(); - // Establish connections. + + // Establish connections. createConnections(); updateMessageButtonsAvailability(); updateFeedButtonsAvailability(); - // Setup some appearance of the window. + + // Setup some appearance of the window. setupIcons(); loadSize(); m_statusBar->loadSavedActions(); @@ -107,9 +113,11 @@ StatusBar* FormMain::statusBar() const { void FormMain::showDbCleanupAssistant() { if (qApp->feedUpdateLock()->tryLock()) { - QScopedPointer form_pointer(new FormDatabaseCleanup(this)); - form_pointer.data()->setCleaner(qApp->feedReader()->databaseCleaner()); - form_pointer.data()->exec(); + FormDatabaseCleanup form(new FormDatabaseCleanup(this)); + form.setCleaner(qApp->feedReader()->databaseCleaner()); + form.exec(); + + // Reload needed stuff. qApp->feedUpdateLock()->unlock(); tabWidget()->feedMessageViewer()->messagesView()->reloadSelections(); qApp->feedReader()->feedsModel()->reloadCountsOfWholeModel(); @@ -130,21 +138,26 @@ QList FormMain::allActions() const { actions << m_ui->m_actionBackupDatabaseSettings; actions << m_ui->m_actionRestart; actions << m_ui->m_actionQuit; + #if !defined(Q_OS_MAC) actions << m_ui->m_actionFullscreen; #endif + actions << m_ui->m_actionAboutGuard; actions << m_ui->m_actionSwitchFeedsList; actions << m_ui->m_actionSwitchMainWindow; + #if !defined(Q_OS_MAC) actions << m_ui->m_actionSwitchMainMenu; #endif + actions << m_ui->m_actionSwitchToolBars; actions << m_ui->m_actionSwitchListHeaders; actions << m_ui->m_actionSwitchStatusBar; actions << m_ui->m_actionSwitchMessageListOrientation; - // Add feeds/messages actions. - actions << m_ui->m_actionOpenSelectedSourceArticlesExternally; + actions << m_ui->m_actionTabsNext; + actions << m_ui->m_actionTabsPrevious; + actions << m_ui->m_actionOpenSelectedSourceArticlesExternally; actions << m_ui->m_actionOpenSelectedMessagesInternally; actions << m_ui->m_actionMarkAllItemsRead; actions << m_ui->m_actionMarkSelectedItemsAsRead; @@ -174,9 +187,11 @@ QList FormMain::allActions() const { actions << m_ui->m_actionSelectPreviousMessage; actions << m_ui->m_actionSelectNextUnreadMessage; actions << m_ui->m_actionExpandCollapseItem; + #if defined(USE_WEBENGINE) actions << m_ui->m_actionTabNewWebBrowser; #endif + actions << m_ui->m_actionTabsCloseAll; actions << m_ui->m_actionTabsCloseAllExceptCurrent; return actions; @@ -434,6 +449,7 @@ void FormMain::display() { void FormMain::setupIcons() { IconFactory* icon_theme_factory = qApp->icons(); + // Setup icons of this main window. m_ui->m_actionDownloadManager->setIcon(icon_theme_factory->fromTheme(QSL("emblem-downloads"))); m_ui->m_actionSettings->setIcon(icon_theme_factory->fromTheme(QSL("document-properties"))); @@ -447,6 +463,7 @@ void FormMain::setupIcons() { m_ui->m_actionRestoreDatabaseSettings->setIcon(icon_theme_factory->fromTheme(QSL("document-import"))); m_ui->m_actionDonate->setIcon(icon_theme_factory->fromTheme(QSL("applications-office"))); m_ui->m_actionDisplayWiki->setIcon(icon_theme_factory->fromTheme(QSL("applications-science"))); + // View. m_ui->m_actionSwitchMainWindow->setIcon(icon_theme_factory->fromTheme(QSL("window-close"))); m_ui->m_actionFullscreen->setIcon(icon_theme_factory->fromTheme(QSL("view-fullscreen"))); @@ -457,6 +474,7 @@ void FormMain::setupIcons() { m_ui->m_actionSwitchStatusBar->setIcon(icon_theme_factory->fromTheme(QSL("dialog-information"))); m_ui->m_actionSwitchMessageListOrientation->setIcon(icon_theme_factory->fromTheme(QSL("view-restore"))); m_ui->m_menuShowHide->setIcon(icon_theme_factory->fromTheme(QSL("view-restore"))); + // Feeds/messages. m_ui->m_menuAddItem->setIcon(icon_theme_factory->fromTheme(QSL("list-add"))); m_ui->m_actionStopRunningItemsUpdate->setIcon(icon_theme_factory->fromTheme(QSL("process-stop"))); @@ -492,11 +510,15 @@ void FormMain::setupIcons() { m_ui->m_actionServiceDelete->setIcon(icon_theme_factory->fromTheme(QSL("list-remove"))); m_ui->m_actionAddFeedIntoSelectedAccount->setIcon(icon_theme_factory->fromTheme(QSL("application-rss+xml"))); m_ui->m_actionAddCategoryIntoSelectedAccount->setIcon(icon_theme_factory->fromTheme(QSL("folder"))); + // Tabs & web browser. m_ui->m_actionTabNewWebBrowser->setIcon(icon_theme_factory->fromTheme(QSL("tab-new"))); m_ui->m_actionTabsCloseAll->setIcon(icon_theme_factory->fromTheme(QSL("window-close"))); m_ui->m_actionTabsCloseAllExceptCurrent->setIcon(icon_theme_factory->fromTheme(QSL("window-close"))); - // Setup icons on TabWidget too. + m_ui->m_actionTabsNext->setIcon(icon_theme_factory->fromTheme(QSL("go-next"))); + m_ui->m_actionTabsPrevious->setIcon(icon_theme_factory->fromTheme(QSL("go-previous"))); + + // Setup icons on TabWidget too. m_ui->m_tabWidget->setupIcons(); } @@ -566,24 +588,28 @@ void FormMain::createConnections() { connect(m_ui->m_menuAccounts, &QMenu::aboutToShow, this, &FormMain::updateAccountsMenu); connect(m_ui->m_actionServiceDelete, &QAction::triggered, m_ui->m_actionDeleteSelectedItem, &QAction::triggered); connect(m_ui->m_actionServiceEdit, &QAction::triggered, m_ui->m_actionEditSelectedItem, &QAction::triggered); - // Menu "File" connections. + + // Menu "File" connections. connect(m_ui->m_actionBackupDatabaseSettings, &QAction::triggered, this, &FormMain::backupDatabaseSettings); connect(m_ui->m_actionRestoreDatabaseSettings, &QAction::triggered, this, &FormMain::restoreDatabaseSettings); connect(m_ui->m_actionQuit, &QAction::triggered, qApp, &Application::quit); connect(m_ui->m_actionServiceAdd, &QAction::triggered, this, &FormMain::showAddAccountDialog); connect(m_ui->m_actionRestart, &QAction::triggered, qApp, &Application::restart); - // Menu "View" connections. + + // Menu "View" connections. connect(m_ui->m_actionFullscreen, &QAction::toggled, this, &FormMain::switchFullscreenMode); connect(m_ui->m_actionSwitchMainMenu, &QAction::toggled, m_ui->m_menuBar, &QMenuBar::setVisible); connect(m_ui->m_actionSwitchMainWindow, &QAction::triggered, this, &FormMain::switchVisibility); connect(m_ui->m_actionSwitchStatusBar, &QAction::toggled, statusBar(), &StatusBar::setVisible); - // Menu "Tools" connections. + + // Menu "Tools" connections. connect(m_ui->m_actionSettings, &QAction::triggered, [this]() { FormSettings(*this).exec(); }); connect(m_ui->m_actionDownloadManager, &QAction::triggered, m_ui->m_tabWidget, &TabWidget::showDownloadManager); connect(m_ui->m_actionCleanupDatabase, &QAction::triggered, this, &FormMain::showDbCleanupAssistant); - // Menu "Help" connections. + + // Menu "Help" connections. connect(m_ui->m_actionAboutGuard, &QAction::triggered, [this]() { FormAbout(this).exec(); }); @@ -593,7 +619,10 @@ void FormMain::createConnections() { connect(m_ui->m_actionReportBug, &QAction::triggered, this, &FormMain::reportABug); connect(m_ui->m_actionDonate, &QAction::triggered, this, &FormMain::donate); connect(m_ui->m_actionDisplayWiki, &QAction::triggered, this, &FormMain::showWiki); - // Tab widget connections. + + // Tab widget connections. + connect(m_ui->m_actionTabsNext, &QAction::triggered, m_ui->m_tabWidget, &TabWidget::gotoNextTab); + connect(m_ui->m_actionTabsPrevious, &QAction::triggered, m_ui->m_tabWidget, &TabWidget::gotoPreviousTab); connect(m_ui->m_actionTabsCloseAllExceptCurrent, &QAction::triggered, m_ui->m_tabWidget, &TabWidget::closeAllTabsExceptCurrent); connect(m_ui->m_actionTabsCloseAll, &QAction::triggered, m_ui->m_tabWidget, &TabWidget::closeAllTabs); connect(m_ui->m_actionTabNewWebBrowser, &QAction::triggered, m_ui->m_tabWidget, &TabWidget::addEmptyBrowser); @@ -607,7 +636,8 @@ void FormMain::createConnections() { connect(qApp->feedReader(), &FeedReader::feedUpdatesStarted, this, &FormMain::onFeedUpdatesStarted); connect(qApp->feedReader(), &FeedReader::feedUpdatesProgress, this, &FormMain::onFeedUpdatesProgress); connect(qApp->feedReader(), &FeedReader::feedUpdatesFinished, this, &FormMain::onFeedUpdatesFinished); - // Toolbar forwardings. + + // Toolbar forwardings. connect(m_ui->m_actionAddFeedIntoSelectedAccount, &QAction::triggered, tabWidget()->feedMessageViewer()->feedsView(), &FeedsView::addFeedIntoSelectedAccount); connect(m_ui->m_actionAddCategoryIntoSelectedAccount, &QAction::triggered, @@ -653,7 +683,7 @@ void FormMain::createConnections() { connect(m_ui->m_actionSwitchFeedsList, &QAction::triggered, tabWidget()->feedMessageViewer(), &FeedMessageViewer::switchFeedComponentVisibility); connect(m_ui->m_actionSelectNextItem, - &QAction::triggered, tabWidget()->feedMessageViewer()->feedsView(), &FeedsView::selectNextItem); + &QAction::triggered, tabWidget()->feedMessageViewer()->feedsView(), &FeedsView::selectNextItem); connect(m_ui->m_actionSwitchToolBars, &QAction::toggled, tabWidget()->feedMessageViewer(), &FeedMessageViewer::setToolBarsEnabled); connect(m_ui->m_actionSwitchListHeaders, &QAction::toggled, @@ -663,7 +693,7 @@ void FormMain::createConnections() { connect(m_ui->m_actionSelectNextMessage, &QAction::triggered, tabWidget()->feedMessageViewer()->messagesView(), &MessagesView::selectNextItem); connect(m_ui->m_actionSelectNextUnreadMessage, - &QAction::triggered, tabWidget()->feedMessageViewer()->messagesView(), &MessagesView::selectNextUnreadItem); + &QAction::triggered, tabWidget()->feedMessageViewer()->feedsView(), &FeedsView::selectNextUnreadItem); connect(m_ui->m_actionSelectPreviousMessage, &QAction::triggered, tabWidget()->feedMessageViewer()->messagesView(), &MessagesView::selectPreviousItem); connect(m_ui->m_actionSwitchMessageListOrientation, &QAction::triggered, diff --git a/src/gui/dialogs/formmain.ui b/src/gui/dialogs/formmain.ui index 29ec6f2da..45cc822aa 100755 --- a/src/gui/dialogs/formmain.ui +++ b/src/gui/dialogs/formmain.ui @@ -165,8 +165,12 @@ Web browser && tabs + + + + @@ -721,6 +725,16 @@ Close all tabs except current + + + Go to &next tab + + + + + Go to &previous tab + + diff --git a/src/gui/feedmessageviewer.cpp b/src/gui/feedmessageviewer.cpp index a1a878148..7bbe31fda 100755 --- a/src/gui/feedmessageviewer.cpp +++ b/src/gui/feedmessageviewer.cpp @@ -206,6 +206,8 @@ void FeedMessageViewer::createConnections() { #endif // If user selects feeds, load their messages. connect(m_feedsView, &FeedsView::itemSelected, m_messagesView, &MessagesView::loadItem); + connect(m_feedsView, &FeedsView::requestViewNextUnreadMessage, m_messagesView, &MessagesView::selectNextUnreadItem); + // State of many messages is changed, then we need // to reload selections. connect(m_feedsView->sourceModel(), &FeedsModel::reloadMessageListRequested, diff --git a/src/gui/feedsview.cpp b/src/gui/feedsview.cpp index 2841a3ae1..1a607f852 100755 --- a/src/gui/feedsview.cpp +++ b/src/gui/feedsview.cpp @@ -49,16 +49,19 @@ FeedsView::FeedsView(QWidget* parent) m_contextMenuEmptySpace(nullptr), m_contextMenuOtherItems(nullptr) { setObjectName(QSL("FeedsView")); + // Allocate models. m_sourceModel = qApp->feedReader()->feedsModel(); m_proxyModel = qApp->feedReader()->feedsProxyModel(); - // Connections. + + // Connections. connect(m_sourceModel, &FeedsModel::requireItemValidationAfterDragDrop, this, &FeedsView::validateItemAfterDragDrop); connect(m_sourceModel, &FeedsModel::itemExpandRequested, this, &FeedsView::onItemExpandRequested); connect(m_sourceModel, &FeedsModel::itemExpandStateSaveRequested, this, &FeedsView::onItemExpandStateSaveRequested); connect(header(), &QHeaderView::sortIndicatorChanged, this, &FeedsView::saveSortState); connect(m_proxyModel, &FeedsProxyModel::expandAfterFilterIn, this, &FeedsView::expandItemDelayed); - setModel(m_proxyModel); + + setModel(m_proxyModel); setupAppearance(); } @@ -346,7 +349,70 @@ void FeedsView::selectPreviousItem() { if (index_previous.isValid()) { setCurrentIndex(index_previous); setFocus(); - } + } +} + +void FeedsView::selectNextUnreadItem() { + QModelIndex next_unread_row; + + if (currentIndex().isValid()) { + next_unread_row = nextPreviousUnreadItem(currentIndex()); + } + else { + next_unread_row = nextPreviousUnreadItem(m_proxyModel->index(0, MSG_DB_READ_INDEX)); + } + + if (next_unread_row.isValid()) { + setCurrentIndex(next_unread_row); + emit requestViewNextUnreadMessage(); + } +} + +QModelIndex FeedsView::nextPreviousUnreadItem(QModelIndex default_row) { + const bool started_from_zero = default_row.row() == 0 && !default_row.parent().isValid(); + QModelIndex next_index = nextUnreadItem(default_row); + + // There is no next message, check previous. + if (!next_index.isValid() && !started_from_zero) { + next_index = nextUnreadItem(m_proxyModel->index(0, 0)); + } + + return next_index; +} + +QModelIndex FeedsView::nextUnreadItem(QModelIndex default_row) { + default_row = m_proxyModel->index(default_row.row(), 0, default_row.parent()); + const QModelIndex starting_row = default_row; + + while (true) { + bool has_unread = m_sourceModel->itemForIndex(m_proxyModel->mapToSource(default_row))->countOfUnreadMessages() > 0; + + if (has_unread) { + if (m_proxyModel->hasChildren(default_row)) { + // Current index has unread items, but is expandable, go to first child. + expand(default_row); + default_row = indexBelow(default_row); + continue; + } + else { + // We found unread feed, return it. + return default_row; + } + } + else { + QModelIndex next_row = indexBelow(default_row); + + if (next_row == default_row || !next_row.isValid() || starting_row == next_row) { + // We came to last row probably. + break; + } + else { + default_row = next_row; + } + } + } + + return QModelIndex(); } void FeedsView::switchVisibility() { @@ -354,8 +420,7 @@ void FeedsView::switchVisibility() { } void FeedsView::expandItemDelayed(const QModelIndex& idx) { - QTimer::singleShot(100, this, [ = ] { - // TODO: Z nastavení. + QTimer::singleShot(100, this, [ = ] { setExpanded(m_proxyModel->mapFromSource(idx), true); }); } @@ -445,6 +510,9 @@ void FeedsView::setupAppearance() { // Setup column resize strategies. header()->setSectionResizeMode(FDS_MODEL_TITLE_INDEX, QHeaderView::Stretch); header()->setSectionResizeMode(FDS_MODEL_COUNTS_INDEX, QHeaderView::ResizeToContents); + header()->setStretchLastSection(false); + header()->setSortIndicatorShown(false); + setUniformRowHeights(true); setAnimated(true); setSortingEnabled(true); @@ -460,8 +528,6 @@ void FeedsView::setupAppearance() { setRootIsDecorated(false); setSelectionMode(QAbstractItemView::SingleSelection); setItemDelegate(new StyledItemDelegateWithoutFocus(this)); - header()->setStretchLastSection(false); - header()->setSortIndicatorShown(false); } void FeedsView::selectionChanged(const QItemSelection& selected, const QItemSelection& deselected) { @@ -541,7 +607,6 @@ void FeedsView::onItemExpandRequested(const QList& items, bool exp) { foreach (const RootItem* item, items) { QModelIndex source_index = m_sourceModel->indexForItem(item); QModelIndex proxy_index = m_proxyModel->mapFromSource(source_index); - //setExpanded(proxy_index, !exp); setExpanded(proxy_index, exp); } } diff --git a/src/gui/feedsview.h b/src/gui/feedsview.h index fa0a1cc30..bdd79cf21 100755 --- a/src/gui/feedsview.h +++ b/src/gui/feedsview.h @@ -90,26 +90,20 @@ class FeedsView : public QTreeView { void selectNextItem(); void selectPreviousItem(); + void selectNextUnreadItem(); + // Switches visibility of the widget. void switchVisibility(); signals: - // Emitted if user selects new feeds. void itemSelected(RootItem* item); - - // Requests opening of given messages in newspaper mode. + void requestViewNextUnreadMessage(); void openMessagesInNewspaperView(RootItem* root, const QList& messages); protected: - // Handle selections. void selectionChanged(const QItemSelection& selected, const QItemSelection& deselected); - - // React on "Del" key. void keyPressEvent(QKeyEvent* event); - - // Show custom context menu. void contextMenuEvent(QContextMenuEvent* event); - void mouseDoubleClickEvent(QMouseEvent* event); private slots: @@ -123,6 +117,9 @@ class FeedsView : public QTreeView { void onItemExpandStateSaveRequested(RootItem* item); private: + QModelIndex nextPreviousUnreadItem(QModelIndex default_row); + QModelIndex nextUnreadItem(QModelIndex default_row); + // Initializes context menus. QMenu* initializeContextMenuCategories(RootItem* clicked_item); QMenu* initializeContextMenuFeeds(RootItem* clicked_item); diff --git a/src/gui/messagepreviewer.cpp b/src/gui/messagepreviewer.cpp index 3bfc1fe4f..9b3e0f9f5 100755 --- a/src/gui/messagepreviewer.cpp +++ b/src/gui/messagepreviewer.cpp @@ -174,7 +174,7 @@ void MessagePreviewer::switchMessageImportance(bool checked) { m_message.m_isImportant ? RootItem::NotImportant : RootItem::Important))) { - DatabaseQueries::switchMessagesImportance(qApp->database()->connection(objectName(), DatabaseFactory::FromSettings), + DatabaseQueries::switchMessagesImportance(qApp->database()->connection(objectName(), DatabaseFactory::FromSettings), QStringList() << QString::number(m_message.m_id)); m_root->getParentServiceRoot()->onAfterSwitchMessageImportance(m_root.data(), QList() << ImportanceChange(m_message, diff --git a/src/gui/tabwidget.cpp b/src/gui/tabwidget.cpp index 60282c56c..42152e0d7 100755 --- a/src/gui/tabwidget.cpp +++ b/src/gui/tabwidget.cpp @@ -269,6 +269,24 @@ int TabWidget::addBrowser(bool move_after_current, bool make_active, const QUrl& #endif } +void TabWidget::gotoNextTab() { + if (currentIndex() == count() - 1) { + setCurrentIndex(0); + } + else { + setCurrentIndex(currentIndex() + 1); + } +} + +void TabWidget::gotoPreviousTab() { + if (currentIndex() == 0) { + setCurrentIndex(count() - 1); + } + else { + setCurrentIndex(currentIndex() - 1); + } +} + void TabWidget::indentTabText(int index) { #if defined (Q_OS_MACOS) diff --git a/src/gui/tabwidget.h b/src/gui/tabwidget.h index 949468e12..865d4e91c 100755 --- a/src/gui/tabwidget.h +++ b/src/gui/tabwidget.h @@ -113,6 +113,9 @@ class TabWidget : public QTabWidget { // General method for adding WebBrowsers. int addBrowser(bool move_after_current, bool make_active, const QUrl& initial_url = QUrl()); + void gotoNextTab(); + void gotoPreviousTab(); + private slots: // Fixes tabs indexes. void fixContentsAfterMove(int from, int to);