rssguard/src/gui/feedsview.cpp
2014-09-09 15:45:44 +02:00

570 lines
19 KiB
C++
Executable File

// This file is part of RSS Guard.
//
// Copyright (C) 2011-2014 by Martin Rotter <rotter.martinos@gmail.com>
//
// RSS Guard is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// RSS Guard 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with RSS Guard. If not, see <http://www.gnu.org/licenses/>.
#include "gui/feedsview.h"
#include "definitions/definitions.h"
#include "core/feedsmodelfeed.h"
#include "core/feedsmodel.h"
#include "core/feedsproxymodel.h"
#include "core/feedsmodelrootitem.h"
#include "core/feedsmodelcategory.h"
#include "core/feedsmodelfeed.h"
#include "miscellaneous/systemfactory.h"
#include "gui/formmain.h"
#include "gui/formcategorydetails.h"
#include "gui/formfeeddetails.h"
#include "gui/systemtrayicon.h"
#include "gui/messagebox.h"
#include <QMenu>
#include <QHeaderView>
#include <QContextMenuEvent>
#include <QPointer>
#include <QPainter>
#include <QTimer>
FeedsView::FeedsView(QWidget *parent)
: QTreeView(parent),
m_contextMenuCategoriesFeeds(NULL),
m_contextMenuEmptySpace(NULL),
m_autoUpdateTimer(new QTimer(this)) {
setObjectName("FeedsView");
// Allocate models.
m_proxyModel = new FeedsProxyModel(this);
m_sourceModel = m_proxyModel->sourceModel();
// Timed actions.
connect(m_autoUpdateTimer, SIGNAL(timeout()),
this, SLOT(executeNextAutoUpdate()));
setModel(m_proxyModel);
setupAppearance();
// Setup the timer.
updateAutoUpdateStatus();
}
FeedsView::~FeedsView() {
qDebug("Destroying FeedsView instance.");
}
void FeedsView::quit() {
if (m_autoUpdateTimer->isActive()) {
m_autoUpdateTimer->stop();
}
}
void FeedsView::updateAutoUpdateStatus() {
// Restore global intervals.
// NOTE: Specific per-feed interval are left intact.
m_globalAutoUpdateInitialInterval = qApp->settings()->value(APP_CFG_FEEDS, "auto_update_interval", DEFAULT_AUTO_UPDATE_INTERVAL).toInt();
m_globalAutoUpdateRemainingInterval = m_globalAutoUpdateInitialInterval;
m_globalAutoUpdateEnabled = qApp->settings()->value(APP_CFG_FEEDS, "auto_update_enabled", false).toBool();
// Start global auto-update timer if it is not running yet.
// NOTE: The timer must run even if global auto-update
// is not enabled because user can still enable auto-update
// for individual feeds.
if (!m_autoUpdateTimer->isActive()) {
m_autoUpdateTimer->setInterval(AUTO_UPDATE_INTERVAL);
m_autoUpdateTimer->start();
qDebug("Auto-update timer started with interval %d.", m_autoUpdateTimer->interval());
}
}
void FeedsView::setSortingEnabled(bool enable) {
QTreeView::setSortingEnabled(enable);
header()->setSortIndicatorShown(false);
}
QList<FeedsModelFeed*> FeedsView::selectedFeeds() const {
QModelIndexList selection = selectionModel()->selectedRows();
QModelIndexList mapped_selection = m_proxyModel->mapListToSource(selection);
return m_sourceModel->feedsForIndexes(mapped_selection);
}
QList<FeedsModelFeed*> FeedsView::allFeeds() const {
return m_sourceModel->allFeeds();
}
FeedsModelCategory *FeedsView::isCurrentIndexCategory() const {
QModelIndex current_mapped = m_proxyModel->mapToSource(currentIndex());
return m_sourceModel->categoryForIndex(current_mapped);
}
FeedsModelFeed *FeedsView::isCurrentIndexFeed() const {
QModelIndex current_mapped = m_proxyModel->mapToSource(currentIndex());
return m_sourceModel->feedForIndex(current_mapped);
}
void FeedsView::saveExpandedStates() {
Settings *settings = qApp->settings();
// Iterate all categories and save their expand statuses.
foreach (FeedsModelCategory *category, sourceModel()->allCategories().values()) {
settings->setValue(APP_CFG_CAT_EXP,
QString::number(category->id()),
isExpanded(model()->mapFromSource(sourceModel()->indexForItem(category))));
}
}
void FeedsView::loadExpandedStates() {
Settings *settings = qApp->settings();
// Iterate all categories and save their expand statuses.
foreach (FeedsModelCategory *category, sourceModel()->allCategories().values()) {
setExpanded(model()->mapFromSource(sourceModel()->indexForItem(category)),
settings->value(APP_CFG_CAT_EXP,
QString::number(category->id()),
true).toBool());
}
}
void FeedsView::updateAllFeeds() {
if (qApp->closeLock()->tryLock()) {
emit feedsUpdateRequested(allFeeds());
}
else {
qApp->showGuiMessage(tr("Cannot update all items"),
tr("You cannot update all items because another feed update is ongoing."),
QSystemTrayIcon::Warning, qApp->mainForm());
}
}
void FeedsView::updateAllFeedsOnStartup() {
if (qApp->settings()->value(APP_CFG_FEEDS, "feeds_update_on_startup", false).toBool()) {
qDebug("Requesting update for all feeds on application startup.");
QTimer::singleShot(STARTUP_UPDATE_DELAY, this, SLOT(updateAllFeeds()));
}
}
void FeedsView::updateSelectedFeeds() {
if (qApp->closeLock()->tryLock()) {
emit feedsUpdateRequested(selectedFeeds());
}
else {
qApp->showGuiMessage(tr("Cannot update selected items"),
tr("You cannot update selected items because another feed update is ongoing."),
QSystemTrayIcon::Warning, qApp->mainForm());
}
}
void FeedsView::executeNextAutoUpdate() {
if (!qApp->closeLock()->tryLock()) {
qDebug("Delaying scheduled feed auto-updates for one minute "
"due to another running update.");
// Cannot update, quit.
return;
}
// If global auto-update is enabled
// and its interval counter reached zero,
// then we need to restore it.
if (m_globalAutoUpdateEnabled && --m_globalAutoUpdateRemainingInterval < 0) {
// We should start next auto-update interval.
m_globalAutoUpdateRemainingInterval = m_globalAutoUpdateInitialInterval;
}
qDebug("Starting auto-update event, pass %d/%d.", m_globalAutoUpdateRemainingInterval, m_globalAutoUpdateInitialInterval);
// Pass needed interval data and lets the model decide which feeds
// should be updated in this pass.
QList<FeedsModelFeed*> feeds_for_update = m_sourceModel->feedsForScheduledUpdate(m_globalAutoUpdateEnabled &&
m_globalAutoUpdateRemainingInterval == 0);
if (feeds_for_update.isEmpty()) {
// No feeds are scheduled for update now, unlock the master lock.
qApp->closeLock()->unlock();
}
else {
// Request update for given feeds.
emit feedsUpdateRequested(feeds_for_update);
// NOTE: OSD/bubble informing about performing
// of scheduled update can be shown now.
}
}
void FeedsView::setSelectedFeedsClearStatus(int clear) {
m_sourceModel->markFeedsDeleted(selectedFeeds(), clear, 0);
updateCountsOfSelectedFeeds();
emit feedsNeedToBeReloaded(1);
}
void FeedsView::setAllFeedsClearStatus(int clear) {
m_sourceModel->markFeedsDeleted(allFeeds(), clear, 0);
updateCountsOfAllFeeds();
emit feedsNeedToBeReloaded(1);
}
void FeedsView::clearSelectedFeeds() {
setSelectedFeedsClearStatus(1);
}
void FeedsView::clearAllFeeds() {
setAllFeedsClearStatus(1);
}
void FeedsView::addNewCategory() {
if (!qApp->closeLock()->tryLock()) {
// Lock was not obtained because
// it is used probably by feed updater or application
// is quitting.
qApp->showGuiMessage(tr("Cannot add standard category"),
tr("You cannot add new standard category now because feed update is ongoing."),
QSystemTrayIcon::Warning, qApp->mainForm());
return;
}
QPointer<FormCategoryDetails> form_pointer = new FormCategoryDetails(m_sourceModel, this);
form_pointer.data()->exec(NULL);
delete form_pointer.data();
// Changes are done, unlock the update master lock.
qApp->closeLock()->unlock();
}
void FeedsView::editCategory(FeedsModelCategory *category) {
QPointer<FormCategoryDetails> form_pointer = new FormCategoryDetails(m_sourceModel, this);
form_pointer.data()->exec(category);
delete form_pointer.data();
}
void FeedsView::addNewFeed() {
if (!qApp->closeLock()->tryLock()) {
// Lock was not obtained because
// it is used probably by feed updater or application
// is quitting.
qApp->showGuiMessage(tr("Cannot add standard feed"),
tr("You cannot add new standard feed now because feed update is ongoing."),
QSystemTrayIcon::Warning, qApp->mainForm());
return;
}
QPointer<FormFeedDetails> form_pointer = new FormFeedDetails(m_sourceModel, this);
form_pointer.data()->exec(NULL);
delete form_pointer.data();
// Changes are done, unlock the update master lock.
qApp->closeLock()->unlock();
}
void FeedsView::editFeed(FeedsModelFeed *feed) {
QPointer<FormFeedDetails> form_pointer = new FormFeedDetails(m_sourceModel, this);
form_pointer.data()->exec(feed);
delete form_pointer.data();
}
void FeedsView::editSelectedItem() {
if (!qApp->closeLock()->tryLock()) {
// Lock was not obtained because
// it is used probably by feed updater or application
// is quitting.
qApp->showGuiMessage(tr("Cannot edit item"),
tr("Selected item cannot be edited because feed update is ongoing."),
QSystemTrayIcon::Warning, qApp->mainForm());
// Thus, cannot delete and quit the method.
return;
}
FeedsModelCategory *category;
FeedsModelFeed *feed;
if ((category = isCurrentIndexCategory()) != NULL) {
editCategory(static_cast<FeedsModelCategory*>(category));
}
else if ((feed = isCurrentIndexFeed()) != NULL) {
// Feed is selected.
switch (feed->type()) {
case FeedsModelFeed::Atom10:
case FeedsModelFeed::Rdf:
case FeedsModelFeed::Rss0X:
case FeedsModelFeed::Rss2X: {
// User wants to edit standard feed.
editFeed(static_cast<FeedsModelFeed*>(feed));
break;
}
default:
break;
}
}
// Changes are done, unlock the update master lock.
qApp->closeLock()->unlock();
}
void FeedsView::deleteSelectedItem() {
if (!qApp->closeLock()->tryLock()) {
// Lock was not obtained because
// it is used probably by feed updater or application
// is quitting.
qApp->showGuiMessage(tr("Cannot delete item"),
tr("Selected item cannot be deleted because feed update is ongoing."),
QSystemTrayIcon::Warning, qApp->mainForm());
// Thus, cannot delete and quit the method.
return;
}
QModelIndex current_index = currentIndex();
QItemSelectionModel *selection_model = selectionModel();
if (!current_index.isValid()) {
// Changes are done, unlock the update master lock and exit.
qApp->closeLock()->unlock();
return;
}
if (selection_model->selectedRows().size() > 1) {
selection_model->clearSelection();
selection_model->select(current_index, QItemSelectionModel::Rows | QItemSelectionModel::SelectCurrent);
qApp->showGuiMessage(tr("You selected multiplet items for deletion."),
tr("You can delete feeds/categories only one by one."),
QSystemTrayIcon::Warning, qApp->mainForm());
}
if (m_sourceModel->removeItem(m_proxyModel->mapToSource(current_index))) {
// Item WAS removed, update counts.
// TODO: I do not need to update counts of all items here.
// Updating counts of parent item (feed) should be enough.
updateCountsOfAllFeeds(true);
}
else {
// Item WAS NOT removed, either database-related error occurred
// or update is undergoing.
qApp->showGuiMessage(tr("Deletion of item failed."),
tr("Selected item was not deleted due to error."),
QSystemTrayIcon::Warning, qApp->mainForm());
}
// Changes are done, unlock the update master lock.
qApp->closeLock()->unlock();
}
void FeedsView::markSelectedFeedsReadStatus(int read) {
m_sourceModel->markFeedsRead(selectedFeeds(), read);
updateCountsOfSelectedFeeds(false);
emit feedsNeedToBeReloaded(read);
}
void FeedsView::markSelectedFeedsRead() {
markSelectedFeedsReadStatus(1);
}
void FeedsView::markSelectedFeedsUnread() {
markSelectedFeedsReadStatus(0);
}
void FeedsView::markAllFeedsReadStatus(int read) {
m_sourceModel->markFeedsRead(allFeeds(), read);
updateCountsOfAllFeeds(false);
emit feedsNeedToBeReloaded(read);
}
void FeedsView::markAllFeedsRead() {
markAllFeedsReadStatus(1);
}
void FeedsView::clearAllReadMessages() {
m_sourceModel->markFeedsDeleted(allFeeds(), 1, 1);
}
void FeedsView::openSelectedFeedsInNewspaperMode() {
QList<Message> messages = m_sourceModel->messagesForFeeds(selectedFeeds());
if (!messages.isEmpty()) {
emit openMessagesInNewspaperView(messages);
markSelectedFeedsRead();
}
}
void FeedsView::updateCountsOfSelectedFeeds(bool update_total_too) {
QList<FeedsModelFeed*> selected_feeds = selectedFeeds();
if (!selected_feeds.isEmpty()) {
foreach (FeedsModelFeed *feed, selected_feeds) {
feed->updateCounts(update_total_too);
}
// Make sure that selected view reloads changed indexes.
m_sourceModel->reloadChangedLayout(m_proxyModel->mapListToSource(selectionModel()->selectedRows()));
notifyWithCounts();
}
}
void FeedsView::updateCountsOfAllFeeds(bool update_total_too) {
foreach (FeedsModelFeed *feed, allFeeds()) {
feed->updateCounts(update_total_too);
}
// Make sure that all views reloads its data.
m_sourceModel->reloadWholeLayout();
notifyWithCounts();
}
void FeedsView::updateCountsOfParticularFeed(FeedsModelFeed *feed,
bool update_total_too) {
QModelIndex index = m_sourceModel->indexForItem(feed);
if (index.isValid()) {
feed->updateCounts(update_total_too, false);
m_sourceModel->reloadChangedLayout(QModelIndexList() << index);
}
notifyWithCounts();
}
void FeedsView::selectNextItem() {
QModelIndex index_next = moveCursor(QAbstractItemView::MoveDown, Qt::NoModifier);
if (index_next.isValid()) {
setCurrentIndex(index_next);
selectionModel()->select(index_next, QItemSelectionModel::Select | QItemSelectionModel::Rows);
}
}
void FeedsView::selectPreviousItem() {
QModelIndex index_previous = moveCursor(QAbstractItemView::MoveUp, Qt::NoModifier);
if (index_previous.isValid()) {
setCurrentIndex(index_previous);
selectionModel()->select(index_previous, QItemSelectionModel::Select | QItemSelectionModel::Rows);
}
}
void FeedsView::initializeContextMenuCategoriesFeeds() {
m_contextMenuCategoriesFeeds = new QMenu(tr("Context menu for feeds"), this);
m_contextMenuCategoriesFeeds->addActions(QList<QAction*>() <<
qApp->mainForm()->m_ui->m_actionUpdateSelectedFeedsCategories <<
qApp->mainForm()->m_ui->m_actionEditSelectedFeedCategory <<
qApp->mainForm()->m_ui->m_actionViewSelectedItemsNewspaperMode <<
qApp->mainForm()->m_ui->m_actionMarkSelectedFeedsAsRead <<
qApp->mainForm()->m_ui->m_actionMarkSelectedFeedsAsUnread);
}
void FeedsView::initializeContextMenuEmptySpace() {
m_contextMenuEmptySpace = new QMenu(tr("Context menu for feeds"), this);
m_contextMenuEmptySpace->addActions(QList<QAction*>() <<
qApp->mainForm()->m_ui->m_actionUpdateAllFeeds <<
qApp->mainForm()->m_ui->m_actionAddCategory <<
qApp->mainForm()->m_ui->m_actionAddFeed);
}
void FeedsView::setupAppearance() {
#if QT_VERSION >= 0x050000
// Setup column resize strategies.
header()->setSectionResizeMode(FDS_MODEL_TITLE_INDEX, QHeaderView::Stretch);
header()->setSectionResizeMode(FDS_MODEL_COUNTS_INDEX, QHeaderView::ResizeToContents);
#else
// Setup column resize strategies.
header()->setResizeMode(FDS_MODEL_TITLE_INDEX, QHeaderView::Stretch);
header()->setResizeMode(FDS_MODEL_COUNTS_INDEX, QHeaderView::ResizeToContents);
#endif
header()->setStretchLastSection(false);
setUniformRowHeights(true);
setAnimated(true);
setSortingEnabled(true);
setItemsExpandable(true);
setExpandsOnDoubleClick(true);
setEditTriggers(QAbstractItemView::NoEditTriggers);
setIndentation(10);
setAcceptDrops(false);
setDragEnabled(false);
setDropIndicatorShown(false);
setDragDropMode(QAbstractItemView::NoDragDrop);
setAllColumnsShowFocus(true);
setSelectionMode(QAbstractItemView::ExtendedSelection);
setRootIsDecorated(false);
// Sort in ascending order, that is categories are
// "bigger" than feeds.
sortByColumn(0, Qt::AscendingOrder);
}
void FeedsView::selectionChanged(const QItemSelection &selected,
const QItemSelection &deselected) {
QTreeView::selectionChanged(selected, deselected);
m_selectedFeeds.clear();
foreach (FeedsModelFeed *feed, selectedFeeds()) {
#if defined(DEBUG)
QModelIndex index_for_feed = m_sourceModel->indexForItem(feed);
qDebug("Selecting feed '%s' (source index [%d, %d]).",
qPrintable(feed->title()), index_for_feed.row(), index_for_feed.column());
#endif
m_selectedFeeds << feed->id();
}
emit feedsSelected(m_selectedFeeds);
}
void FeedsView::keyPressEvent(QKeyEvent *event) {
QTreeView::keyPressEvent(event);
if (event->key() == Qt::Key_Delete) {
deleteSelectedItem();
}
}
void FeedsView::contextMenuEvent(QContextMenuEvent *event) {
if (indexAt(event->pos()).isValid()) {
// Display context menu for categories.
if (m_contextMenuCategoriesFeeds == NULL) {
// Context menu is not initialized, initialize.
initializeContextMenuCategoriesFeeds();
}
m_contextMenuCategoriesFeeds->exec(event->globalPos());
}
else {
// Display menu for empty space.
if (m_contextMenuEmptySpace == NULL) {
// Context menu is not initialized, initialize.
initializeContextMenuEmptySpace();
}
m_contextMenuEmptySpace->exec(event->globalPos());
}
}