diff --git a/openlp/core/lib/imagemanager.py b/openlp/core/lib/imagemanager.py
index d89cefccc..37d1de79c 100644
--- a/openlp/core/lib/imagemanager.py
+++ b/openlp/core/lib/imagemanager.py
@@ -32,6 +32,7 @@ to wait for the conversion to happen.
"""
import logging
import time
+import Queue
from PyQt4 import QtCore
@@ -42,8 +43,8 @@ log = logging.getLogger(__name__)
class ImageThread(QtCore.QThread):
"""
- A special Qt thread class to speed up the display of text based frames.
- This is threaded so it loads the frames in background
+ A special Qt thread class to speed up the display of images. This is
+ threaded so it loads the frames and generates byte stream in background.
"""
def __init__(self, manager):
QtCore.QThread.__init__(self, None)
@@ -53,15 +54,87 @@ class ImageThread(QtCore.QThread):
"""
Run the thread.
"""
- self.imageManager.process()
+ self.imageManager._process()
+
+
+class Priority(object):
+ """
+ Enumeration class for different priorities.
+
+ ``Lowest``
+ Only the image's byte stream has to be generated. But neither the
+ ``QImage`` nor the byte stream has been requested yet.
+
+ ``Low``
+ Only the image's byte stream has to be generated. Because the image's
+ ``QImage`` has been requested previously it is reasonable to assume that
+ the byte stream will be needed before the byte stream of other images
+ whose ``QImage`` were not generated due to a request.
+
+ ``Normal``
+ The image's byte stream as well as the image has to be generated.
+ Neither the ``QImage`` nor the byte stream has been requested yet.
+
+ ``High``
+ The image's byte stream as well as the image has to be generated. The
+ ``QImage`` for this image has been requested.
+ **Note**, this priority is only set when the ``QImage`` has not been
+ generated yet.
+
+ ``Urgent``
+ The image's byte stream as well as the image has to be generated. The
+ byte stream for this image has been requested.
+ **Note**, this priority is only set when the byte stream has not been
+ generated yet.
+ """
+ Lowest = 4
+ Low = 3
+ Normal = 2
+ High = 1
+ Urgent = 0
class Image(object):
- name = ''
- path = ''
- dirty = True
- image = None
- image_bytes = None
+ """
+ This class represents an image. To mark an image as *dirty* set the instance
+ variables ``image`` and ``image_bytes`` to ``None`` and add the image object
+ to the queue of images to process.
+ """
+ def __init__(self, name='', path=''):
+ self.name = name
+ self.path = path
+ self.image = None
+ self.image_bytes = None
+ self.priority = Priority.Normal
+
+
+class PriorityQueue(Queue.PriorityQueue):
+ """
+ Customised ``Queue.PriorityQueue``.
+ """
+ def modify_priority(self, image, new_priority):
+ """
+ Modifies the priority of the given ``image``.
+
+ ``image``
+ The image to remove. This should be an ``Image`` instance.
+
+ ``new_priority``
+ The image's new priority.
+ """
+ self.remove(image)
+ image.priority = new_priority
+ self.put((image.priority, image))
+
+ def remove(self, image):
+ """
+ Removes the given ``image`` from the queue.
+
+ ``image``
+ The image to remove. This should be an ``Image`` instance.
+ """
+ if (image.priority, image) in self.queue:
+ self.queue.remove((image.priority, image))
class ImageManager(QtCore.QObject):
@@ -76,96 +149,117 @@ class ImageManager(QtCore.QObject):
self.width = current_screen[u'size'].width()
self.height = current_screen[u'size'].height()
self._cache = {}
- self._thread_running = False
- self._cache_dirty = False
- self.image_thread = ImageThread(self)
+ self._imageThread = ImageThread(self)
+ self._conversion_queue = PriorityQueue()
def update_display(self):
"""
- Screen has changed size so rebuild the cache to new size
+ Screen has changed size so rebuild the cache to new size.
"""
log.debug(u'update_display')
current_screen = ScreenList.get_instance().current
self.width = current_screen[u'size'].width()
self.height = current_screen[u'size'].height()
- # mark the images as dirty for a rebuild
- for key in self._cache.keys():
- image = self._cache[key]
- image.dirty = True
- image.image = resize_image(image.path, self.width, self.height)
- self._cache_dirty = True
- # only one thread please
- if not self._thread_running:
- self.image_thread.start()
+ # Mark the images as dirty for a rebuild by setting the image and byte
+ # stream to None.
+ self._conversion_queue = PriorityQueue()
+ for key, image in self._cache.iteritems():
+ image.priority = Priority.Normal
+ image.image = None
+ image.image_bytes = None
+ self._conversion_queue.put((image.priority, image))
+ # We want only one thread.
+ if not self._imageThread.isRunning():
+ self._imageThread.start()
def get_image(self, name):
"""
- Return the Qimage from the cache
+ Return the ``QImage`` from the cache. If not present wait for the
+ background thread to process it.
"""
log.debug(u'get_image %s' % name)
- return self._cache[name].image
+ image = self._cache[name]
+ if image.image is None:
+ self._conversion_queue.modify_priority(image, Priority.High)
+ while image.image is None:
+ log.debug(u'get_image - waiting')
+ time.sleep(0.1)
+ elif image.image_bytes is None:
+ # Set the priority to Low, because the image was requested but the
+ # byte stream was not generated yet. However, we only need to do
+ # this, when the image was generated before it was requested
+ # (otherwise this is already taken care of).
+ self._conversion_queue.modify_priority(image, Priority.Low)
+ return image.image
def get_image_bytes(self, name):
"""
- Returns the byte string for an image
- If not present wait for the background thread to process it.
+ Returns the byte string for an image. If not present wait for the
+ background thread to process it.
"""
log.debug(u'get_image_bytes %s' % name)
- if not self._cache[name].image_bytes:
- while self._cache[name].dirty:
+ image = self._cache[name]
+ if image.image_bytes is None:
+ self._conversion_queue.modify_priority(image, Priority.Urgent)
+ while image.image_bytes is None:
log.debug(u'get_image_bytes - waiting')
time.sleep(0.1)
- return self._cache[name].image_bytes
+ return image.image_bytes
def del_image(self, name):
"""
- Delete the Image from the Cache
+ Delete the Image from the cache.
"""
log.debug(u'del_image %s' % name)
if name in self._cache:
+ self._conversion_queue.remove(self._cache[name])
del self._cache[name]
def add_image(self, name, path):
"""
- Add image to cache if it is not already there
+ Add image to cache if it is not already there.
"""
log.debug(u'add_image %s:%s' % (name, path))
if not name in self._cache:
- image = Image()
- image.name = name
- image.path = path
- image.image = resize_image(path, self.width, self.height)
+ image = Image(name, path)
self._cache[name] = image
+ self._conversion_queue.put((image.priority, image))
else:
log.debug(u'Image in cache %s:%s' % (name, path))
- self._cache_dirty = True
- # only one thread please
- if not self._thread_running:
- self.image_thread.start()
+ # We want only one thread.
+ if not self._imageThread.isRunning():
+ self._imageThread.start()
- def process(self):
+ def _process(self):
"""
- Controls the processing called from a QThread
+ Controls the processing called from a ``QtCore.QThread``.
"""
- log.debug(u'process - started')
- self._thread_running = True
- self.clean_cache()
- # data loaded since we started ?
- while self._cache_dirty:
- log.debug(u'process - recycle')
- self.clean_cache()
- self._thread_running = False
- log.debug(u'process - ended')
+ log.debug(u'_process - started')
+ while not self._conversion_queue.empty():
+ self._process_cache()
+ log.debug(u'_process - ended')
- def clean_cache(self):
+ def _process_cache(self):
"""
Actually does the work.
"""
- log.debug(u'clean_cache')
- # we will clean the cache now
- self._cache_dirty = False
- for key in self._cache.keys():
- image = self._cache[key]
- if image.dirty:
- image.image_bytes = image_to_byte(image.image)
- image.dirty = False
+ log.debug(u'_process_cache')
+ image = self._conversion_queue.get()[1]
+ # Generate the QImage for the image.
+ if image.image is None:
+ image.image = resize_image(image.path, self.width, self.height)
+ # Set the priority to Lowest and stop here as we need to process
+ # more important images first.
+ if image.priority == Priority.Normal:
+ self._conversion_queue.modify_priority(image, Priority.Lowest)
+ return
+ # For image with high priority we set the priority to Low, as the
+ # byte stream might be needed earlier the byte stream of image with
+ # Normal priority. We stop here as we need to process more important
+ # images first.
+ elif image.priority == Priority.High:
+ self._conversion_queue.modify_priority(image, Priority.Low)
+ return
+ # Generate the byte stream for the image.
+ if image.image_bytes is None:
+ image.image_bytes = image_to_byte(image.image)
diff --git a/openlp/core/lib/mediamanageritem.py b/openlp/core/lib/mediamanageritem.py
index a79355c88..0cc25717c 100644
--- a/openlp/core/lib/mediamanageritem.py
+++ b/openlp/core/lib/mediamanageritem.py
@@ -288,6 +288,7 @@ class MediaManagerItem(QtGui.QWidget):
self.listView, u':/general/general_add.png',
translate('OpenLP.MediaManagerItem',
'&Add to selected Service Item'), self.onAddEditClick)
+ self.addCustomContextActions()
# Create the context menu and add all actions from the listView.
self.menu = QtGui.QMenu()
self.menu.addActions(self.listView.actions())
@@ -301,6 +302,13 @@ class MediaManagerItem(QtGui.QWidget):
QtCore.SIGNAL('customContextMenuRequested(QPoint)'),
self.contextMenu)
+ def addCustomContextActions(self):
+ """
+ Implement this method in your descendent media manager item to
+ add any context menu items. This method is called automatically.
+ """
+ pass
+
def initialise(self):
"""
Implement this method in your descendent media manager item to
diff --git a/openlp/core/lib/serviceitem.py b/openlp/core/lib/serviceitem.py
index ad762e326..1245988b4 100644
--- a/openlp/core/lib/serviceitem.py
+++ b/openlp/core/lib/serviceitem.py
@@ -35,7 +35,7 @@ import logging
import os
import uuid
-from openlp.core.lib import build_icon, clean_tags, expand_tags
+from openlp.core.lib import build_icon, clean_tags, expand_tags, translate
from openlp.core.lib.ui import UiStrings
log = logging.getLogger(__name__)
@@ -352,6 +352,9 @@ class ServiceItem(object):
Updates the _uuid with the value from the original one
The _uuid is unique for a given service item but this allows one to
replace an original version.
+
+ ``other``
+ The service item to be merged with
"""
self._uuid = other._uuid
self.notes = other.notes
@@ -447,10 +450,12 @@ class ServiceItem(object):
start = None
end = None
if self.start_time != 0:
- start = UiStrings().StartTimeCode % \
+ start = unicode(translate('OpenLP.ServiceItem',
+ 'Start: %s')) % \
unicode(datetime.timedelta(seconds=self.start_time))
if self.media_length != 0:
- end = UiStrings().LengthTime % \
+ end = unicode(translate('OpenLP.ServiceItem',
+ 'Length: %s')) % \
unicode(datetime.timedelta(seconds=self.media_length))
if not start and not end:
return None
@@ -459,5 +464,16 @@ class ServiceItem(object):
elif not start and end:
return end
else:
- return u'%s : %s' % (start, end)
+ return u'%s
%s' % (start, end)
+
+ def update_theme(self, theme):
+ """
+ updates the theme in the service item
+
+ ``theme``
+ The new theme to be replaced in the service item
+ """
+ self.theme = theme
+ self._new_item()
+ self.render()
diff --git a/openlp/core/lib/spelltextedit.py b/openlp/core/lib/spelltextedit.py
index 3e58738ce..57a176a69 100644
--- a/openlp/core/lib/spelltextedit.py
+++ b/openlp/core/lib/spelltextedit.py
@@ -29,6 +29,7 @@ import re
try:
import enchant
from enchant import DictNotFoundError
+ from enchant.errors import Error
ENCHANT_AVAILABLE = True
except ImportError:
ENCHANT_AVAILABLE = False
@@ -56,7 +57,7 @@ class SpellTextEdit(QtGui.QPlainTextEdit):
self.dictionary = enchant.Dict()
self.highlighter = Highlighter(self.document())
self.highlighter.spellingDictionary = self.dictionary
- except DictNotFoundError:
+ except Error, DictNotFoundError:
ENCHANT_AVAILABLE = False
log.debug(u'Could not load default dictionary')
diff --git a/openlp/core/lib/theme.py b/openlp/core/lib/theme.py
index cc9c055c5..7ad50490d 100644
--- a/openlp/core/lib/theme.py
+++ b/openlp/core/lib/theme.py
@@ -178,10 +178,6 @@ class HorizontalType(object):
Center = 2
Names = [u'left', u'right', u'center']
- TranslatedNames = [
- translate('OpenLP.ThemeWizard', 'Left'),
- translate('OpenLP.ThemeWizard', 'Right'),
- translate('OpenLP.ThemeWizard', 'Center')]
class VerticalType(object):
@@ -193,7 +189,6 @@ class VerticalType(object):
Bottom = 2
Names = [u'top', u'middle', u'bottom']
- TranslatedNames = [UiStrings().Top, UiStrings().Middle, UiStrings().Bottom]
BOOLEAN_LIST = [u'bold', u'italics', u'override', u'outline', u'shadow',
diff --git a/openlp/core/lib/ui.py b/openlp/core/lib/ui.py
index 5d20b212d..756df36c3 100644
--- a/openlp/core/lib/ui.py
+++ b/openlp/core/lib/ui.py
@@ -64,6 +64,7 @@ class UiStrings(object):
self.Cancel = translate('OpenLP.Ui', 'Cancel')
self.CCLINumberLabel = translate('OpenLP.Ui', 'CCLI number:')
self.CreateService = translate('OpenLP.Ui', 'Create a new service.')
+ self.ConfirmDelete = translate('OpenLP.Ui', 'Confirm Delete')
self.Continuous = translate('OpenLP.Ui', 'Continuous')
self.Default = unicode(translate('OpenLP.Ui', 'Default'))
self.Delete = translate('OpenLP.Ui', '&Delete')
@@ -82,7 +83,6 @@ class UiStrings(object):
self.Image = translate('OpenLP.Ui', 'Image')
self.Import = translate('OpenLP.Ui', 'Import')
self.LayoutStyle = translate('OpenLP.Ui', 'Layout style:')
- self.LengthTime = unicode(translate('OpenLP.Ui', 'Length %s'))
self.Live = translate('OpenLP.Ui', 'Live')
self.LiveBGError = translate('OpenLP.Ui', 'Live Background Error')
self.LiveToolbar = translate('OpenLP.Ui', 'Live Toolbar')
@@ -102,6 +102,8 @@ class UiStrings(object):
self.OpenLPStart = translate('OpenLP.Ui', 'OpenLP is already running. '
'Do you wish to continue?')
self.OpenService = translate('OpenLP.Ui', 'Open service.')
+ self.PlaySlidesInLoop = translate('OpenLP.Ui','Play Slides in Loop')
+ self.PlaySlidesToEnd = translate('OpenLP.Ui','Play Slides to End')
self.Preview = translate('OpenLP.Ui', 'Preview')
self.PrintService = translate('OpenLP.Ui', 'Print Service')
self.ReplaceBG = translate('OpenLP.Ui', 'Replace Background')
@@ -123,6 +125,10 @@ class UiStrings(object):
self.SplitToolTip = translate('OpenLP.Ui', 'Split a slide into two '
'only if it does not fit on the screen as one slide.')
self.StartTimeCode = unicode(translate('OpenLP.Ui', 'Start %s'))
+ self.StopPlaySlidesInLoop = translate('OpenLP.Ui',
+ 'Stop Play Slides in Loop')
+ self.StopPlaySlidesToEnd = translate('OpenLP.Ui',
+ 'Stop Play Slides to End')
self.Theme = translate('OpenLP.Ui', 'Theme', 'Singular')
self.Themes = translate('OpenLP.Ui', 'Themes', 'Plural')
self.Tools = translate('OpenLP.Ui', 'Tools')
@@ -323,8 +329,9 @@ def shortcut_action(parent, name, shortcuts, function, icon=None, checked=None,
if checked is not None:
action.setCheckable(True)
action.setChecked(checked)
- action.setShortcuts(shortcuts)
- action.setShortcutContext(context)
+ if shortcuts:
+ action.setShortcuts(shortcuts)
+ action.setShortcutContext(context)
action_list = ActionList.get_instance()
action_list.add_action(action, category)
QtCore.QObject.connect(action, QtCore.SIGNAL(u'triggered(bool)'), function)
diff --git a/openlp/core/ui/displaytagdialog.py b/openlp/core/ui/displaytagdialog.py
index fcef1b782..65e900bbb 100644
--- a/openlp/core/ui/displaytagdialog.py
+++ b/openlp/core/ui/displaytagdialog.py
@@ -35,13 +35,10 @@ class Ui_DisplayTagDialog(object):
def setupUi(self, displayTagDialog):
displayTagDialog.setObjectName(u'displayTagDialog')
displayTagDialog.resize(725, 548)
- self.widget = QtGui.QWidget(displayTagDialog)
- self.widget.setGeometry(QtCore.QRect(10, 10, 701, 521))
- self.widget.setObjectName(u'widget')
- self.listdataGridLayout = QtGui.QGridLayout(self.widget)
- self.listdataGridLayout.setMargin(0)
+ self.listdataGridLayout = QtGui.QGridLayout(displayTagDialog)
+ self.listdataGridLayout.setMargin(8)
self.listdataGridLayout.setObjectName(u'listdataGridLayout')
- self.tagTableWidget = QtGui.QTableWidget(self.widget)
+ self.tagTableWidget = QtGui.QTableWidget(displayTagDialog)
self.tagTableWidget.setHorizontalScrollBarPolicy(
QtCore.Qt.ScrollBarAlwaysOff)
self.tagTableWidget.setEditTriggers(
@@ -55,6 +52,7 @@ class Ui_DisplayTagDialog(object):
self.tagTableWidget.setObjectName(u'tagTableWidget')
self.tagTableWidget.setColumnCount(4)
self.tagTableWidget.setRowCount(0)
+ self.tagTableWidget.horizontalHeader().setStretchLastSection(True)
item = QtGui.QTableWidgetItem()
self.tagTableWidget.setHorizontalHeaderItem(0, item)
item = QtGui.QTableWidgetItem()
@@ -69,11 +67,11 @@ class Ui_DisplayTagDialog(object):
spacerItem = QtGui.QSpacerItem(40, 20,
QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
self.horizontalLayout.addItem(spacerItem)
- self.deletePushButton = QtGui.QPushButton(self.widget)
+ self.deletePushButton = QtGui.QPushButton(displayTagDialog)
self.deletePushButton.setObjectName(u'deletePushButton')
self.horizontalLayout.addWidget(self.deletePushButton)
self.listdataGridLayout.addLayout(self.horizontalLayout, 1, 0, 1, 1)
- self.editGroupBox = QtGui.QGroupBox(self.widget)
+ self.editGroupBox = QtGui.QGroupBox(displayTagDialog)
self.editGroupBox.setObjectName(u'editGroupBox')
self.dataGridLayout = QtGui.QGridLayout(self.editGroupBox)
self.dataGridLayout.setObjectName(u'dataGridLayout')
@@ -115,9 +113,8 @@ class Ui_DisplayTagDialog(object):
self.dataGridLayout.addWidget(self.savePushButton, 4, 2, 1, 1)
self.listdataGridLayout.addWidget(self.editGroupBox, 2, 0, 1, 1)
self.buttonBox = QtGui.QDialogButtonBox(displayTagDialog)
- closeButton = QtGui.QDialogButtonBox.Close
self.buttonBox.setObjectName('displayTagDialogButtonBox')
- self.buttonBox.setStandardButtons(closeButton)
+ self.buttonBox.setStandardButtons(QtGui.QDialogButtonBox.Close)
self.listdataGridLayout.addWidget(self.buttonBox, 3, 0, 1, 1)
self.retranslateUi(displayTagDialog)
@@ -148,6 +145,5 @@ class Ui_DisplayTagDialog(object):
self.tagTableWidget.horizontalHeaderItem(3).setText(
translate('OpenLP.DisplayTagDialog', 'End HTML'))
self.tagTableWidget.setColumnWidth(0, 120)
- self.tagTableWidget.setColumnWidth(1, 40)
- self.tagTableWidget.setColumnWidth(2, 240)
- self.tagTableWidget.setColumnWidth(3, 240)
+ self.tagTableWidget.setColumnWidth(1, 80)
+ self.tagTableWidget.setColumnWidth(2, 330)
diff --git a/openlp/core/ui/displaytagform.py b/openlp/core/ui/displaytagform.py
index 8ec4d59ae..22ac38f06 100644
--- a/openlp/core/ui/displaytagform.py
+++ b/openlp/core/ui/displaytagform.py
@@ -138,6 +138,7 @@ class DisplayTagForm(QtGui.QDialog, Ui_DisplayTagDialog):
# Highlight new row
self.tagTableWidget.selectRow(self.tagTableWidget.rowCount() - 1)
self.onRowSelected()
+ self.tagTableWidget.scrollToBottom()
def onDeletePushed(self):
"""
diff --git a/openlp/core/ui/maindisplay.py b/openlp/core/ui/maindisplay.py
index b661b1e49..72a998efe 100644
--- a/openlp/core/ui/maindisplay.py
+++ b/openlp/core/ui/maindisplay.py
@@ -233,10 +233,12 @@ class MainDisplay(QtGui.QGraphicsView):
API for replacement backgrounds so Images are added directly to cache
"""
self.image_manager.add_image(name, path)
- self.image(name)
if hasattr(self, u'serviceItem'):
self.override[u'image'] = name
self.override[u'theme'] = self.serviceItem.themedata.theme_name
+ self.image(name)
+ return True
+ return False
def image(self, name):
"""
@@ -349,6 +351,9 @@ class MainDisplay(QtGui.QGraphicsView):
"""
Loads and starts a video to run with the option of sound
"""
+ # We request a background video but have no service Item
+ if isBackground and not hasattr(self, u'serviceItem'):
+ return None
if not self.mediaObject:
self.createMediaObject()
log.debug(u'video')
diff --git a/openlp/core/ui/mainwindow.py b/openlp/core/ui/mainwindow.py
index 0210da52d..c88508672 100644
--- a/openlp/core/ui/mainwindow.py
+++ b/openlp/core/ui/mainwindow.py
@@ -65,6 +65,12 @@ MEDIA_MANAGER_STYLE = """
}
"""
+PROGRESSBAR_STYLE = """
+ QProgressBar{
+ height: 10px;
+ }
+"""
+
class Ui_MainWindow(object):
def setupUi(self, mainWindow):
"""
@@ -93,6 +99,8 @@ class Ui_MainWindow(object):
self.previewController.panel.setVisible(previewVisible)
liveVisible = QtCore.QSettings().value(u'user interface/live panel',
QtCore.QVariant(True)).toBool()
+ panelLocked = QtCore.QSettings().value(u'user interface/lock panel',
+ QtCore.QVariant(False)).toBool()
self.liveController.panel.setVisible(liveVisible)
# Create menu
self.menuBar = QtGui.QMenuBar(mainWindow)
@@ -128,6 +136,7 @@ class Ui_MainWindow(object):
self.statusBar.addPermanentWidget(self.loadProgressBar)
self.loadProgressBar.hide()
self.loadProgressBar.setValue(0)
+ self.loadProgressBar.setStyleSheet(PROGRESSBAR_STYLE)
self.defaultThemeLabel = QtGui.QLabel(self.statusBar)
self.defaultThemeLabel.setObjectName(u'defaultThemeLabel')
self.statusBar.addPermanentWidget(self.defaultThemeLabel)
@@ -213,11 +222,15 @@ class Ui_MainWindow(object):
self.viewLivePanel = shortcut_action(mainWindow, u'viewLivePanel',
[QtGui.QKeySequence(u'F12')], self.setLivePanelVisibility,
checked=liveVisible, category=UiStrings().View)
- action_list.add_category(UiStrings().ViewMode, CategoryOrder.standardMenu)
+ self.lockPanel = shortcut_action(mainWindow, u'lockPanel',
+ None, self.setLockPanel,
+ checked=panelLocked, category=None)
+ action_list.add_category(UiStrings().ViewMode,
+ CategoryOrder.standardMenu)
self.modeDefaultItem = checkable_action(
mainWindow, u'modeDefaultItem', category=UiStrings().ViewMode)
self.modeSetupItem = checkable_action(
- mainWindow, u'modeLiveItem', category=UiStrings().ViewMode)
+ mainWindow, u'modeSetupItem', category=UiStrings().ViewMode)
self.modeLiveItem = checkable_action(
mainWindow, u'modeLiveItem', True, UiStrings().ViewMode)
self.modeGroup = QtGui.QActionGroup(mainWindow)
@@ -233,7 +246,8 @@ class Ui_MainWindow(object):
category=UiStrings().Tools)
self.updateThemeImages = base_action(mainWindow,
u'updateThemeImages', category=UiStrings().Tools)
- action_list.add_category(UiStrings().Settings, CategoryOrder.standardMenu)
+ action_list.add_category(UiStrings().Settings,
+ CategoryOrder.standardMenu)
self.settingsPluginListItem = shortcut_action(mainWindow,
u'settingsPluginListItem', [QtGui.QKeySequence(u'Alt+F7')],
self.onPluginItemClicked, u':/system/settings_plugin_list.png',
@@ -262,19 +276,22 @@ class Ui_MainWindow(object):
u'settingsConfigureItem', u':/system/system_settings.png',
category=UiStrings().Settings)
action_list.add_category(UiStrings().Help, CategoryOrder.standardMenu)
- self.helpDocumentationItem = icon_action(mainWindow,
- u'helpDocumentationItem', u':/system/system_help_contents.png',
- category=None)#UiStrings().Help)
- self.helpDocumentationItem.setEnabled(False)
- self.helpAboutItem = shortcut_action(mainWindow, u'helpAboutItem',
- [QtGui.QKeySequence(u'Ctrl+F1')], self.onHelpAboutItemClicked,
+ self.aboutItem = shortcut_action(mainWindow, u'aboutItem',
+ [QtGui.QKeySequence(u'Ctrl+F1')], self.onAboutItemClicked,
u':/system/system_about.png', category=UiStrings().Help)
- self.helpOnlineHelpItem = shortcut_action(
- mainWindow, u'helpOnlineHelpItem', [QtGui.QKeySequence(u'F1')],
- self.onHelpOnlineHelpClicked, u':/system/system_online_help.png',
- category=UiStrings().Help)
- self.helpWebSiteItem = base_action(
- mainWindow, u'helpWebSiteItem', category=UiStrings().Help)
+ if os.name == u'nt':
+ self.localHelpFile = os.path.join(
+ AppLocation.get_directory(AppLocation.AppDir), 'OpenLP.chm')
+ self.offlineHelpItem = shortcut_action(
+ mainWindow, u'offlineHelpItem', [QtGui.QKeySequence(u'F1')],
+ self.onOfflineHelpClicked,
+ u':/system/system_help_contents.png', category=UiStrings().Help)
+ self.onlineHelpItem = shortcut_action(
+ mainWindow, u'onlineHelpItem',
+ [QtGui.QKeySequence(u'Alt+F1')], self.onOnlineHelpClicked,
+ u':/system/system_online_help.png', category=UiStrings().Help)
+ self.webSiteItem = base_action(
+ mainWindow, u'webSiteItem', category=UiStrings().Help)
add_actions(self.fileImportMenu,
(self.importThemeItem, self.importLanguageItem))
add_actions(self.fileExportMenu,
@@ -288,7 +305,7 @@ class Ui_MainWindow(object):
add_actions(self.viewMenu, (self.viewModeMenu.menuAction(),
None, self.viewMediaManagerItem, self.viewServiceManagerItem,
self.viewThemeManagerItem, None, self.viewPreviewPanel,
- self.viewLivePanel))
+ self.viewLivePanel, None, self.lockPanel))
# i18n add Language Actions
add_actions(self.settingsLanguageMenu, (self.autoLanguageItem, None))
add_actions(self.settingsLanguageMenu, self.languageGroup.actions())
@@ -307,9 +324,13 @@ class Ui_MainWindow(object):
add_actions(self.toolsMenu, (self.toolsAddToolItem, None))
add_actions(self.toolsMenu, (self.toolsOpenDataFolder, None))
add_actions(self.toolsMenu, [self.updateThemeImages])
- add_actions(self.helpMenu, (self.helpDocumentationItem,
- self.helpOnlineHelpItem, None, self.helpWebSiteItem,
- self.helpAboutItem))
+ if os.name == u'nt':
+ add_actions(self.helpMenu, (self.offlineHelpItem,
+ self.onlineHelpItem, None, self.webSiteItem,
+ self.aboutItem))
+ else:
+ add_actions(self.helpMenu, (self.onlineHelpItem, None,
+ self.webSiteItem, self.aboutItem))
add_actions(self.menuBar, (self.fileMenu.menuAction(),
self.viewMenu.menuAction(), self.toolsMenu.menuAction(),
self.settingsMenu.menuAction(), self.helpMenu.menuAction()))
@@ -324,7 +345,7 @@ class Ui_MainWindow(object):
self.toolsAddToolItem.setVisible(False)
self.importLanguageItem.setVisible(False)
self.exportLanguageItem.setVisible(False)
- self.helpDocumentationItem.setVisible(False)
+ self.setLockPanel(panelLocked)
def retranslateUi(self, mainWindow):
"""
@@ -414,20 +435,25 @@ class Ui_MainWindow(object):
translate('OpenLP.MainWindow', '&Live Panel'))
self.viewLivePanel.setToolTip(
translate('OpenLP.MainWindow', 'Toggle Live Panel'))
+ self.lockPanel.setText(
+ translate('OpenLP.MainWindow', 'L&ock Panels'))
+ self.lockPanel.setStatusTip(
+ translate('OpenLP.MainWindow', 'Prevent the panels being moved.'))
self.viewLivePanel.setStatusTip(translate('OpenLP.MainWindow',
'Toggle the visibility of the live panel.'))
self.settingsPluginListItem.setText(translate('OpenLP.MainWindow',
'&Plugin List'))
self.settingsPluginListItem.setStatusTip(
translate('OpenLP.MainWindow', 'List the Plugins'))
- self.helpDocumentationItem.setText(
- translate('OpenLP.MainWindow', '&User Guide'))
- self.helpAboutItem.setText(translate('OpenLP.MainWindow', '&About'))
- self.helpAboutItem.setStatusTip(
+ self.aboutItem.setText(translate('OpenLP.MainWindow', '&About'))
+ self.aboutItem.setStatusTip(
translate('OpenLP.MainWindow', 'More information about OpenLP'))
- self.helpOnlineHelpItem.setText(
+ if os.name == u'nt':
+ self.offlineHelpItem.setText(
+ translate('OpenLP.MainWindow', '&User Guide'))
+ self.onlineHelpItem.setText(
translate('OpenLP.MainWindow', '&Online Help'))
- self.helpWebSiteItem.setText(
+ self.webSiteItem.setText(
translate('OpenLP.MainWindow', '&Web Site'))
for item in self.languageGroup.actions():
item.setText(item.objectName())
@@ -516,7 +542,7 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
QtCore.QObject.connect(self.themeManagerDock,
QtCore.SIGNAL(u'visibilityChanged(bool)'),
self.viewThemeManagerItem.setChecked)
- QtCore.QObject.connect(self.helpWebSiteItem,
+ QtCore.QObject.connect(self.webSiteItem,
QtCore.SIGNAL(u'triggered()'), self.onHelpWebSiteClicked)
QtCore.QObject.connect(self.toolsOpenDataFolder,
QtCore.SIGNAL(u'triggered()'), self.onToolsOpenDataFolderClicked)
@@ -653,7 +679,7 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
QtCore.QVariant(False)).toBool():
self.serviceManagerContents.loadLastFile()
view_mode = QtCore.QSettings().value(u'%s/view mode' % \
- self.generalSettingsSection, u'default')
+ self.generalSettingsSection, u'default').toString()
if view_mode == u'default':
self.modeDefaultItem.setChecked(True)
elif view_mode == u'setup':
@@ -723,14 +749,20 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
import webbrowser
webbrowser.open_new(u'http://openlp.org/')
- def onHelpOnlineHelpClicked(self):
+ def onOfflineHelpClicked(self):
+ """
+ Load the local OpenLP help file
+ """
+ os.startfile(self.localHelpFile)
+
+ def onOnlineHelpClicked(self):
"""
Load the online OpenLP manual
"""
import webbrowser
webbrowser.open_new(u'http://manual.openlp.org/')
- def onHelpAboutItemClicked(self):
+ def onAboutItemClicked(self):
"""
Show the About form
"""
@@ -936,7 +968,8 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
self.mediaManagerDock.setVisible(not self.mediaManagerDock.isVisible())
def toggleServiceManager(self):
- self.serviceManagerDock.setVisible(not self.serviceManagerDock.isVisible())
+ self.serviceManagerDock.setVisible(
+ not self.serviceManagerDock.isVisible())
def toggleThemeManager(self):
self.themeManagerDock.setVisible(not self.themeManagerDock.isVisible())
@@ -956,6 +989,37 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
QtCore.QVariant(visible))
self.viewPreviewPanel.setChecked(visible)
+ def setLockPanel(self, lock):
+ """
+ Sets the ability to stop the toolbars being changed.
+ """
+ if lock:
+ self.themeManagerDock.setFeatures(
+ QtGui.QDockWidget.NoDockWidgetFeatures)
+ self.serviceManagerDock.setFeatures(
+ QtGui.QDockWidget.NoDockWidgetFeatures)
+ self.mediaManagerDock.setFeatures(
+ QtGui.QDockWidget.NoDockWidgetFeatures)
+ self.viewMediaManagerItem.setEnabled(False)
+ self.viewServiceManagerItem.setEnabled(False)
+ self.viewThemeManagerItem.setEnabled(False)
+ self.viewPreviewPanel.setEnabled(False)
+ self.viewLivePanel.setEnabled(False)
+ else:
+ self.themeManagerDock.setFeatures(
+ QtGui.QDockWidget.AllDockWidgetFeatures)
+ self.serviceManagerDock.setFeatures(
+ QtGui.QDockWidget.AllDockWidgetFeatures)
+ self.mediaManagerDock.setFeatures(
+ QtGui.QDockWidget.AllDockWidgetFeatures)
+ self.viewMediaManagerItem.setEnabled(True)
+ self.viewServiceManagerItem.setEnabled(True)
+ self.viewThemeManagerItem.setEnabled(True)
+ self.viewPreviewPanel.setEnabled(True)
+ self.viewLivePanel.setEnabled(True)
+ QtCore.QSettings().setValue(u'user interface/lock panel',
+ QtCore.QVariant(lock))
+
def setLivePanelVisibility(self, visible):
"""
Sets the visibility of the live panel including saving the setting and
@@ -986,6 +1050,13 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
self.restoreGeometry(
settings.value(u'main window geometry').toByteArray())
self.restoreState(settings.value(u'main window state').toByteArray())
+ self.liveController.splitter.restoreState(
+ settings.value(u'live splitter geometry').toByteArray())
+ self.previewController.splitter.restoreState(
+ settings.value(u'preview splitter geometry').toByteArray())
+ self.controlSplitter.restoreState(
+ settings.value(u'mainwindow splitter geometry').toByteArray())
+
settings.endGroup()
def saveSettings(self):
@@ -1006,6 +1077,12 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
QtCore.QVariant(self.saveState()))
settings.setValue(u'main window geometry',
QtCore.QVariant(self.saveGeometry()))
+ settings.setValue(u'live splitter geometry',
+ QtCore.QVariant(self.liveController.splitter.saveState()))
+ settings.setValue(u'preview splitter geometry',
+ QtCore.QVariant(self.previewController.splitter.saveState()))
+ settings.setValue(u'mainwindow splitter geometry',
+ QtCore.QVariant(self.controlSplitter.saveState()))
settings.endGroup()
def updateFileMenu(self):
diff --git a/openlp/core/ui/printservicedialog.py b/openlp/core/ui/printservicedialog.py
index 1d80eaa07..b0065df99 100644
--- a/openlp/core/ui/printservicedialog.py
+++ b/openlp/core/ui/printservicedialog.py
@@ -41,11 +41,6 @@ class ZoomSize(object):
Fifty = 4
TwentyFive = 5
- Sizes = [
- translate('OpenLP.PrintServiceDialog', 'Fit Page'),
- translate('OpenLP.PrintServiceDialog', 'Fit Width'),
- u'100%', u'75%', u'50%', u'25%']
-
class Ui_PrintServiceDialog(object):
def setupUi(self, printServiceDialog):
@@ -59,18 +54,14 @@ class Ui_PrintServiceDialog(object):
self.toolbar.setIconSize(QtCore.QSize(22, 22))
self.toolbar.setToolButtonStyle(QtCore.Qt.ToolButtonTextBesideIcon)
self.printButton = self.toolbar.addAction(
- build_icon(u':/general/general_print.png'), 'Print')
+ build_icon(u':/general/general_print.png'),
+ translate('OpenLP.PrintServiceForm', 'Print'))
self.optionsButton = QtGui.QToolButton(self.toolbar)
- self.optionsButton.setText(translate('OpenLP.PrintServiceForm',
- 'Options'))
self.optionsButton.setToolButtonStyle(
QtCore.Qt.ToolButtonTextBesideIcon)
self.optionsButton.setIcon(build_icon(u':/system/system_configure.png'))
self.optionsButton.setCheckable(True)
self.toolbar.addWidget(self.optionsButton)
- self.closeButton = self.toolbar.addAction(
- build_icon(u':/system/system_close.png'),
- translate('OpenLP.PrintServiceForm', 'Close'))
self.toolbar.addSeparator()
self.plainCopy = self.toolbar.addAction(
build_icon(u':/system/system_edit_copy.png'),
@@ -81,24 +72,18 @@ class Ui_PrintServiceDialog(object):
self.toolbar.addSeparator()
self.zoomInButton = QtGui.QToolButton(self.toolbar)
self.zoomInButton.setIcon(build_icon(u':/general/general_zoom_in.png'))
- self.zoomInButton.setToolTip(translate('OpenLP.PrintServiceForm',
- 'Zoom In'))
self.zoomInButton.setObjectName(u'zoomInButton')
self.zoomInButton.setIconSize(QtCore.QSize(22, 22))
self.toolbar.addWidget(self.zoomInButton)
self.zoomOutButton = QtGui.QToolButton(self.toolbar)
self.zoomOutButton.setIcon(
build_icon(u':/general/general_zoom_out.png'))
- self.zoomOutButton.setToolTip(translate('OpenLP.PrintServiceForm',
- 'Zoom Out'))
self.zoomOutButton.setObjectName(u'zoomOutButton')
self.zoomOutButton.setIconSize(QtCore.QSize(22, 22))
self.toolbar.addWidget(self.zoomOutButton)
self.zoomOriginalButton = QtGui.QToolButton(self.toolbar)
self.zoomOriginalButton.setIcon(
build_icon(u':/general/general_zoom_original.png'))
- self.zoomOriginalButton.setToolTip(translate('OpenLP.PrintServiceForm',
- 'Zoom Original'))
self.zoomOriginalButton.setObjectName(u'zoomOriginalButton')
self.zoomOriginalButton.setIconSize(QtCore.QSize(22, 22))
self.toolbar.addWidget(self.zoomOriginalButton)
@@ -116,20 +101,17 @@ class Ui_PrintServiceDialog(object):
self.optionsLayout.setContentsMargins(8, 8, 8, 8)
self.titleLabel = QtGui.QLabel(self.optionsWidget)
self.titleLabel.setObjectName(u'titleLabel')
- self.titleLabel.setText(u'Title:')
self.optionsLayout.addWidget(self.titleLabel)
self.titleLineEdit = QtGui.QLineEdit(self.optionsWidget)
self.titleLineEdit.setObjectName(u'titleLineEdit')
self.optionsLayout.addWidget(self.titleLineEdit)
self.footerLabel = QtGui.QLabel(self.optionsWidget)
self.footerLabel.setObjectName(u'footerLabel')
- self.footerLabel.setText(u'Custom Footer Text:')
self.optionsLayout.addWidget(self.footerLabel)
self.footerTextEdit = SpellTextEdit(self.optionsWidget)
self.footerTextEdit.setObjectName(u'footerTextEdit')
self.optionsLayout.addWidget(self.footerTextEdit)
- self.optionsGroupBox = QtGui.QGroupBox(
- translate('OpenLP.PrintServiceForm','Other Options'))
+ self.optionsGroupBox = QtGui.QGroupBox()
self.groupLayout = QtGui.QVBoxLayout()
self.slideTextCheckBox = QtGui.QCheckBox()
self.groupLayout.addWidget(self.slideTextCheckBox)
@@ -150,6 +132,19 @@ class Ui_PrintServiceDialog(object):
def retranslateUi(self, printServiceDialog):
printServiceDialog.setWindowTitle(UiStrings().PrintService)
+ self.zoomOutButton.setToolTip(translate('OpenLP.PrintServiceForm',
+ 'Zoom Out'))
+ self.zoomOriginalButton.setToolTip(translate('OpenLP.PrintServiceForm',
+ 'Zoom Original'))
+ self.zoomInButton.setToolTip(translate('OpenLP.PrintServiceForm',
+ 'Zoom In'))
+ self.optionsButton.setText(translate('OpenLP.PrintServiceForm',
+ 'Options'))
+ self.titleLabel.setText(translate('OpenLP.PrintServiceForm', 'Title:'))
+ self.footerLabel.setText(translate('OpenLP.PrintServiceForm',
+ 'Custom Footer Text:'))
+ self.optionsGroupBox.setTitle(
+ translate('OpenLP.PrintServiceForm','Other Options'))
self.slideTextCheckBox.setText(translate('OpenLP.PrintServiceForm',
'Include slide text if available'))
self.pageBreakAfterText.setText(translate('OpenLP.PrintServiceForm',
@@ -160,10 +155,13 @@ class Ui_PrintServiceDialog(object):
'Include play length of media items'))
self.titleLineEdit.setText(translate('OpenLP.PrintServiceForm',
'Service Sheet'))
- self.zoomComboBox.addItem(ZoomSize.Sizes[ZoomSize.Page])
- self.zoomComboBox.addItem(ZoomSize.Sizes[ZoomSize.Width])
- self.zoomComboBox.addItem(ZoomSize.Sizes[ZoomSize.OneHundred])
- self.zoomComboBox.addItem(ZoomSize.Sizes[ZoomSize.SeventyFive])
- self.zoomComboBox.addItem(ZoomSize.Sizes[ZoomSize.Fifty])
- self.zoomComboBox.addItem(ZoomSize.Sizes[ZoomSize.TwentyFive])
+ # Do not change the order.
+ self.zoomComboBox.addItems([
+ translate('OpenLP.PrintServiceDialog', 'Fit Page'),
+ translate('OpenLP.PrintServiceDialog', 'Fit Width'),
+ u'100%',
+ u'75%',
+ u'50%',
+ u'25%']
+ )
diff --git a/openlp/core/ui/printserviceform.py b/openlp/core/ui/printserviceform.py
index 0c8d53466..2bbf2ab56 100644
--- a/openlp/core/ui/printserviceform.py
+++ b/openlp/core/ui/printserviceform.py
@@ -137,8 +137,6 @@ class PrintServiceForm(QtGui.QDialog, Ui_PrintServiceDialog):
# Signals
QtCore.QObject.connect(self.printButton,
QtCore.SIGNAL(u'triggered()'), self.printServiceOrder)
- QtCore.QObject.connect(self.closeButton,
- QtCore.SIGNAL(u'triggered()'), self.accept)
QtCore.QObject.connect(self.zoomOutButton,
QtCore.SIGNAL(u'clicked()'), self.zoomOut)
QtCore.QObject.connect(self.zoomInButton,
@@ -326,8 +324,7 @@ class PrintServiceForm(QtGui.QDialog, Ui_PrintServiceDialog):
"""
Copies the display text to the clipboard as plain text
"""
- self.mainWindow.clipboard.setText(
- self.document.toPlainText())
+ self.mainWindow.clipboard.setText(self.document.toPlainText())
def copyHtmlText(self):
"""
diff --git a/openlp/core/ui/serviceitemeditdialog.py b/openlp/core/ui/serviceitemeditdialog.py
index a00feafc7..d821430b2 100644
--- a/openlp/core/ui/serviceitemeditdialog.py
+++ b/openlp/core/ui/serviceitemeditdialog.py
@@ -35,6 +35,8 @@ class Ui_ServiceItemEditDialog(object):
def setupUi(self, serviceItemEditDialog):
serviceItemEditDialog.setObjectName(u'serviceItemEditDialog')
self.dialogLayout = QtGui.QGridLayout(serviceItemEditDialog)
+ self.dialogLayout.setContentsMargins(8, 8, 8, 8)
+ self.dialogLayout.setSpacing(8)
self.dialogLayout.setObjectName(u'dialogLayout')
self.listWidget = QtGui.QListWidget(serviceItemEditDialog)
self.listWidget.setAlternatingRowColors(True)
diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py
index 8fc796ea4..f6c069525 100644
--- a/openlp/core/ui/servicemanager.py
+++ b/openlp/core/ui/servicemanager.py
@@ -48,18 +48,18 @@ class ServiceManagerList(QtGui.QTreeWidget):
"""
Set up key bindings and mouse behaviour for the service list
"""
- def __init__(self, mainwindow, parent=None, name=None):
+ def __init__(self, serviceManager, parent=None, name=None):
QtGui.QTreeWidget.__init__(self, parent)
- self.mainwindow = mainwindow
+ self.serviceManager = serviceManager
def keyPressEvent(self, event):
if isinstance(event, QtGui.QKeyEvent):
# here accept the event and do something
if event.key() == QtCore.Qt.Key_Up:
- self.mainwindow.onMoveSelectionUp()
+ self.serviceManager.onMoveSelectionUp()
event.accept()
elif event.key() == QtCore.Qt.Key_Down:
- self.mainwindow.onMoveSelectionDown()
+ self.serviceManager.onMoveSelectionDown()
event.accept()
event.ignore()
else:
@@ -688,7 +688,7 @@ class ServiceManager(QtGui.QWidget):
QtGui.QAction, serviceItem[u'service_item'].theme)
if themeAction is not None:
themeAction.setChecked(True)
- action = self.menu.exec_(self.serviceManagerList.mapToGlobal(point))
+ self.menu.exec_(self.serviceManagerList.mapToGlobal(point))
def onServiceItemNoteForm(self):
item = self.findServiceItem()[0]
@@ -832,7 +832,7 @@ class ServiceManager(QtGui.QWidget):
"""
for item in self.serviceItems:
item[u'expanded'] = False
- self.regenerateServiceItems()
+ self.serviceManagerList.collapseAll()
def collapsed(self, item):
"""
@@ -848,7 +848,7 @@ class ServiceManager(QtGui.QWidget):
"""
for item in self.serviceItems:
item[u'expanded'] = True
- self.regenerateServiceItems()
+ self.serviceManagerList.expandAll()
def expanded(self, item):
"""
@@ -856,7 +856,7 @@ class ServiceManager(QtGui.QWidget):
correct state.
"""
pos = item.data(0, QtCore.Qt.UserRole).toInt()[0]
- self.serviceItems[pos -1 ][u'expanded'] = True
+ self.serviceItems[pos - 1][u'expanded'] = True
def onServiceTop(self):
"""
@@ -956,7 +956,19 @@ class ServiceManager(QtGui.QWidget):
treewidgetitem.setIcon(0,
build_icon(u':/general/general_delete.png'))
treewidgetitem.setText(0, serviceitem.get_display_title())
- treewidgetitem.setToolTip(0, serviceitem.notes)
+ tips = []
+ if serviceitem.theme and serviceitem.theme != -1:
+ tips.append(u'%s: %s' %
+ (unicode(translate('OpenLP.ServiceManager', 'Slide theme')),
+ serviceitem.theme))
+ if serviceitem.notes:
+ tips.append(u'%s: %s' %
+ (unicode(translate('OpenLP.ServiceManager', 'Notes')),
+ unicode(serviceitem.notes)))
+ if item[u'service_item'] \
+ .is_capable(ItemCapabilities.AllowsVariableStartTime):
+ tips.append(item[u'service_item'].get_media_time())
+ treewidgetitem.setToolTip(0, u'
'.join(tips))
treewidgetitem.setData(0, QtCore.Qt.UserRole,
QtCore.QVariant(item[u'order']))
treewidgetitem.setSelected(item[u'selected'])
@@ -966,11 +978,6 @@ class ServiceManager(QtGui.QWidget):
text = frame[u'title'].replace(u'\n', u' ')
child.setText(0, text[:40])
child.setData(0, QtCore.Qt.UserRole, QtCore.QVariant(count))
- if item[u'service_item'] \
- .is_capable(ItemCapabilities.AllowsVariableStartTime):
- tip = item[u'service_item'].get_media_time()
- if tip:
- child.setToolTip(0, tip)
if serviceItem == itemcount:
if item[u'expanded'] and serviceItemChild == count:
self.serviceManagerList.setCurrentItem(child)
@@ -1338,7 +1345,7 @@ class ServiceManager(QtGui.QWidget):
if not theme:
theme = None
item = self.findServiceItem()[0]
- self.serviceItems[item][u'service_item'].theme = theme
+ self.serviceItems[item][u'service_item'].update_theme(theme)
self.regenerateServiceItems()
def _getParentItemData(self, item):
diff --git a/openlp/core/ui/servicenoteform.py b/openlp/core/ui/servicenoteform.py
index d361c567e..3bc55e242 100644
--- a/openlp/core/ui/servicenoteform.py
+++ b/openlp/core/ui/servicenoteform.py
@@ -49,6 +49,8 @@ class ServiceNoteForm(QtGui.QDialog):
def setupUi(self):
self.setObjectName(u'serviceNoteEdit')
self.dialogLayout = QtGui.QVBoxLayout(self)
+ self.dialogLayout.setContentsMargins(8, 8, 8, 8)
+ self.dialogLayout.setSpacing(8)
self.dialogLayout.setObjectName(u'verticalLayout')
self.textEdit = QtGui.QTextEdit(self)
self.textEdit.setObjectName(u'textEdit')
diff --git a/openlp/core/ui/shortcutlistform.py b/openlp/core/ui/shortcutlistform.py
index 0be7fc85d..6bdcc46bc 100644
--- a/openlp/core/ui/shortcutlistform.py
+++ b/openlp/core/ui/shortcutlistform.py
@@ -98,6 +98,9 @@ class ShortcutListForm(QtGui.QDialog, Ui_ShortcutListDialog):
if event.modifiers() & QtCore.Qt.ShiftModifier == \
QtCore.Qt.ShiftModifier:
key_string = u'Shift+' + key_string
+ if event.modifiers() & QtCore.Qt.MetaModifier == \
+ QtCore.Qt.MetaModifier:
+ key_string = u'Meta+' + key_string
key_sequence = QtGui.QKeySequence(key_string)
if self._validiate_shortcut(self._currentItemAction(), key_sequence):
if self.primaryPushButton.isChecked():
diff --git a/openlp/core/ui/slidecontroller.py b/openlp/core/ui/slidecontroller.py
index 30341b9dd..9f9541ba4 100644
--- a/openlp/core/ui/slidecontroller.py
+++ b/openlp/core/ui/slidecontroller.py
@@ -27,12 +27,14 @@
import logging
import os
+import time
+import copy
from PyQt4 import QtCore, QtGui
from PyQt4.phonon import Phonon
from openlp.core.lib import OpenLPToolbar, Receiver, resize_image, \
- ItemCapabilities, translate
+ ItemCapabilities, translate, build_icon
from openlp.core.lib.ui import UiStrings, shortcut_action
from openlp.core.ui import HideMode, MainDisplay, ScreenList
from openlp.core.utils.actions import ActionList, CategoryOrder
@@ -193,13 +195,11 @@ class SlideController(QtGui.QWidget):
self.playSlidesLoop = shortcut_action(self.playSlidesMenu,
u'playSlidesLoop', [], self.onPlaySlidesLoop,
u':/media/media_time.png', False, UiStrings().LiveToolbar)
- self.playSlidesLoop.setText(
- translate('OpenLP.SlideController', 'Play Slides in Loop'))
+ self.playSlidesLoop.setText(UiStrings().PlaySlidesInLoop)
self.playSlidesOnce = shortcut_action(self.playSlidesMenu,
u'playSlidesOnce', [], self.onPlaySlidesOnce,
u':/media/media_time.png', False, UiStrings().LiveToolbar)
- self.playSlidesOnce.setText(
- translate('OpenLP.SlideController', 'Play Slides to End'))
+ self.playSlidesOnce.setText(UiStrings().PlaySlidesToEnd)
if QtCore.QSettings().value(self.parent().generalSettingsSection +
u'/enable slide loop', QtCore.QVariant(True)).toBool():
self.playSlidesMenu.setDefaultAction(self.playSlidesLoop)
@@ -412,9 +412,11 @@ class SlideController(QtGui.QWidget):
self.display.videoStop()
def servicePrevious(self):
+ time.sleep(0.1)
Receiver.send_message('servicemanager_previous_item')
def serviceNext(self):
+ time.sleep(0.1)
Receiver.send_message('servicemanager_next_item')
def screenSizeChanged(self):
@@ -500,7 +502,9 @@ class SlideController(QtGui.QWidget):
"""
Allows the live toolbar to be customised
"""
- self.toolbar.setVisible(True)
+ # Work-around for OS X, hide and then show the toolbar
+ # See bug #791050
+ self.toolbar.hide()
self.mediabar.setVisible(False)
self.toolbar.makeWidgetsInvisible([u'Song Menu'])
self.toolbar.makeWidgetsInvisible(self.loopList)
@@ -515,12 +519,18 @@ class SlideController(QtGui.QWidget):
if item.is_media():
self.toolbar.setVisible(False)
self.mediabar.setVisible(True)
+ else:
+ # Work-around for OS X, hide and then show the toolbar
+ # See bug #791050
+ self.toolbar.show()
def enablePreviewToolBar(self, item):
"""
Allows the Preview toolbar to be customised
"""
- self.toolbar.setVisible(True)
+ # Work-around for OS X, hide and then show the toolbar
+ # See bug #791050
+ self.toolbar.hide()
self.mediabar.setVisible(False)
self.toolbar.makeWidgetsInvisible(self.songEditList)
if item.is_capable(ItemCapabilities.AllowsEdit) and item.from_plugin:
@@ -529,6 +539,10 @@ class SlideController(QtGui.QWidget):
self.toolbar.setVisible(False)
self.mediabar.setVisible(True)
self.volumeSlider.setAudioOutput(self.audio)
+ if not item.is_media():
+ # Work-around for OS X, hide and then show the toolbar
+ # See bug #791050
+ self.toolbar.show()
def refreshServiceItem(self):
"""
@@ -585,7 +599,8 @@ class SlideController(QtGui.QWidget):
log.debug(u'processManagerItem live = %s' % self.isLive)
self.onStopLoop()
old_item = self.serviceItem
- self.serviceItem = serviceItem
+ # take a copy not a link to the servicemeanager copy.
+ self.serviceItem = copy.copy(serviceItem)
if old_item and self.isLive and old_item.is_capable(
ItemCapabilities.ProvidesOwnDisplay):
self._resetBlank()
@@ -1044,6 +1059,14 @@ class SlideController(QtGui.QWidget):
else:
self.playSlidesLoop.setChecked(checked)
log.debug(u'onPlaySlidesLoop %s' % checked)
+ if checked:
+ self.playSlidesLoop.setIcon(build_icon(u':/media/media_stop.png'))
+ self.playSlidesLoop.setText(UiStrings().StopPlaySlidesInLoop)
+ self.playSlidesOnce.setIcon(build_icon(u':/media/media_time.png'))
+ self.playSlidesOnce.setText(UiStrings().PlaySlidesToEnd)
+ else:
+ self.playSlidesLoop.setIcon(build_icon(u':/media/media_time.png'))
+ self.playSlidesLoop.setText(UiStrings().PlaySlidesInLoop)
self.playSlidesMenu.setDefaultAction(self.playSlidesLoop)
self.playSlidesOnce.setChecked(False)
self.onToggleLoop()
@@ -1057,6 +1080,14 @@ class SlideController(QtGui.QWidget):
else:
self.playSlidesOnce.setChecked(checked)
log.debug(u'onPlaySlidesOnce %s' % checked)
+ if checked:
+ self.playSlidesOnce.setIcon(build_icon(u':/media/media_stop.png'))
+ self.playSlidesOnce.setText(UiStrings().StopPlaySlidesToEnd)
+ self.playSlidesLoop.setIcon(build_icon(u':/media/media_time.png'))
+ self.playSlidesLoop.setText(UiStrings().PlaySlidesInLoop)
+ else:
+ self.playSlidesOnce.setIcon(build_icon(u':/media/media_time'))
+ self.playSlidesOnce.setText(UiStrings().PlaySlidesToEnd)
self.playSlidesMenu.setDefaultAction(self.playSlidesOnce)
self.playSlidesLoop.setChecked(False)
self.onToggleLoop()
diff --git a/openlp/core/ui/thememanager.py b/openlp/core/ui/thememanager.py
index 3a309cfd0..69c229532 100644
--- a/openlp/core/ui/thememanager.py
+++ b/openlp/core/ui/thememanager.py
@@ -39,7 +39,8 @@ from openlp.core.lib import OpenLPToolbar, get_text_file_string, build_icon, \
check_directory_exists
from openlp.core.lib.theme import ThemeXML, BackgroundType, VerticalType, \
BackgroundGradientType
-from openlp.core.lib.ui import UiStrings, critical_error_message_box
+from openlp.core.lib.ui import UiStrings, critical_error_message_box, \
+ context_menu_action, context_menu_separator
from openlp.core.theme import Theme
from openlp.core.ui import FileRenameForm, ThemeForm
from openlp.core.utils import AppLocation, delete_file, file_is_unicode, \
@@ -104,25 +105,29 @@ class ThemeManager(QtGui.QWidget):
self.contextMenu)
# build the context menu
self.menu = QtGui.QMenu()
- self.editAction = self.menu.addAction(
- translate('OpenLP.ThemeManager', '&Edit Theme'))
- self.editAction.setIcon(build_icon(u':/themes/theme_edit.png'))
- self.copyAction = self.menu.addAction(
- translate('OpenLP.ThemeManager', '&Copy Theme'))
- self.copyAction.setIcon(build_icon(u':/themes/theme_edit.png'))
- self.renameAction = self.menu.addAction(
- translate('OpenLP.ThemeManager', '&Rename Theme'))
- self.renameAction.setIcon(build_icon(u':/themes/theme_edit.png'))
- self.deleteAction = self.menu.addAction(
- translate('OpenLP.ThemeManager', '&Delete Theme'))
- self.deleteAction.setIcon(build_icon(u':/general/general_delete.png'))
- self.separator = self.menu.addSeparator()
- self.globalAction = self.menu.addAction(
- translate('OpenLP.ThemeManager', 'Set As &Global Default'))
- self.globalAction.setIcon(build_icon(u':/general/general_export.png'))
- self.exportAction = self.menu.addAction(
- translate('OpenLP.ThemeManager', '&Export Theme'))
- self.exportAction.setIcon(build_icon(u':/general/general_export.png'))
+ self.editAction = context_menu_action(
+ self.menu, u':/themes/theme_edit.png',
+ translate('OpenLP.ThemeManager', '&Edit Theme'), self.onEditTheme)
+ self.copyAction = context_menu_action(
+ self.menu, u':/themes/theme_edit.png',
+ translate('OpenLP.ThemeManager', '&Copy Theme'), self.onCopyTheme)
+ self.renameAction = context_menu_action(
+ self.menu, u':/themes/theme_edit.png',
+ translate('OpenLP.ThemeManager', '&Rename Theme'),
+ self.onRenameTheme)
+ self.deleteAction = context_menu_action(
+ self.menu, u':/general/general_delete.png',
+ translate('OpenLP.ThemeManager', '&Delete Theme'),
+ self.onDeleteTheme)
+ context_menu_separator(self.menu)
+ self.globalAction = context_menu_action(
+ self.menu, u':/general/general_export.png',
+ translate('OpenLP.ThemeManager', 'Set As &Global Default'),
+ self.changeGlobalFromScreen)
+ self.exportAction = context_menu_action(
+ self.menu, u':/general/general_export.png',
+ translate('OpenLP.ThemeManager', '&Export Theme'),
+ self.onExportTheme)
# Signals
QtCore.QObject.connect(self.themeListWidget,
QtCore.SIGNAL(u'doubleClicked(QModelIndex)'),
@@ -198,19 +203,7 @@ class ThemeManager(QtGui.QWidget):
self.deleteAction.setVisible(True)
self.renameAction.setVisible(True)
self.globalAction.setVisible(True)
- action = self.menu.exec_(self.themeListWidget.mapToGlobal(point))
- if action == self.editAction:
- self.onEditTheme()
- if action == self.copyAction:
- self.onCopyTheme()
- if action == self.renameAction:
- self.onRenameTheme()
- if action == self.deleteAction:
- self.onDeleteTheme()
- if action == self.globalAction:
- self.changeGlobalFromScreen()
- if action == self.exportAction:
- self.onExportTheme()
+ self.menu.exec_(self.themeListWidget.mapToGlobal(point))
def changeGlobalFromTab(self, themeName):
"""
@@ -299,7 +292,9 @@ class ThemeManager(QtGui.QWidget):
"""
item = self.themeListWidget.currentItem()
oldThemeName = unicode(item.data(QtCore.Qt.UserRole).toString())
- self.fileRenameForm.fileNameEdit.setText(oldThemeName)
+ self.fileRenameForm.fileNameEdit.setText(
+ unicode(translate('OpenLP.ThemeManager',
+ 'Copy of %s','Copy of ')) % oldThemeName)
if self.fileRenameForm.exec_(True):
newThemeName = unicode(self.fileRenameForm.fileNameEdit.text())
if self.checkIfThemeExists(newThemeName):
diff --git a/openlp/plugins/bibles/lib/http.py b/openlp/plugins/bibles/lib/http.py
index 03b094e82..28ceaad68 100644
--- a/openlp/plugins/bibles/lib/http.py
+++ b/openlp/plugins/bibles/lib/http.py
@@ -147,7 +147,10 @@ class BGExtract(object):
send_error_message(u'download')
return None
page_source = page.read()
- page_source = unicode(page_source, 'utf8')
+ try:
+ page_source = unicode(page_source, u'utf8')
+ except UnicodeDecodeError:
+ page_source = unicode(page_source, u'cp1251')
page_source_temp = re.search(u'', page_source, re.DOTALL)
if page_source_temp:
diff --git a/openlp/plugins/bibles/lib/mediaitem.py b/openlp/plugins/bibles/lib/mediaitem.py
index cb8273327..0734df818 100644
--- a/openlp/plugins/bibles/lib/mediaitem.py
+++ b/openlp/plugins/bibles/lib/mediaitem.py
@@ -34,7 +34,8 @@ from openlp.core.lib import MediaManagerItem, Receiver, ItemCapabilities, \
translate
from openlp.core.lib.searchedit import SearchEdit
from openlp.core.lib.ui import UiStrings, add_widget_completer, \
- media_item_combo_box, critical_error_message_box, find_and_set_in_combo_box
+ media_item_combo_box, critical_error_message_box, \
+ find_and_set_in_combo_box, build_icon
from openlp.plugins.bibles.forms import BibleImportForm
from openlp.plugins.bibles.lib import LayoutStyle, DisplayStyle, \
VerseReferenceList, get_reference_match
@@ -57,8 +58,8 @@ class BibleMediaItem(MediaManagerItem):
def __init__(self, parent, plugin, icon):
self.IconPath = u'songs/song'
- self.lockIcon = QtGui.QIcon(u':/bibles/bibles_search_lock.png')
- self.unlockIcon = QtGui.QIcon(u':/bibles/bibles_search_unlock.png')
+ self.lockIcon = build_icon(u':/bibles/bibles_search_lock.png')
+ self.unlockIcon = build_icon(u':/bibles/bibles_search_unlock.png')
MediaManagerItem.__init__(self, parent, plugin, icon)
# Place to store the search results for both bibles.
self.settings = self.plugin.settings_tab
@@ -983,7 +984,8 @@ class BibleMediaItem(MediaManagerItem):
Search for some Bible verses (by reference).
"""
bible = unicode(self.quickVersionComboBox.currentText())
- search_results = self.plugin.manager.get_verses(bible, string, False, False)
+ search_results = self.plugin.manager.get_verses(bible, string, False,
+ False)
if search_results:
versetext = u' '.join([verse.text for verse in search_results])
return [[string, versetext]]
diff --git a/openlp/plugins/custom/forms/editcustomform.py b/openlp/plugins/custom/forms/editcustomform.py
index 904fa598c..a3a80caf9 100644
--- a/openlp/plugins/custom/forms/editcustomform.py
+++ b/openlp/plugins/custom/forms/editcustomform.py
@@ -93,7 +93,6 @@ class EditCustomForm(QtGui.QDialog, Ui_CustomEditDialog):
self.titleEdit.setText(u'')
self.creditEdit.setText(u'')
self.themeComboBox.setCurrentIndex(0)
- self.titleEdit.setFocus(QtCore.Qt.OtherFocusReason)
else:
self.customSlide = self.manager.get_object(CustomSlide, id)
self.titleEdit.setText(self.customSlide.title)
@@ -104,10 +103,9 @@ class EditCustomForm(QtGui.QDialog, Ui_CustomEditDialog):
self.slideListView.addItem(slide[1])
theme = self.customSlide.theme_name
find_and_set_in_combo_box(self.themeComboBox, theme)
+ self.titleEdit.setFocus(QtCore.Qt.OtherFocusReason)
# If not preview hide the preview button.
- self.previewButton.setVisible(False)
- if preview:
- self.previewButton.setVisible(True)
+ self.previewButton.setVisible(preview)
def reject(self):
Receiver.send_message(u'custom_edit_clear')
diff --git a/openlp/plugins/custom/lib/mediaitem.py b/openlp/plugins/custom/lib/mediaitem.py
index 2f7c7f9b3..667434a8b 100644
--- a/openlp/plugins/custom/lib/mediaitem.py
+++ b/openlp/plugins/custom/lib/mediaitem.py
@@ -200,6 +200,17 @@ class CustomMediaItem(MediaManagerItem):
Remove a custom item from the list and database
"""
if check_item_selected(self.listView, UiStrings().SelectDelete):
+ items = self.listView.selectedIndexes()
+ if QtGui.QMessageBox.question(self,
+ UiStrings().ConfirmDelete,
+ translate('CustomPlugin.MediaItem',
+ 'Are you sure you want to delete the %n selected custom'
+ ' slides(s)?', '',
+ QtCore.QCoreApplication.CodecForTr, len(items)),
+ QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Yes |
+ QtGui.QMessageBox.No),
+ QtGui.QMessageBox.Yes) == QtGui.QMessageBox.No:
+ return
row_list = [item.row() for item in self.listView.selectedIndexes()]
row_list.sort(reverse=True)
id_list = [(item.data(QtCore.Qt.UserRole)).toInt()[0]
diff --git a/openlp/plugins/images/lib/mediaitem.py b/openlp/plugins/images/lib/mediaitem.py
index 481b55c09..3c4489bbc 100644
--- a/openlp/plugins/images/lib/mediaitem.py
+++ b/openlp/plugins/images/lib/mediaitem.py
@@ -208,8 +208,13 @@ class ImageMediaItem(MediaManagerItem):
filename = unicode(bitem.data(QtCore.Qt.UserRole).toString())
if os.path.exists(filename):
(path, name) = os.path.split(filename)
- self.plugin.liveController.display.directImage(name, filename)
- self.resetAction.setVisible(True)
+ if self.plugin.liveController.display.directImage(name,
+ filename):
+ self.resetAction.setVisible(True)
+ else:
+ critical_error_message_box(UiStrings().LiveBGError,
+ translate('ImagePlugin.MediaItem',
+ 'There was no display item to amend.'))
else:
critical_error_message_box(UiStrings().LiveBGError,
unicode(translate('ImagePlugin.MediaItem',
diff --git a/openlp/plugins/media/lib/mediaitem.py b/openlp/plugins/media/lib/mediaitem.py
index 1e39a0426..77f91a529 100644
--- a/openlp/plugins/media/lib/mediaitem.py
+++ b/openlp/plugins/media/lib/mediaitem.py
@@ -114,8 +114,12 @@ class MediaMediaItem(MediaManagerItem):
filename = unicode(item.data(QtCore.Qt.UserRole).toString())
if os.path.exists(filename):
(path, name) = os.path.split(filename)
- self.plugin.liveController.display.video(filename, 0, True)
- self.resetAction.setVisible(True)
+ if self.plugin.liveController.display.video(filename, 0, True):
+ self.resetAction.setVisible(True)
+ else:
+ critical_error_message_box(UiStrings().LiveBGError,
+ translate('MediaPlugin.MediaItem',
+ 'There was no display item to amend.'))
else:
critical_error_message_box(UiStrings().LiveBGError,
unicode(translate('MediaPlugin.MediaItem',
diff --git a/openlp/plugins/songs/forms/editsongdialog.py b/openlp/plugins/songs/forms/editsongdialog.py
index 39612ba1a..26c799c00 100644
--- a/openlp/plugins/songs/forms/editsongdialog.py
+++ b/openlp/plugins/songs/forms/editsongdialog.py
@@ -68,6 +68,8 @@ class Ui_EditSongDialog(object):
QtCore.Qt.AlignTop)
self.verseListWidget = QtGui.QTableWidget(self.lyricsTab)
self.verseListWidget.horizontalHeader().setVisible(False)
+ self.verseListWidget.horizontalHeader().setStretchLastSection(True)
+ self.verseListWidget.horizontalHeader().setMinimumSectionSize(16)
self.verseListWidget.setAlternatingRowColors(True)
self.verseListWidget.setColumnCount(1)
self.verseListWidget.setSelectionBehavior(
diff --git a/openlp/plugins/songs/forms/editsongform.py b/openlp/plugins/songs/forms/editsongform.py
index 05c891a55..6f268bd2f 100644
--- a/openlp/plugins/songs/forms/editsongform.py
+++ b/openlp/plugins/songs/forms/editsongform.py
@@ -209,9 +209,11 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
self.alternativeEdit.setText(u'')
if self.song.song_book_id != 0:
book_name = self.manager.get_object(Book, self.song.song_book_id)
- find_and_set_in_combo_box(self.songBookComboBox, unicode(book_name.name))
+ find_and_set_in_combo_box(
+ self.songBookComboBox, unicode(book_name.name))
if self.song.theme_name:
- find_and_set_in_combo_box(self.themeComboBox, unicode(self.song.theme_name))
+ find_and_set_in_combo_box(
+ self.themeComboBox, unicode(self.song.theme_name))
if self.song.copyright:
self.copyrightEdit.setText(self.song.copyright)
else:
@@ -233,7 +235,6 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
# lazy xml migration for now
self.verseListWidget.clear()
self.verseListWidget.setRowCount(0)
- self.verseListWidget.setColumnWidth(0, self.width)
# This is just because occasionally the lyrics come back as a "buffer"
if isinstance(self.song.lyrics, buffer):
self.song.lyrics = unicode(self.song.lyrics)
diff --git a/openlp/plugins/songs/lib/__init__.py b/openlp/plugins/songs/lib/__init__.py
index 308ff6aa1..e2996ff8f 100644
--- a/openlp/plugins/songs/lib/__init__.py
+++ b/openlp/plugins/songs/lib/__init__.py
@@ -267,6 +267,12 @@ def clean_song(manager, song):
``song``
The song object.
"""
+ if isinstance(song.title, buffer):
+ song.title = unicode(song.title)
+ if isinstance(song.alternate_title, buffer):
+ song.alternate_title = unicode(song.alternate_title)
+ if isinstance(song.lyrics, buffer):
+ song.lyrics = unicode(song.lyrics)
song.title = song.title.rstrip() if song.title else u''
if song.alternate_title is None:
song.alternate_title = u''
diff --git a/openlp/plugins/songs/lib/ewimport.py b/openlp/plugins/songs/lib/ewimport.py
index 09f84fbe2..448d629d5 100644
--- a/openlp/plugins/songs/lib/ewimport.py
+++ b/openlp/plugins/songs/lib/ewimport.py
@@ -31,6 +31,7 @@ EasyWorship song databases into the current installation database.
import os
import struct
+import re
from openlp.core.lib import translate
from openlp.core.ui.wizard import WizardStrings
@@ -38,11 +39,26 @@ from openlp.plugins.songs.lib import VerseType
from openlp.plugins.songs.lib import retrieve_windows_encoding
from songimport import SongImport
+RTF_STRIPPING_REGEX = re.compile(r'\{\\tx[^}]*\}')
+# regex: at least two newlines, can have spaces between them
+SLIDE_BREAK_REGEX = re.compile(r'\n *?\n[\n ]*')
+NUMBER_REGEX = re.compile(r'[0-9]+')
+NOTE_REGEX = re.compile(r'\(.*?\)')
+
def strip_rtf(blob, encoding):
depth = 0
control = False
clear_text = []
control_word = []
+
+ # workaround for \tx bug: remove one pair of curly braces
+ # if \tx is encountered
+ match = RTF_STRIPPING_REGEX.search(blob)
+ if match:
+ # start and end indices of match are curly braces - filter them out
+ blob = ''.join([blob[i] for i in xrange(len(blob))
+ if i != match.start() and i !=match.end()])
+
for c in blob:
if control:
# for delimiters, set control to False
@@ -259,9 +275,45 @@ class EasyWorshipSongImport(SongImport):
if words:
# Format the lyrics
words = strip_rtf(words, self.encoding)
- for verse in words.split(u'\n\n'):
+ verse_type = VerseType.Tags[VerseType.Verse]
+ for verse in SLIDE_BREAK_REGEX.split(words):
+ verse = verse.strip()
+ if not verse:
+ continue
+ verse_split = verse.split(u'\n', 1)
+ first_line_is_tag = False
+ # EW tags: verse, chorus, pre-chorus, bridge, tag,
+ # intro, ending, slide
+ for type in VerseType.Names+[u'tag', u'slide']:
+ type = type.lower()
+ ew_tag = verse_split[0].strip().lower()
+ if ew_tag.startswith(type):
+ verse_type = type[0]
+ if type == u'tag' or type == u'slide':
+ verse_type = VerseType.Tags[VerseType.Other]
+ first_line_is_tag = True
+ number_found = False
+ # check if tag is followed by number and/or note
+ if len(ew_tag) > len(type):
+ match = NUMBER_REGEX.search(ew_tag)
+ if match:
+ number = match.group()
+ verse_type += number
+ number_found = True
+ match = NOTE_REGEX.search(ew_tag)
+ if match:
+ self.comments += ew_tag + u'\n'
+ if not number_found:
+ verse_type += u'1'
+ break
self.add_verse(
- verse.strip(), VerseType.Tags[VerseType.Verse])
+ verse_split[-1].strip() if first_line_is_tag else verse,
+ verse_type)
+ if len(self.comments) > 5:
+ self.comments += unicode(
+ translate('SongsPlugin.EasyWorshipSongImport',
+ '\n[above are Song Tags with notes imported from \
+ EasyWorship]'))
if self.stop_import_flag:
break
if not self.finish():
diff --git a/openlp/plugins/songs/lib/mediaitem.py b/openlp/plugins/songs/lib/mediaitem.py
index cd7484df9..a2814a1df 100644
--- a/openlp/plugins/songs/lib/mediaitem.py
+++ b/openlp/plugins/songs/lib/mediaitem.py
@@ -35,7 +35,8 @@ from sqlalchemy.sql import or_
from openlp.core.lib import MediaManagerItem, Receiver, ItemCapabilities, \
translate, check_item_selected, PluginStatus
from openlp.core.lib.searchedit import SearchEdit
-from openlp.core.lib.ui import UiStrings
+from openlp.core.lib.ui import UiStrings, context_menu_action, \
+ context_menu_separator
from openlp.plugins.songs.forms import EditSongForm, SongMaintenanceForm, \
SongImportForm, SongExportForm
from openlp.plugins.songs.lib import OpenLyrics, SongXML, VerseType, \
@@ -128,6 +129,13 @@ class SongMediaItem(MediaManagerItem):
QtCore.SIGNAL(u'searchTypeChanged(int)'),
self.onSearchTextButtonClick)
+ def addCustomContextActions(self):
+ context_menu_separator(self.listView)
+ context_menu_action(
+ self.listView, u':/general/general_clone.png',
+ translate('OpenLP.MediaManagerItem',
+ '&Clone'), self.onCloneClick)
+
def onFocus(self):
self.searchTextEdit.setFocus()
@@ -353,19 +361,37 @@ class SongMediaItem(MediaManagerItem):
if check_item_selected(self.listView, UiStrings().SelectDelete):
items = self.listView.selectedIndexes()
if QtGui.QMessageBox.question(self,
- translate('SongsPlugin.MediaItem', 'Delete Song(s)?'),
+ UiStrings().ConfirmDelete,
translate('SongsPlugin.MediaItem',
'Are you sure you want to delete the %n selected song(s)?', '',
QtCore.QCoreApplication.CodecForTr, len(items)),
- QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Ok |
- QtGui.QMessageBox.Cancel),
- QtGui.QMessageBox.Ok) == QtGui.QMessageBox.Cancel:
+ QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Yes |
+ QtGui.QMessageBox.No),
+ QtGui.QMessageBox.Yes) == QtGui.QMessageBox.No:
return
for item in items:
item_id = (item.data(QtCore.Qt.UserRole)).toInt()[0]
self.plugin.manager.delete_object(Song, item_id)
self.onSearchTextButtonClick()
+ def onCloneClick(self):
+ """
+ Clone a Song
+ """
+ log.debug(u'onCloneClick')
+ if check_item_selected(self.listView, UiStrings().SelectEdit):
+ self.editItem = self.listView.currentItem()
+ item_id = (self.editItem.data(QtCore.Qt.UserRole)).toInt()[0]
+ old_song = self.plugin.manager.get_object(Song, item_id)
+ song_xml = self.openLyrics.song_to_xml(old_song)
+ new_song_id = self.openLyrics.xml_to_song(song_xml)
+ new_song = self.plugin.manager.get_object(Song, new_song_id)
+ new_song.title = u'%s <%s>' % (new_song.title,
+ translate('SongsPlugin.MediaItem', 'copy',
+ 'For song cloning'))
+ self.plugin.manager.save_object(new_song)
+ self.onSongListLoad()
+
def generateSlideData(self, service_item, item=None, xmlVersion=False):
log.debug(u'generateSlideData (%s:%s)' % (service_item, item))
item_id = self._getIdOfItemToGenerate(item, self.remoteSong)
diff --git a/openlp/plugins/songs/lib/oooimport.py b/openlp/plugins/songs/lib/oooimport.py
index 62a5f0cdd..ba442bb35 100644
--- a/openlp/plugins/songs/lib/oooimport.py
+++ b/openlp/plugins/songs/lib/oooimport.py
@@ -37,14 +37,16 @@ log = logging.getLogger(__name__)
if os.name == u'nt':
from win32com.client import Dispatch
- PAGE_BEFORE = 4
- PAGE_AFTER = 5
- PAGE_BOTH = 6
NoConnectException = Exception
else:
import uno
from com.sun.star.connection import NoConnectException
+try:
from com.sun.star.style.BreakType import PAGE_BEFORE, PAGE_AFTER, PAGE_BOTH
+except ImportError:
+ PAGE_BEFORE = 4
+ PAGE_AFTER = 5
+ PAGE_BOTH = 6
class OooImport(SongImport):
"""
diff --git a/openlp/plugins/songs/lib/sofimport.py b/openlp/plugins/songs/lib/sofimport.py
index 4168670e7..e0134f282 100644
--- a/openlp/plugins/songs/lib/sofimport.py
+++ b/openlp/plugins/songs/lib/sofimport.py
@@ -41,20 +41,23 @@ from oooimport import OooImport
log = logging.getLogger(__name__)
if os.name == u'nt':
- BOLD = 150.0
- ITALIC = 2
from oooimport import PAGE_BEFORE, PAGE_AFTER, PAGE_BOTH
RuntimeException = Exception
else:
try:
- from com.sun.star.awt.FontWeight import BOLD
- from com.sun.star.awt.FontSlant import ITALIC
from com.sun.star.style.BreakType import PAGE_BEFORE, PAGE_AFTER, \
PAGE_BOTH
from com.sun.star.uno import RuntimeException
except ImportError:
pass
-
+try:
+ from com.sun.star.awt.FontWeight import BOLD
+except ImportError:
+ BOLD = 150.0
+try:
+ from com.sun.star.awt.FontSlant import ITALIC
+except ImportError:
+ ITALIC = 2
class SofImport(OooImport):
"""
diff --git a/openlp/plugins/songusage/songusageplugin.py b/openlp/plugins/songusage/songusageplugin.py
index 7304b5196..575295f37 100644
--- a/openlp/plugins/songusage/songusageplugin.py
+++ b/openlp/plugins/songusage/songusageplugin.py
@@ -48,8 +48,10 @@ class SongUsagePlugin(Plugin):
Plugin.__init__(self, u'SongUsage', plugin_helpers)
self.weight = -4
self.icon = build_icon(u':/plugins/plugin_songusage.png')
+ self.activeIcon = build_icon(u':/songusage/song_usage_active.png')
+ self.inactiveIcon = build_icon(u':/songusage/song_usage_inactive.png')
self.manager = None
- self.songusageActive = False
+ self.songUsageActive = False
def addToolsMenuItem(self, tools_menu):
"""
@@ -84,17 +86,29 @@ class SongUsagePlugin(Plugin):
self.songUsageStatus.setText(translate(
'SongUsagePlugin', 'Toggle Tracking'))
self.songUsageStatus.setStatusTip(translate('SongUsagePlugin',
- 'Toggle the tracking of song usage.'))
- #Add Menus together
+ 'Toggle the tracking of song usage.'))
+ # Add Menus together
self.toolsMenu.addAction(self.songUsageMenu.menuAction())
self.songUsageMenu.addAction(self.songUsageStatus)
self.songUsageMenu.addSeparator()
self.songUsageMenu.addAction(self.songUsageDelete)
self.songUsageMenu.addAction(self.songUsageReport)
+ self.songUsageActiveButton = QtGui.QToolButton(
+ self.formparent.statusBar)
+ self.songUsageActiveButton.setCheckable(True)
+ self.songUsageActiveButton.setStatusTip(translate('SongUsagePlugin',
+ 'Toggle the tracking of song usage.'))
+ self.songUsageActiveButton.setObjectName(u'songUsageActiveButton')
+ self.formparent.statusBar.insertPermanentWidget(1,
+ self.songUsageActiveButton)
+ self.songUsageActiveButton.hide()
# Signals and slots
QtCore.QObject.connect(self.songUsageStatus,
QtCore.SIGNAL(u'visibilityChanged(bool)'),
self.songUsageStatus.setChecked)
+ QtCore.QObject.connect(self.songUsageActiveButton,
+ QtCore.SIGNAL(u'toggled(bool)'),
+ self.toggleSongUsageState)
QtCore.QObject.connect(self.songUsageDelete,
QtCore.SIGNAL(u'triggered()'), self.onSongUsageDelete)
QtCore.QObject.connect(self.songUsageReport,
@@ -107,23 +121,25 @@ class SongUsagePlugin(Plugin):
QtCore.QObject.connect(Receiver.get_receiver(),
QtCore.SIGNAL(u'slidecontroller_live_started'),
self.onReceiveSongUsage)
- self.SongUsageActive = QtCore.QSettings().value(
+ self.songUsageActive = QtCore.QSettings().value(
self.settingsSection + u'/active',
QtCore.QVariant(False)).toBool()
- self.songUsageStatus.setChecked(self.SongUsageActive)
+ # Set the button and checkbox state
+ self.setButtonState()
action_list = ActionList.get_instance()
+ action_list.add_action(self.songUsageStatus,
+ translate('SongUsagePlugin', 'Song Usage'))
action_list.add_action(self.songUsageDelete,
translate('SongUsagePlugin', 'Song Usage'))
action_list.add_action(self.songUsageReport,
translate('SongUsagePlugin', 'Song Usage'))
- action_list.add_action(self.songUsageStatus,
- translate('SongUsagePlugin', 'Song Usage'))
if self.manager is None:
self.manager = Manager(u'songusage', init_schema)
self.songUsageDeleteForm = SongUsageDeleteForm(self.manager,
self.formparent)
self.songUsageDetailForm = SongUsageDetailForm(self, self.formparent)
self.songUsageMenu.menuAction().setVisible(True)
+ self.songUsageActiveButton.show()
def finalise(self):
"""
@@ -134,26 +150,55 @@ class SongUsagePlugin(Plugin):
Plugin.finalise(self)
self.songUsageMenu.menuAction().setVisible(False)
action_list = ActionList.get_instance()
+ action_list.remove_action(self.songUsageStatus,
+ translate('SongUsagePlugin', 'Song Usage'))
action_list.remove_action(self.songUsageDelete,
translate('SongUsagePlugin', 'Song Usage'))
action_list.remove_action(self.songUsageReport,
translate('SongUsagePlugin', 'Song Usage'))
- action_list.remove_action(self.songUsageStatus,
- translate('SongUsagePlugin', 'Song Usage'))
- #stop any events being processed
- self.SongUsageActive = False
+ self.songUsageActiveButton.hide()
+ # stop any events being processed
+ self.songUsageActive = False
def toggleSongUsageState(self):
- self.SongUsageActive = not self.SongUsageActive
+ """
+ Manage the state of the audit collection and amend
+ the UI when necessary,
+ """
+ self.songUsageActive = not self.songUsageActive
QtCore.QSettings().setValue(self.settingsSection + u'/active',
- QtCore.QVariant(self.SongUsageActive))
+ QtCore.QVariant(self.songUsageActive))
+ self.setButtonState()
+
+ def setButtonState(self):
+ """
+ Keep buttons inline. Turn of signals to stop dead loop but we need the
+ button and check box set correctly.
+ """
+ self.songUsageActiveButton.blockSignals(True)
+ self.songUsageStatus.blockSignals(True)
+ if self.songUsageActive:
+ self.songUsageActiveButton.setIcon(self.activeIcon)
+ self.songUsageStatus.setChecked(True)
+ self.songUsageActiveButton.setChecked(True)
+ self.songUsageActiveButton.setToolTip(translate('SongUsagePlugin',
+ 'Song usage tracking is active.'))
+ else:
+ self.songUsageActiveButton.setIcon(self.inactiveIcon)
+ self.songUsageStatus.setChecked(False)
+ self.songUsageActiveButton.setChecked(False)
+ self.songUsageActiveButton.setToolTip(translate('SongUsagePlugin',
+ 'Song usage tracking is inactive.'))
+ self.songUsageActiveButton.blockSignals(False)
+ self.songUsageStatus.blockSignals(False)
+
def onReceiveSongUsage(self, item):
"""
Song Usage for live song from SlideController
"""
audit = item[0].audit
- if self.SongUsageActive and audit:
+ if self.songUsageActive and audit:
song_usage_item = SongUsageItem()
song_usage_item.usagedate = datetime.today()
song_usage_item.usagetime = datetime.now().time()
diff --git a/resources/forms/amendthemedialog.ui b/resources/forms/amendthemedialog.ui
deleted file mode 100644
index 90ed34cc5..000000000
--- a/resources/forms/amendthemedialog.ui
+++ /dev/null
@@ -1,1219 +0,0 @@
-
-
- AmendThemeDialog
-
-
- Qt::ApplicationModal
-
-
-
- 0
- 0
- 586
- 651
-
-
-
- Theme Maintance
-
-
-
- :/icon/openlp.org-icon-32.bmp:/icon/openlp.org-icon-32.bmp
-
-
- true
-
-
-
- 8
-
-
- 8
-
- -
-
-
-
- 8
-
-
- 0
-
-
-
-
-
- Theme Name:
-
-
-
- -
-
-
-
-
-
- -
-
-
-
- 8
-
-
- 0
-
-
-
-
-
- 2
-
-
-
- Background
-
-
-
- 8
-
-
- 8
-
-
- 8
-
-
-
-
-
- Background:
-
-
-
- -
-
-
-
-
- Opaque
-
-
- -
-
- Transparent
-
-
-
-
- -
-
-
- Background Type:
-
-
-
- -
-
-
-
-
- Solid Color
-
-
- -
-
- Gradient
-
-
- -
-
- Image
-
-
-
-
- -
-
-
- <Color1>
-
-
-
- -
-
-
-
-
-
-
- -
-
-
- <Color2>
-
-
-
- -
-
-
-
-
-
-
- -
-
-
- Image:
-
-
-
- -
-
-
- Gradient :
-
-
-
- -
-
-
-
-
- Horizontal
-
-
- -
-
- Vertical
-
-
- -
-
- Circular
-
-
-
-
- -
-
-
-
- 0
-
-
- 0
-
-
-
-
-
- -
-
-
-
-
-
-
- :/images/image_load.png:/images/image_load.png
-
-
-
-
-
-
- -
-
-
- Qt::Vertical
-
-
-
- 20
- 40
-
-
-
-
-
-
-
-
- Font Main
-
-
-
- 8
-
-
- 8
-
- -
-
-
-
- 8
-
-
- 0
-
-
-
-
-
- Main Font
-
-
-
- Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter
-
-
- 8
-
-
- 8
-
-
- 8
-
-
-
-
-
- Font:
-
-
-
- -
-
-
- -
-
-
- Font Color:
-
-
-
- -
-
-
-
-
-
-
- -
-
-
- Size:
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 70
- 0
-
-
-
- pt
-
-
- 999
-
-
- 16
-
-
-
- -
-
-
- Wrap Indentation
-
-
-
- -
-
-
- -
-
-
- TextLabel
-
-
-
-
-
-
- -
-
-
- Qt::Vertical
-
-
-
- 20
- 40
-
-
-
-
-
-
-
- -
-
-
-
- 8
-
-
- 0
-
-
-
-
-
- Display Location
-
-
-
- 8
-
-
- 8
-
-
- 8
-
-
-
-
-
- Use Default Location:
-
-
-
- -
-
-
-
-
-
- false
-
-
-
- -
-
-
- X Position:
-
-
-
- -
-
-
- Y Position:
-
-
-
- -
-
-
- Width:
-
-
-
- -
-
-
- Height:
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 78
- 0
-
-
-
- px
-
-
- 9999
-
-
- 0
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 78
- 0
-
-
-
- px
-
-
- 9999
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 78
- 0
-
-
-
- px
-
-
- 9999
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 78
- 0
-
-
-
- px
-
-
- 9999
-
-
-
-
-
-
- -
-
-
- Qt::Vertical
-
-
-
- 20
- 40
-
-
-
-
-
-
-
-
-
-
-
- Font Footer
-
-
-
- 8
-
-
- 8
-
- -
-
-
-
- 8
-
-
- 0
-
-
-
-
-
- Footer Font
-
-
-
- QFormLayout::ExpandingFieldsGrow
-
-
- Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter
-
-
- 8
-
-
- 8
-
-
- 8
-
-
-
-
-
- Font:
-
-
-
- -
-
-
- -
-
-
- Font Color:
-
-
-
- -
-
-
-
-
-
-
- -
-
-
- Size:
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 70
- 0
-
-
-
- pt
-
-
- 999
-
-
- 10
-
-
-
-
-
-
- -
-
-
- Qt::Vertical
-
-
-
- 20
- 40
-
-
-
-
-
-
-
- -
-
-
-
- 8
-
-
- 0
-
-
-
-
-
- Display Location
-
-
-
- QFormLayout::ExpandingFieldsGrow
-
-
- Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter
-
-
- 8
-
-
- 8
-
-
- 8
-
-
-
-
-
- Use Default Location:
-
-
-
- -
-
-
-
-
-
- false
-
-
-
- -
-
-
- X Position:
-
-
-
- -
-
-
- Y Position:
-
-
-
- -
-
-
- Width:
-
-
-
- -
-
-
- Height:
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 78
- 0
-
-
-
- px
-
-
- 9999
-
-
- 0
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 78
- 0
-
-
-
- px
-
-
- 9999
-
-
- 0
-
-
-
- -
-
-
-
- 78
- 0
-
-
-
- px
-
-
- 9999
-
-
-
- -
-
-
-
- 78
- 0
-
-
-
- px
-
-
- 9999
-
-
-
-
-
-
- -
-
-
- Qt::Vertical
-
-
-
- 20
- 40
-
-
-
-
-
-
-
-
-
-
-
- Other Options
-
-
-
- 8
-
-
- 8
-
- -
-
-
-
- 8
-
-
- 0
-
-
-
-
-
- Shadow && Outline
-
-
-
- 8
-
-
- 8
-
-
-
-
-
-
- 8
-
-
- 8
-
-
- 0
-
-
-
-
-
-
-
-
-
- -
-
-
- Outline Color:
-
-
-
- -
-
-
-
-
-
-
- -
-
-
- Show Outline:
-
-
-
-
-
-
- -
-
-
-
- 8
-
-
- 8
-
-
- 0
-
-
-
-
-
-
-
-
-
- -
-
-
- Shadow Color:
-
-
-
- -
-
-
-
-
-
-
- -
-
-
- Show Shadow:
-
-
-
-
-
-
-
-
-
- -
-
-
- Qt::Vertical
-
-
-
- 20
- 40
-
-
-
-
-
-
-
- -
-
-
-
- 8
-
-
- 0
-
-
-
-
-
- Alignment
-
-
-
-
-
-
- Horizontal Align:
-
-
-
- -
-
-
-
-
- Left
-
-
- -
-
- Right
-
-
- -
-
- Center
-
-
-
-
- -
-
-
- Vertical Align:
-
-
-
- -
-
-
-
-
- Top
-
-
- -
-
- Middle
-
-
- -
-
- Bottom
-
-
-
-
-
-
-
- -
-
-
- Qt::Vertical
-
-
-
- 20
- 40
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
- Preview
-
-
-
- 8
-
-
- 8
-
-
-
-
-
- Qt::Horizontal
-
-
-
- 40
- 20
-
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 300
- 225
-
-
-
- QFrame::WinPanel
-
-
- QFrame::Sunken
-
-
- 1
-
-
-
-
-
- true
-
-
-
- -
-
-
- Qt::Horizontal
-
-
-
- 40
- 20
-
-
-
-
-
-
-
- -
-
-
- QDialogButtonBox::Cancel|QDialogButtonBox::Ok
-
-
-
-
-
-
- ThemeButtonBox
- ThemeNameEdit
- ThemeTabWidget
- BackgroundComboBox
- BackgroundTypeComboBox
- Color1PushButton
- Color2PushButton
- ImageLineEdit
- ImageToolButton
- GradientComboBox
- FontMainComboBox
- FontMainColorPushButton
- FontMainSizeSpinBox
- FontMainDefaultCheckBox
- FontMainXSpinBox
- FontMainYSpinBox
- FontMainWidthSpinBox
- FontMainHeightSpinBox
- FontFooterComboBox
- FontFooterColorPushButton
- FontFooterSizeSpinBox
- FontFooterDefaultCheckBox
- FontFooterXSpinBox
- FontFooterYSpinBox
- FontFooterWidthSpinBox
- FontFooterHeightSpinBox
- OutlineCheckBox
- OutlineColorPushButton
- ShadowCheckBox
- ShadowColorPushButton
- HorizontalComboBox
- VerticalComboBox
-
-
-
-
-
-
- ThemeButtonBox
- accepted()
- AmendThemeDialog
- accept()
-
-
- 375
- 466
-
-
- 375
- 241
-
-
-
-
- ThemeButtonBox
- rejected()
- AmendThemeDialog
- reject()
-
-
- 375
- 466
-
-
- 375
- 241
-
-
-
-
-
diff --git a/resources/forms/displaytab.ui b/resources/forms/displaytab.ui
deleted file mode 100644
index 7d2d78798..000000000
--- a/resources/forms/displaytab.ui
+++ /dev/null
@@ -1,295 +0,0 @@
-
-
- DisplaysDialog
-
-
-
- 0
- 0
- 620
- 716
-
-
-
- Amend Display Settings
-
-
-
-
- 0
- 40
- 241
- 79
-
-
-
- -
-
-
- Default Settings
-
-
-
-
-
-
-
-
-
- X
-
-
- Qt::AlignCenter
-
-
-
- -
-
-
- 0
-
-
- Qt::AlignCenter
-
-
-
-
-
- -
-
-
-
-
-
- Y
-
-
- Qt::AlignCenter
-
-
-
- -
-
-
- 0
-
-
- Qt::AlignCenter
-
-
-
-
-
- -
-
-
-
-
-
-
- 100
- 16777215
-
-
-
- Height
-
-
- Qt::AlignCenter
-
-
-
- -
-
-
- 0
-
-
- Qt::AlignCenter
-
-
-
-
-
- -
-
-
-
-
-
- Width
-
-
- Qt::AlignCenter
-
-
-
- -
-
-
- 0
-
-
- Qt::AlignCenter
-
-
-
-
-
-
-
-
-
-
-
-
-
- 0
- 130
- 248
- 87
-
-
-
-
- 500
- 16777215
-
-
-
- Amend Settings
-
-
- -
-
-
-
-
-
- X
-
-
- Qt::AlignCenter
-
-
-
- -
-
-
-
- 50
- 16777215
-
-
-
- 4
-
-
-
-
-
- -
-
-
-
-
-
- Y
-
-
- Qt::AlignCenter
-
-
-
- -
-
-
-
- 50
- 16777215
-
-
-
- 4
-
-
-
-
-
- -
-
-
-
-
-
- Height
-
-
- Qt::AlignCenter
-
-
-
- -
-
-
-
- 50
- 16777215
-
-
-
- 4
-
-
-
-
-
- -
-
-
- QLayout::SetMinimumSize
-
-
-
-
-
-
- 100
- 16777215
-
-
-
- Width
-
-
- Qt::AlignCenter
-
-
-
- -
-
-
-
- 60
- 16777215
-
-
-
-
-
-
-
- layoutWidget
- YAmendLabel
- HeightAmendLabel
- WidthAmendLabel
- YAmendLabel
-
-
-
-
- 0
- 10
- 191
- 23
-
-
-
- Override Output Display
-
-
-
-
-
-
diff --git a/resources/images/OpenLP.ico b/resources/images/OpenLP.ico
index b275542c3..c4aa7c41e 100644
Binary files a/resources/images/OpenLP.ico and b/resources/images/OpenLP.ico differ
diff --git a/resources/images/README.txt b/resources/images/README.txt
new file mode 100644
index 000000000..1ec31b48b
--- /dev/null
+++ b/resources/images/README.txt
@@ -0,0 +1,6 @@
+OpenLP.ico
+
+This Windows icon contains several images with different resolution.
+It can be recreated by command:
+
+icotool -c -o OpenLP.ico openlp-logo-16x16.png openlp-logo-32x32.png openlp-logo-48x48.png openlp-logo-64x64.png openlp-logo-128x128.png
diff --git a/resources/images/general_clone.png b/resources/images/general_clone.png
new file mode 100644
index 000000000..db1d9fbaf
Binary files /dev/null and b/resources/images/general_clone.png differ
diff --git a/resources/images/openlp-2.qrc b/resources/images/openlp-2.qrc
index fc76ad434..fff1f75b8 100644
--- a/resources/images/openlp-2.qrc
+++ b/resources/images/openlp-2.qrc
@@ -56,6 +56,7 @@
general_save.png
general_email.png
general_revert.png
+ general_clone.png
slide_close.png
@@ -137,6 +138,10 @@
messagebox_info.png
messagebox_warning.png
+
+ song_usage_active.png
+ song_usage_inactive.png
+
tools_add.png
tools_alert.png
diff --git a/resources/images/song_usage_active.png b/resources/images/song_usage_active.png
new file mode 100644
index 000000000..1221e1310
Binary files /dev/null and b/resources/images/song_usage_active.png differ
diff --git a/resources/images/song_usage_inactive.png b/resources/images/song_usage_inactive.png
new file mode 100644
index 000000000..cdcf944ee
Binary files /dev/null and b/resources/images/song_usage_inactive.png differ
diff --git a/resources/osx/openlp.cfg b/resources/osx/openlp.cfg
index 3be085dc0..954bf861d 100755
--- a/resources/osx/openlp.cfg
+++ b/resources/osx/openlp.cfg
@@ -1,8 +1,8 @@
[openlp]
openlp_appname = OpenLP
-openlp_dmgname = OpenLP-1.9.4-bzrXXXX
-openlp_version = XXXX
-openlp_basedir = /Users/openlp/trunk
+openlp_dmgname = OpenLP-1.9.6-bzr1684
+openlp_version = 1684
+openlp_basedir = /Users/openlp/repo/osx-fixes
openlp_icon_file = openlp-logo-with-text.icns
openlp_dmg_icon_file = openlp-logo-420x420.png
installer_backgroundimage_file = installation-background.png
diff --git a/resources/windows/OpenLP.spec b/resources/windows/OpenLP.spec
deleted file mode 100644
index 47a1952f3..000000000
--- a/resources/windows/OpenLP.spec
+++ /dev/null
@@ -1,14 +0,0 @@
-# -*- mode: python -*-
-a = Analysis([
- os.path.join(HOMEPATH, 'support\\_mountzlib.py'),
- os.path.join(HOMEPATH, 'support\\useUnicode.py'),
- os.path.abspath('openlp.pyw')],
- pathex=[os.path.abspath('.')])
-pyz = PYZ(a.pure)
-exe = EXE(pyz, a.scripts, exclude_binaries=1,
- name=os.path.abspath(os.path.join('build', 'pyi.win32', 'OpenLP',
- 'OpenLP.exe')),
- debug=False, strip=False, upx=True, console=False,
- icon=os.path.abspath(os.path.join('resources', 'images', 'OpenLP.ico')))
-coll = COLLECT(exe, a.binaries, a.zipfiles, a.datas, strip=False, upx=True,
- name=os.path.abspath(os.path.join('dist', 'OpenLP')))
diff --git a/resources/windows/WizImageBig.bmp b/resources/windows/WizImageBig.bmp
index 8fc9e2091..32b2ca092 100644
Binary files a/resources/windows/WizImageBig.bmp and b/resources/windows/WizImageBig.bmp differ
diff --git a/resources/windows/WizImageSmall.bmp b/resources/windows/WizImageSmall.bmp
index b8685c808..58c716fdf 100644
Binary files a/resources/windows/WizImageSmall.bmp and b/resources/windows/WizImageSmall.bmp differ
diff --git a/scripts/check_dependencies.py b/scripts/check_dependencies.py
new file mode 100755
index 000000000..4abd1504d
--- /dev/null
+++ b/scripts/check_dependencies.py
@@ -0,0 +1,192 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4
+
+###############################################################################
+# OpenLP - Open Source Lyrics Projection #
+# --------------------------------------------------------------------------- #
+# Copyright (c) 2011 Raoul Snyman #
+# --------------------------------------------------------------------------- #
+# 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; version 2 of the License. #
+# #
+# 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, write to the Free Software Foundation, Inc., 59 #
+# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
+###############################################################################
+
+"""
+This script is used to check dependencies of OpenLP. It checks availability
+of required python modules and their version. To verify availability of Python
+modules, simply run this script::
+
+ @:~$ ./check_dependencies.py
+
+"""
+import os
+import sys
+
+is_win = sys.platform.startswith('win')
+
+VERS = {
+ 'Python': '2.6',
+ 'PyQt4': '4.6',
+ 'Qt4': '4.6',
+ 'sqlalchemy': '0.5',
+ # pyenchant 1.6 required on Windows
+ 'enchant': '1.6' if is_win else '1.3'
+ }
+
+# pywin32
+WIN32_MODULES = [
+ 'win32com',
+ 'win32ui',
+ 'pywintypes',
+ ]
+
+MODULES = [
+ 'PyQt4',
+ 'PyQt4.QtCore',
+ 'PyQt4.QtGui',
+ 'PyQt4.QtNetwork',
+ 'PyQt4.QtOpenGL',
+ 'PyQt4.QtSvg',
+ 'PyQt4.QtTest',
+ 'PyQt4.QtWebKit',
+ 'PyQt4.phonon',
+ 'sqlalchemy',
+ 'sqlite3',
+ 'lxml',
+ 'chardet',
+ 'enchant',
+ 'BeautifulSoup',
+ 'mako',
+ ]
+
+
+OPTIONAL_MODULES = [
+ ('sqlite', ' (SQLite 2 support)'),
+ ('MySQLdb', ' (MySQL support)'),
+ ('psycopg2', ' (PostgreSQL support)'),
+ ]
+
+w = sys.stdout.write
+
+
+def check_vers(version, required, text):
+ if type(version) is str:
+ version = version.split('.')
+ version = [int(x) for x in version]
+ if type(required) is str:
+ required = required.split('.')
+ required = [int(x) for x in required]
+ w(' %s >= %s ... ' % (text, '.'.join([str(x) for x in required])))
+ if version >= required:
+ w('.'.join([str(x) for x in version]) + os.linesep)
+ return True
+ else:
+ w('FAIL' + os.linesep)
+ return False
+
+
+def print_vers_fail(required, text):
+ print(' %s >= %s ... FAIL' % (text, required))
+
+
+def verify_python():
+ if not check_vers(list(sys.version_info), VERS['Python'], text='Python'):
+ exit(1)
+
+
+def verify_versions():
+ print('Verifying version of modules...')
+ try:
+ from PyQt4 import QtCore
+ check_vers(QtCore.PYQT_VERSION_STR, VERS['PyQt4'],
+ 'PyQt4')
+ check_vers(QtCore.qVersion(), VERS['Qt4'],
+ 'Qt4')
+ except ImportError:
+ print_vers_fail(VERS['PyQt4'], 'PyQt4')
+ print_vers_fail(VERS['Qt4'], 'Qt4')
+ try:
+ import sqlalchemy
+ check_vers(sqlalchemy.__version__, VERS['sqlalchemy'], 'sqlalchemy')
+ except ImportError:
+ print_vers_fail(VERS['sqlalchemy'], 'sqlalchemy')
+ try:
+ import enchant
+ check_vers(enchant.__version__, VERS['enchant'], 'enchant')
+ except ImportError:
+ print_vers_fail(VERS['enchant'], 'enchant')
+
+
+def check_module(mod, text='', indent=' '):
+ space = (30 - len(mod) - len(text)) * ' '
+ w(indent + '%s%s... ' % (mod, text) + space)
+ try:
+ __import__(mod)
+ w('OK')
+ except ImportError:
+ w('FAIL')
+ w(os.linesep)
+
+
+def verify_pyenchant():
+ w('Enchant (spell checker)... ')
+ try:
+ import enchant
+ w(os.linesep)
+ backends = ', '.join([x.name for x in enchant.Broker().describe()])
+ print(' available backends: %s' % backends)
+ langs = ', '.join(enchant.list_languages())
+ print(' available languages: %s' % langs)
+ except ImportError:
+ w('FAIL' + os.linesep)
+
+
+def verify_pyqt():
+ w('Qt4 image formats... ')
+ try:
+ from PyQt4 import QtGui
+ read_f = ', '.join([unicode(format).lower() \
+ for format in QtGui.QImageReader.supportedImageFormats()])
+ write_f= ', '.join([unicode(format).lower() \
+ for format in QtGui.QImageWriter.supportedImageFormats()])
+ w(os.linesep)
+ print(' read: %s' % read_f)
+ print(' write: %s' % write_f)
+ except ImportError:
+ w('FAIL' + os.linesep)
+
+
+def main():
+
+ verify_python()
+
+ print('Checking for modules...')
+ for m in MODULES:
+ check_module(m)
+
+ print('Checking for optional modules...')
+ for m in OPTIONAL_MODULES:
+ check_module(m[0], text=m[1])
+
+ if is_win:
+ print('Checking for Windows specific modules...')
+ for m in WIN32_MODULES:
+ check_module(m)
+
+ verify_versions()
+ verify_pyqt()
+ verify_pyenchant()
+
+
+if __name__ == u'__main__':
+ main()
diff --git a/scripts/windows-builder.py b/scripts/windows-builder.py
index 854c927c9..9c96fe251 100644
--- a/scripts/windows-builder.py
+++ b/scripts/windows-builder.py
@@ -32,8 +32,7 @@ Windows Build Script
This script is used to build the Windows binary and the accompanying installer.
For this script to work out of the box, it depends on a number of things:
-Python 2.6
- This build script only works with Python 2.6.
+Python 2.6/2.7
PyQt4
You should already have this installed, OpenLP doesn't work without it. The
@@ -49,7 +48,7 @@ Inno Setup 5
UPX
This is used to compress DLLs and EXEs so that they take up less space, but
- still function exactly the same. To install UPS, download it from
+ still function exactly the same. To install UPX, download it from
http://upx.sourceforge.net/, extract it into C:\%PROGRAMFILES%\UPX, and then
add that directory to your PATH environment variable.
@@ -61,7 +60,7 @@ HTML Help Workshop
This is used to create the help file
PyInstaller
- PyInstaller should be a checkout of revision 844 of trunk, and in a
+ PyInstaller should be a checkout of revision 1470 of trunk, and in a
directory called, "pyinstaller" on the same level as OpenLP's Bazaar shared
repository directory. The revision is very important as there is currently
a major regression in HEAD.
@@ -73,13 +72,8 @@ PyInstaller
http://svn.pyinstaller.org/trunk
Then you need to copy the two hook-*.py files from the "pyinstaller"
- subdirectory in OpenLP's "resources" directory into PyInstaller's "hooks"
- directory.
-
- Once you've done that, open a command prompt (DOS shell), navigate to the
- PyInstaller directory and run::
-
- C:\Projects\pyinstaller>python Configure.py
+ subdirectory in OpenLP's "resources" directory into PyInstaller's
+ "PyInstaller/hooks" directory.
Bazaar
You need the command line "bzr" client installed.
@@ -102,7 +96,7 @@ psvince.dll
the install will fail. The dll can be obtained from here:
http://www.vincenzo.net/isxkb/index.php?title=PSVince)
-Mako
+Mako
Mako Templates for Python. This package is required for building the
remote plugin. It can be installed by going to your
python_directory\scripts\.. and running "easy_install Mako". If you do not
@@ -137,9 +131,18 @@ site_packages = os.path.join(os.path.split(python_exe)[0], u'Lib',
# Files and executables
pyi_build = os.path.abspath(os.path.join(branch_path, u'..', u'..',
- u'pyinstaller', u'Build.py'))
-lrelease_exe = os.path.join(site_packages, u'PyQt4', u'bin', u'lrelease.exe')
+ u'pyinstaller', u'pyinstaller.py'))
+openlp_main_script = os.path.abspath(os.path.join(branch_path, 'openlp.pyw'))
+if os.path.exists(os.path.join(site_packages, u'PyQt4', u'bin')):
+ # Older versions of the PyQt4 Windows installer put their binaries in the
+ # "bin" directory
+ lrelease_exe = os.path.join(site_packages, u'PyQt4', u'bin', u'lrelease.exe')
+else:
+ # Newer versions of the PyQt4 Windows installer put their binaries in the
+ # base directory of the installation
+ lrelease_exe = os.path.join(site_packages, u'PyQt4', u'lrelease.exe')
i18n_utils = os.path.join(script_path, u'translation_utils.py')
+win32_icon = os.path.join(branch_path, u'resources', u'images', 'OpenLP.ico')
# Paths
source_path = os.path.join(branch_path, u'openlp')
@@ -148,9 +151,8 @@ manual_build_path = os.path.join(manual_path, u'build')
helpfile_path = os.path.join(manual_build_path, u'htmlhelp')
i18n_path = os.path.join(branch_path, u'resources', u'i18n')
winres_path = os.path.join(branch_path, u'resources', u'windows')
-build_path = os.path.join(branch_path, u'build', u'pyi.win32', u'OpenLP')
+build_path = os.path.join(branch_path, u'build')
dist_path = os.path.join(branch_path, u'dist', u'OpenLP')
-enchant_path = os.path.join(site_packages, u'enchant')
pptviewlib_path = os.path.join(source_path, u'plugins', u'presentations',
u'lib', u'pptviewlib')
@@ -174,8 +176,15 @@ def update_code():
def run_pyinstaller():
print u'Running PyInstaller...'
os.chdir(branch_path)
- pyinstaller = Popen((python_exe, pyi_build, u'-y', u'-o', build_path,
- os.path.join(winres_path, u'OpenLP.spec')), stdout=PIPE)
+ pyinstaller = Popen((python_exe, pyi_build,
+ u'--noconfirm',
+ u'--windowed',
+ u'-o', branch_path,
+ u'-i', win32_icon,
+ u'-p', branch_path,
+ u'-n', 'OpenLP',
+ openlp_main_script),
+ stdout=PIPE)
output, error = pyinstaller.communicate()
code = pyinstaller.wait()
if code != 0:
@@ -208,19 +217,6 @@ def write_version_file():
f.write(versionstring)
f.close()
-def copy_enchant():
- print u'Copying enchant/pyenchant...'
- source = enchant_path
- dest = os.path.join(dist_path, u'enchant')
- for root, dirs, files in os.walk(source):
- for filename in files:
- if not filename.endswith(u'.pyc') and not filename.endswith(u'.pyo'):
- dest_path = os.path.join(dest, root[len(source) + 1:])
- if not os.path.exists(dest_path):
- os.makedirs(dest_path)
- copy(os.path.join(root, filename),
- os.path.join(dest_path, filename))
-
def copy_plugins():
print u'Copying plugins...'
source = os.path.join(source_path, u'plugins')
@@ -242,10 +238,10 @@ def copy_windows_files():
os.path.join(dist_path, u'LICENSE.txt'))
copy(os.path.join(winres_path, u'psvince.dll'),
os.path.join(dist_path, u'psvince.dll'))
- if os.path.isfile(os.path.join(helpfile_path, u'Openlp.chm')):
+ if os.path.isfile(os.path.join(helpfile_path, u'OpenLP.chm')):
print u' Windows help file found'
- copy(os.path.join(helpfile_path, u'Openlp.chm'),
- os.path.join(dist_path, u'Openlp.chm'))
+ copy(os.path.join(helpfile_path, u'OpenLP.chm'),
+ os.path.join(dist_path, u'OpenLP.chm'))
else:
print u' WARNING ---- Windows help file not found ---- WARNING'
@@ -330,17 +326,19 @@ def main():
import sys
for arg in sys.argv:
if arg == u'-v' or arg == u'--verbose':
- print "Script path:", script_path
- print "Branch path:", branch_path
- print "Source path:", source_path
- print "\"dist\" path:", dist_path
- print "PyInstaller:", pyi_build
+ print "OpenLP main script: ......", openlp_main_script
+ print "Script path: .............", script_path
+ print "Branch path: .............", branch_path
+ print "Source path: .............", source_path
+ print "\"dist\" path: .............", dist_path
+ print "PyInstaller: .............", pyi_build
print "Documentation branch path:", doc_branch_path
- print "Help file build path;", helpfile_path
- print "Inno Setup path:", innosetup_exe
- print "Windows resources:", winres_path
- print "VCBuild path:", vcbuild_exe
- print "PPTVIEWLIB path:", pptviewlib_path
+ print "Help file build path: ....", helpfile_path
+ print "Inno Setup path: .........", innosetup_exe
+ print "Windows resources: .......", winres_path
+ print "VCBuild path: ............", vcbuild_exe
+ print "PPTVIEWLIB path: .........", pptviewlib_path
+ print ""
elif arg == u'--skip-update':
skip_update = True
elif arg == u'/?' or arg == u'-h' or arg == u'--help':
@@ -353,7 +351,6 @@ def main():
build_pptviewlib()
run_pyinstaller()
write_version_file()
- copy_enchant()
copy_plugins()
if os.path.exists(manual_path):
run_sphinx()