Source code for seriesmarker.gui.main_window

# ==============================================================================
# -*- coding: utf-8 -*-
#
# Copyright (C) 2013 - 2016 Tobias Röttger <toroettg@gmail.com>
#
# This file is part of SeriesMarker.
#
# SeriesMarker is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 3 as
# published by the Free Software Foundation.
#
# SeriesMarker 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 SeriesMarker.  If not, see <http://www.gnu.org/licenses/>.
# ==============================================================================

import logging

from PySide.QtCore import Slot, QModelIndex, Qt, QCoreApplication
from PySide.QtGui import QMainWindow, QListView, QMessageBox, QIcon, \
    QHeaderView, QApplication, QMenu

from seriesmarker.gui.about_dialog import AboutDialog
from seriesmarker.gui.model.episode_node import EpisodeNode
from seriesmarker.gui.model.season_node import SeasonNode
from seriesmarker.gui.model.series_node import SeriesNode
from seriesmarker.gui.model.sort_filter_proxy_model import SortFilterProxyModel
from seriesmarker.gui.model.tree_series_model import TreeSeriesModel
from seriesmarker.gui.resources.ui_main_window import Ui_MainWindow
from seriesmarker.gui.search_dialog import SearchDialog
from seriesmarker.net.tvdb import tvdb
from seriesmarker.persistence.database import db_get_series, db_add_series, \
    db_remove_series, db_commit
from seriesmarker.persistence.exception import EntityExistsException
from seriesmarker.persistence.factory.series_factory import SeriesFactory

logger = logging.getLogger(__name__)


[docs]class MainWindow(QMainWindow): """Displays the main application window."""
[docs] def __init__(self, parent=None): """Creates a new window instance. Initializes the different views and loads series information from the database into the view's models for displaying them. :param parent: The parent widget of the window. :type parent: :class:`PySide.QtGui.QWidget` """ super(MainWindow, self).__init__(parent) self.ui = Ui_MainWindow() self.ui.setupUi(self) self.ui.action_about_qt.setIcon( QIcon(":/trolltech/qmessagebox/images/qtlogo-64.png")) self.ui.action_about.setMenu(self.ui.menuAbout) self.ui.menubar.setVisible(False) self.model = TreeSeriesModel() self.tree_proxy_model = SortFilterProxyModel() self.tree_proxy_model.setSourceModel(self.model) self.ui.tree_view.setModel(self.tree_proxy_model) self.ui.tree_view.sortByColumn(0, Qt.AscendingOrder) self.ui.tree_view.header().setResizeMode(QHeaderView.ResizeToContents) self.ui.tree_view.setContextMenuPolicy(Qt.CustomContextMenu) self.ui.tree_view.customContextMenuRequested.connect( self._handle_context_menu) self.ui.list_view.setModel(self.model) self.ui.list_view.setMouseTracking(True)
@Slot()
[docs] def on_action_add_triggered(self): """Displays a :class:`.SearchDialog` to add a new series to the database.""" dialog = SearchDialog(self) if dialog.exec_() == SearchDialog.Accepted: pytvdb_show = dialog.result_value() series = SeriesFactory().new_series(pytvdb_show) try: db_add_series(series) self.model.add_item(series) except EntityExistsException: logger.warning("Series '{name}' already exists, " "ignoring add request".format( name=series.series_name))
@Slot()
[docs] def on_action_remove_triggered(self): """Removes a series from the database and the views.""" index = self.tree_proxy_model.mapToSource( self.ui.tree_view.currentIndex()) series = self.model.pop_related_series(index) if series is not None: db_remove_series(series)
@Slot()
[docs] def on_action_update_triggered(self): """Sequentially updates all series in the database. .. todo:: Update should also recognize/announce updated episodes. """ series_factory = SeriesFactory() for series in db_get_series(): logger.info("Updating series '{}'".format(series.series_name)) tvdb_show = tvdb.get_series(series.id, "en", cache=False) tvdb_show.update() tvdb_show.load_banners() series_factory.new_series(tvdb_show, update=series) db_commit() series_index = self.model.index_of(series) for removed_season in series_factory.removed: season_index = self.model.index_of(removed_season, series_index) season_row = self.model.node_at(season_index).child_index() self.model.removeRow(season_row, series_index) logger.info(" Removed season {} from series '{}'".format( removed_season.season_number, series.series_name)) for added_season in series_factory.added: self.model.add_item(added_season, series_index) logger.info( " Added season {}".format(added_season.season_number)) for updated_season in series_factory.updated: season, added_episodes, removed_episodes = updated_season logger.info( " Updated season {} (Episodes added: {}, removed: {})".format( season.season_number, len(added_episodes), len(removed_episodes))) season_index = self.model.index_of(season, series_index) for removed_episode in removed_episodes: episode_index = self.model.index_of(removed_episode, season_index) episode_row = self.model.node_at( episode_index).child_index() self.model.removeRow(episode_row, season_index) for added_episode in added_episodes: self.model.add_item(added_episode, season_index) series_factory.reset() QCoreApplication.processEvents()
@Slot(QModelIndex)
[docs] def on_tree_view_clicked(self, index): """Changes the style of the list view according to context. The list view shows icons in portrait mode if a series or season is selected in the tree view. It changes to list mode if an episode has been selected. .. todo:: Should change view only if next style differs from current one - needs to compare with previous selection. """ if index.column() is not 0: index = self.tree_proxy_model.index(index.row(), 0, index.parent()) index = self.tree_proxy_model.mapToSource(index) node = self.model.node_at(index) self.ui.list_view.setRootIndex(index) if isinstance(node, SeriesNode): self._enable_icon_mode() elif isinstance(node, SeasonNode): self._enable_list_mode()
[docs] def _enable_list_mode(self): """Changes the style of the list view to list mode.""" self.ui.list_view.setFlow(QListView.TopToBottom) self.ui.list_view.setWrapping(False) self.ui.list_view.setWordWrap(False) self.ui.list_view.setViewMode(QListView.ListMode)
[docs] def _enable_icon_mode(self): """Changes the style of the list view to icon mode.""" self.ui.list_view.setFlow(QListView.LeftToRight) self.ui.list_view.setWrapping(True) self.ui.list_view.setWordWrap(True) self.ui.list_view.setViewMode(QListView.IconMode)
[docs] def _handle_context_menu(self, pos): index = self.ui.tree_view.indexAt(pos) if index.isValid(): pos = self.ui.tree_view.viewport().mapToGlobal(pos) menu = QMenu() menu.addAction(self.ui.action_mark_watched) menu.addAction(self.ui.action_mark_unwatched) menu.addSeparator() menu.addAction(self.ui.action_remove) menu.exec_(pos)
@Slot()
[docs] def on_action_mark_watched_triggered(self): """Marks all episodes of the currently selected series as watched.""" self.tree_proxy_model.setData(self.ui.tree_view.currentIndex(), Qt.Checked, role=Qt.CheckStateRole)
@Slot()
[docs] def on_action_mark_unwatched_triggered(self): """Marks all episodes of the currently selected series as unwatched.""" self.tree_proxy_model.setData(self.ui.tree_view.currentIndex(), Qt.Unchecked, role=Qt.CheckStateRole)
@Slot(QModelIndex)
[docs] def on_list_view_entered(self, index): """Sets the tooltip of the list view. A tooltip is set, when the mouse hovers above an episode in the list view. :param index: The index of the item whose view has been entered. :type index: :class:`~.PySide.QtCore.QModelIndex` """ node = self.model.node_at(index) if isinstance(node, EpisodeNode): # HTML-style fonts makes tooltips 'rich text', # which enables automatic word wrapping for them. self.ui.list_view.setToolTip( "<FONT COLOR=black>{}</FONT>".format(node.data.overview))
@Slot()
[docs] def on_list_view_viewportEntered(self): """Removes the tooltip from the list view. A tooltip is removed, when the mouse leaves the area of an episode in the list view. """ self.ui.list_view.setToolTip("")
@Slot()
[docs] def on_action_about_triggered(self): """Displays an :class:`.AboutDialog` for additional information.""" AboutDialog(self).exec_()
@Slot()
[docs] def on_action_home_triggered(self): """Restores the initial perspective of the application.""" self.ui.list_view.setRootIndex(QModelIndex()) self._enable_icon_mode()
@Slot()
[docs] def on_action_about_qt_triggered(self): """Displays a dialog with information about Qt.""" QMessageBox.aboutQt(self)
@Slot()
[docs] def on_action_exit_triggered(self): """Terminates the application normally.""" QApplication.exit()