spv-dev team mailing list archive
-
spv-dev team
-
Mailing list archive
-
Message #00050
[Merge] lp:~claude-bolduc/slidepresenterview/choose-projector-screens into lp:slidepresenterview
C. Bolduc has proposed merging lp:~claude-bolduc/slidepresenterview/choose-projector-screens into lp:slidepresenterview.
Requested reviews:
C. Bolduc (claude-bolduc)
For more details, see:
https://code.launchpad.net/~claude-bolduc/slidepresenterview/choose-projector-screens/+merge/45326
Implement the feature presenter-choose-projector-screens.
--
https://code.launchpad.net/~claude-bolduc/slidepresenterview/choose-projector-screens/+merge/45326
Your team SlidePresenterView Development Team is subscribed to branch lp:slidepresenterview.
=== modified file '.bzrignore'
--- .bzrignore 2010-05-03 02:19:38 +0000
+++ .bzrignore 2011-01-06 06:25:00 +0000
@@ -6,3 +6,6 @@
slidepresenterview/ui/qt_forms/*.py
run_spv.bat
slidepresenterview.egg-info
+*.pro.user
+doc/_build
+doc/developers/_api_doc
=== modified file 'HACKING'
--- HACKING 2010-07-11 17:27:55 +0000
+++ HACKING 2011-01-06 06:25:00 +0000
@@ -14,13 +14,7 @@
* Since 2010-01, we use a patched version of Mock. That version
in already included in thirdparty/mock.py.
- Freshen (for functionnal tests)
- * Since 2010-05, we use a pre-release of Freshen 0.2 (currently
- the Master branch) available for download at:
- http://github.com/rlisagor/freshen
- To install it:
- python setup.py install
- - PyYaml (needed by Freshen)
- * easy_install pyyaml
+ * easy_install freshen
How to compile/generate UI files
=== added directory 'doc'
=== added directory 'doc/_static'
=== added directory 'doc/_templates'
=== added file 'doc/conf.py'
--- doc/conf.py 1970-01-01 00:00:00 +0000
+++ doc/conf.py 2011-01-06 06:25:00 +0000
@@ -0,0 +1,228 @@
+# -*- coding: utf-8 -*-
+#
+# SlidePresenterView documentation build configuration file, created by
+# sphinx-quickstart on Sun Jan 02 17:50:49 2011.
+#
+# This file is execfile()d with the current directory set to its containing dir.
+#
+# Note that not all possible configuration values are present in this
+# autogenerated file.
+#
+# All configuration values have a default; values that are commented out
+# serve to show the default.
+
+import sys, os
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+sys.path.insert(0, os.path.abspath('../'))
+
+# -- General configuration -----------------------------------------------------
+
+# If your documentation needs a minimal Sphinx version, state it here.
+#needs_sphinx = '1.0'
+
+# Add any Sphinx extension module names here, as strings. They can be extensions
+# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
+extensions = ['sphinx.ext.autodoc',
+ 'sphinx.ext.autosummary',
+ 'sphinx.ext.pngmath',
+ 'sphinx.ext.viewcode']
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# The suffix of source filenames.
+source_suffix = '.rst'
+
+# The encoding of source files.
+#source_encoding = 'utf-8-sig'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = u'SlidePresenterView'
+copyright = u'2011, SlidePresenterView Development Team'
+
+# The version info for the project you're documenting, acts as replacement for
+# |version| and |release|, also used in various other places throughout the
+# built documents.
+#
+# The short X.Y version.
+version = '0.1'
+# The full version, including alpha/beta/rc tags.
+release = '0.1.0'
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+#language = None
+
+# There are two options for replacing |today|: either, you set today to some
+# non-false value, then it is used:
+#today = ''
+# Else, today_fmt is used as the format for a strftime call.
+#today_fmt = '%B %d, %Y'
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+exclude_patterns = ['_build']
+
+# The reST default role (used for this markup: `text`) to use for all documents.
+#default_role = None
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+#add_function_parentheses = True
+
+# If true, the current module name will be prepended to all description
+# unit titles (such as .. function::).
+#add_module_names = True
+
+# If true, sectionauthor and moduleauthor directives will be shown in the
+# output. They are ignored by default.
+#show_authors = False
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+# A list of ignored prefixes for module index sorting.
+#modindex_common_prefix = []
+
+# -- Specific autodoc configuration --------------------------------------------
+
+autoclass_content = "both"
+
+autodoc_default_flags = ['members', 'undoc-members']
+
+# -- Specific autosummary configuration ----------------------------------------
+
+autosummary_generate = True
+
+# -- Options for HTML output ---------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages. See the documentation for
+# a list of builtin themes.
+html_theme = 'default'
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further. For a list of options available for each theme, see the
+# documentation.
+#html_theme_options = {}
+
+# Add any paths that contain custom themes here, relative to this directory.
+#html_theme_path = []
+
+# The name for this set of Sphinx documents. If None, it defaults to
+# "<project> v<release> documentation".
+#html_title = None
+
+# A shorter title for the navigation bar. Default is the same as html_title.
+#html_short_title = None
+
+# The name of an image file (relative to this directory) to place at the top
+# of the sidebar.
+#html_logo = None
+
+# The name of an image file (within the static path) to use as favicon of the
+# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
+# pixels large.
+#html_favicon = None
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['_static']
+
+# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
+# using the given strftime format.
+#html_last_updated_fmt = '%b %d, %Y'
+
+# If true, SmartyPants will be used to convert quotes and dashes to
+# typographically correct entities.
+#html_use_smartypants = True
+
+# Custom sidebar templates, maps document names to template names.
+#html_sidebars = {}
+
+# Additional templates that should be rendered to pages, maps page names to
+# template names.
+#html_additional_pages = {}
+
+# If false, no module index is generated.
+#html_domain_indices = True
+
+# If false, no index is generated.
+#html_use_index = True
+
+# If true, the index is split into individual pages for each letter.
+#html_split_index = False
+
+# If true, links to the reST sources are added to the pages.
+#html_show_sourcelink = True
+
+# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
+#html_show_sphinx = True
+
+# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
+#html_show_copyright = True
+
+# If true, an OpenSearch description file will be output, and all pages will
+# contain a <link> tag referring to it. The value of this option must be the
+# base URL from which the finished HTML is served.
+#html_use_opensearch = ''
+
+# This is the file name suffix for HTML files (e.g. ".xhtml").
+#html_file_suffix = None
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'SlidePresenterViewdoc'
+
+
+# -- Options for LaTeX output --------------------------------------------------
+
+# The paper size ('letter' or 'a4').
+#latex_paper_size = 'letter'
+
+# The font size ('10pt', '11pt' or '12pt').
+#latex_font_size = '10pt'
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title, author, documentclass [howto/manual]).
+latex_documents = [
+ ('index', 'SlidePresenterView.tex', u'SlidePresenterView Documentation',
+ u'SlidePresenterView Development Team', 'manual'),
+]
+
+# The name of an image file (relative to this directory) to place at the top of
+# the title page.
+#latex_logo = None
+
+# For "manual" documents, if this is true, then toplevel headings are parts,
+# not chapters.
+#latex_use_parts = False
+
+# If true, show page references after internal links.
+#latex_show_pagerefs = False
+
+# If true, show URL addresses after external links.
+#latex_show_urls = False
+
+# Additional stuff for the LaTeX preamble.
+#latex_preamble = ''
+
+# Documents to append as an appendix to all manuals.
+#latex_appendices = []
+
+# If false, no module index is generated.
+#latex_domain_indices = True
+
+
+# -- Options for manual page output --------------------------------------------
+
+# One entry per manual page. List of tuples
+# (source start file, name, description, authors, manual section).
+man_pages = [
+ ('index', 'slidepresenterview', u'SlidePresenterView Documentation',
+ [u'SlidePresenterView Development Team'], 1)
+]
=== added directory 'doc/developers'
=== added file 'doc/developers/api_reference.rst'
--- doc/developers/api_reference.rst 1970-01-01 00:00:00 +0000
+++ doc/developers/api_reference.rst 2011-01-06 06:25:00 +0000
@@ -0,0 +1,39 @@
+SlidePresenterView's modules
+============================
+
+GUI modules
+-----------
+
+.. autosummary::
+ :toctree: _api_doc/
+
+ slidepresenterview
+ slidepresenterview.ui
+ slidepresenterview.ui.console_window
+ slidepresenterview.ui.presentation
+
+ slidepresenterview.ui.widgets
+ slidepresenterview.ui.widgets.screen
+ slidepresenterview.ui.widgets.preferred_projectors
+
+
+Configuration modules
+---------------------
+
+.. autosummary::
+ :toctree: _api_doc/
+
+ slidepresenterview.config
+ slidepresenterview.config.qt_configuration
+ slidepresenterview.config.user_configuration
+
+
+Utility modules
+---------------
+
+.. autosummary::
+ :toctree: _api_doc/
+
+ slidepresenterview.utils
+ slidepresenterview.utils.observation
+ slidepresenterview.utils.pyqt
\ No newline at end of file
=== added directory 'doc/en'
=== added file 'doc/en/user_manual.rst'
--- doc/en/user_manual.rst 1970-01-01 00:00:00 +0000
+++ doc/en/user_manual.rst 2011-01-06 06:25:00 +0000
@@ -0,0 +1,4 @@
+User manual for SlidePresenterView
+==================================
+
+TODO
\ No newline at end of file
=== added file 'doc/index.rst'
--- doc/index.rst 1970-01-01 00:00:00 +0000
+++ doc/index.rst 2011-01-06 06:25:00 +0000
@@ -0,0 +1,25 @@
+Welcome to SlidePresenterView's documentation!
+==============================================
+
+Console to allow presenters to control their presentations on multiple screens.
+
+Designed for seminars, talks and courses using mostly PDF slides.
+
+See the :doc:`user manual <en/user_manual>` to get started with SPV.
+
+Contents
+--------
+
+.. toctree::
+ :maxdepth: 1
+
+ en/user_manual
+ developers/api_reference
+
+Indices and tables
+==================
+
+* :ref:`genindex`
+* :ref:`modindex`
+* :ref:`search`
+
=== added file 'resources/spv_qtproject.pro'
--- resources/spv_qtproject.pro 1970-01-01 00:00:00 +0000
+++ resources/spv_qtproject.pro 2011-01-06 06:25:00 +0000
@@ -0,0 +1,6 @@
+QT += svg \
+ xml
+TARGET = slidepresenterview_form
+TEMPLATE = app
+FORMS += ui/console.ui \
+ ui/preferred_projectors.ui
=== removed file 'resources/tests/unicode_éè.pdf'
Binary files resources/tests/unicode_éè.pdf 2009-08-28 22:53:03 +0000 and resources/tests/unicode_éè.pdf 1970-01-01 00:00:00 +0000 differ
=== modified file 'resources/ui/console.ui'
--- resources/ui/console.ui 2009-07-04 22:34:33 +0000
+++ resources/ui/console.ui 2011-01-06 06:25:00 +0000
@@ -109,7 +109,7 @@
<x>0</x>
<y>0</y>
<width>800</width>
- <height>24</height>
+ <height>21</height>
</rect>
</property>
<widget class="QMenu" name="menuFile">
@@ -124,8 +124,10 @@
<property name="title">
<string>Presentation</string>
</property>
- <addaction name="action_start"/>
- <addaction name="action_stop"/>
+ <addaction name="action_show_presentation"/>
+ <addaction name="action_end_presentation"/>
+ <addaction name="separator"/>
+ <addaction name="action_configure_preferred_projectors"/>
</widget>
<addaction name="menuFile"/>
<addaction name="menuPresentation"/>
@@ -135,38 +137,31 @@
<property name="text">
<string>Open...</string>
</property>
- <property name="shortcut">
- <string>Ctrl+O</string>
- </property>
</action>
<action name="action_exit">
<property name="text">
<string>Exit</string>
</property>
- <property name="shortcut">
- <string>Ctrl+Q</string>
- </property>
- </action>
- <action name="action_start">
- <property name="enabled">
- <bool>false</bool>
- </property>
- <property name="text">
- <string>Start</string>
- </property>
- <property name="shortcut">
- <string>Ctrl+L</string>
- </property>
- </action>
- <action name="action_stop">
- <property name="enabled">
- <bool>false</bool>
- </property>
- <property name="text">
- <string>Stop</string>
- </property>
- <property name="shortcut">
- <string>Ctrl+E</string>
+ </action>
+ <action name="action_show_presentation">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>Show</string>
+ </property>
+ </action>
+ <action name="action_end_presentation">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>End</string>
+ </property>
+ </action>
+ <action name="action_configure_preferred_projectors">
+ <property name="text">
+ <string>Configure preferred projector(s)</string>
</property>
</action>
</widget>
=== added file 'resources/ui/preferred_projectors.ui'
--- resources/ui/preferred_projectors.ui 1970-01-01 00:00:00 +0000
+++ resources/ui/preferred_projectors.ui 2011-01-06 06:25:00 +0000
@@ -0,0 +1,153 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>preferred_projectors</class>
+ <widget class="QDialog" name="preferred_projectors">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>300</width>
+ <height>350</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Choose preferred projector(s)</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Show presentation on</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QRadioButton" name="console_screen_button">
+ <property name="text">
+ <string>console sreen</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QRadioButton" name="other_screens_button">
+ <property name="text">
+ <string>other screen(s)</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QRadioButton" name="selected_screens_button">
+ <property name="text">
+ <string>selected screen(s):</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="screen_list_widget" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="sizeConstraint">
+ <enum>QLayout::SetDefaultConstraint</enum>
+ </property>
+ <property name="margin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QGroupBox" name="selected_screens_group">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="title">
+ <string/>
+ </property>
+ <layout class="QVBoxLayout" name="selected_screens_items_layout"/>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="refresh_button">
+ <property name="text">
+ <string> Refresh screen list </string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="choose_projectors_dialog_buttons">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>choose_projectors_dialog_buttons</sender>
+ <signal>accepted()</signal>
+ <receiver>preferred_projectors</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>257</x>
+ <y>340</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>choose_projectors_dialog_buttons</sender>
+ <signal>rejected()</signal>
+ <receiver>preferred_projectors</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>290</x>
+ <y>340</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>selected_screens_button</sender>
+ <signal>toggled(bool)</signal>
+ <receiver>selected_screens_group</receiver>
+ <slot>setEnabled(bool)</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>50</x>
+ <y>83</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>145</x>
+ <y>193</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
=== added directory 'resources/ui/qss'
=== added file 'resources/ui/qss/common.qss'
--- resources/ui/qss/common.qss 1970-01-01 00:00:00 +0000
+++ resources/ui/qss/common.qss 2011-01-06 06:25:00 +0000
@@ -0,0 +1,21 @@
+/* SlidePresenterView - Console for presenters
+ * https://launchpad.net/slidepresenterview
+ *
+ * Copyright (C) 2010 Claude Bolduc <claude.bolduc _AT@. gmail.com>
+ *
+ * This program 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.
+ *
+ * This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* Common Qt style sheet for the whole SPV application. */
+
=== added file 'resources/ui/qss/windows.qss'
--- resources/ui/qss/windows.qss 1970-01-01 00:00:00 +0000
+++ resources/ui/qss/windows.qss 2011-01-06 06:25:00 +0000
@@ -0,0 +1,45 @@
+/* SlidePresenterView - Console for presenters
+ * https://launchpad.net/slidepresenterview
+ *
+ * Copyright (C) 2010 Claude Bolduc <claude.bolduc _AT@. gmail.com>
+ *
+ * This program 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.
+ *
+ * This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* Qt style sheet for the whole SPV application
+ specific to the Windows platform. */
+
+QMenuBar {
+ background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1,
+ stop:0 lightgray, stop:1 darkgray);
+}
+
+QMenuBar::item {
+ background: transparent;
+}
+
+QMenuBar::item:selected {
+ background: #a8a8a8;
+}
+
+QMenuBar::item:pressed {
+ background: #888888;
+}
+
+QToolTip {
+ border: 2px solid #ffffcc;
+ padding: 5px;
+ border-radius: 3px;
+ opacity: 200;
+}
=== modified file 'setup.cfg'
--- setup.cfg 2010-07-11 17:27:55 +0000
+++ setup.cfg 2011-01-06 06:25:00 +0000
@@ -3,6 +3,7 @@
with-doctest=1
#detailed-errors=1
with-freshen=1
+tags=~pending
testmatch=(?:^|[\b_\.%s-])([Tt]est|should)
where=slidepresenterview/
exclude=slidepresenterview/tests/manual
=== modified file 'setup.py'
--- setup.py 2010-05-02 23:05:14 +0000
+++ setup.py 2011-01-06 06:25:00 +0000
@@ -32,7 +32,7 @@
#####
import os
import sys
-
+import shutil
from distutils.command.build import build as build_cmd
from distutils.core import Command
@@ -56,10 +56,30 @@
sys.path.insert(0, path)
+# Needed for default documentation location.
+_DIR_TO_BUILD_DOC = 'doc/_build'
+_DIR_TO_BUILD_API_DOC = 'doc/developers/_api_doc'
+_base_path = os.path.dirname(__file__)
+_builded_doc_path = os.path.join(_base_path, _DIR_TO_BUILD_DOC)
+_builded_api_doc_path = os.path.join(_base_path, _DIR_TO_BUILD_API_DOC)
+
+
##########
# Custom commands
#####
+def _get_gen_ui_cmd():
+ """
+ :return: a list of strings representing the invocation of the gen_ui.py script.
+ """
+ cur_dir = os.path.dirname(__file__)
+ cmd = [sys.executable] # First, the python command.
+ tool_dir = os.path.join(cur_dir, 'tools')
+ cmd.append(os.path.join(tool_dir, 'gen_ui.py')) # Second, the script
+ # to run.
+ return cmd
+
+
class SPVGenUICommand(Command):
description = "Generate .py from .ui files"
@@ -72,11 +92,7 @@
pass
def run(self):
- cur_dir = os.path.dirname(__file__)
- cmd = [sys.executable] # First, the python command.
- tool_dir = os.path.join(cur_dir, 'tools')
- cmd.append(os.path.join(tool_dir, 'gen_ui.py')) # Second, the script
- # to run.
+ cmd = _get_gen_ui_cmd()
spawn(cmd)
@@ -94,11 +110,12 @@
def run(self):
self._remove_pyc()
self._remove_ui()
+ self._remove_doc()
def _remove_pyc(self):
log.info("-- Removing pyc files...")
## See: http://bugs.python.org/file14146
- for root, dirs, files in os.walk(os.getcwd(), topdown=False):
+ for root, _dirs, files in os.walk(os.getcwd(), topdown=False):
for name in files:
if (name.endswith('.pyc') and
os.path.isfile(os.path.join(root, name))):
@@ -106,16 +123,24 @@
os.remove(os.path.join(root, name))
def _remove_ui(self):
- log.info("-- Removing Qt UI generated files...")
- cur_dir = os.path.dirname(__file__)
- cmd = [sys.executable] # First, the python command.
- tool_dir = os.path.join(cur_dir, 'tools')
- cmd.append(os.path.join(tool_dir, 'gen_ui.py')) # Second, the script
- # to run.
+ log.info("-- Removing Qt generated files...")
+ cmd = _get_gen_ui_cmd()
cmd.append("--clean")
+
spawn(cmd)
-
-
+
+
+ def _remove_doc(self):
+ log.info("-- Removing Sphinx documentation generated files...")
+ self._remove_dir(_builded_doc_path)
+ self._remove_dir(_builded_api_doc_path)
+
+ def _remove_dir(self, path):
+ if os.path.isdir(path):
+ log.info('removing: %s' % path)
+ shutil.rmtree(path)
+
+
class SPVBuildCommand(build_cmd):
def has_ui(self):
@@ -143,6 +168,30 @@
self.run_command(cmd_name)
+try:
+ from sphinx.setup_command import BuildDoc
+
+ _sphinx_is_installed = True
+
+ class SPVBuildDocCommand(BuildDoc):
+
+ def finalize_options(self):
+ if self.build_dir is None:
+ # We build at a different default location than Sphinx.
+ self.build_dir = _builded_doc_path
+ self.mkpath(self.build_dir)
+ self.ensure_dirname('build_dir')
+ BuildDoc.finalize_options(self)
+
+
+ def run(self):
+ self.run_command('gen_ui') # Must be done before building the documentation.
+ BuildDoc.run(self)
+
+except ImportError:
+ _sphinx_is_installed = False
+
+
##########
# META INFORMATION
# http://docs.python.org/dist/meta-data.html
@@ -185,9 +234,12 @@
'gen_ui' : SPVGenUICommand,
'build' : SPVBuildCommand,
'clean_gen' : SPVCleanGeneratedCommand,
- 'clean_all' : SPVCleanAllCommand,
+ 'clean_all' : SPVCleanAllCommand,
}
+if _sphinx_is_installed:
+ _cmdclass['build_sphinx'] = SPVBuildDocCommand
+
_extras_requires = {
}
@@ -197,6 +249,8 @@
_tests_requires = [
'nose',
+ 'unittest2',
+ 'freshen'
]
_entry_points = {
=== modified file 'slidepresenterview/__init__.py'
--- slidepresenterview/__init__.py 2011-01-06 03:40:30 +0000
+++ slidepresenterview/__init__.py 2011-01-06 06:25:00 +0000
@@ -25,15 +25,27 @@
##########
# Packages
#####
-import sys
-from itertools import imap
+import os, sys
+import itertools
+
+
+##########
+# Paths
+#####
+
+BASE_DIR = '../'
+RESOURCES_DIR = 'resources' # Relative to BASE_DIR
+
+
+_file_path = os.path.dirname(__file__)
+_base_path = os.path.abspath(os.path.join(_file_path, BASE_DIR))
+_resources_path = os.path.join(_base_path, RESOURCES_DIR)
##########
# Version
#####
-
# Version tuple (major, minor, fix, type, type_ver)
# type can be: release, candidate, beta, alpha, dev
# type_ver is a sub-release number (Alpha1 or Alpha2)
@@ -70,8 +82,8 @@
...
ValueError: Unknown version type (Alpha).
- @raise ValueError: If the information from version_info is not valid.
- @return: String with the version
+ :raise ValueError: If the information from version_info is not valid.
+ :return: String with the version
"""
base = '%d.%d.%d' % version[:3]
@@ -121,8 +133,8 @@
...
ValueError: Unknown version type (Alpha).
- @raise ValueError: Version info is not valid.
- @return: The PyPi Development Status string classifier.
+ :raise ValueError: Version info is not valid.
+ :return: The PyPi Development Status string classifier.
"""
version_type = version[3]
if version_type == 'release':
@@ -149,12 +161,12 @@
##
def check_env():
"""
- Checks if all dependencies are installed and if SPV car run wth
+ Checks if all dependencies are installed and if SPV can run with
the current setup (environment).
- @return: True if SPV can be run, False if not.
+ :return: True if SPV can be run, False if not.
- @note: It prints errors to the stderr.
+ :note: It prints errors to the stderr.
"""
return (
_check_python() and
@@ -164,20 +176,20 @@
def _check_python():
- if sys.version_info < (2, 5):
- print >> sys.stderr, "[ERROR] Python >=2.5."
+ if sys.version_info < (2, 6):
+ print >> sys.stderr, "[ERROR] Python >=2.6."
return False
return True
def _check_qt():
try:
+ from PyQt4 import QtGui # pylint: disable-msg=W0612
from PyQt4 import QtCore
- from PyQt4 import QtGui # pylint: disable-msg=W0612
from PyQt4 import QtXml # pylint: disable-msg=W0612
qt_version_str = QtCore.QT_VERSION_STR
- qt_version = tuple(imap(int, qt_version_str.split('.')))
+ qt_version = tuple(itertools.imap(int, qt_version_str.split('.')))
except ImportError:
print >> sys.stderr, "[ERROR] PyQt4 not found."
@@ -207,20 +219,53 @@
return False
return True
-
+
+
+def _load_stylesheet():
+ common_stylesheet = _load_stylesheet_for_platform('all')
+ specific_stylesheet = _load_stylesheet_for_platform(sys.platform)
+ return common_stylesheet + specific_stylesheet
+
+
+def _load_stylesheet_for_platform(platform):
+ from slidepresenterview.config import qt_configuration
+ qss_path = os.path.join(_resources_path, qt_configuration.QSS_DIR)
+
+ try:
+
+ qss = ""
+ for qss_filename in qt_configuration.QSS_LIST_FOR_PLATFORM[platform]:
+ qss_file = os.path.join(qss_path, qss_filename)
+ with open(qss_file, "r") as file_:
+ qss = qss + file_.read()
+ return qss
+ except: # pylint: disable-msg=W0702
+ return "" # There is no style sheet.
+
+
def main_gui():
+ """
+ Main function of the SPV program.
+ """
if not check_env():
sys.exit(1)
-
- from PyQt4 import QtGui
-
+
+ from slidepresenterview.utils import pyqt
from slidepresenterview.ui import console_window
from slidepresenterview.ui import presentation
+ from slidepresenterview.ui.widgets.screen import ScreenProviderQt
+ from slidepresenterview.config import user_configuration
- app = QtGui.QApplication(sys.argv)
+ app = pyqt.get_application()
+ stylesheet = _load_stylesheet()
+ app.setStyleSheet(stylesheet)
+ user_config = user_configuration.UserConfiguration()
presentation_mediator = presentation.Presentation()
- window = console_window.ConsoleWindow(presentation_mediator)
+ screen_provider = ScreenProviderQt(app.desktop())
+ window = console_window.ConsoleWindow(user_config, presentation_mediator,
+ screen_provider)
window.show()
+ if sys.platform == "darwin":
+ window.raise_()
sys.exit(app.exec_())
-
=== added directory 'slidepresenterview/config'
=== added file 'slidepresenterview/config/__init__.py'
--- slidepresenterview/config/__init__.py 1970-01-01 00:00:00 +0000
+++ slidepresenterview/config/__init__.py 2011-01-06 06:25:00 +0000
@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+#
+# SlidePresenterView - Console for presenters
+# https://launchpad.net/slidepresenterview
+#
+# Copyright (C) 2010 Claude Bolduc <claude.bolduc _AT@. gmail.com>
+#
+# This program 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.
+#
+# This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
+#
+"""
+Configurations used by SlidePresenterView.
+"""
=== added file 'slidepresenterview/config/qt_configuration.py'
--- slidepresenterview/config/qt_configuration.py 1970-01-01 00:00:00 +0000
+++ slidepresenterview/config/qt_configuration.py 2011-01-06 06:25:00 +0000
@@ -0,0 +1,35 @@
+# -*- coding: utf-8 -*-
+#
+# SlidePresenterView - Console for presenters
+# https://launchpad.net/slidepresenterview
+#
+# Copyright (C) 2010 Claude Bolduc <claude.bolduc _AT@. gmail.com>
+#
+# This program 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.
+#
+# This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
+#
+"""
+Configurations used by SlidePresenterView for the Qt environment.
+
+It is loaded at the start of the application.
+"""
+
+QSS_DIR = 'ui/qss' # Relative to RESOURCES_DIR
+
+QSS_LIST_FOR_PLATFORM = {
+ 'all': ['common.qss'],
+ 'win32': ['windows.qss'],
+ 'cygwin': [],
+ 'darwin': [],
+ 'linux2': []
+}
=== added file 'slidepresenterview/config/user_configuration.py'
--- slidepresenterview/config/user_configuration.py 1970-01-01 00:00:00 +0000
+++ slidepresenterview/config/user_configuration.py 2011-01-06 06:25:00 +0000
@@ -0,0 +1,72 @@
+# -*- coding: utf-8 -*-
+#
+# SlidePresenterView - Console for presenters
+# https://launchpad.net/slidepresenterview
+#
+# Copyright (C) 2010 Claude Bolduc <claude.bolduc _AT@. gmail.com>
+#
+# This program 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.
+#
+# This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
+#
+"""
+User-defined configurations for SlidePresenterView.
+
+:note: Currently, this contains only configurations for the preferred projectors.
+"""
+
+class UserConfiguration(object):
+
+ OPTION_CONSOLE_SCREEN = 1
+ OPTION_OTHER_SCREENS = 2
+ OPTION_SELECTED_SCREENS = 3
+
+ def __init__(self):
+ self.set_preferred_projectors_configuration(UserConfiguration.OPTION_OTHER_SCREENS, [])
+
+
+ def set_preferred_projectors_configuration(self, projector_type_option,
+ preferred_selected_screens):
+ """
+ :param projector_type_option: Option chose for the type of preferred projectors.
+ Can be either OPTION_CONSOLE_SCREEN, OPTION_OTHER_SCREENS
+ or OPTION_SELECTED_SCREENS.
+
+ :param preferred_selected_screens: List of screen numbers (provided by the screen provider).
+ """
+ # pylint: disable-msg=W0201
+ self._projector_type_option = projector_type_option
+ self._preferred_selected_screens_as_projector = preferred_selected_screens
+
+
+ def is_preferring_presentation_showed_on_other_screens(self):
+ return self._projector_type_option == UserConfiguration.OPTION_OTHER_SCREENS
+
+
+ def is_preferring_presentation_showed_on_console_screen(self):
+ return self._projector_type_option == UserConfiguration.OPTION_CONSOLE_SCREEN
+
+
+ def is_preferring_presentation_showed_on_selected_screens(self):
+ return self._projector_type_option == UserConfiguration.OPTION_SELECTED_SCREENS
+
+
+ def get_preferred_selected_screens_as_projectors(self):
+ return self._preferred_selected_screens_as_projector
+
+
+ def is_screen_selected_as_preferred_projector(self, screen_number):
+ """
+ :param screen_number: The screen numbers is provided by the screen provider of the
+ application.
+ """
+ return screen_number in self._preferred_selected_screens_as_projector
=== modified file 'slidepresenterview/docformats/format_pdf.py'
--- slidepresenterview/docformats/format_pdf.py 2010-05-03 03:13:08 +0000
+++ slidepresenterview/docformats/format_pdf.py 2011-01-06 06:25:00 +0000
@@ -23,10 +23,6 @@
"""
import os
-from PyQt4 import QtGui # pylint: disable-msg=W0611,E0611
- # This import is not necessary, but it removes a bug
- # in the order of the doctests.
-
from QtPoppler import Poppler
from slidepresenterview.docformats import errors
=== modified file 'slidepresenterview/tests/__init__.py'
--- slidepresenterview/tests/__init__.py 2010-07-11 23:14:59 +0000
+++ slidepresenterview/tests/__init__.py 2011-01-06 06:25:00 +0000
@@ -27,6 +27,8 @@
import doctest
import unittest2
+from shutil import copyfile
+
##########
@@ -92,9 +94,16 @@
@param resource_file_name: The file name
- @raise ValueError: If the file doen't exist
- @return: The full path the resource file
+ @raise ValueError: If the file does not exist
+ @return: The full path of the resource file
"""
+ file_path = _get_absolute_path_of_resource(resource_file_name)
+ if not os.path.isfile(file_path):
+ raise ValueError("Test resource file not found at %s." % file_path)
+ return file_path
+
+
+def _get_absolute_path_of_resource(resource_file_name):
res_dir = TEST_RESOURCES_DIR
cur_path = os.path.dirname(__file__)
base_dir = os.path.normpath(BASE_DIR)
@@ -103,7 +112,30 @@
res_path_rel = os.path.join(base_dir, res_dir)
res_path_full = os.path.normpath(os.path.abspath(res_path_rel))
file_path = os.path.join(res_path_full, resource_file_name)
-
- if not os.path.isfile(file_path):
- raise ValueError("Test resource file not found at %s." % file_path)
return file_path
+
+
+def copy_resource(source_file_path, target_resource_file_name):
+ """
+ Copy a source file in a new target resource file.
+
+ @param source_file_path: The source file absolute and normalized path
+ @param target_resource_file_name: The target file name
+
+ @return: The full path of the target resource file
+ """
+ target_file_path = _get_absolute_path_of_resource(target_resource_file_name)
+ copyfile(source_file_path, target_file_path)
+ return target_file_path
+
+
+def delete_resource(resource_file_name):
+ """
+ Delete a resource file.
+
+ @param resource_file_name: The file name
+
+ @raise ValueError: If the file does not exist
+ """
+ file_path = get_resources_path(resource_file_name)
+ os.remove(file_path)
=== added file 'slidepresenterview/tests/functional/choose_projector_screens.feature'
--- slidepresenterview/tests/functional/choose_projector_screens.feature 1970-01-01 00:00:00 +0000
+++ slidepresenterview/tests/functional/choose_projector_screens.feature 2011-01-06 06:25:00 +0000
@@ -0,0 +1,77 @@
+Using step definitions from: 'steps/qt_environment_steps', 'steps/preferred_projectors_steps'
+
+Feature: Choose the projector screens to show the presentation
+
+ In order to give my talk
+ As a presenter
+ I want to be able to choose the projector screens to show the presentation.
+
+
+ Background:
+ Given I have 2 screens
+ And I can select my preferred projectors
+
+
+ Scenario: Hot plugging a screen
+ Given I hot plug a screen
+ When I refresh the available screens
+ Then 3 screens are listed
+
+
+ Scenario: Hot unplugging a screen
+ Given I hot unplug a screen
+ When I refresh the available screens
+ Then 1 screen is listed
+
+
+ Scenario: Hot unplugging all screens
+ Given I hot unplug all screens
+ When I refresh the available screens
+ Then 0 screen is listed
+
+
+ Scenario: Selecting other screens as projectors
+ When I select other screens as projectors
+ And I apply the changes
+ Then the other screens are selected as projectors
+
+
+ Scenario: Selecting the console screen as projector
+ When I select the console screen as projector
+ And I apply the changes
+ Then the console screen is selected as projector
+
+
+ Scenario Outline: Selecting screens as projector
+ When I select screens <screens> as projectors
+ And I apply the changes
+ Then screens <screens> are selected as projectors
+ And the other screens are not selected as projectors
+ Examples:
+ | screens |
+ | 1 |
+ | 2 |
+ | 1, 2 |
+
+
+ Scenario: Applying the changes stores current view
+ When I select screen 2 as projectors
+ And I select other screens as projectors
+ And I apply the changes
+ Then the other screens are selected as projectors
+ And screen 2 is selected as projector
+
+
+ Scenario: Discarding the changes does not modify the initial configuration
+ Given my old preferences was to select the console screen as projector
+ When I select other screens as projectors
+ And I discard the changes
+ Then my old preferences are kept
+
+
+ Scenario: Bumping old preferences that do not exist in the current configuration
+ Given my old preferences was to select screens 1 and 3 as projectors
+ When I select other screens as projectors
+ And I apply the changes
+ Then the other screens are selected as projectors
+ And screen 1 is selected as projector
=== modified file 'slidepresenterview/tests/functional/open_pdf.feature'
--- slidepresenterview/tests/functional/open_pdf.feature 2010-05-03 00:07:27 +0000
+++ slidepresenterview/tests/functional/open_pdf.feature 2011-01-06 06:25:00 +0000
@@ -57,7 +57,8 @@
When I open a file named with spaces
Then the requested document is opened
-
+
+ @need_unicode_file
Scenario: Open a file with Unicode characters
Given no already opened document
When I open a file named with Unicode characters
=== modified file 'slidepresenterview/tests/functional/steps/console_steps.py'
--- slidepresenterview/tests/functional/steps/console_steps.py 2010-05-03 00:18:13 +0000
+++ slidepresenterview/tests/functional/steps/console_steps.py 2011-01-06 06:25:00 +0000
@@ -24,11 +24,9 @@
"""
__all__ = []
-from PyQt4 import QtCore, QtGui
from mock import Mock
-from freshen import *
-from nose.tools import assert_true
+from freshen import Given, assert_true
from slidepresenterview.tests.functional.steps.slide_view_steps import *
=== modified file 'slidepresenterview/tests/functional/steps/document_steps.py'
--- slidepresenterview/tests/functional/steps/document_steps.py 2010-05-03 00:34:47 +0000
+++ slidepresenterview/tests/functional/steps/document_steps.py 2011-01-06 06:25:00 +0000
@@ -24,15 +24,12 @@
"""
__all__ = []
-from PyQt4 import QtCore, QtGui
-
-from freshen import *
-from nose.tools import assert_not_equals, assert_true
-
-from slidepresenterview.utils import pyqt as pyqtutils
+from freshen import (Before, After, Given, When, Then, scc, run_steps,
+ assert_true, assert_equals, assert_not_equals)
from slidepresenterview.tests.helpers.open_documents import (PDF_3PAGES,
- PDF_WITH_SPACE, PDF_UNICODE, PDF_1PAGE_100X100, PDF_INVALID,
+ PDF_WITH_SPACE, PDF_1PAGE_100X100, PDF_INVALID,
+ create_unicode_file,
simulate_open_file_dialog, simulate_cancel_file_dialog,
WarningRaised)
@@ -42,10 +39,19 @@
'3PAGES': PDF_3PAGES,
'INVALID': PDF_INVALID,
'SPACE': PDF_WITH_SPACE,
- 'UNICODE': PDF_UNICODE,
'NOTFOUND': 'not_found.pdf',
}
-
+
+@Before("@need_unicode_file")
+def create_pdf_unicode_file(scenario):
+ file_path = create_unicode_file()
+ DOCS['UNICODE'] = file_path
+
+@After("@need_unicode_file")
+def delete_pdf_unicode_file(scenario):
+ DOCS['UNICODE'] = None
+ #delete_unicode_file() #FIXME not working
+
@Given("a document opened at page (\d+)[.]?$")
def set_already_some_doc_at_page(page):
@@ -63,8 +69,10 @@
@Given("the document ([A-Z0-9_]+) opened at page (\d+|None)[.]?$")
def set_already_doc_at_page(doc, page):
- run_steps('Given I previously opened the document %s' % doc)
- run_steps('Given is currently at page %s' % page)
+ run_steps("""
+ Given I previously opened the document %s
+ And is currently at page %s
+ """ % (doc, page))
@Given("is currently at page (\d+|None).*")
def set_already_at_page(page):
@@ -141,8 +149,10 @@
@Then("the document ([A-Z0-9_]*|None) is(?: still)? opened at page (\d+|None)")
def check_opened_at_page(doc, page):
- run_steps('Then the document %s is still opened' % doc)
- run_steps('Then is at page %s' % page)
+ run_steps("""
+ Then the document %s is still opened
+ And is at page %s
+ """ % (doc, page))
@Then("the document ([A-Z0-9_]*|None) is(?: still)? opened[.]?$")
def check_doc_opened(doc):
@@ -156,8 +166,10 @@
@Then("the requested document is opened at page (\d+|None)$")
def check_requested_doc_at_page(page):
- run_steps('Then the requested document is opened')
- run_steps('Then is at page %s' % page)
+ run_steps("""
+ Then the requested document is opened
+ And is at page %s
+ """ % page)
@Then("the requested document is opened$")
def check_requested_doc():
@@ -204,8 +216,10 @@
@Then("both buttons are (enabled|disabled)[.]?$")
def check_both_buttons(status):
- run_steps('Then the next button is %s' % status)
- run_steps('Then the previous button is %s' % status)
+ run_steps("""
+ Then the next button is %s
+ And the previous button is %s
+ """ % (status, status))
@Then("no warning was shown")
def check_no_warning():
=== added file 'slidepresenterview/tests/functional/steps/preferred_projectors_steps.py'
--- slidepresenterview/tests/functional/steps/preferred_projectors_steps.py 1970-01-01 00:00:00 +0000
+++ slidepresenterview/tests/functional/steps/preferred_projectors_steps.py 2011-01-06 06:25:00 +0000
@@ -0,0 +1,182 @@
+# -*- coding: utf-8 -*-
+#
+# SlidePresenterView - Console for presenters
+# https://launchpad.net/slidepresenterview
+#
+# Copyright (C) 2010 Claude Bolduc <claude.bolduc _AT@. gmail.com>
+#
+# This program 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.
+#
+# This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+"""
+Step definitions when configuring the preferred projectors.
+"""
+__all__ = []
+
+from PyQt4 import QtGui, QtCore
+
+from mock import Mock
+
+from freshen import (Before, Given, When, Then, Transform, scc,
+ assert_true, assert_false, assert_equal)
+
+
+from slidepresenterview.ui.widgets.screen import convert_to_spv_screen_number
+from slidepresenterview.ui.widgets.preferred_projectors import PreferredProjectors
+
+
+class _FakeScreenInformationProvider(object):
+ FAKE_SCREEN_SIZE = QtCore.QRect(0,0,1000,1000)
+
+ def __init__(self, screen_info_provider):
+ self.mock = Mock(spec=QtGui.QDesktopWidget)
+ self.mock.screenGeometry.return_value = _FakeScreenInformationProvider.FAKE_SCREEN_SIZE
+ self.mock.screenNumber.return_value = 0
+
+ self.screen_info_provider = screen_info_provider
+ self.screen_info_provider._screen_information_provider = self.mock
+
+
+ def set_fake_screens(self, number_of_screens):
+ self.mock.numScreens.return_value = number_of_screens
+
+
+ def add_fake_screens(self, number_of_screens_added):
+ old_number_of_screens = self.mock.numScreens()
+ self.set_fake_screens(old_number_of_screens + number_of_screens_added)
+
+
+@Before
+def replace_screen_info_provider(scenario):
+ scc.mock_screen_info_provider = _FakeScreenInformationProvider(scc.screen_info_provider)
+
+
+@Transform(r"^(\d+) screen$")
+def transform_number_of_screens(number_of_screens):
+ return int(number_of_screens)
+
+@Transform(r"^screen[s]? (.+)$")
+def transform_screens(screens):
+ screens_string = screens.split(',')
+ screens_int = [int(screen_string.strip()) for screen_string in screens_string]
+ return screens_int
+
+
+@Given("^I can select my preferred projectors$")
+def can_select_projectors():
+ scc.pref_projectors = PreferredProjectors(scc.user_config,
+ scc.mock_screen_info_provider.screen_info_provider,
+ scc.win,
+ scc.win)
+
+
+@Given("^I have (\d+ screen)[s]?$")
+def have_number_of_screens(number_of_screens):
+ scc.mock_screen_info_provider.set_fake_screens(number_of_screens)
+
+@Given("^I hot plug a screen$")
+def hot_plugging_a_screen():
+ scc.mock_screen_info_provider.add_fake_screens(1)
+
+@Given("^I hot unplug a screen$")
+def hot_unplugging_a_screen():
+ scc.mock_screen_info_provider.add_fake_screens(-1)
+
+@Given("^I hot unplug all screens$")
+def hot_unplugging_all_screens():
+ scc.mock_screen_info_provider.set_fake_screens(0)
+
+@Given("^my old preferences was to select screens 1 and 3 as projectors$")
+def set_old_preference():
+ scc.user_config.set_preferred_projectors_configuration(
+ scc.user_config.OPTION_SELECTED_SCREENS, [1, 3])
+ scc.pref_projectors.on_refresh_button_clicked(True)
+
+@Given("^my old preferences was to select the console screen as projector$")
+def set_old_preference_console_screen():
+ scc.user_config.set_preferred_projectors_configuration(
+ scc.user_config.OPTION_CONSOLE_SCREEN, [])
+ scc.pref_projectors.on_refresh_button_clicked(True)
+
+
+
+@When("^I select other screens as projectors$")
+def select_other_screens_as_projectors():
+ scc.pref_projectors.other_screens_button.setChecked(True)
+
+@When("^I select the console screen as projector$")
+def select_console_screen_as_projector():
+ scc.pref_projectors.console_screen_button.setChecked(True)
+
+@When("^I select (screen[s]? .+) as projector[s]?$")
+def select_specific_screens_as_projectors(screen_numbers):
+ scc.pref_projectors.selected_screens_button.setChecked(True)
+ _set_check_state_for_selected_screens(screen_numbers)
+
+def _set_check_state_for_selected_screens(screen_numbers):
+ screen_checkboxes = scc.pref_projectors._screen_items
+ num_available_screens = len(screen_checkboxes)
+ if num_available_screens < max(screen_numbers):
+ _fail("Cannot select a screen as projector that have a number bigger than"
+ + " the number of available screens.")
+
+ for row, screen_checkbox in enumerate(screen_checkboxes):
+ _set_check_state_for_item_at_according_to(screen_checkbox, row, screen_numbers)
+
+def _fail(message):
+ assert_true(False, message)
+
+def _set_check_state_for_item_at_according_to(screen_checkbox, row, screen_numbers):
+ spv_screen_number = convert_to_spv_screen_number(row)
+ is_checked = spv_screen_number in screen_numbers
+ screen_checkbox.setChecked(is_checked)
+
+@When("^I refresh the available screens$")
+def refresh_screens():
+ scc.pref_projectors.on_refresh_button_clicked(True)
+
+@When("^I apply the changes$")
+def apply_changes():
+ scc.pref_projectors.accept()
+
+@When("^I discard the changes$")
+def discard_changes():
+ scc.pref_projectors.reject()
+
+
+@Then("^the other screens are selected as projectors$")
+def check_other_screens_selected():
+ assert_true(scc.user_config.is_preferring_presentation_showed_on_other_screens())
+
+@Then("^the console screen is selected as projector$")
+def check_console_screen_selected():
+ assert_true(scc.user_config.is_preferring_presentation_showed_on_console_screen())
+
+@Then("^my old preferences are kept$")
+def check_old_preferences_are_kept():
+ assert_true(scc.user_config.is_preferring_presentation_showed_on_console_screen())
+
+
+@Then("^(\d+ screen)[s]? (?:are|is) listed$")
+def check_number_of_screens(number_of_screens):
+ num_listed_screens = len(scc.pref_projectors._screen_items)
+ assert_equal(num_listed_screens, number_of_screens)
+
+@Then("^(screen[s]? .+) (?:are|is) selected as projector[s]?$")
+def check_screens_selected(screen_numbers):
+ assert_equal(scc.user_config.get_preferred_selected_screens_as_projectors(), screen_numbers)
+
+@Then("^the other screens are not selected as projectors$")
+def check_other_screens_not_selected():
+ assert_false(scc.user_config.is_preferring_presentation_showed_on_other_screens())
\ No newline at end of file
=== modified file 'slidepresenterview/tests/functional/steps/qt_environment_steps.py'
--- slidepresenterview/tests/functional/steps/qt_environment_steps.py 2010-05-03 02:50:56 +0000
+++ slidepresenterview/tests/functional/steps/qt_environment_steps.py 2011-01-06 06:25:00 +0000
@@ -24,22 +24,25 @@
"""
__all__ = []
-from PyQt4 import QtCore, QtGui
+from PyQt4 import QtCore
from freshen import Before, After, scc, glc
from slidepresenterview.utils import pyqt as pyqtutils
+from slidepresenterview.config import user_configuration
from slidepresenterview.ui import presentation
from slidepresenterview.ui.console_window import ConsoleWindow
+from slidepresenterview.ui.widgets.screen import ScreenProviderQt
@Before
def before(sc):
- if glc.app is None: # QApplication must be started ONLY ONCE!
- glc.app = QtGui.QApplication([])
+ glc.app = pyqtutils.get_application()
QtCore.qInstallMsgHandler(pyqtutils.msg_handler_no_message)
+ scc.user_config = user_configuration.UserConfiguration()
scc.presentation_mediator = presentation.Presentation()
- scc.win = ConsoleWindow(scc.presentation_mediator)
+ scc.screen_info_provider = ScreenProviderQt(glc.app.desktop())
+ scc.win = ConsoleWindow(scc.user_config, scc.presentation_mediator, scc.screen_info_provider)
scc.slide_view = scc.win.current_slide_view
@After
=== modified file 'slidepresenterview/tests/functional/steps/slide_view_steps.py'
--- slidepresenterview/tests/functional/steps/slide_view_steps.py 2010-05-03 00:18:13 +0000
+++ slidepresenterview/tests/functional/steps/slide_view_steps.py 2011-01-06 06:25:00 +0000
@@ -22,10 +22,7 @@
"""
Step definitions when using a slide view.
"""
-from PyQt4 import QtCore, QtGui
-
-from freshen import *
-from nose.tools import assert_true
+from freshen import When, Then, scc, assert_equal
class _NextAction: pass
@@ -66,4 +63,4 @@
elif action is _PREVIOUS:
button = scc.win.current_slide_previous_button
- assert_equals(should_be_enabled, button.isEnabled())
\ No newline at end of file
+ assert_equal(should_be_enabled, button.isEnabled())
\ No newline at end of file
=== modified file 'slidepresenterview/tests/helpers/open_documents.py'
--- slidepresenterview/tests/helpers/open_documents.py 2010-05-03 00:34:47 +0000
+++ slidepresenterview/tests/helpers/open_documents.py 2011-01-06 06:25:00 +0000
@@ -29,7 +29,6 @@
'PDF_INVALID',
'PDF_1PAGE_100X100',
'PDF_1PAGE_100X100',
- 'PDF_UNICODE',
'PDF_WITH_SPACE',
]
@@ -38,16 +37,25 @@
from PyQt4 import QtCore
import mock
-from slidepresenterview.tests import get_resources_path
+from slidepresenterview.tests import get_resources_path, copy_resource, delete_resource
PDF_3PAGES = get_resources_path(u'slides_3p.pdf')
PDF_2PAGES = get_resources_path(u'slides_2p.pdf')
PDF_INVALID = get_resources_path(u'invalid.pdf')
PDF_1PAGE_100X100 = get_resources_path(u'100x100.pdf')
-PDF_UNICODE = get_resources_path(u'unicode_éè.pdf')
PDF_WITH_SPACE = get_resources_path(u'with space.pdf')
+PDF_UNICODE_FILE_NAME = u'unicode_éè.pdf'
+def create_unicode_file():
+ """
+ @return: the absolute path of the created unicode file
+ """
+ return copy_resource(PDF_1PAGE_100X100, PDF_UNICODE_FILE_NAME)
+
+def delete_unicode_file():
+ delete_resource(PDF_UNICODE_FILE_NAME)
+
_QT4_OPEN_METHOD = 'PyQt4.QtGui.QFileDialog.getOpenFileName'
_QT4_WARNING_DIALOG = 'PyQt4.QtGui.QMessageBox.warning'
=== modified file 'slidepresenterview/tests/unit/docformats/tests_pdf.py'
--- slidepresenterview/tests/unit/docformats/tests_pdf.py 2010-07-11 17:27:55 +0000
+++ slidepresenterview/tests/unit/docformats/tests_pdf.py 2011-01-06 06:25:00 +0000
@@ -25,7 +25,7 @@
import os
import unittest2
from nose.tools import assert_equals, assert_true
-from mock import Mock, sentinel, patch_new, patch
+from mock import Mock, patch
from PyQt4 import QtCore
=== added file 'slidepresenterview/tests/unit/ui/widgets/tests_screen.py'
--- slidepresenterview/tests/unit/ui/widgets/tests_screen.py 1970-01-01 00:00:00 +0000
+++ slidepresenterview/tests/unit/ui/widgets/tests_screen.py 2011-01-06 06:25:00 +0000
@@ -0,0 +1,98 @@
+# -*- coding: utf-8 -*-
+#
+# SlidePresenterView - Console for presenters
+# https://launchpad.net/slidepresenterview
+#
+# Copyright (C) 2010 Claude Bolduc <claude.bolduc _AT@. gmail.com>
+#
+# This program 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.
+#
+# This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+"""
+Unit tests for slidepresenterview.ui.widgets.screen.
+"""
+import unittest2
+
+from mock import Mock, patch
+
+from PyQt4 import QtCore
+
+from slidepresenterview.ui.widgets.screen import ScreenDescription, Resolution, ScreenProviderQt
+
+
+class TestResolution(unittest2.TestCase):
+ """
+ Tests for the class Resolution.
+ """
+
+ def setUp(self):
+ self.resolution_100_x_200 = Resolution(100, 200)
+
+
+ def should_have_correct_string_representation(self):
+ self.assertEqual(str(self.resolution_100_x_200), "100x200")
+
+
+class TestScreenDescription(unittest2.TestCase):
+ """
+ Tests for the class ScreenDescription.
+ """
+
+ def setUp(self):
+ self.screen = ScreenDescription(2, Resolution(100, 200))
+
+
+ def should_have_correct_string_representation(self):
+ self.assertEqual(str(self.screen), "Screen #2 (100x200)")
+
+
+class TestScreenProviderQt(unittest2.TestCase):
+ """
+ Tests for the class ScreenProviderQt.
+ """
+
+ def setUp(self):
+ self.mock_info_provider = Mock(spec=['numScreens', 'screenGeometry', 'screenNumber'])
+ self.mock_info_provider.numScreens.return_value = 2
+ self.mock_info_provider.screenGeometry.return_value = QtCore.QRect(0,0,100,200)
+ self.mock_info_provider.screenNumber.return_value = 0
+ with patch('PyQt4.QtCore.QObject.connect'):
+ self.screen_provider = ScreenProviderQt(self.mock_info_provider)
+
+
+ def should_give_two_screen_descriptions(self):
+ screen_descriptions = self.screen_provider.get_screen_descriptions()
+
+ self.assertEqual(2, len(screen_descriptions))
+ self.assertEqual(ScreenDescription(1, Resolution(100, 200)), screen_descriptions[0])
+ self.assertEqual(ScreenDescription(2, Resolution(100, 200)), screen_descriptions[1])
+
+ def should_give_correct_first_screen_description(self):
+ screen_description = self.screen_provider.get_screen_description(1)
+ self.assertEqual(ScreenDescription(1, Resolution(100, 200)), screen_description)
+
+
+ def should_give_correct_second_screen_description(self):
+ screen_description = self.screen_provider.get_screen_description(2)
+ self.assertEqual(ScreenDescription(2, Resolution(100, 200)), screen_description)
+
+
+ def should_give_correct_screen_description_when_getting_screen_containing_a_widget(self):
+ screen_description = self.screen_provider.get_screen_description_containing(Mock())
+ self.assertEqual(ScreenDescription(1, Resolution(100, 200)), screen_description)
+
+
+if __name__ == "__main__":
+ from slidepresenterview.tests import MultiplePrefixesTestLoader
+ unittest2.main(testLoader=MultiplePrefixesTestLoader())
=== modified file 'slidepresenterview/tests/unit/ui/widgets/tests_slideview.py'
--- slidepresenterview/tests/unit/ui/widgets/tests_slideview.py 2010-07-11 17:27:55 +0000
+++ slidepresenterview/tests/unit/ui/widgets/tests_slideview.py 2011-01-06 06:25:00 +0000
@@ -31,8 +31,6 @@
from slidepresenterview.utils import pyqt as pyqtutils
-from slidepresenterview import tests
-from slidepresenterview.docformats import errors
from slidepresenterview.docformats.format_pdf import PDFDocumentQt
from slidepresenterview.ui.presentation import Presentation
from slidepresenterview.ui.widgets.slideview import SlideViewStretchable
@@ -54,14 +52,13 @@
"""
def setUp(self):
+ self.app = pyqtutils.get_application()
QtCore.qInstallMsgHandler(pyqtutils.msg_handler_no_message)
- self.app = QtGui.QApplication([])
self.view = SlideViewStretchable()
def tearDown(self):
- self.app.quit()
QtCore.qInstallMsgHandler(None)
@@ -117,8 +114,8 @@
* Create a mock presentation associated to this slide view.
* The mock presentation contains a mock document.
"""
+ self.app = pyqtutils.get_application()
QtCore.qInstallMsgHandler(pyqtutils.msg_handler_no_message)
- self.app = QtGui.QApplication([])
self.view = SlideViewStretchable()
self.mock_presentation = Mock(spec=Presentation)
@@ -128,7 +125,6 @@
def tearDown(self):
- self.app.quit()
QtCore.qInstallMsgHandler(None)
=== added directory 'slidepresenterview/tests/unit/utils'
=== added file 'slidepresenterview/tests/unit/utils/tests_observation.py'
--- slidepresenterview/tests/unit/utils/tests_observation.py 1970-01-01 00:00:00 +0000
+++ slidepresenterview/tests/unit/utils/tests_observation.py 2011-01-06 06:25:00 +0000
@@ -0,0 +1,156 @@
+# -*- coding: utf-8 -*-
+#
+# SlidePresenterView - Console for presenters
+# https://launchpad.net/slidepresenterview
+#
+# Copyright (C) 2010 F.-A. Bourbonnais <bouf10pub _AT@. rubico.info>
+#
+# This program 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.
+#
+# This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
+#
+"""
+Tests for `observation` module.
+"""
+
+__all__ = ['ObservableTestCase']
+
+
+import unittest2
+
+from mock import Mock
+
+from slidepresenterview.utils.observation import Observable
+
+
+EVENT = 'on_some_event'
+
+
+
+class ObservableTestCase(unittest2.TestCase):
+
+
+ def assert_received_notification(self, observer, event):
+ attr = getattr(observer, event, Mock())
+ self.assertTrue(attr.called)
+
+ def assert_not_received_notification(self, observer, event):
+ attr = getattr(observer, event, Mock())
+ self.assertFalse(attr.called)
+
+
+ def assert_is_register_in(self, observer, observable):
+ self.assertIn(observer, observable._observers,
+ "'%s' should be an observer." % observer)
+
+ def assert_is_not_register_in(self, observer, observable):
+ self.assertNotIn(observer, observable._observers,
+ "'%s' should not be an observer." % observer)
+
+
+
+class TestObserverGivenNoObserver(ObservableTestCase):
+
+ def setUp(self):
+ self.observer = Mock(spec=[EVENT])
+ self.observable = Observable()
+
+
+ def test_unregister_object_should_do_nothing(self):
+ obj = object()
+ self.observable.unregister_observer(obj)
+
+
+ def test_register_object_should_add_observer(self):
+ self.observable.register_observer(self.observer)
+ self.assert_is_register_in(self.observer, self.observable)
+
+
+ def test_notify_should_do_nothing(self):
+ self.observable._notify_observers(EVENT) #pylint: disable-msg=W0212
+ self.assert_not_received_notification(self.observer, EVENT)
+
+
+class TestObserverGivenSomeObservers(ObservableTestCase):
+
+ def setUp(self):
+ self.observer = Mock(spec=[EVENT])
+ self.other_observer = Mock(spec=[EVENT])
+ self.observable = Observable()
+ self.observable.register_observer(self.observer)
+ self.observable.register_observer(self.other_observer)
+
+
+ def test_register_twice_should_do_nothing(self):
+ self.observable.register_observer(self.observer) #Already registered
+
+ #pylint: disable-msg=W0212
+ self.assertEquals(2, len(self.observable._observers))
+
+
+ def test_unregister_observer_should_remove_it(self):
+ self.observable.unregister_observer(self.observer)
+ self.assert_is_not_register_in(self.observer, self.observable)
+
+
+ def test_unregister_observer_should_remove_only_it(self):
+ self.observable.unregister_observer(self.observer)
+ self.assert_is_register_in(self.other_observer, self.observable)
+
+
+ def should_notify_all_observers(self):
+ self.observable._notify_observers(EVENT) #pylint: disable-msg=W0212
+ self.assert_received_notification(self.observer, EVENT)
+ self.assert_received_notification(self.other_observer, EVENT)
+
+
+ def should_be_able_to_unregister_and_register_again(self):
+ self.observable.unregister_observer(self.observer)
+ self.observable.register_observer(self.observer)
+
+ self.assert_is_register_in(self.observer, self.observable)
+
+
+ def should_ignore_observer_that_doesnt_handle_the_event(self):
+ obj = Mock(spec=[])
+ self.observable.register_observer(obj)
+ self.observable._notify_observers(EVENT) #pylint: disable-msg=W0212
+
+ self.assert_not_received_notification(obj, EVENT)
+
+
+# pylint: disable-msg=R0904,C0103
+class TestObserverGivenUnregisteringAllObservers(ObservableTestCase):
+
+ def setUp(self):
+ self.observer = Mock(spec=[EVENT])
+ self.observable = Observable()
+ self.observable.register_observer(self.observer)
+ self.observable.unregister_observer(self.observer)
+
+
+ def should_be_able_to_register_one(self):
+ obj = object()
+ self.observable.register_observer(obj)
+
+ self.assert_is_register_in(obj, self.observable)
+
+
+ def test_notify_should_do_nothing(self):
+ self.observable._notify_observers(EVENT) #pylint: disable-msg=W0212
+ self.assert_not_received_notification(self.observer, EVENT)
+
+
+
+if __name__ == "__main__":
+ from slidepresenterview.tests import MultiplePrefixesTestLoader
+ unittest2.main(testLoader=MultiplePrefixesTestLoader())
=== modified file 'slidepresenterview/ui/console_window.py'
--- slidepresenterview/ui/console_window.py 2010-05-02 23:36:17 +0000
+++ slidepresenterview/ui/console_window.py 2011-01-06 06:25:00 +0000
@@ -26,69 +26,88 @@
##########
# Imports
#####
-from PyQt4 import QtCore # pylint: disable-msg=E0611
from PyQt4 import QtGui
+from PyQt4 import QtCore
from slidepresenterview.ui.qt_forms.console import Ui_console_window
+from slidepresenterview.ui.widgets.preferred_projectors import PreferredProjectors
from slidepresenterview.utils import pyqt as pyqtutils
-class ConsoleWindow(QtGui.QMainWindow, # pylint: disable-msg=E1101
+class ConsoleWindow(QtGui.QMainWindow,
Ui_console_window):
"""
The main console window for SlidePresenterView.
It is the presenter's console that allows to control projectors.
- @ivar presentation_mediator: a presentation mediator.
+ :ivar presentation_mediator: a presentation mediator.
"""
- def __init__(self, presentation_mediator):
- """
- Constructor. Set up the UI and manage the presentation mediator.
-
- @param presentation_mediator: the presentation mediator.
- """
- QtGui.QMainWindow.__init__(self) # pylint: disable-msg=E1101
+ def __init__(self, user_config, presentation_mediator, screen_provider):
+ QtGui.QMainWindow.__init__(self)
Ui_console_window.__init__(self)
self.setupUi(self)
+ self.user_config = user_config
+ self.screen_provider = screen_provider
+
self.set_presentation_mediator(presentation_mediator)
presentation_mediator.register_console_window(self)
- self.current_slide_view.set_presentation_mediator(\
- presentation_mediator)
- presentation_mediator.register_current_slide_view(\
- self.current_slide_view)
+ self.current_slide_view.set_presentation_mediator(presentation_mediator)
+ presentation_mediator.register_current_slide_view(self.current_slide_view)
# Disables buttons by default (mainly because we must be sure that
# the workaround for some versions of Qt is applied).
pyqtutils.set_button_enabled(self.current_slide_next_button, False)
pyqtutils.set_button_enabled(self.current_slide_previous_button, False)
-
+ self._set_shortcuts_for_actions()
+
+
+ def _set_shortcuts_for_actions(self):
+ """
+ .. note::
+
+ We do not use QtCreator or designer to do this because
+ it does not seem to handle standard shortcuts and
+ multiple shortcuts.
+ """
+ self.action_open.setShortcut(QtGui.QKeySequence.Open)
+ self.action_exit.setShortcut(QtGui.QKeySequence.Close)
+ self.action_show_presentation.setShortcuts([QtCore.Qt.Key_F5,
+ QtCore.Qt.CTRL + QtCore.Qt.Key_L])
+ self.action_end_presentation.setShortcuts([QtCore.Qt.Key_Escape,
+ QtCore.Qt.CTRL + QtCore.Qt.Key_E])
+
+
def set_presentation_mediator(self, presentation):
"""
Set the presentation mediator.
- @param presentation: the presentation mediator.
+
+ :param presentation: the presentation mediator.
"""
self.presentation_mediator = presentation # pylint: disable-msg=W0201
def on_action_exit_triggered(self, checked=None):
"""
- Actions to be taken when the exit button is triggered.
+ Actions to be taken when the :guilabel:`exit` button is triggered.
"""
- if checked is None: return
- self.close() # pylint: disable-msg=E1101
+ if checked is None:
+ return
+ self.close()
def on_action_open_triggered(self, checked=None):
"""
- Actions to be taken when the open button is triggered.
+ Actions to be taken when the :guilabel:`open` button is triggered.
"""
- if checked is None: return
+ if checked is None:
+ return
+
filename_qstr = \
- QtGui.QFileDialog.getOpenFileName( # pylint: disable-msg=E1101
+ QtGui.QFileDialog.getOpenFileName(
self, "Open file",
- QtCore.QString(), # pylint: disable-msg=E1101
+ QtCore.QString(),
"PDF file (*.pdf);;")
filename = unicode(filename_qstr)
if filename is None or len(filename) < 1:
@@ -104,13 +123,13 @@
self._enable_next_previous_button()
- def on_current_slide_previous_button_clicked( # pylint: disable-msg=C0103
- self,
+ def on_current_slide_previous_button_clicked(self,
checked=None):
"""
- Actions to be taken when the previous button is triggered.
+ Actions to be taken when the :guilabel:`previous` button is triggered.
"""
- if checked is None: return
+ if checked is None:
+ return
page = self.presentation_mediator.current_page_no
doc = self.presentation_mediator.document
@@ -126,13 +145,13 @@
self.presentation_mediator.go_to_slide(page - 1)
- def on_current_slide_next_button_clicked( # pylint: disable-msg=C0103
- self,
+ def on_current_slide_next_button_clicked(self,
checked=None):
"""
- Actions to be taken when the next button is triggered.
+ Actions to be taken when the :guilabel:`next` button is triggered.
"""
- if checked is None: return
+ if checked is None:
+ return
page = self.presentation_mediator.current_page_no
doc = self.presentation_mediator.document
@@ -156,30 +175,36 @@
def _enable_next_previous_button(self):
"""
- Decide to enable/disable the next and previous buttons.
+ Decide to enable/disable the :guilabel:`next` and :guilabel:`previous` buttons.
"""
page = self.presentation_mediator.current_page_no
doc = self.presentation_mediator.document
if doc is None or not self.presentation_mediator.is_file_opened():
- pyqtutils.set_button_enabled(self.current_slide_next_button,
- False)
- pyqtutils.set_button_enabled(self.current_slide_previous_button,
- False)
+ pyqtutils.set_button_enabled(self.current_slide_next_button, False)
+ pyqtutils.set_button_enabled(self.current_slide_previous_button, False)
return
doc_page_count = doc.get_page_count()
if page < doc_page_count and page > 0:
- pyqtutils.set_button_enabled(self.current_slide_next_button,
- True)
+ pyqtutils.set_button_enabled(self.current_slide_next_button, True)
else:
- pyqtutils.set_button_enabled(self.current_slide_next_button,
- False)
+ pyqtutils.set_button_enabled(self.current_slide_next_button, False)
if page > 1 and page <= doc_page_count:
- pyqtutils.set_button_enabled(self.current_slide_previous_button,
- True)
+ pyqtutils.set_button_enabled(self.current_slide_previous_button, True)
else:
- pyqtutils.set_button_enabled(self.current_slide_previous_button,
- False)
+ pyqtutils.set_button_enabled(self.current_slide_previous_button, False)
+
+
+ def on_action_configure_preferred_projectors_triggered(self, checked=None):
+ """
+ Actions to be taken when the :guilabel:`configure preferred projector(s)` button is
+ triggered.
+ """
+ if checked is None:
+ return
+
+ pref_projectors = PreferredProjectors(self.user_config, self.screen_provider, self, self)
+ pref_projectors.exec_()
=== modified file 'slidepresenterview/ui/presentation.py'
--- slidepresenterview/ui/presentation.py 2010-01-27 06:34:41 +0000
+++ slidepresenterview/ui/presentation.py 2011-01-06 06:25:00 +0000
@@ -31,8 +31,6 @@
from slidepresenterview.docformats import errors
from slidepresenterview.docformats.format_pdf import PDFDocumentQt
-#from slidepresenterview.ui.console_window import ConsoleWindow
-#from slidepresenterview.ui.widgets.slideview import SlideViewStretchable
class Presentation(object):
"""
@@ -40,22 +38,18 @@
It allows to notify the presenter view, the projectors and other objects
of any change in the current presentation.
- @ivar document: Opened document OR None
-
- @ivar current_page_no: Current page number OR None if no document opened
-
- @ivar console_window: Main console window of the application.
- @type console_window: ConsoleWindow object (or None if not set).
-
- @ivar current_slide_view: The current slide view for the presentation.
- @type current_slide_view: SlideViewStretchable object
- (or None if not set).
+ :ivar document: Opened document OR None
+
+ :ivar current_page_no: Current page number OR None if no document opened
+
+ :ivar console_window: Main console window of the application.
+ :type console_window: ConsoleWindow object (or None if not set).
+
+ :ivar current_slide_view: The current slide view for the presentation.
+ :type current_slide_view: SlideViewStretchable object (or None if not set).
"""
def __init__(self):
- """
- Initialization of a L{PresentationMediator}.
- """
self.document = None
self.current_page_no = None
self.console_window = None
@@ -64,9 +58,7 @@
def number_of_colleagues(self):
"""
- Returns the number of colleagues currently registered.
-
- @return: the number of colleagues currently registered.
+ :return: The number of colleagues currently registered.
"""
number_of_colleagues = 0
if self.console_window is not None:
@@ -81,7 +73,7 @@
Test if the received potential colleague is a colleague of the
presentation.
- @return: A boolean stating if the presentation has the potential
+ :return: A boolean stating if the presentation has the potential
colleague as a registered colleague.
"""
return (self.console_window == potential_colleague
@@ -92,8 +84,8 @@
"""
Register the console window for the mediator.
- @param console_window: the console window to register.
- @type console_window: ConsoleWindow object.
+ :param console_window: the console window to register.
+ :type console_window: ConsoleWindow object.
"""
self.console_window = console_window
@@ -102,24 +94,25 @@
"""
Register the current slide view for the mediator.
- @param current_slide_view: the slide view to register.
- @type current_slide_view: SlideViewStretchable object.
+ :param current_slide_view: the slide view to register.
+ :type current_slide_view: SlideViewStretchable object.
"""
self.current_slide_view = current_slide_view
def open_presentation(self, file_name, page=1):# pylint: disable-msg=R0911
"""
- Open the file "file_name" at page "page".
-
- @note: The document attribute is changed only if the file
- can be opened.
-
- @param file_name: The file to be opened
- @type file_name: A unicode string (Python)
- @param page: The page number (starting at 1).
-
- @return: True on success, False if not (error or document already open)
+ Open a file at a given page.
+
+ .. note::
+
+ The document attribute is changed only if the file can be opened.
+
+ :param file_name: The file to be opened
+ :type file_name: A unicode string (Python)
+ :param page: The page number (starting at 1).
+
+ :return: True on success, False if not (error or document already open)
"""
#TODO: open_presentation should not handle exceptions, but must only
# propagate it. Also, if the exceptions is just propagated, the
@@ -166,15 +159,15 @@
def go_to_slide(self, page=1):
"""
- Go to the page "page" of the currently opened document.
-
- @note: The current_page_no attribute is modified only if the page
- can be reached.
-
- @param page: The page number (starting at 1).
-
- @return: True on success, False if not (invalid page or document
- not opened).
+ Go to the given page of the currently opened document.
+
+ .. note::
+
+ The current_page_no attribute is modified only if the page can be reached.
+
+ :param page: The page number (starting at 1).
+
+ :return: True on success, False if not (invalid page or document not opened).
"""
#TODO: go_to_slide should not handle exceptions, but must only
# propagate it. Also, if the exceptions is just propagated, the
@@ -206,15 +199,12 @@
def is_file_opened(self):
- """
- Indicates if a file is currently opened.
- """
return self.document is not None
def get_file_opened(self):
"""
- @return: The file path of the document currently opened OR None if
+ :return: The file path of the document currently opened OR None if
no document is opened.
"""
if self.document is None:
@@ -223,7 +213,7 @@
def get_page_for(self, colleague):
"""
- @return: The current page for the specified colleague OR None if
+ :return: The current page for the specified colleague OR None if
no document is opened or the colleague is not registered.
"""
if self.document is None:
=== added file 'slidepresenterview/ui/widgets/preferred_projectors.py'
--- slidepresenterview/ui/widgets/preferred_projectors.py 1970-01-01 00:00:00 +0000
+++ slidepresenterview/ui/widgets/preferred_projectors.py 2011-01-06 06:25:00 +0000
@@ -0,0 +1,185 @@
+# -*- coding: utf-8 -*-
+#
+# SlidePresenterView - Console for presenters
+# https://launchpad.net/slidepresenterview
+#
+# Copyright (C) 2010 Claude Bolduc <claude.bolduc _AT@. gmail.com>
+#
+# This program 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.
+#
+# This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
+#
+"""
+The preferred projectors configuration widget for SlidePresenterView.
+"""
+
+from PyQt4 import QtGui
+
+from slidepresenterview.ui.qt_forms.preferred_projectors import Ui_preferred_projectors
+
+
+class PreferredProjectors(QtGui.QDialog, Ui_preferred_projectors):
+ """
+ The preferred projectors configuration dialog box for SlidePresenterView.
+ """
+
+ def __init__(self, user_config, screen_provider, console_window, parent=None):
+ """
+ :param user_config: User-defined configuration.
+
+ :param screen_provider: The SPV object that provides access to screens.
+ :type screen_provider: ScreenProviderQt
+
+ :param console_window: The console to identify in the list of available screens.
+ :type console_window: ConsoleWindow
+
+ :param parent: The parent of the widget (if it is the default, then
+ the widget becomes a window).
+ """
+ QtGui.QDialog.__init__(self, parent) # pylint: disable-msg=E1101
+ Ui_preferred_projectors.__init__(self)
+ self.setupUi(self)
+
+ self._user_config = user_config
+ self._screen_provider = screen_provider
+ self._screen_provider.register_observer(self)
+ self._console_window = console_window
+ self._parent = parent
+ self._set_initial_view()
+
+
+ def _set_initial_view(self):
+ self._initialize_the_group_of_screen_items_to_be_empty()
+ self._update_group_of_screen_items()
+ self._update_preferred_projectors_options()
+
+
+ def _initialize_the_group_of_screen_items_to_be_empty(self):
+ self._screen_items = [] # pylint: disable-msg=W0201
+ self._screen_items_spacer = QtGui.QSpacerItem(1, 1, # pylint: disable-msg=W0201
+ QtGui.QSizePolicy.Minimum,
+ QtGui.QSizePolicy.Expanding)
+ self.selected_screens_items_layout.addItem(self._screen_items_spacer)
+
+
+ def _update_group_of_screen_items(self):
+ self._clear_old_group_of_screen_items()
+ self._refresh_screen_items_information()
+ self._create_new_screen_items()
+ self._add_new_screen_items_to_group()
+
+
+ def _clear_old_group_of_screen_items(self):
+ for screen_item in self._screen_items:
+ self._remove_screen_item_from_group(screen_item)
+ self.selected_screens_items_layout.removeItem(self._screen_items_spacer)
+
+
+ def _remove_screen_item_from_group(self, screen_item):
+ self.selected_screens_items_layout.removeWidget(screen_item)
+ screen_item.setParent(None)
+
+
+ def _refresh_screen_items_information(self):
+ # pylint: disable-msg=W0201
+ self._screens_listed = self._screen_provider.get_screen_descriptions()
+
+
+ def _create_new_screen_items(self):
+ self._screen_items = [self._create_screen_item(screen) # pylint: disable-msg=W0201
+ for screen in self._screens_listed]
+
+
+ def _create_screen_item(self, screen):
+ screen_name = self._identify_if_console_screen(screen) + str(screen)
+ checkbox = QtGui.QCheckBox(screen_name)
+ checkbox.setChecked(
+ self._user_config.is_screen_selected_as_preferred_projector(screen.number))
+ return checkbox
+
+
+ def _identify_if_console_screen(self, screen):
+ console_screen = self._screen_provider.get_screen_description_containing(
+ self._console_window)
+ if screen == console_screen:
+ return "(console) "
+ else:
+ return ""
+
+
+ def _add_new_screen_items_to_group(self):
+ for screen_item in self._screen_items:
+ self.selected_screens_items_layout.addWidget(screen_item)
+ self.selected_screens_items_layout.addItem(self._screen_items_spacer)
+
+
+ def _update_preferred_projectors_options(self):
+ self.console_screen_button.setChecked(
+ self._user_config.is_preferring_presentation_showed_on_console_screen())
+ self.other_screens_button.setChecked(
+ self._user_config.is_preferring_presentation_showed_on_other_screens())
+ self.selected_screens_button.setChecked(
+ self._user_config.is_preferring_presentation_showed_on_selected_screens())
+
+
+ def on_refresh_button_clicked(self, checked=None):
+ """
+ Actions to be taken when the :guilabel:`refresh screens list` button is triggered.
+ """
+ if checked is None:
+ return
+ self._update_group_of_screen_items()
+
+
+ def reject(self):
+ """
+ Actions to be taken when the :guilabel:`cancel` button is triggered
+ and when the window is closed by the close button.
+ """
+ self._screen_provider.unregister_observer(self)
+ QtGui.QDialog.reject(self)
+
+
+ def accept(self):
+ """
+ Actions to be taken when the :guilabel:`OK` button is triggered.
+ """
+ self._screen_provider.unregister_observer(self)
+ self._set_preferred_projectors_configuration()
+ QtGui.QDialog.accept(self)
+
+
+ def _set_preferred_projectors_configuration(self):
+ self._user_config.set_preferred_projectors_configuration(
+ self._get_preferred_projector_type_option(),
+ self._get_all_selected_screens_numbers())
+
+
+ def _get_preferred_projector_type_option(self):
+ if self.other_screens_button.isChecked():
+ return self._user_config.OPTION_OTHER_SCREENS
+ elif self.selected_screens_button.isChecked():
+ return self._user_config.OPTION_SELECTED_SCREENS
+ else:
+ return self._user_config.OPTION_CONSOLE_SCREEN
+
+
+ def _get_all_selected_screens_numbers(self):
+ return [screen.number for row, screen in enumerate(self._screens_listed)
+ if self._screen_items[row].isChecked()]
+
+
+ def on_screen_changed(self):
+ """
+ Actions to be taken when the screen provider warns that the screens changed.
+ """
+ self._update_group_of_screen_items()
\ No newline at end of file
=== added file 'slidepresenterview/ui/widgets/screen.py'
--- slidepresenterview/ui/widgets/screen.py 1970-01-01 00:00:00 +0000
+++ slidepresenterview/ui/widgets/screen.py 2011-01-06 06:25:00 +0000
@@ -0,0 +1,168 @@
+# -*- coding: utf-8 -*-
+#
+# SlidePresenterView - Console for presenters
+# https://launchpad.net/slidepresenterview
+#
+# Copyright (C) 2010 Claude Bolduc <claude.bolduc _AT@. gmail.com>
+#
+# This program 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.
+#
+# This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
+#
+"""
+The screen abstraction for SlidePresenterView.
+"""
+
+##########
+# Imports
+#####
+from PyQt4 import QtCore
+
+from slidepresenterview.utils.observation import Observable
+
+EVENT_SCREEN_CHANGED = 'on_screen_changed'
+
+SPV_FIRST_SCREEN_NUMBER = 1
+
+def convert_to_spv_screen_number(qt_screen_number):
+ return qt_screen_number + SPV_FIRST_SCREEN_NUMBER
+
+
+def convert_to_qt_screen_number(spv_screen_number):
+ return spv_screen_number - SPV_FIRST_SCREEN_NUMBER
+
+
+def convert_qrect_to_resolution(qrect):
+ return Resolution(qrect.width(), qrect.height())
+
+
+class ScreenProviderQt(Observable):
+ """
+ Adapter for the QtGui.QDesktopWidget class.
+ It is responsible to create and manage ScreenDescription objects.
+
+ **Observable**: To listen to an event, the observer must implement the corresponding method
+ (see EVENT_* constants).
+ If the method exists, it will be called.
+ If not, this observer is simply ignored.
+
+ :type screen_information_provider: QtGui.QDesktopWidget
+ :param screen_information_provider: In Qt, there is one and only one
+ screen_information_provider that is registered for screen
+ modification events.
+ It is the one that is linked to the unique
+ QtGui.QApplication. It is accessible via the static method
+ desktop() of QtGui.QApplication.
+ """
+
+ def __init__(self, screen_information_provider):
+ Observable.__init__(self)
+ self._screen_information_provider = screen_information_provider
+ self._register_to_events_of_screen_information_provider()
+
+
+ def _register_to_events_of_screen_information_provider(self):
+ QtCore.QObject.connect(self._screen_information_provider,
+ QtCore.SIGNAL("resized(int)"),
+ self._notify_screen_changed)
+ QtCore.QObject.connect(self._screen_information_provider,
+ QtCore.SIGNAL("screenCountChanged(int)"),
+ self._notify_screen_changed)
+
+
+ def _notify_screen_changed(self, number):
+ self._notify_observers(EVENT_SCREEN_CHANGED)
+
+
+ def get_screen_descriptions(self):
+ num_screens = self._screen_information_provider.numScreens()
+ return [self._create_screen_description(qt_screen_number) for qt_screen_number
+ in range(num_screens)]
+
+
+ def _create_screen_description(self, qt_screen_number):
+ spv_screen_number = convert_to_spv_screen_number(qt_screen_number)
+ resolution = self._get_resolution_of(qt_screen_number)
+ return ScreenDescription(spv_screen_number, resolution)
+
+
+ def _get_resolution_of(self, qt_screen_number):
+ resolution_qrect = self._screen_information_provider.screenGeometry(qt_screen_number)
+ return convert_qrect_to_resolution(resolution_qrect)
+
+
+ def get_screen_description(self, spv_screen_number):
+ return next((screen_description for screen_description in self.get_screen_descriptions()
+ if screen_description.number == spv_screen_number),
+ None)
+
+
+ def get_screen_description_containing(self, widget):
+ qt_screen_number = self._screen_information_provider.screenNumber(widget)
+ spv_screen_number = convert_to_spv_screen_number(qt_screen_number)
+ return self.get_screen_description(spv_screen_number)
+
+
+class ScreenDescription(object):
+ """
+ Data transfer object that holds a screen information.
+ It is immutable.
+ Comparison of ScreenDescriptions is done by comparing attribute values.
+
+ :ivar number: The screen number identified by SlidePresenterView (spv).
+
+ :type resolution: Resolution
+ """
+
+ def __init__(self, number, resolution):
+ self.number = number
+ self.resolution = resolution
+
+
+ def __str__(self):
+ return "Screen #%d (%s)" % (self.number, self.resolution)
+
+
+ def __cmp__(self, another_screen):
+ """
+ Comparison is done by checking screen number and resolution (in this order).
+ """
+ if self.number != another_screen.number:
+ return self.number.__cmp__(another_screen.number)
+ else:
+ return self.resolution.__cmp__(another_screen.resolution)
+
+
+class Resolution(object):
+ """
+ Data transfer object that holds a resolution for a ScreenDescription object.
+ It is immutable.
+ Comparison of Resolutions is done by comparing attribute values.
+ """
+
+ def __init__(self, width, height):
+ self.width = width
+ self.height = height
+
+
+ def __str__(self):
+ return "%dx%d" % (self.width, self.height)
+
+
+ def __cmp__(self, another_resolution):
+ """
+ Comparison is done by checking width and height (in this order).
+ """
+ if self.width != another_resolution.width:
+ return self.width.__cmp__(another_resolution.width)
+ else:
+ return self.height.__cmp__(another_resolution.height)
=== modified file 'slidepresenterview/ui/widgets/slideview.py'
--- slidepresenterview/ui/widgets/slideview.py 2010-05-03 02:19:38 +0000
+++ slidepresenterview/ui/widgets/slideview.py 2011-01-06 06:25:00 +0000
@@ -23,8 +23,8 @@
Extension to QGraphicsView that displays a slide using a given format's
renderer.
"""
+from PyQt4 import QtGui
from PyQt4 import QtCore
-from PyQt4 import QtGui
class SlideViewStretchable(QtGui.QGraphicsView): # pylint: disable-msg=E1101
=== added file 'slidepresenterview/utils/observation.py'
--- slidepresenterview/utils/observation.py 1970-01-01 00:00:00 +0000
+++ slidepresenterview/utils/observation.py 2011-01-06 06:25:00 +0000
@@ -0,0 +1,52 @@
+# -*- coding: utf-8 -*-
+#
+# SlidePresenterView - Console for presenters
+# https://launchpad.net/slidepresenterview
+#
+# Copyright (C) 2010 Felix-Antoine Bourbonnais <bouf10pub _AT@. rubico.info>
+#
+# This program 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.
+#
+# This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
+#
+"""
+Add observation behaviour to an object. It allows peers to register to be informed of
+some events.
+"""
+
+__all__ = ['Observable']
+
+
+class Observable(object):
+
+
+ def __init__(self):
+ self._observers = set()
+
+
+ def register_observer(self, observer):
+ self._observers.add(observer)
+
+
+ def unregister_observer(self, observer):
+ self._observers.discard(observer)
+
+
+ def _notify_observers(self, event, *args, **kw):
+ """
+ Notify every observer that has a method matching the name of the event parameter. If
+ not, the observer is ignored. Extra arguments are passed to the event's method.
+ """
+ for observer in self._observers:
+ attr = getattr(observer, event, None)
+ if attr is not None:
+ attr(*args, **kw)
=== modified file 'slidepresenterview/utils/pyqt.py'
--- slidepresenterview/utils/pyqt.py 2009-11-23 05:05:10 +0000
+++ slidepresenterview/utils/pyqt.py 2011-01-06 06:25:00 +0000
@@ -22,9 +22,28 @@
Helper class/functions for PyQt
"""
+import sys
+
+from PyQt4 import QtGui
from PyQt4 import QtCore
+_PYQT_APP = None
+def get_application():
+ """
+ :return: The QtGui.QApplication.
+
+ .. note::
+
+ Use this function instead of creating QApplication manually.
+ This will ensure that there will be only one QApplication in the program (singleton).
+ """
+ global _PYQT_APP
+ if _PYQT_APP is None:
+ _PYQT_APP = QtGui.QApplication(sys.argv)
+ return _PYQT_APP
+
+
def msg_handler_no_message(msg_type, message):
"""
Don't log any message (except fatal messages)
@@ -37,8 +56,10 @@
"""
Set a button enabled or not.
- WORKAROUND: This is required because of a bug in QT 4.5.2. In that version
- disabled buttons are not grayed out...
+ .. warning::
+
+ WORKAROUND: This is required because of a bug in QT 4.5.2. In that version
+ disabled buttons are not grayed out...
"""
if enabled:
button.setEnabled(True)
Follow ups
-
Re: lp:~claude-bolduc/slidepresenterview/choose-projector-screens into lp:slidepresenterview
From: F.-A. Bourbonnais, 2011-01-09
-
Re: lp:~claude-bolduc/slidepresenterview/choose-projector-screens into lp:slidepresenterview
From: F.-A. Bourbonnais, 2011-01-09
-
Re: lp:~claude-bolduc/slidepresenterview/choose-projector-screens into lp:slidepresenterview
From: F.-A. Bourbonnais, 2011-01-09
-
Re: lp:~claude-bolduc/slidepresenterview/choose-projector-screens into lp:slidepresenterview
From: F.-A. Bourbonnais, 2011-01-09
-
Re: lp:~claude-bolduc/slidepresenterview/choose-projector-screens into lp:slidepresenterview
From: F.-A. Bourbonnais, 2011-01-09
-
Re: lp:~claude-bolduc/slidepresenterview/choose-projector-screens into lp:slidepresenterview
From: F.-A. Bourbonnais, 2011-01-09
-
Re: lp:~claude-bolduc/slidepresenterview/choose-projector-screens into lp:slidepresenterview
From: F.-A. Bourbonnais, 2011-01-09
-
Re: lp:~claude-bolduc/slidepresenterview/choose-projector-screens into lp:slidepresenterview
From: F.-A. Bourbonnais, 2011-01-08
-
Re: lp:~claude-bolduc/slidepresenterview/choose-projector-screens into lp:slidepresenterview
From: F.-A. Bourbonnais, 2011-01-08
-
Re: lp:~claude-bolduc/slidepresenterview/choose-projector-screens into lp:slidepresenterview
From: F.-A. Bourbonnais, 2011-01-08