← Back to team overview

openlp-core team mailing list archive

[Merge] lp:~trb143/openlp/general into lp:openlp

 

Tim Bentley has proposed merging lp:~trb143/openlp/general into lp:openlp.

Requested reviews:
  Andreas Preikschat (googol-hush)
  Jonathan Corwin (j-corwin)
Related bugs:
  Bug #730294 in OpenLP: "Auto-preview, not previewing next item if go live from preview"
  https://bugs.launchpad.net/openlp/+bug/730294
  Bug #735367 in OpenLP: "Blank to theme won't unblank "
  https://bugs.launchpad.net/openlp/+bug/735367
  Bug #742122 in OpenLP: "Prevent multiple instances"
  https://bugs.launchpad.net/openlp/+bug/742122

For more details, see:
https://code.launchpad.net/~trb143/openlp/general/+merge/55007

Bug #730294: Auto-preview, not previewing next item if go live from preview  
Bug #735367: Blank to theme won't unblank  
Bug #742122: Prevent multiple instances  

Also add ability to stop media in the middle.

Add ability to use command line or double click from desktop to pass service file.
(Needs build script changes)
-- 
https://code.launchpad.net/~trb143/openlp/general/+merge/55007
Your team OpenLP Core is subscribed to branch lp:openlp.
=== modified file 'openlp.pyw'
--- openlp.pyw	2011-03-26 18:23:34 +0000
+++ openlp.pyw	2011-03-27 16:42:30 +0000
@@ -38,6 +38,7 @@
 from PyQt4 import QtCore, QtGui
 
 from openlp.core.lib import Receiver, check_directory_exists
+from openlp.core.lib.ui import UiStrings
 from openlp.core.resources import qInitResources
 from openlp.core.ui.mainwindow import MainWindow
 from openlp.core.ui.firsttimelanguageform import FirstTimeLanguageForm
@@ -77,6 +78,13 @@
     class in order to provide the core of the application.
     """
 
+    def exec_(self):
+        """
+        Override exec method to allow the shared memory to be released on exit
+        """
+        QtGui.QApplication.exec_()
+        self.sharedMemory.detach()
+
     def run(self):
         """
         Run the OpenLP application.
@@ -107,7 +115,7 @@
         # make sure Qt really display the splash screen
         self.processEvents()
         # start the main app window
-        self.mainWindow = MainWindow(screens, self.clipboard())
+        self.mainWindow = MainWindow(screens, self)
         self.mainWindow.show()
         if show_splash:
             # now kill the splashscreen
@@ -122,6 +130,24 @@
             VersionThread(self.mainWindow).start()
         return self.exec_()
 
+    def isAlreadyRunning(self):
+        """
+        Look to see if OpenLP is already running and ask if a 2nd copy
+        is to be started.
+        """
+        self.sharedMemory = QtCore.QSharedMemory('OpenLP')
+        if self.sharedMemory.attach():
+            status = QtGui.QMessageBox.critical(None,
+                UiStrings.Error, UiStrings.OpenLPStart,
+                QtGui.QMessageBox.StandardButtons(
+                QtGui.QMessageBox.Yes | QtGui.QMessageBox.No))
+            if status == QtGui.QMessageBox.No:
+                return True
+            return False
+        else:
+            self.sharedMemory.create(1)
+            return False
+
     def hookException(self, exctype, value, traceback):
         if not hasattr(self, u'mainWindow'):
             log.exception(''.join(format_exception(exctype, value, traceback)))
@@ -194,6 +220,9 @@
     qInitResources()
     # Now create and actually run the application.
     app = OpenLP(qt_args)
+    # Instance check
+    if app.isAlreadyRunning():
+        sys.exit()
     app.setOrganizationName(u'OpenLP')
     app.setOrganizationDomain(u'openlp.org')
     app.setApplicationName(u'OpenLP')

=== modified file 'openlp/core/lib/eventreceiver.py'
--- openlp/core/lib/eventreceiver.py	2011-03-24 19:04:02 +0000
+++ openlp/core/lib/eventreceiver.py	2011-03-27 16:42:30 +0000
@@ -101,6 +101,10 @@
     ``servicemanager_previous_item``
         Display the previous item in the service
 
+    ``servicemanager_preview_live``
+        Requests a Preview item from the Service Manager to update live and
+        add a new item to the preview panel
+
     ``servicemanager_next_item``
         Display the next item in the service
 

=== modified file 'openlp/core/lib/rendermanager.py'
--- openlp/core/lib/rendermanager.py	2011-03-24 19:04:02 +0000
+++ openlp/core/lib/rendermanager.py	2011-03-27 16:42:30 +0000
@@ -145,7 +145,8 @@
             else:
                 self.theme = self.service_theme
         else:
-            if theme:
+            # Images have a theme of -1
+            if theme and theme != -1:
                 self.theme = theme
             elif theme_level == ThemeLevel.Song or \
                 theme_level == ThemeLevel.Service:

=== modified file 'openlp/core/lib/serviceitem.py'
--- openlp/core/lib/serviceitem.py	2011-03-24 19:04:02 +0000
+++ openlp/core/lib/serviceitem.py	2011-03-27 16:42:30 +0000
@@ -109,7 +109,9 @@
         self.edit_id = None
         self.xml_version = None
         self.start_time = 0
+        self.end_time = 0
         self.media_length = 0
+        self.from_service = False
         self._new_item()
 
     def _new_item(self):
@@ -261,6 +263,7 @@
             u'data': self.data_string,
             u'xml_version': self.xml_version,
             u'start_time': self.start_time,
+            u'end_time': self.end_time,
             u'media_length': self.media_length
         }
         service_data = []
@@ -307,6 +310,8 @@
             self.xml_version = header[u'xml_version']
         if u'start_time' in header:
             self.start_time = header[u'start_time']
+        if u'end_time' in header:
+            self.end_time = header[u'end_time']
         if u'media_length' in header:
             self.media_length = header[u'media_length']
         if self.service_item_type == ServiceItemType.Text:
@@ -449,4 +454,3 @@
             return end
         else:
             return u'%s : %s' % (start, end)
-

=== modified file 'openlp/core/lib/ui.py'
--- openlp/core/lib/ui.py	2011-03-25 17:49:26 +0000
+++ openlp/core/lib/ui.py	2011-03-27 16:42:30 +0000
@@ -57,6 +57,7 @@
     Export = translate('OpenLP.Ui', 'Export')
     FontSizePtUnit = translate('OpenLP.Ui', 'pt',
         'Abbreviated font pointsize unit')
+    Hours = translate('OpenLP.Ui', 'h', 'The abbreviated unit for hours')
     Image = translate('OpenLP.Ui', 'Image')
     Import = translate('OpenLP.Ui', 'Import')
     LengthTime = unicode(translate('OpenLP.Ui', 'Length %s'))
@@ -64,6 +65,7 @@
     LiveBGError = translate('OpenLP.Ui', 'Live Background Error')
     LivePanel = translate('OpenLP.Ui', 'Live Panel')
     Load = translate('OpenLP.Ui', 'Load')
+    Minutes = translate('OpenLP.Ui', 'm', 'The abbreviated unit for minutes')
     Middle = translate('OpenLP.Ui', 'Middle')
     New = translate('OpenLP.Ui', 'New')
     NewService = translate('OpenLP.Ui', 'New Service')
@@ -74,6 +76,8 @@
     NISp = translate('OpenLP.Ui', 'No Items Selected', 'Plural')
     OLPV1 = translate('OpenLP.Ui', 'openlp.org 1.x')
     OLPV2 = translate('OpenLP.Ui', 'OpenLP 2.0')
+    OpenLPStart = translate('OpenLP.Ui', 'OpenLP is already running. Do you '
+        'wish to continue?')
     OpenService = translate('OpenLP.Ui', 'Open Service')
     Preview = translate('OpenLP.Ui', 'Preview')
     PreviewPanel = translate('OpenLP.Ui', 'Preview Panel')
@@ -82,7 +86,7 @@
     ReplaceLiveBG = translate('OpenLP.Ui', 'Replace Live Background')
     ResetBG = translate('OpenLP.Ui', 'Reset Background')
     ResetLiveBG = translate('OpenLP.Ui', 'Reset Live Background')
-    S = translate('OpenLP.Ui', 's', 'The abbreviated unit for seconds')
+    Seconds = translate('OpenLP.Ui', 's', 'The abbreviated unit for seconds')
     SaveAndPreview = translate('OpenLP.Ui', 'Save && Preview')
     Search = translate('OpenLP.Ui', 'Search')
     SelectDelete = translate('OpenLP.Ui', 'You must select an item to delete.')

=== modified file 'openlp/core/ui/generaltab.py'
--- openlp/core/ui/generaltab.py	2011-03-24 19:04:02 +0000
+++ openlp/core/ui/generaltab.py	2011-03-27 16:42:30 +0000
@@ -105,6 +105,9 @@
         self.saveCheckServiceCheckBox = QtGui.QCheckBox(self.settingsGroupBox)
         self.saveCheckServiceCheckBox.setObjectName(u'saveCheckServiceCheckBox')
         self.settingsLayout.addRow(self.saveCheckServiceCheckBox)
+        self.autoUnblankCheckBox = QtGui.QCheckBox(self.settingsGroupBox)
+        self.autoUnblankCheckBox.setObjectName(u'autoUnblankCheckBox')
+        self.settingsLayout.addRow(self.autoUnblankCheckBox)
         self.autoPreviewCheckBox = QtGui.QCheckBox(self.settingsGroupBox)
         self.autoPreviewCheckBox.setObjectName(u'autoPreviewCheckBox')
         self.settingsLayout.addRow(self.autoPreviewCheckBox)
@@ -224,6 +227,8 @@
             translate('OpenLP.GeneralTab', 'Application Settings'))
         self.saveCheckServiceCheckBox.setText(translate('OpenLP.GeneralTab',
             'Prompt to save before starting a new service'))
+        self.autoUnblankCheckBox.setText(translate('OpenLP.GeneralTab',
+            'Unblank display when adding new live item'))
         self.autoPreviewCheckBox.setText(translate('OpenLP.GeneralTab',
             'Automatically preview next item in service'))
         self.timeoutLabel.setText(translate('OpenLP.GeneralTab',
@@ -262,6 +267,8 @@
             u'songselect password', QtCore.QVariant(u'')).toString()))
         self.saveCheckServiceCheckBox.setChecked(settings.value(u'save prompt',
             QtCore.QVariant(False)).toBool())
+        self.autoUnblankCheckBox.setChecked(settings.value(u'auto unblank',
+            QtCore.QVariant(False)).toBool())
         self.monitorComboBox.setCurrentIndex(self.monitorNumber)
         self.displayOnMonitorCheck.setChecked(self.screens.display)
         self.warningCheckBox.setChecked(settings.value(u'blank warning',
@@ -312,6 +319,8 @@
             QtCore.QVariant(self.checkForUpdatesCheckBox.isChecked()))
         settings.setValue(u'save prompt',
             QtCore.QVariant(self.saveCheckServiceCheckBox.isChecked()))
+        settings.setValue(u'auto unblank',
+            QtCore.QVariant(self.autoUnblankCheckBox.isChecked()))
         settings.setValue(u'auto preview',
             QtCore.QVariant(self.autoPreviewCheckBox.isChecked()))
         settings.setValue(u'loop delay',

=== modified file 'openlp/core/ui/maindisplay.py'
--- openlp/core/ui/maindisplay.py	2011-03-24 19:04:02 +0000
+++ openlp/core/ui/maindisplay.py	2011-03-27 16:42:30 +0000
@@ -367,7 +367,7 @@
             self.mediaObject.setCurrentSource(Phonon.MediaSource(videoPath))
             # Need the timer to trigger set the trigger to 200ms
             # Value taken from web documentation.
-            if self.serviceItem.start_time != 0:
+            if self.serviceItem.end_time != 0:
                 self.mediaObject.setTickInterval(200)
             self.mediaObject.play()
             self.webView.setVisible(False)
@@ -401,9 +401,9 @@
     def videoTick(self, tick):
         """
         Triggered on video tick every 200 milli seconds
-        Will be used to manage stop time later
         """
-        pass
+        if tick > self.serviceItem.end_time * 1000:
+            self.videoFinished()
 
     def isWebLoaded(self):
         """
@@ -489,7 +489,11 @@
             self.footer(serviceItem.foot_text)
         # if was hidden keep it hidden
         if self.hideMode and self.isLive:
-            self.hideDisplay(self.hideMode)
+            if QtCore.QSettings().value(u'general/auto unblank',
+                QtCore.QVariant(False)).toBool():
+                Receiver.send_message(u'slidecontroller_live_unblank')
+            else:
+                self.hideDisplay(self.hideMode)
         # display hidden for video end we have a new item so must be shown
         if self.videoHide and self.isLive:
             self.videoHide = False

=== modified file 'openlp/core/ui/mainwindow.py'
--- openlp/core/ui/mainwindow.py	2011-03-26 18:56:36 +0000
+++ openlp/core/ui/mainwindow.py	2011-03-27 16:42:30 +0000
@@ -469,14 +469,15 @@
 
     actionList = ActionList()
 
-    def __init__(self, screens, clipboard):
+    def __init__(self, screens, application):
         """
         This constructor sets up the interface, the various managers, and the
         plugins.
         """
         QtGui.QMainWindow.__init__(self)
         self.screens = screens
-        self.clipboard = clipboard
+
+        self.application = application
         # Set up settings sections for the main application
         # (not for use by plugins)
         self.uiSettingsSection = u'user interface'
@@ -660,7 +661,12 @@
         if self.liveController.display.isVisible():
             self.liveController.display.setFocus()
         self.activateWindow()
-        if QtCore.QSettings().value(
+        if len(self.application.arguments()) > 0:
+            args = []
+            for a in self.application.arguments():
+                args.extend([a])
+            self.ServiceManagerContents.loadFile(unicode(args[0]))
+        elif QtCore.QSettings().value(
             self.generalSettingsSection + u'/auto open',
             QtCore.QVariant(False)).toBool():
             self.ServiceManagerContents.loadLastFile()

=== modified file 'openlp/core/ui/printserviceform.py'
--- openlp/core/ui/printserviceform.py	2011-03-24 19:04:02 +0000
+++ openlp/core/ui/printserviceform.py	2011-03-27 16:42:30 +0000
@@ -33,12 +33,12 @@
 
 class PrintServiceForm(QtGui.QDialog, Ui_PrintServiceDialog):
 
-    def __init__(self, parent, serviceManager):
+    def __init__(self, mainWindow, serviceManager):
         """
         Constructor
         """
-        QtGui.QDialog.__init__(self, parent)
-        self.parent = parent
+        QtGui.QDialog.__init__(self, mainWindow)
+        self.mainWindow = mainWindow
         self.serviceManager = serviceManager
         self.printer = QtGui.QPrinter()
         self.printDialog = QtGui.QPrintDialog(self.printer, self)
@@ -134,9 +134,12 @@
                         item.notes.replace(u'\n', u'<br />'))
             # Add play length of media files.
             if item.is_media() and self.metaDataCheckBox.isChecked():
+                tme = item.media_length
+                if item.end_time > 0:
+                    tme = item.end_time - item.start_time
                 text += u'<p><strong>%s</strong> %s</p>' % (translate(
                     'OpenLP.ServiceManager', u'Playing time:'),
-                    unicode(datetime.timedelta(seconds=item.media_length)))
+                    unicode(datetime.timedelta(seconds=tme)))
         if self.footerTextEdit.toPlainText():
             text += u'<h4>%s</h4>%s' % (translate('OpenLP.ServiceManager',
                 u'Custom Service Notes:'), self.footerTextEdit.toPlainText())
@@ -181,13 +184,14 @@
         """
         Copies the display text to the clipboard as plain text
         """
-        self.parent.clipboard.setText(self.document.toPlainText())
+        self.mainWindow.application.clipboard.setText(
+            self.document.toPlainText())
 
     def copyHtmlText(self):
         """
         Copies the display text to the clipboard as Html
         """
-        self.parent.clipboard.setText(self.document.toHtml())
+        self.mainWindow.application.clipboard.setText(self.document.toHtml())
 
     def printServiceOrder(self):
         """

=== modified file 'openlp/core/ui/serviceitemeditform.py'
--- openlp/core/ui/serviceitemeditform.py	2011-03-24 19:04:02 +0000
+++ openlp/core/ui/serviceitemeditform.py	2011-03-27 16:42:30 +0000
@@ -141,4 +141,3 @@
             else:
                 self.upButton.setEnabled(True)
             self.deleteButton.setEnabled(True)
-

=== modified file 'openlp/core/ui/servicemanager.py'
--- openlp/core/ui/servicemanager.py	2011-03-24 21:13:51 +0000
+++ openlp/core/ui/servicemanager.py	2011-03-27 16:42:30 +0000
@@ -231,7 +231,7 @@
         QtCore.QObject.connect(self.themeComboBox,
             QtCore.SIGNAL(u'activated(int)'), self.onThemeComboBoxSelected)
         QtCore.QObject.connect(self.serviceManagerList,
-            QtCore.SIGNAL(u'doubleClicked(QModelIndex)'), self.makeLive)
+            QtCore.SIGNAL(u'doubleClicked(QModelIndex)'), self.onMakeLive)
         QtCore.QObject.connect(self.serviceManagerList,
            QtCore.SIGNAL(u'itemCollapsed(QTreeWidgetItem*)'), self.collapsed)
         QtCore.QObject.connect(self.serviceManagerList,
@@ -239,6 +239,8 @@
         QtCore.QObject.connect(Receiver.get_receiver(),
             QtCore.SIGNAL(u'theme_update_list'), self.updateThemeList)
         QtCore.QObject.connect(Receiver.get_receiver(),
+            QtCore.SIGNAL(u'servicemanager_preview_live'), self.previewLive)
+        QtCore.QObject.connect(Receiver.get_receiver(),
             QtCore.SIGNAL(u'servicemanager_next_item'), self.nextItem)
         QtCore.QObject.connect(Receiver.get_receiver(),
             QtCore.SIGNAL(u'servicemanager_previous_item'), self.previousItem)
@@ -561,6 +563,7 @@
                 self.newFile()
                 for item in items:
                     serviceItem = ServiceItem()
+                    serviceItem.from_service = True
                     serviceItem.render_manager = self.mainwindow.renderManager
                     serviceItem.set_from_service(item, self.servicePath)
                     self.validateItem(serviceItem)
@@ -658,10 +661,6 @@
         item = self.findServiceItem()[0]
         self.startTimeForm.item = self.serviceItems[item]
         if self.startTimeForm.exec_():
-            self.serviceItems[item][u'service_item'].start_time = \
-                self.startTimeForm.hourSpinBox.value() * 3600 + \
-                self.startTimeForm.minuteSpinBox.value() * 60 + \
-                self.startTimeForm.secondSpinBox.value()
             self.repaintServiceList(item, -1)
 
     def onServiceItemEditForm(self):
@@ -672,6 +671,19 @@
             self.addServiceItem(self.serviceItemEditForm.getServiceItem(),
                 replace=True, expand=self.serviceItems[item][u'expanded'])
 
+    def previewLive(self, message):
+        """
+        Called by the SlideController to request a preview item be made live
+        and allows the next preview to be updated if relevent.
+        """
+        id, row = message.split(u':')
+        for sitem in self.serviceItems:
+            if sitem[u'service_item']._uuid == id:
+                item = self.serviceManagerList.topLevelItem(sitem[u'order'] - 1)
+                self.serviceManagerList.setCurrentItem(item)
+                self.makeLive(int(row))
+                return
+
     def nextItem(self):
         """
         Called by the SlideController to select the next service item.
@@ -1027,6 +1039,7 @@
         if expand is None:
             expand = self.expandTabs
         item.render()
+        item.from_service = True
         if replace:
             sitem, child = self.findServiceItem()
             item.merge(self.serviceItems[sitem][u'service_item'])
@@ -1081,11 +1094,24 @@
         else:
             return self.serviceItems[item][u'service_item']
 
-    def makeLive(self):
+    def onMakeLive(self):
+        """
+        Send the current item to the Live slide controller but triggered
+        by a tablewidget click event.
+        """
+        self.makeLive()
+
+    def makeLive(self, row=-1):
         """
         Send the current item to the Live slide controller
+
+        ``row``
+            Row number to be displayed if from preview.
+            -1 is passed if the value is not set
         """
         item, child = self.findServiceItem()
+        if row != -1:
+            child = row
         if self.serviceItems[item][u'service_item'].is_valid:
             self.mainwindow.liveController.addServiceManagerItem(
                 self.serviceItems[item][u'service_item'], child)

=== modified file 'openlp/core/ui/slidecontroller.py'
--- openlp/core/ui/slidecontroller.py	2011-03-24 21:19:17 +0000
+++ openlp/core/ui/slidecontroller.py	2011-03-27 16:42:30 +0000
@@ -185,7 +185,7 @@
             self.delaySpinBox.setMinimum(1)
             self.delaySpinBox.setMaximum(180)
             self.toolbar.addToolbarWidget(u'Image SpinBox', self.delaySpinBox)
-            self.delaySpinBox.setSuffix(UiStrings.S)
+            self.delaySpinBox.setSuffix(UiStrings.Seconds)
             self.delaySpinBox.setToolTip(translate('OpenLP.SlideController',
                 'Delay between slides in seconds'))
         else:
@@ -1014,8 +1014,12 @@
         """
         row = self.previewListWidget.currentRow()
         if row > -1 and row < self.previewListWidget.rowCount():
-            self.parent.liveController.addServiceManagerItem(
-                self.serviceItem, row)
+            if self.serviceItem.from_service:
+                Receiver.send_message('servicemanager_preview_live',
+                    u'%s:%s' % (self.serviceItem._uuid, row))
+            else:
+                self.parent.liveController.addServiceManagerItem(
+                    self.serviceItem, row)
 
     def onMediaStart(self, item):
         """

=== modified file 'openlp/core/ui/starttimedialog.py'
--- openlp/core/ui/starttimedialog.py	2011-03-24 19:04:02 +0000
+++ openlp/core/ui/starttimedialog.py	2011-03-27 16:42:30 +0000
@@ -32,39 +32,90 @@
 class Ui_StartTimeDialog(object):
     def setupUi(self, StartTimeDialog):
         StartTimeDialog.setObjectName(u'StartTimeDialog')
-        StartTimeDialog.resize(300, 10)
+        StartTimeDialog.resize(350, 10)
         self.dialogLayout = QtGui.QGridLayout(StartTimeDialog)
         self.dialogLayout.setObjectName(u'dialogLayout')
+        self.startLabel = QtGui.QLabel(StartTimeDialog)
+        self.startLabel.setObjectName(u'startLabel')
+        self.startLabel.setAlignment(QtCore.Qt.AlignHCenter)
+        self.dialogLayout.addWidget(self.startLabel, 0, 1, 1, 1)
+        self.finishLabel = QtGui.QLabel(StartTimeDialog)
+        self.finishLabel.setObjectName(u'finishLabel')
+        self.finishLabel.setAlignment(QtCore.Qt.AlignHCenter)
+        self.dialogLayout.addWidget(self.finishLabel, 0, 2, 1, 1)
+        self.lengthLabel = QtGui.QLabel(StartTimeDialog)
+        self.lengthLabel.setObjectName(u'startLabel')
+        self.lengthLabel.setAlignment(QtCore.Qt.AlignHCenter)
+        self.dialogLayout.addWidget(self.lengthLabel, 0, 3, 1, 1)
         self.hourLabel = QtGui.QLabel(StartTimeDialog)
-        self.hourLabel.setObjectName("hourLabel")
-        self.dialogLayout.addWidget(self.hourLabel, 0, 0, 1, 1)
+        self.hourLabel.setObjectName(u'hourLabel')
+        self.dialogLayout.addWidget(self.hourLabel, 1, 0, 1, 1)
         self.hourSpinBox = QtGui.QSpinBox(StartTimeDialog)
-        self.hourSpinBox.setObjectName("hourSpinBox")
-        self.dialogLayout.addWidget(self.hourSpinBox, 0, 1, 1, 1)
+        self.hourSpinBox.setObjectName(u'hourSpinBox')
+        self.hourSpinBox.setMinimum(0)
+        self.hourSpinBox.setMaximum(4)
+        self.dialogLayout.addWidget(self.hourSpinBox, 1, 1, 1, 1)
+        self.hourFinishSpinBox = QtGui.QSpinBox(StartTimeDialog)
+        self.hourFinishSpinBox.setObjectName(u'hourFinishSpinBox')
+        self.hourFinishSpinBox.setMinimum(0)
+        self.hourFinishSpinBox.setMaximum(4)
+        self.dialogLayout.addWidget(self.hourFinishSpinBox, 1, 2, 1, 1)
+        self.hourFinishLabel = QtGui.QLabel(StartTimeDialog)
+        self.hourFinishLabel.setObjectName(u'hourLabel')
+        self.hourFinishLabel.setAlignment(QtCore.Qt.AlignRight)
+        self.dialogLayout.addWidget(self.hourFinishLabel, 1, 3, 1, 1)
         self.minuteLabel = QtGui.QLabel(StartTimeDialog)
-        self.minuteLabel.setObjectName("minuteLabel")
-        self.dialogLayout.addWidget(self.minuteLabel, 1, 0, 1, 1)
+        self.minuteLabel.setObjectName(u'minuteLabel')
+        self.dialogLayout.addWidget(self.minuteLabel, 2, 0, 1, 1)
         self.minuteSpinBox = QtGui.QSpinBox(StartTimeDialog)
-        self.minuteSpinBox.setObjectName("minuteSpinBox")
-        self.dialogLayout.addWidget(self.minuteSpinBox, 1, 1, 1, 1)
+        self.minuteSpinBox.setObjectName(u'minuteSpinBox')
+        self.minuteSpinBox.setMinimum(0)
+        self.minuteSpinBox.setMaximum(59)
+        self.dialogLayout.addWidget(self.minuteSpinBox, 2, 1, 1, 1)
+        self.minuteFinishSpinBox = QtGui.QSpinBox(StartTimeDialog)
+        self.minuteFinishSpinBox.setObjectName(u'minuteFinishSpinBox')
+        self.minuteFinishSpinBox.setMinimum(0)
+        self.minuteFinishSpinBox.setMaximum(59)
+        self.dialogLayout.addWidget(self.minuteFinishSpinBox, 2, 2, 1, 1)
+        self.minuteFinishLabel = QtGui.QLabel(StartTimeDialog)
+        self.minuteFinishLabel.setObjectName(u'minuteLabel')
+        self.minuteFinishLabel.setAlignment(QtCore.Qt.AlignRight)
+        self.dialogLayout.addWidget(self.minuteFinishLabel, 2, 3, 1, 1)
         self.secondLabel = QtGui.QLabel(StartTimeDialog)
-        self.secondLabel.setObjectName("secondLabel")
-        self.dialogLayout.addWidget(self.secondLabel, 2, 0, 1, 1)
+        self.secondLabel.setObjectName(u'secondLabel')
+        self.dialogLayout.addWidget(self.secondLabel, 3, 0, 1, 1)
         self.secondSpinBox = QtGui.QSpinBox(StartTimeDialog)
-        self.secondSpinBox.setObjectName("secondSpinBox")
-        self.dialogLayout.addWidget(self.secondSpinBox, 2, 1, 1, 1)
+        self.secondSpinBox.setObjectName(u'secondSpinBox')
+        self.secondSpinBox.setMinimum(0)
+        self.secondSpinBox.setMaximum(59)
+        self.secondFinishSpinBox = QtGui.QSpinBox(StartTimeDialog)
+        self.secondFinishSpinBox.setObjectName(u'secondFinishSpinBox')
+        self.secondFinishSpinBox.setMinimum(0)
+        self.secondFinishSpinBox.setMaximum(59)
+        self.dialogLayout.addWidget(self.secondFinishSpinBox, 3, 2, 1, 1)
+        self.secondFinishLabel = QtGui.QLabel(StartTimeDialog)
+        self.secondFinishLabel.setObjectName(u'secondLabel')
+        self.secondFinishLabel.setAlignment(QtCore.Qt.AlignRight)
+        self.dialogLayout.addWidget(self.secondFinishLabel, 3, 3, 1, 1)
+        self.dialogLayout.addWidget(self.secondSpinBox, 3, 1, 1, 1)
         self.buttonBox = create_accept_reject_button_box(StartTimeDialog, True)
-        self.dialogLayout.addWidget(self.buttonBox, 4, 0, 1, 2)
+        self.dialogLayout.addWidget(self.buttonBox, 5, 2, 1, 2)
         self.retranslateUi(StartTimeDialog)
         self.setMaximumHeight(self.sizeHint().height())
         QtCore.QMetaObject.connectSlotsByName(StartTimeDialog)
 
     def retranslateUi(self, StartTimeDialog):
         self.setWindowTitle(translate('OpenLP.StartTimeForm',
-            'Item Start Time'))
+            'Item Start and Finish Time'))
+        self.hourSpinBox.setSuffix(UiStrings.Hours)
+        self.minuteSpinBox.setSuffix(UiStrings.Minutes)
+        self.secondSpinBox.setSuffix(UiStrings.Seconds)
+        self.hourFinishSpinBox.setSuffix(UiStrings.Hours)
+        self.minuteFinishSpinBox.setSuffix(UiStrings.Minutes)
+        self.secondFinishSpinBox.setSuffix(UiStrings.Seconds)
         self.hourLabel.setText(translate('OpenLP.StartTimeForm', 'Hours:'))
-        self.hourSpinBox.setSuffix(translate('OpenLP.StartTimeForm', 'h'))
-        self.minuteSpinBox.setSuffix(translate('OpenLP.StartTimeForm', 'm'))
-        self.secondSpinBox.setSuffix(UiStrings.S)
         self.minuteLabel.setText(translate('OpenLP.StartTimeForm', 'Minutes:'))
         self.secondLabel.setText(translate('OpenLP.StartTimeForm', 'Seconds:'))
+        self.startLabel.setText(translate('OpenLP.StartTimeForm', 'Start'))
+        self.finishLabel.setText(translate('OpenLP.StartTimeForm', 'Finish'))
+        self.lengthLabel.setText(translate('OpenLP.StartTimeForm', 'Length'))

=== modified file 'openlp/core/ui/starttimeform.py'
--- openlp/core/ui/starttimeform.py	2011-03-24 19:04:02 +0000
+++ openlp/core/ui/starttimeform.py	2011-03-27 16:42:30 +0000
@@ -28,6 +28,9 @@
 
 from starttimedialog import Ui_StartTimeDialog
 
+from openlp.core.lib import translate
+from openlp.core.lib.ui import UiStrings, critical_error_message_box
+
 class StartTimeForm(QtGui.QDialog, Ui_StartTimeDialog):
     """
     The exception dialog
@@ -40,13 +43,51 @@
         """
         Run the Dialog with correct heading.
         """
-        seconds = self.item[u'service_item'].start_time
+        hour, minutes, seconds = self._time_split(
+            self.item[u'service_item'].start_time)
+        self.hourSpinBox.setValue(hour)
+        self.minuteSpinBox.setValue(minutes)
+        self.secondSpinBox.setValue(seconds)
+        hours, minutes, seconds = self._time_split(
+            self.item[u'service_item'].media_length)
+        self.hourFinishSpinBox.setValue(hours)
+        self.minuteFinishSpinBox.setValue(minutes)
+        self.secondFinishSpinBox.setValue(seconds)
+        self.hourFinishLabel.setText(u'%s%s' % (unicode(hour), UiStrings.Hours))
+        self.minuteFinishLabel.setText(u'%s%s' %
+            (unicode(minutes), UiStrings.Minutes))
+        self.secondFinishLabel.setText(u'%s%s' %
+            (unicode(seconds), UiStrings.Seconds))
+        return QtGui.QDialog.exec_(self)
+
+    def accept(self):
+        start = self.hourSpinBox.value() * 3600 + \
+            self.minuteSpinBox.value() * 60 + \
+            self.secondSpinBox.value()
+        end = self.hourFinishSpinBox.value() * 3600 + \
+            self.minuteFinishSpinBox.value() * 60 + \
+            self.secondFinishSpinBox.value()
+        if end > self.item[u'service_item'].media_length:
+            critical_error_message_box(
+                title=translate('OpenLP.StartTimeForm',
+                'Time Validation Error'),
+                message=translate('OpenLP.StartTimeForm',
+                'End time is set after the end of the media item'))
+            return
+        elif start > end:
+            critical_error_message_box(
+                title=translate('OpenLP.StartTimeForm',
+                'Time Validation Error'),
+                message=translate('OpenLP.StartTimeForm',
+                'Start time is after the End Time of the media item'))
+            return
+        self.item[u'service_item'].start_time = start
+        self.item[u'service_item'].end_time = end
+        return QtGui.QDialog.accept(self)
+
+    def _time_split(self, seconds):
         hours = seconds / 3600
         seconds -= 3600 * hours
         minutes = seconds / 60
         seconds -= 60 * minutes
-        self.hourSpinBox.setValue(hours)
-        self.minuteSpinBox.setValue(minutes)
-        self.secondSpinBox.setValue(seconds)
-        return QtGui.QDialog.exec_(self)
-
+        return hours, minutes, seconds

=== modified file 'openlp/plugins/alerts/lib/alertstab.py'
--- openlp/plugins/alerts/lib/alertstab.py	2011-03-24 19:04:02 +0000
+++ openlp/plugins/alerts/lib/alertstab.py	2011-03-27 16:42:30 +0000
@@ -112,7 +112,7 @@
         self.FontSizeSpinBox.setSuffix(UiStrings.FontSizePtUnit)
         self.TimeoutLabel.setText(
             translate('AlertsPlugin.AlertsTab', 'Alert timeout:'))
-        self.TimeoutSpinBox.setSuffix(UiStrings.S)
+        self.TimeoutSpinBox.setSuffix(UiStrings.Seconds)
         self.PreviewGroupBox.setTitle(UiStrings.Preview)
         self.FontPreview.setText(UiStrings.OLPV2)
 


Follow ups