← Back to team overview

spv-dev team mailing list archive

[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:
  SlidePresenterView Development Team (spv-dev)


Implement the feature presenter-choose-projector-screens.
-- 
https://code.launchpad.net/~claude-bolduc/slidepresenterview/choose-projector-screens/+merge/30878
Your team SlidePresenterView Development Team is requested to review the proposed merge of lp:~claude-bolduc/slidepresenterview/choose-projector-screens into lp:slidepresenterview.
=== modified file '.bzrignore'
--- .bzrignore	2010-05-03 02:19:38 +0000
+++ .bzrignore	2010-07-25 04:39:42 +0000
@@ -6,3 +6,4 @@
 slidepresenterview/ui/qt_forms/*.py
 run_spv.bat
 slidepresenterview.egg-info
+*.pro.user

=== modified file 'HACKING'
--- HACKING	2010-07-11 17:27:55 +0000
+++ HACKING	2010-07-25 04:39:42 +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 file 'resources/spv_qtproject.pro'
--- resources/spv_qtproject.pro	1970-01-01 00:00:00 +0000
+++ resources/spv_qtproject.pro	2010-07-25 04:39:42 +0000
@@ -0,0 +1,6 @@
+QT += svg \
+    xml
+TARGET = slidepresenterview_form
+TEMPLATE = app
+FORMS += ui/console.ui \
+    ui/preferred_projectors.ui

=== modified file 'resources/ui/console.ui'
--- resources/ui/console.ui	2009-07-04 22:34:33 +0000
+++ resources/ui/console.ui	2010-07-25 04:39:42 +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	2010-07-25 04:39:42 +0000
@@ -0,0 +1,168 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>preferred_projectors</class>
+ <widget class="QWidget" name="preferred_projectors">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>180</width>
+    <height>207</height>
+   </rect>
+  </property>
+  <property name="sizePolicy">
+   <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+    <horstretch>0</horstretch>
+    <verstretch>0</verstretch>
+   </sizepolicy>
+  </property>
+  <property name="minimumSize">
+   <size>
+    <width>180</width>
+    <height>207</height>
+   </size>
+  </property>
+  <property name="maximumSize">
+   <size>
+    <width>250</width>
+    <height>300</height>
+   </size>
+  </property>
+  <property name="windowTitle">
+   <string>Preferred projector(s)</string>
+  </property>
+  <property name="locale">
+   <locale language="English" country="Canada"/>
+  </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="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="widget_2" native="true">
+     <layout class="QHBoxLayout" name="horizontalLayout">
+      <property name="margin">
+       <number>0</number>
+      </property>
+      <item>
+       <spacer name="horizontalSpacer">
+        <property name="orientation">
+         <enum>Qt::Horizontal</enum>
+        </property>
+        <property name="sizeType">
+         <enum>QSizePolicy::Fixed</enum>
+        </property>
+        <property name="sizeHint" stdset="0">
+         <size>
+          <width>20</width>
+          <height>2</height>
+         </size>
+        </property>
+       </spacer>
+      </item>
+      <item>
+       <widget class="QListWidget" name="selected_screens_list">
+        <property name="editTriggers">
+         <set>QAbstractItemView::NoEditTriggers</set>
+        </property>
+        <property name="selectionMode">
+         <enum>QAbstractItemView::NoSelection</enum>
+        </property>
+        <property name="viewMode">
+         <enum>QListView::ListMode</enum>
+        </property>
+       </widget>
+      </item>
+     </layout>
+    </widget>
+   </item>
+   <item>
+    <widget class="QWidget" name="widget" native="true">
+     <layout class="QHBoxLayout" name="horizontalLayout_3">
+      <property name="margin">
+       <number>0</number>
+      </property>
+      <item>
+       <spacer name="horizontalSpacer_3">
+        <property name="orientation">
+         <enum>Qt::Horizontal</enum>
+        </property>
+        <property name="sizeHint" stdset="0">
+         <size>
+          <width>10</width>
+          <height>2</height>
+         </size>
+        </property>
+       </spacer>
+      </item>
+      <item>
+       <widget class="QPushButton" name="refresh_button">
+        <property name="text">
+         <string> Refresh screen(s) </string>
+        </property>
+       </widget>
+      </item>
+     </layout>
+    </widget>
+   </item>
+   <item>
+    <widget class="QWidget" name="widget_3" native="true">
+     <layout class="QHBoxLayout" name="horizontalLayout_2">
+      <property name="leftMargin">
+       <number>0</number>
+      </property>
+      <property name="topMargin">
+       <number>2</number>
+      </property>
+      <property name="rightMargin">
+       <number>0</number>
+      </property>
+      <property name="bottomMargin">
+       <number>0</number>
+      </property>
+      <item>
+       <spacer name="horizontalSpacer_2">
+        <property name="orientation">
+         <enum>Qt::Horizontal</enum>
+        </property>
+        <property name="sizeHint" stdset="0">
+         <size>
+          <width>40</width>
+          <height>20</height>
+         </size>
+        </property>
+       </spacer>
+      </item>
+      <item>
+       <widget class="QDialogButtonBox" name="apply_button">
+        <property name="standardButtons">
+         <set>QDialogButtonBox::Apply</set>
+        </property>
+       </widget>
+      </item>
+     </layout>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>

=== modified file 'setup.cfg'
--- setup.cfg	2010-07-11 17:27:55 +0000
+++ setup.cfg	2010-07-25 04:39:42 +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 'slidepresenterview/__init__.py'
--- slidepresenterview/__init__.py	2010-01-07 04:29:41 +0000
+++ slidepresenterview/__init__.py	2010-07-25 04:39:42 +0000
@@ -25,7 +25,7 @@
 ##########
 # Packages
 #####
-import sys
+import os, sys
 from itertools import imap
 
 
@@ -207,7 +207,29 @@
         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):
+    try:
+        from slidepresenterview.config import qt_configuration
+        
+        qss = ""
+        current_dir = os.path.dirname(__file__)
+        for qss_filename in qt_configuration.QSS_LIST_FOR_PLATFORM[platform]:
+            qss_file = os.path.join(current_dir, qss_filename)
+            with open(qss_file, "r") as file:
+                qss = qss + file.read() 
+        return qss
+    except:
+        return "" # There is no style sheet.
+
+
 
 def main_gui():
     if not check_env():
@@ -217,10 +239,15 @@
     
     from slidepresenterview.ui import console_window
     from slidepresenterview.ui import presentation
+    from slidepresenterview.config import user_configuration
     
     app = QtGui.QApplication(sys.argv)
+    stylesheet = _load_stylesheet()
+    app.setStyleSheet(stylesheet)
+    user_config = user_configuration.UserConfiguration() 
     presentation_mediator = presentation.Presentation()
-    window = console_window.ConsoleWindow(presentation_mediator)
+    screen_information_provider = QtGui.QApplication.desktop()
+    window = console_window.ConsoleWindow(user_config, presentation_mediator, 
+                                          screen_information_provider)
     window.show()
     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	2010-07-25 04:39:42 +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 SPV.
+"""

=== added file 'slidepresenterview/config/qt_configuration.py'
--- slidepresenterview/config/qt_configuration.py	1970-01-01 00:00:00 +0000
+++ slidepresenterview/config/qt_configuration.py	2010-07-25 04:39:42 +0000
@@ -0,0 +1,33 @@
+# -*- 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 SPV for the Qt environment.
+
+It is loaded at the start of the application. 
+"""
+
+QSS_LIST_FOR_PLATFORM = {
+  'all': ['ui/qt_forms/qss/common.qss'],
+  'win32': ['ui/qt_forms/qss/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	2010-07-25 04:39:42 +0000
@@ -0,0 +1,38 @@
+# -*- 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 SPV.
+
+Some configurations may refer to elements of Qt. When doing so, we keep the element given by Qt.
+For example, the configuration for the `preferred_selected_screens_as_projector` is a list 
+of physical screen numbers as given by Qt (so it is 0-based).
+"""
+
+class UserConfiguration(object):
+    
+    def __init__(self):
+        self.must_show_preferred_projectors_at_startup = False
+        self.is_preferring_presentation_showed_on_other_screens = True
+        self.preferred_selected_screens_as_projector = []
+        
+        
+    def is_screen_selected_as_preferred_projector(self, screen_number):
+        return screen_number in self.preferred_selected_screens_as_projector

=== 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	2010-07-25 04:39:42 +0000
@@ -0,0 +1,64 @@
+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 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: Bumping old preferences that do not exist in the current configuration
+	    Given my old preference 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/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	2010-07-25 04:39:42 +0000
@@ -63,8 +63,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 +143,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 +160,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 +210,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	2010-07-25 04:39:42 +0000
@@ -0,0 +1,153 @@
+# -*- 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 QtCore, QtGui
+
+from mock import Mock
+
+from freshen import *
+
+from slidepresenterview.config.user_configuration import UserConfiguration
+from slidepresenterview.ui.presentation import Presentation
+from slidepresenterview.ui.console_window import ConsoleWindow
+
+
+class _FakeScreenInformationProvider(object):
+    
+    def __init__(self):
+        self.mock = Mock(spec=QtGui.QDesktopWidget)
+        
+    
+    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()
+        new_number_of_screens = old_number_of_screens + number_of_screens_added
+        self.set_fake_screens(new_number_of_screens)
+
+
+@Before
+def create_fake_screen_info_provider(scenario):
+    scc.mock_screen_info_provider = _FakeScreenInformationProvider()
+
+
+@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.user_config = UserConfiguration()
+    presentation_mediator = Presentation()
+    scc.win = ConsoleWindow(scc.user_config, presentation_mediator, 
+                            scc.mock_screen_info_provider.mock)
+    scc.pref_projectors = scc.win.pref_projectors
+
+@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 preference was to select screens 1 and 3 as projectors$")
+def set_old_preference():
+    scc.user_config.is_preferring_presentation_showed_on_other_screens = False
+    scc.user_config.preferred_selected_screens_as_projector = [0, 2]
+    scc.pref_projectors.on_refresh_button_clicked(True)
+
+
+@When("^I select other screens as projectors$")
+def select_other_screens_as_projectors():
+    scc.pref_projectors.on_other_screens_button_toggled(True)
+    
+@When("^I select (screen[s]? .+) as projector[s]?$")
+def select_specific_screens_as_projectors(logical_screens):
+    scc.pref_projectors.on_selected_screens_button_toggled(True)
+    
+    num_available_screens = scc.pref_projectors.selected_screens_list.count()
+    if num_available_screens < max(logical_screens):
+        _fail("Cannot select a screen as projector that have a number bigger than" 
+             + " the number of available screens.") 
+
+    for index in range(num_available_screens):
+        _set_check_state_for_item_at_according_to(index, logical_screens)
+
+def _fail(message):
+    assert_true(False, message)
+    
+def _set_check_state_for_item_at_according_to(physical_index, logical_screens):
+    item = scc.pref_projectors.selected_screens_list.item(physical_index)
+    logical_index = physical_index + 1
+    if logical_index in logical_screens:
+        item.setCheckState(QtCore.Qt.Checked)
+    else:
+        item.setCheckState(QtCore.Qt.Unchecked)
+
+@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.on_apply_button_clicked(True)
+
+
+@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("^(\d+ screen)[s]? (?:are|is) listed$")
+def check_number_of_screens(number_of_screens):
+    num_listed_screens = scc.pref_projectors.selected_screens_list.count()
+    assert_equal(num_listed_screens, number_of_screens)
+    
+@Then("^(screen[s]? .+) (?:are|is) selected as projector[s]?$")
+def check_screens_selected(logical_screens):
+    physical_screens = [logical_screen - 1 for logical_screen in logical_screens]
+    assert_equal(scc.user_config.preferred_selected_screens_as_projector, physical_screens)
+    
+@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	2010-07-25 04:39:42 +0000
@@ -28,6 +28,7 @@
 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
 
@@ -38,8 +39,10 @@
         glc.app = QtGui.QApplication([])
     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 = QtGui.QApplication.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/ui/console_window.py'
--- slidepresenterview/ui/console_window.py	2010-05-02 23:36:17 +0000
+++ slidepresenterview/ui/console_window.py	2010-07-25 04:39:42 +0000
@@ -30,6 +30,8 @@
 from PyQt4 import QtGui
 
 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
@@ -41,29 +43,55 @@
     @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.
-        """
+    def __init__(self, user_config, presentation_mediator, screen_information_provider):
         QtGui.QMainWindow.__init__(self) # pylint: disable-msg=E1101
         Ui_console_window.__init__(self)
         
         self.setupUi(self)
         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()
+        
+        self._create_docks(user_config, screen_information_provider)
+        
+        
+    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 _create_docks(self, user_config, screen_information_provider):
+        self._dock_preferred_projectors = self._create_preferred_projectors_dock(user_config, 
+                                                                        screen_information_provider)
+        
+        
+    def _create_preferred_projectors_dock(self, user_config, screen_information_provider):
+        self.pref_projectors = PreferredProjectors(user_config, screen_information_provider)
+        dock_widget = QtGui.QDockWidget("Preferred Projector(s)", self)
+        dock_widget.setWidget(self.pref_projectors)
+        dock_widget.setVisible(user_config.must_show_preferred_projectors_at_startup)
+        self.addDockWidget(QtCore.Qt.LeftDockWidgetArea, dock_widget)
+        
+        return dock_widget
+        
+        
     def set_presentation_mediator(self, presentation):
         """
         Set the presentation mediator.
@@ -162,24 +190,27 @@
         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)
-        else:
-            pyqtutils.set_button_enabled(self.current_slide_previous_button, 
-                                         False)
+            pyqtutils.set_button_enabled(self.current_slide_previous_button, True)
+        else:
+            pyqtutils.set_button_enabled(self.current_slide_previous_button, False)
+
+
+    def on_action_configure_preferred_projectors_triggered(self, checked=None):
+        if checked is None: return
+        
+        if self._dock_preferred_projectors.isHidden():
+            self._dock_preferred_projectors.show()
+        else:
+            self._dock_preferred_projectors.hide()

=== added directory 'slidepresenterview/ui/qt_forms/qss'
=== added file 'slidepresenterview/ui/qt_forms/qss/common.qss'
--- slidepresenterview/ui/qt_forms/qss/common.qss	1970-01-01 00:00:00 +0000
+++ slidepresenterview/ui/qt_forms/qss/common.qss	2010-07-25 04:39:42 +0000
@@ -0,0 +1,38 @@
+/* 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. */
+
+* {
+  font-size: 15px; /* The font is big. 
+                      This eases reading when presenting. */
+}
+
+QPushButton {
+  min-width: 80px;
+}
+
+QRadioButton:hover {
+  background-color: #ccccff;
+}
+
+QGroupBox {
+  font-size: 20px;
+  color: #bbbbbb;
+}

=== added file 'slidepresenterview/ui/qt_forms/qss/windows.qss'
--- slidepresenterview/ui/qt_forms/qss/windows.qss	1970-01-01 00:00:00 +0000
+++ slidepresenterview/ui/qt_forms/qss/windows.qss	2010-07-25 04:39:42 +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;
+}

=== 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	2010-07-25 04:39:42 +0000
@@ -0,0 +1,170 @@
+# -*- 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. 
+"""
+
+##########
+# Imports
+#####
+from PyQt4 import QtCore # pylint: disable-msg=E0611
+from PyQt4 import QtGui
+
+from slidepresenterview.ui.qt_forms.preferred_projectors import Ui_preferred_projectors
+
+class PreferredProjectors(QtGui.QWidget, Ui_preferred_projectors):# pylint: disable-msg=E1101 
+    """
+    The preferred projectors configuration widget for SlidePresenterView.
+    
+    :ivar user_config: User-defined configuration.
+    """
+
+    def __init__(self, user_config, screen_information_provider, parent=None):
+        """
+        :type screen_information_provider: QtGui.QDesktopWidget
+        
+        :param parent: The parent of the widget (if it is the default, then
+                       the widget becomes a window).
+        """
+        QtGui.QWidget.__init__(self, parent) # pylint: disable-msg=E1101
+        Ui_preferred_projectors.__init__(self)
+        self.setupUi(self)
+        
+        self._user_config = user_config
+        self._screen_information_provider = screen_information_provider
+        self._add_list_available_screens()
+        self._check_radio_button_for_user_config_choice()
+        
+        
+    def _add_list_available_screens(self):
+        num_screens = self._screen_information_provider.numScreens()
+        for physical_screen_number in range(num_screens):
+            self._add_screen_item(physical_screen_number)
+
+        
+    def _add_screen_item(self, physical_screen_number):
+        screen_name = self._create_screen_name(physical_screen_number)
+        screen_item = QtGui.QListWidgetItem(screen_name, self.selected_screens_list)
+        self._set_check_state(screen_item, physical_screen_number)
+        
+
+    def _create_screen_name(self, physical_screen_number):
+        screen_size = self._screen_information_provider.screenGeometry(physical_screen_number)
+        identify_if_current_screen = self._get_identify_if_current_screen(physical_screen_number)
+        logical_screen_number = self._get_logical_screen_number(physical_screen_number)
+        return 'Screen %s (%sx%s)%s' % (logical_screen_number, 
+                                        screen_size.width(),
+                                        screen_size.height(),
+                                        identify_if_current_screen)
+        
+        
+    def _get_identify_if_current_screen(self, physical_screen_number):
+        if self._is_current_screen(physical_screen_number):
+            return " [current]"
+        else:
+            return ""
+        
+        
+    def _get_logical_screen_number(self, physical_screen_number):
+        return physical_screen_number + 1
+        
+        
+    def _is_current_screen(self, physical_screen_number):
+        return physical_screen_number == self._screen_information_provider.screenNumber(self)
+    
+    
+    def _set_check_state(self, screen_item, physical_screen_number):
+        screen_item.setFlags(QtCore.Qt.ItemIsUserCheckable | QtCore.Qt.ItemIsEnabled)
+
+        if self._user_config.is_screen_selected_as_preferred_projector(physical_screen_number):
+            screen_item.setCheckState(QtCore.Qt.Checked)
+        else:
+            screen_item.setCheckState(QtCore.Qt.Unchecked)
+            
+            
+    def _check_radio_button_for_user_config_choice(self):
+        if self._user_config.is_preferring_presentation_showed_on_other_screens:
+            self._enable_other_screens_button()
+        else:
+            self._enable_selected_screens_button()
+
+
+    def _enable_other_screens_button(self):
+        self.other_screens_button.setChecked(True)
+        self.selected_screens_list.setEnabled(False)
+        
+        
+    def _enable_selected_screens_button(self):
+        self.selected_screens_button.setChecked(True)
+        self.selected_screens_list.setEnabled(True)
+
+
+    def on_other_screens_button_toggled(self, checked=None):
+        if checked is None: return
+        if not checked: return
+        
+        self._enable_other_screens_button()
+
+
+    def on_selected_screens_button_toggled(self, checked=None):
+        if checked is None: return
+        if not checked: return
+        
+        self._enable_selected_screens_button()
+
+
+    def on_refresh_button_clicked(self, checked=None):
+        if checked is None: return
+        
+        self.selected_screens_list.clear()
+        self._add_list_available_screens()
+
+
+    def on_apply_button_clicked(self, checked=None):
+        if checked is None: return
+        
+        self._set_is_preferring_presentation_showed_on_other_screens()
+        self._set_preferred_selected_screens_as_projector()
+        
+        
+    def _set_is_preferring_presentation_showed_on_other_screens(self):
+        is_checked = self.other_screens_button.isChecked()
+        self._user_config.is_preferring_presentation_showed_on_other_screens = is_checked
+                                          
+                                          
+                                          
+    def _set_preferred_selected_screens_as_projector(self):
+        selected_screens_numbers = self._get_all_selected_screens_numbers()
+        self._user_config.preferred_selected_screens_as_projector = selected_screens_numbers
+                                              
+                                              
+    def _get_all_selected_screens_numbers(self):
+        selected_screens = []
+        for physical_screen_number in range(self.selected_screens_list.count()):
+            if self._is_screen_checked(physical_screen_number):
+                selected_screens.append(physical_screen_number)
+        
+        return selected_screens
+        
+        
+    def _is_screen_checked(self, physical_screen_number):
+        screen_item = self.selected_screens_list.item(physical_screen_number)
+        return screen_item.checkState() == QtCore.Qt.Checked
\ No newline at end of file


Follow ups