diff --git a/openlp.py b/openlp.py
index 8a53fe965..5d507606d 100755
--- a/openlp.py
+++ b/openlp.py
@@ -36,10 +36,9 @@ if __name__ == '__main__':
"""
Instantiate and run the application.
"""
- # Mac OS X passes arguments like '-psn_XXXX' to gui application.
- # This argument is process serial number. However, this causes
- # conflict with other OpenLP arguments. Since we do not use this
- # argument we can delete it to avoid any potential conflicts.
+ # Mac OS X passes arguments like '-psn_XXXX' to the application. This argument is actually a process serial number.
+ # However, this causes a conflict with other OpenLP arguments. Since we do not use this argument we can delete it
+ # to avoid any potential conflicts.
if sys.platform.startswith('darwin'):
sys.argv = [x for x in sys.argv if not x.startswith('-psn')]
main()
diff --git a/openlp/core/__init__.py b/openlp/core/__init__.py
index 32ef5c5a3..cb9105797 100644
--- a/openlp/core/__init__.py
+++ b/openlp/core/__init__.py
@@ -59,7 +59,7 @@ __all__ = ['OpenLP', 'main']
log = logging.getLogger()
-NT_REPAIR_STYLESHEET = """
+WIN_REPAIR_STYLESHEET = """
QMainWindow::separator
{
border: none;
@@ -127,7 +127,7 @@ class OpenLP(OpenLPMixin, QtGui.QApplication):
'QTableWidget, QListWidget, QTreeWidget {alternate-background-color: ' + base_color.name() + ';}\n'
application_stylesheet += alternate_rows_repair_stylesheet
if is_win():
- application_stylesheet += NT_REPAIR_STYLESHEET
+ application_stylesheet += WIN_REPAIR_STYLESHEET
if application_stylesheet:
self.setStyleSheet(application_stylesheet)
show_splash = Settings().value('core/show splash')
diff --git a/openlp/core/common/openlpmixin.py b/openlp/core/common/openlpmixin.py
index 1c7fe7d5a..3e8a8926a 100644
--- a/openlp/core/common/openlpmixin.py
+++ b/openlp/core/common/openlpmixin.py
@@ -33,6 +33,7 @@ import logging
import inspect
from openlp.core.common import trace_error_handler
+
DO_NOT_TRACE_EVENTS = ['timerEvent', 'paintEvent', 'drag_enter_event', 'drop_event', 'on_controller_size_changed',
'preview_size_changed', 'resizeEvent']
@@ -41,11 +42,8 @@ class OpenLPMixin(object):
"""
Base Calling object for OpenLP classes.
"""
- def __init__(self, parent):
- try:
- super(OpenLPMixin, self).__init__(parent)
- except TypeError:
- super(OpenLPMixin, self).__init__()
+ def __init__(self, *args, **kwargs):
+ super(OpenLPMixin, self).__init__(*args, **kwargs)
self.logger = logging.getLogger("%s.%s" % (self.__module__, self.__class__.__name__))
if self.logger.getEffectiveLevel() == logging.DEBUG:
for name, m in inspect.getmembers(self, inspect.ismethod):
diff --git a/openlp/core/lib/__init__.py b/openlp/core/lib/__init__.py
index a7cba33a0..b85070445 100644
--- a/openlp/core/lib/__init__.py
+++ b/openlp/core/lib/__init__.py
@@ -145,11 +145,13 @@ def build_icon(icon):
return button_icon
-def image_to_byte(image):
+def image_to_byte(image, base_64=True):
"""
Resize an image to fit on the current screen for the web and returns it as a byte stream.
:param image: The image to converted.
+ :param base_64: If True returns the image as Base64 bytes, otherwise the image is returned as a byte array.
+ To preserve original intention, this defaults to True
"""
log.debug('image_to_byte - start')
byte_array = QtCore.QByteArray()
@@ -158,6 +160,8 @@ def image_to_byte(image):
buffie.open(QtCore.QIODevice.WriteOnly)
image.save(buffie, "PNG")
log.debug('image_to_byte - end')
+ if not base_64:
+ return byte_array
# convert to base64 encoding so does not get missed!
return bytes(byte_array.toBase64()).decode('utf-8')
diff --git a/openlp/core/lib/imagemanager.py b/openlp/core/lib/imagemanager.py
index cba393815..46fab10d2 100644
--- a/openlp/core/lib/imagemanager.py
+++ b/openlp/core/lib/imagemanager.py
@@ -106,7 +106,7 @@ class Image(object):
"""
secondary_priority = 0
- def __init__(self, path, source, background):
+ def __init__(self, path, source, background, width=-1, height=-1):
"""
Create an image for the :class:`ImageManager`'s cache.
@@ -115,7 +115,8 @@ class Image(object):
:class:`~openlp.core.lib.ImageSource` class.
:param background: A ``QtGui.QColor`` object specifying the colour to be used to fill the gabs if the image's
ratio does not match with the display ratio.
-
+ :param width: The width of the image, defaults to -1 meaning that the screen width will be used.
+ :param height: The height of the image, defaults to -1 meaning that the screen height will be used.
"""
self.path = path
self.image = None
@@ -124,6 +125,8 @@ class Image(object):
self.source = source
self.background = background
self.timestamp = 0
+ self.width = width
+ self.height = height
# FIXME: We assume that the path exist. The caller has to take care that it exists!
if os.path.exists(path):
self.timestamp = os.stat(path).st_mtime
@@ -210,13 +213,13 @@ class ImageManager(QtCore.QObject):
image.background = background
self._reset_image(image)
- def update_image_border(self, path, source, background):
+ def update_image_border(self, path, source, background, width=-1, height=-1):
"""
Border has changed so update the image affected.
"""
log.debug('update_image_border')
# Mark the image as dirty for a rebuild by setting the image and byte stream to None.
- image = self._cache[(path, source)]
+ image = self._cache[(path, source, width, height)]
if image.source == source:
image.background = background
self._reset_image(image)
@@ -237,12 +240,12 @@ class ImageManager(QtCore.QObject):
if not self.image_thread.isRunning():
self.image_thread.start()
- def get_image(self, path, source):
+ def get_image(self, path, source, width=-1, height=-1):
"""
Return the ``QImage`` from the cache. If not present wait for the background thread to process it.
"""
log.debug('getImage %s' % path)
- image = self._cache[(path, source)]
+ image = self._cache[(path, source, width, height)]
if image.image is None:
self._conversion_queue.modify_priority(image, Priority.High)
# make sure we are running and if not give it a kick
@@ -257,12 +260,12 @@ class ImageManager(QtCore.QObject):
self._conversion_queue.modify_priority(image, Priority.Low)
return image.image
- def get_image_bytes(self, path, source):
+ def get_image_bytes(self, path, source, width=-1, height=-1):
"""
Returns the byte string for an image. If not present wait for the background thread to process it.
"""
log.debug('get_image_bytes %s' % path)
- image = self._cache[(path, source)]
+ image = self._cache[(path, source, width, height)]
if image.image_bytes is None:
self._conversion_queue.modify_priority(image, Priority.Urgent)
# make sure we are running and if not give it a kick
@@ -272,14 +275,14 @@ class ImageManager(QtCore.QObject):
time.sleep(0.1)
return image.image_bytes
- def add_image(self, path, source, background):
+ def add_image(self, path, source, background, width=-1, height=-1):
"""
Add image to cache if it is not already there.
"""
log.debug('add_image %s' % path)
- if not (path, source) in self._cache:
- image = Image(path, source, background)
- self._cache[(path, source)] = image
+ if not (path, source, width, height) in self._cache:
+ image = Image(path, source, background, width, height)
+ self._cache[(path, source, width, height)] = image
self._conversion_queue.put((image.priority, image.secondary_priority, image))
# Check if the there are any images with the same path and check if the timestamp has changed.
for image in list(self._cache.values()):
@@ -308,7 +311,10 @@ class ImageManager(QtCore.QObject):
image = self._conversion_queue.get()[2]
# Generate the QImage for the image.
if image.image is None:
- image.image = resize_image(image.path, self.width, self.height, image.background)
+ # Let's see if the image was requested with specific dimensions
+ width = self.width if image.width == -1 else image.width
+ height = self.height if image.height == -1 else image.height
+ image.image = resize_image(image.path, width, height, image.background)
# 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)
diff --git a/openlp/core/lib/serviceitem.py b/openlp/core/lib/serviceitem.py
index ecd6ca5bd..4e7ff032a 100644
--- a/openlp/core/lib/serviceitem.py
+++ b/openlp/core/lib/serviceitem.py
@@ -39,8 +39,8 @@ import uuid
from PyQt4 import QtGui
-from openlp.core.common import RegistryProperties, Settings, translate
-from openlp.core.lib import ImageSource, build_icon, clean_tags, expand_tags
+from openlp.core.common import RegistryProperties, Settings, translate, AppLocation
+from openlp.core.lib import ImageSource, build_icon, clean_tags, expand_tags, create_thumb
log = logging.getLogger(__name__)
@@ -112,7 +112,17 @@ class ItemCapabilities(object):
The capability to edit the title of the item
``IsOptical``
- .Determines is the service_item is based on an optical device
+ Determines is the service_item is based on an optical device
+
+ ``HasDisplayTitle``
+ The item contains 'displaytitle' on every frame which should be
+ preferred over 'title' when displaying the item
+
+ ``HasNotes``
+ The item contains 'notes'
+
+ ``HasThumbnails``
+ The item has related thumbnails available
"""
CanPreview = 1
@@ -133,6 +143,9 @@ class ItemCapabilities(object):
CanAutoStartForLive = 16
CanEditTitle = 17
IsOptical = 18
+ HasDisplayTitle = 19
+ HasNotes = 20
+ HasThumbnails = 21
class ServiceItem(RegistryProperties):
@@ -272,18 +285,22 @@ class ServiceItem(RegistryProperties):
self.raw_footer = []
self.foot_text = '
'.join([_f for _f in self.raw_footer if _f])
- def add_from_image(self, path, title, background=None):
+ def add_from_image(self, path, title, background=None, thumbnail=None):
"""
Add an image slide to the service item.
:param path: The directory in which the image file is located.
:param title: A title for the slide in the service item.
:param background:
+ :param thumbnail: Optional alternative thumbnail, used for remote thumbnails.
"""
if background:
self.image_border = background
self.service_item_type = ServiceItemType.Image
- self._raw_frames.append({'title': title, 'path': path})
+ if not thumbnail:
+ self._raw_frames.append({'title': title, 'path': path})
+ else:
+ self._raw_frames.append({'title': title, 'path': path, 'image': thumbnail})
self.image_manager.add_image(path, ImageSource.ImagePlugin, self.image_border)
self._new_item()
@@ -301,16 +318,22 @@ class ServiceItem(RegistryProperties):
self._raw_frames.append({'title': title, 'raw_slide': raw_slide, 'verseTag': verse_tag})
self._new_item()
- def add_from_command(self, path, file_name, image):
+ def add_from_command(self, path, file_name, image, display_title=None, notes=None):
"""
Add a slide from a command.
:param path: The title of the slide in the service item.
:param file_name: The title of the slide in the service item.
:param image: The command of/for the slide.
+ :param display_title: Title to show in gui/webinterface, optional.
+ :param notes: Notes to show in the webinteface, optional.
"""
self.service_item_type = ServiceItemType.Command
- self._raw_frames.append({'title': file_name, 'image': image, 'path': path})
+ # If the item should have a display title but this frame doesn't have one, we make one up
+ if self.is_capable(ItemCapabilities.HasDisplayTitle) and not display_title:
+ display_title = translate('OpenLP.ServiceItem', '[slide %d]') % (len(self._raw_frames) + 1)
+ self._raw_frames.append({'title': file_name, 'image': image, 'path': path,
+ 'display_title': display_title, 'notes': notes})
self._new_item()
def get_service_repr(self, lite_save):
@@ -354,7 +377,8 @@ class ServiceItem(RegistryProperties):
service_data = [slide['title'] for slide in self._raw_frames]
elif self.service_item_type == ServiceItemType.Command:
for slide in self._raw_frames:
- service_data.append({'title': slide['title'], 'image': slide['image'], 'path': slide['path']})
+ service_data.append({'title': slide['title'], 'image': slide['image'], 'path': slide['path'],
+ 'display_title': slide['display_title'], 'notes': slide['notes']})
return {'header': service_header, 'data': service_data}
def set_from_service(self, service_item, path=None):
@@ -425,7 +449,8 @@ class ServiceItem(RegistryProperties):
self.add_from_command(text_image['path'], text_image['title'], text_image['image'])
elif path:
self.has_original_files = False
- self.add_from_command(path, text_image['title'], text_image['image'])
+ self.add_from_command(path, text_image['title'], text_image['image'],
+ text_image.get('display_title', ''), text_image.get('notes', ''))
else:
self.add_from_command(text_image['path'], text_image['title'], text_image['image'])
self._new_item()
diff --git a/openlp/core/lib/ui.py b/openlp/core/lib/ui.py
index cbc35e28d..af4b263d3 100644
--- a/openlp/core/lib/ui.py
+++ b/openlp/core/lib/ui.py
@@ -33,7 +33,7 @@ import logging
from PyQt4 import QtCore, QtGui
-from openlp.core.common import Registry, UiStrings, translate
+from openlp.core.common import Registry, UiStrings, translate, is_macosx
from openlp.core.lib import build_icon
from openlp.core.utils.actions import ActionList
@@ -247,6 +247,8 @@ def create_action(parent, name, **kwargs):
"""
action = QtGui.QAction(parent)
action.setObjectName(name)
+ if is_macosx():
+ action.setIconVisibleInMenu(False)
if kwargs.get('text'):
action.setText(kwargs.pop('text'))
if kwargs.get('icon'):
diff --git a/openlp/core/ui/firsttimeform.py b/openlp/core/ui/firsttimeform.py
index d7c16f0d3..8599c8d35 100644
--- a/openlp/core/ui/firsttimeform.py
+++ b/openlp/core/ui/firsttimeform.py
@@ -387,17 +387,21 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard, RegistryProperties):
self.progress_bar.setValue(self.progress_bar.maximum())
if self.has_run_wizard:
self.progress_label.setText(translate('OpenLP.FirstTimeWizard',
- 'Download complete. Click the finish button to return to OpenLP.'))
+ 'Download complete. Click the %s button to return to OpenLP.') %
+ self.buttonText(QtGui.QWizard.FinishButton))
else:
self.progress_label.setText(translate('OpenLP.FirstTimeWizard',
- 'Download complete. Click the finish button to start OpenLP.'))
+ 'Download complete. Click the %s button to start OpenLP.') %
+ self.buttonText(QtGui.QWizard.FinishButton))
else:
if self.has_run_wizard:
self.progress_label.setText(translate('OpenLP.FirstTimeWizard',
- 'Click the finish button to return to OpenLP.'))
+ 'Click the %s button to return to OpenLP.') %
+ self.buttonText(QtGui.QWizard.FinishButton))
else:
self.progress_label.setText(translate('OpenLP.FirstTimeWizard',
- 'Click the finish button to start OpenLP.'))
+ 'Click the %s button to start OpenLP.') %
+ self.buttonText(QtGui.QWizard.FinishButton))
self.finish_button.setVisible(True)
self.finish_button.setEnabled(True)
self.cancel_button.setVisible(False)
diff --git a/openlp/core/ui/firsttimewizard.py b/openlp/core/ui/firsttimewizard.py
index ff1675ff5..c5098eda6 100644
--- a/openlp/core/ui/firsttimewizard.py
+++ b/openlp/core/ui/firsttimewizard.py
@@ -31,9 +31,7 @@ The UI widgets for the first time wizard.
"""
from PyQt4 import QtCore, QtGui
-import sys
-
-from openlp.core.common import translate
+from openlp.core.common import translate, is_macosx
from openlp.core.lib import build_icon
from openlp.core.lib.ui import add_welcome_page
@@ -64,9 +62,12 @@ class Ui_FirstTimeWizard(object):
first_time_wizard.setWindowIcon(build_icon(u':/icon/openlp-logo.svg'))
first_time_wizard.resize(550, 386)
first_time_wizard.setModal(True)
- first_time_wizard.setWizardStyle(QtGui.QWizard.ModernStyle)
first_time_wizard.setOptions(QtGui.QWizard.IndependentPages | QtGui.QWizard.NoBackButtonOnStartPage |
QtGui.QWizard.NoBackButtonOnLastPage | QtGui.QWizard.HaveCustomButton1)
+ if is_macosx():
+ first_time_wizard.setPixmap(QtGui.QWizard.BackgroundPixmap,
+ QtGui.QPixmap(':/wizards/openlp-osx-wizard.png'))
+ first_time_wizard.resize(634, 386)
self.finish_button = self.button(QtGui.QWizard.FinishButton)
self.no_internet_finish_button = self.button(QtGui.QWizard.CustomButton1)
self.cancel_button = self.button(QtGui.QWizard.CancelButton)
@@ -212,7 +213,8 @@ class Ui_FirstTimeWizard(object):
translate('OpenLP.FirstTimeWizard', 'Welcome to the First Time Wizard'))
self.information_label.setText(
translate('OpenLP.FirstTimeWizard', 'This wizard will help you to configure OpenLP for initial use. '
- 'Click the next button below to start.'))
+ 'Click the %s button below to start.') %
+ self.buttonText(QtGui.QWizard.NextButton))
self.plugin_page.setTitle(translate('OpenLP.FirstTimeWizard', 'Activate required Plugins'))
self.plugin_page.setSubTitle(translate('OpenLP.FirstTimeWizard', 'Select the Plugins you wish to use. '))
self.songs_check_box.setText(translate('OpenLP.FirstTimeWizard', 'Songs'))
@@ -236,7 +238,7 @@ class Ui_FirstTimeWizard(object):
'wizard by selecting "Tools/Re-run First Time Wizard" from OpenLP.')
self.cancelWizardText = translate('OpenLP.FirstTimeWizard',
'\n\nTo cancel the First Time Wizard completely (and not start OpenLP), '
- 'click the Cancel button now.')
+ 'click the %s button now.') % self.buttonText(QtGui.QWizard.CancelButton)
self.songs_page.setTitle(translate('OpenLP.FirstTimeWizard', 'Sample Songs'))
self.songs_page.setSubTitle(translate('OpenLP.FirstTimeWizard', 'Select and download public domain songs.'))
self.bibles_page.setTitle(translate('OpenLP.FirstTimeWizard', 'Sample Bibles'))
diff --git a/openlp/core/ui/formattingtagform.py b/openlp/core/ui/formattingtagform.py
index 4f3d5d251..96e25c27d 100644
--- a/openlp/core/ui/formattingtagform.py
+++ b/openlp/core/ui/formattingtagform.py
@@ -60,6 +60,12 @@ class FormattingTagForm(QtGui.QDialog, Ui_FormattingTagDialog, FormattingTagCont
"""
super(FormattingTagForm, self).__init__(parent)
self.setupUi(self)
+ self._setup()
+
+ def _setup(self):
+ """
+ Set up the class. This method is mocked out by the tests.
+ """
self.services = FormattingTagController()
self.tag_table_widget.itemSelectionChanged.connect(self.on_row_selected)
self.new_button.clicked.connect(self.on_new_clicked)
diff --git a/openlp/core/ui/listpreviewwidget.py b/openlp/core/ui/listpreviewwidget.py
index 831eb182b..3909c6a31 100644
--- a/openlp/core/ui/listpreviewwidget.py
+++ b/openlp/core/ui/listpreviewwidget.py
@@ -94,8 +94,8 @@ class ListPreviewWidget(QtGui.QTableWidget, RegistryProperties):
Displays the given slide.
"""
self.service_item = service_item
- self.clear()
self.setRowCount(0)
+ self.clear()
self.setColumnWidth(0, width)
row = 0
text = []
diff --git a/openlp/core/ui/mainwindow.py b/openlp/core/ui/mainwindow.py
index 1a6b688c7..77a903c5f 100644
--- a/openlp/core/ui/mainwindow.py
+++ b/openlp/core/ui/mainwindow.py
@@ -92,6 +92,8 @@ class Ui_MainWindow(object):
main_window.setObjectName('MainWindow')
main_window.setWindowIcon(build_icon(':/icon/openlp-logo.svg'))
main_window.setDockNestingEnabled(True)
+ if is_macosx():
+ main_window.setDocumentMode(True)
# Set up the main container, which contains all the other form widgets.
self.main_content = QtGui.QWidget(main_window)
self.main_content.setObjectName('main_content')
@@ -118,10 +120,12 @@ class Ui_MainWindow(object):
self.recent_files_menu = QtGui.QMenu(self.file_menu)
self.recent_files_menu.setObjectName('recentFilesMenu')
self.file_import_menu = QtGui.QMenu(self.file_menu)
- self.file_import_menu.setIcon(build_icon(u':/general/general_import.png'))
+ if not is_macosx():
+ self.file_import_menu.setIcon(build_icon(u':/general/general_import.png'))
self.file_import_menu.setObjectName('file_import_menu')
self.file_export_menu = QtGui.QMenu(self.file_menu)
- self.file_export_menu.setIcon(build_icon(u':/general/general_export.png'))
+ if not is_macosx():
+ self.file_export_menu.setIcon(build_icon(u':/general/general_export.png'))
self.file_export_menu.setObjectName('file_export_menu')
# View Menu
self.view_menu = QtGui.QMenu(self.menu_bar)
diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py
index 369137694..d0ac1560e 100644
--- a/openlp/core/ui/servicemanager.py
+++ b/openlp/core/ui/servicemanager.py
@@ -1281,7 +1281,11 @@ class ServiceManager(OpenLPMixin, RegistryMixin, QtGui.QWidget, Ui_ServiceManage
# Add the children to their parent tree_widget_item.
for count, frame in enumerate(service_item_from_item.get_frames()):
child = QtGui.QTreeWidgetItem(tree_widget_item)
- text = frame['title'].replace('\n', ' ')
+ # prefer to use a display_title
+ if service_item_from_item.is_capable(ItemCapabilities.HasDisplayTitle):
+ text = frame['display_title'].replace('\n', ' ')
+ else:
+ text = frame['title'].replace('\n', ' ')
child.setText(0, text[:40])
child.setData(0, QtCore.Qt.UserRole, count)
if service_item == item_count:
diff --git a/openlp/core/ui/slidecontroller.py b/openlp/core/ui/slidecontroller.py
index 44c28deb6..b12438679 100644
--- a/openlp/core/ui/slidecontroller.py
+++ b/openlp/core/ui/slidecontroller.py
@@ -873,6 +873,7 @@ class SlideController(DisplayController, RegistryProperties):
if self.service_item.is_command():
Registry().execute('%s_slide' % self.service_item.name.lower(), [self.service_item, self.is_live, index])
self.update_preview()
+ self.selected_row = index
else:
self.preview_widget.change_slide(index)
self.slide_selected()
@@ -1042,8 +1043,8 @@ class SlideController(DisplayController, RegistryProperties):
self.display.image(to_display)
# reset the store used to display first image
self.service_item.bg_image_bytes = None
- self.update_preview()
self.selected_row = row
+ self.update_preview()
self.preview_widget.change_slide(row)
self.display.setFocus()
@@ -1055,6 +1056,7 @@ class SlideController(DisplayController, RegistryProperties):
"""
self.preview_widget.change_slide(row)
self.update_preview()
+ self.selected_row = row
def update_preview(self):
"""
diff --git a/openlp/core/ui/themewizard.py b/openlp/core/ui/themewizard.py
index bda52c807..50200313f 100644
--- a/openlp/core/ui/themewizard.py
+++ b/openlp/core/ui/themewizard.py
@@ -31,7 +31,7 @@ The Create/Edit theme wizard
"""
from PyQt4 import QtCore, QtGui
-from openlp.core.common import UiStrings, translate
+from openlp.core.common import UiStrings, translate, is_macosx
from openlp.core.lib import build_icon
from openlp.core.lib.theme import HorizontalType, BackgroundType, BackgroundGradientType
from openlp.core.lib.ui import add_welcome_page, create_valign_selection_widgets
@@ -41,19 +41,21 @@ class Ui_ThemeWizard(object):
"""
The Create/Edit theme wizard
"""
- def setupUi(self, themeWizard):
+ def setupUi(self, theme_wizard):
"""
Set up the UI
"""
- themeWizard.setObjectName('OpenLP.ThemeWizard')
- themeWizard.setWindowIcon(build_icon(u':/icon/openlp-logo.svg'))
- themeWizard.setModal(True)
- themeWizard.setWizardStyle(QtGui.QWizard.ModernStyle)
- themeWizard.setOptions(QtGui.QWizard.IndependentPages |
- QtGui.QWizard.NoBackButtonOnStartPage | QtGui.QWizard.HaveCustomButton1)
+ theme_wizard.setObjectName('OpenLP.ThemeWizard')
+ theme_wizard.setWindowIcon(build_icon(u':/icon/openlp-logo.svg'))
+ theme_wizard.setModal(True)
+ theme_wizard.setOptions(QtGui.QWizard.IndependentPages |
+ QtGui.QWizard.NoBackButtonOnStartPage | QtGui.QWizard.HaveCustomButton1)
+ if is_macosx():
+ theme_wizard.setPixmap(QtGui.QWizard.BackgroundPixmap, QtGui.QPixmap(':/wizards/openlp-osx-wizard.png'))
+ theme_wizard.resize(646, 400)
self.spacer = QtGui.QSpacerItem(10, 0, QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Minimum)
# Welcome Page
- add_welcome_page(themeWizard, ':/wizards/wizard_createtheme.bmp')
+ add_welcome_page(theme_wizard, ':/wizards/wizard_createtheme.bmp')
# Background Page
self.background_page = QtGui.QWizardPage()
self.background_page.setObjectName('background_page')
@@ -137,7 +139,7 @@ class Ui_ThemeWizard(object):
self.transparent_layout.setObjectName('Transparent_layout')
self.background_stack.addWidget(self.transparent_widget)
self.background_layout.addLayout(self.background_stack)
- themeWizard.addPage(self.background_page)
+ theme_wizard.addPage(self.background_page)
# Main Area Page
self.main_area_page = QtGui.QWizardPage()
self.main_area_page.setObjectName('main_area_page')
@@ -218,7 +220,7 @@ class Ui_ThemeWizard(object):
self.shadow_size_spin_box.setObjectName('shadow_size_spin_box')
self.shadow_layout.addWidget(self.shadow_size_spin_box)
self.main_area_layout.addRow(self.shadow_check_box, self.shadow_layout)
- themeWizard.addPage(self.main_area_page)
+ theme_wizard.addPage(self.main_area_page)
# Footer Area Page
self.footer_area_page = QtGui.QWizardPage()
self.footer_area_page.setObjectName('footer_area_page')
@@ -242,7 +244,7 @@ class Ui_ThemeWizard(object):
self.footer_size_spin_box.setObjectName('FooterSizeSpinBox')
self.footer_area_layout.addRow(self.footer_size_label, self.footer_size_spin_box)
self.footer_area_layout.setItem(3, QtGui.QFormLayout.LabelRole, self.spacer)
- themeWizard.addPage(self.footer_area_page)
+ theme_wizard.addPage(self.footer_area_page)
# Alignment Page
self.alignment_page = QtGui.QWizardPage()
self.alignment_page.setObjectName('alignment_page')
@@ -264,7 +266,7 @@ class Ui_ThemeWizard(object):
self.transitions_check_box.setObjectName('transitions_check_box')
self.alignment_layout.addRow(self.transitions_label, self.transitions_check_box)
self.alignment_layout.setItem(3, QtGui.QFormLayout.LabelRole, self.spacer)
- themeWizard.addPage(self.alignment_page)
+ theme_wizard.addPage(self.alignment_page)
# Area Position Page
self.area_position_page = QtGui.QWizardPage()
self.area_position_page.setObjectName('area_position_page')
@@ -334,7 +336,7 @@ class Ui_ThemeWizard(object):
self.footer_height_spin_box.setObjectName('footer_height_spin_box')
self.footer_position_layout.addRow(self.footer_height_label, self.footer_height_spin_box)
self.area_position_layout.addWidget(self.footer_position_group_box)
- themeWizard.addPage(self.area_position_page)
+ theme_wizard.addPage(self.area_position_page)
# Preview Page
self.preview_page = QtGui.QWizardPage()
self.preview_page.setObjectName('preview_page')
@@ -362,8 +364,8 @@ class Ui_ThemeWizard(object):
self.preview_box_label.setObjectName('preview_box_label')
self.preview_area_layout.addWidget(self.preview_box_label)
self.preview_layout.addWidget(self.preview_area)
- themeWizard.addPage(self.preview_page)
- self.retranslateUi(themeWizard)
+ theme_wizard.addPage(self.preview_page)
+ self.retranslateUi(theme_wizard)
QtCore.QObject.connect(self.background_combo_box, QtCore.SIGNAL('currentIndexChanged(int)'),
self.background_stack, QtCore.SLOT('setCurrentIndex(int)'))
QtCore.QObject.connect(self.outline_check_box, QtCore.SIGNAL('toggled(bool)'), self.outline_color_button,
@@ -391,11 +393,11 @@ class Ui_ThemeWizard(object):
QtCore.QObject.connect(self.footer_position_check_box, QtCore.SIGNAL('toggled(bool)'),
self.footer_height_spin_box, QtCore.SLOT('setDisabled(bool)'))
- def retranslateUi(self, themeWizard):
+ def retranslateUi(self, theme_wizard):
"""
Translate the UI on the fly
"""
- themeWizard.setWindowTitle(translate('OpenLP.ThemeWizard', 'Theme Wizard'))
+ theme_wizard.setWindowTitle(translate('OpenLP.ThemeWizard', 'Theme Wizard'))
self.title_label.setText('%s' %
translate('OpenLP.ThemeWizard', 'Welcome to the Theme Wizard'))
self.information_label.setText(
@@ -484,8 +486,8 @@ class Ui_ThemeWizard(object):
self.footer_height_label.setText(translate('OpenLP.ThemeWizard', 'Height:'))
self.footer_height_spin_box.setSuffix(translate('OpenLP.ThemeWizard', 'px'))
self.footer_position_check_box.setText(translate('OpenLP.ThemeWizard', 'Use default location'))
- themeWizard.setOption(QtGui.QWizard.HaveCustomButton1, False)
- themeWizard.setButtonText(QtGui.QWizard.CustomButton1, translate('OpenLP.ThemeWizard', 'Layout Preview'))
+ theme_wizard.setOption(QtGui.QWizard.HaveCustomButton1, False)
+ theme_wizard.setButtonText(QtGui.QWizard.CustomButton1, translate('OpenLP.ThemeWizard', 'Layout Preview'))
self.preview_page.setTitle(translate('OpenLP.ThemeWizard', 'Preview and Save'))
self.preview_page.setSubTitle(translate('OpenLP.ThemeWizard', 'Preview the theme and save it.'))
self.theme_name_label.setText(translate('OpenLP.ThemeWizard', 'Theme name:'))
diff --git a/openlp/core/ui/wizard.py b/openlp/core/ui/wizard.py
index 23bc0a9e1..7199d1742 100644
--- a/openlp/core/ui/wizard.py
+++ b/openlp/core/ui/wizard.py
@@ -34,7 +34,7 @@ import os
from PyQt4 import QtGui
-from openlp.core.common import Registry, RegistryProperties, Settings, UiStrings, translate
+from openlp.core.common import Registry, RegistryProperties, Settings, UiStrings, translate, is_macosx
from openlp.core.lib import build_icon
from openlp.core.lib.ui import add_welcome_page
@@ -121,9 +121,10 @@ class OpenLPWizard(QtGui.QWizard, RegistryProperties):
"""
self.setWindowIcon(build_icon(u':/icon/openlp-logo.svg'))
self.setModal(True)
- self.setWizardStyle(QtGui.QWizard.ModernStyle)
self.setOptions(QtGui.QWizard.IndependentPages |
QtGui.QWizard.NoBackButtonOnStartPage | QtGui.QWizard.NoBackButtonOnLastPage)
+ if is_macosx():
+ self.setPixmap(QtGui.QWizard.BackgroundPixmap, QtGui.QPixmap(':/wizards/openlp-osx-wizard.png'))
add_welcome_page(self, image)
self.add_custom_pages()
if self.with_progress_page:
diff --git a/openlp/plugins/bibles/lib/manager.py b/openlp/plugins/bibles/lib/manager.py
index 7dab8b3d7..436a7e34b 100644
--- a/openlp/plugins/bibles/lib/manager.py
+++ b/openlp/plugins/bibles/lib/manager.py
@@ -85,7 +85,7 @@ class BibleFormat(object):
BibleFormat.CSV,
BibleFormat.OpenSong,
BibleFormat.WebDownload,
- BibleFormar.Zefania,
+ BibleFormat.Zefania,
]
diff --git a/openlp/plugins/images/lib/mediaitem.py b/openlp/plugins/images/lib/mediaitem.py
index 36df55dac..d5892c250 100644
--- a/openlp/plugins/images/lib/mediaitem.py
+++ b/openlp/plugins/images/lib/mediaitem.py
@@ -551,6 +551,7 @@ class ImageMediaItem(MediaManagerItem):
service_item.add_capability(ItemCapabilities.CanLoop)
service_item.add_capability(ItemCapabilities.CanAppend)
service_item.add_capability(ItemCapabilities.CanEditTitle)
+ service_item.add_capability(ItemCapabilities.HasThumbnails)
# force a nonexistent theme
service_item.theme = -1
missing_items_file_names = []
@@ -589,7 +590,7 @@ class ImageMediaItem(MediaManagerItem):
# Continue with the existing images.
for filename in images_file_names:
name = os.path.split(filename)[1]
- service_item.add_from_image(filename, name, background)
+ service_item.add_from_image(filename, name, background, os.path.join(self.service_path, name))
return True
def check_group_exists(self, new_group):
diff --git a/openlp/plugins/media/forms/mediaclipselectorform.py b/openlp/plugins/media/forms/mediaclipselectorform.py
index 28d37f32e..d63e8a8bb 100644
--- a/openlp/plugins/media/forms/mediaclipselectorform.py
+++ b/openlp/plugins/media/forms/mediaclipselectorform.py
@@ -28,31 +28,29 @@
###############################################################################
import os
-if os.name == 'nt':
- from win32com.client import Dispatch
- import string
-import sys
-
-if sys.platform.startswith('linux'):
- import dbus
import logging
import re
from time import sleep
from datetime import datetime
-
from PyQt4 import QtCore, QtGui
-from openlp.core.common import translate
+from openlp.core.common import translate, is_win, is_linux, is_macosx, RegistryProperties
from openlp.plugins.media.forms.mediaclipselectordialog import Ui_MediaClipSelector
from openlp.core.lib.ui import critical_error_message_box
-from openlp.core.ui.media import format_milliseconds
+
+if is_win():
+ from win32com.client import Dispatch
+
+if is_linux():
+ import dbus
+
try:
from openlp.core.ui.media.vendor import vlc
except (ImportError, NameError, NotImplementedError):
pass
except OSError as e:
- if sys.platform.startswith('win'):
+ if is_win():
if not isinstance(e, WindowsError) and e.winerror != 126:
raise
else:
@@ -61,7 +59,7 @@ except OSError as e:
log = logging.getLogger(__name__)
-class MediaClipSelectorForm(QtGui.QDialog, Ui_MediaClipSelector):
+class MediaClipSelectorForm(QtGui.QDialog, Ui_MediaClipSelector, RegistryProperties):
"""
Class to manage the clip selection
"""
@@ -144,9 +142,9 @@ class MediaClipSelectorForm(QtGui.QDialog, Ui_MediaClipSelector):
# You have to give the id of the QFrame (or similar object)
# to vlc, different platforms have different functions for this.
win_id = int(self.preview_frame.winId())
- if sys.platform == "win32":
+ if is_win():
self.vlc_media_player.set_hwnd(win_id)
- elif sys.platform == "darwin":
+ elif is_macosx():
# We have to use 'set_nsobject' since Qt4 on OSX uses Cocoa
# framework and not the old Carbon.
self.vlc_media_player.set_nsobject(win_id)
@@ -190,7 +188,7 @@ class MediaClipSelectorForm(QtGui.QDialog, Ui_MediaClipSelector):
self.audio_cd = True
self.titles_combo_box.setDisabled(False)
self.titles_combo_box.setCurrentIndex(0)
- self.on_title_combo_box_currentIndexChanged(0)
+ self.on_titles_combo_box_currentIndexChanged(0)
return True
@@ -203,18 +201,21 @@ class MediaClipSelectorForm(QtGui.QDialog, Ui_MediaClipSelector):
"""
log.debug('on_load_disc_button_clicked')
self.disable_all()
+ self.application.set_busy_cursor()
path = self.media_path_combobox.currentText()
# Check if given path is non-empty and exists before starting VLC
if not path:
log.debug('no given path')
critical_error_message_box(message=translate('MediaPlugin.MediaClipSelectorForm', 'No path was given'))
self.toggle_disable_load_media(False)
+ self.application.set_normal_cursor()
return
if not os.path.exists(path):
log.debug('Given path does not exists')
critical_error_message_box(message=translate('MediaPlugin.MediaClipSelectorForm',
'Given path does not exists'))
self.toggle_disable_load_media(False)
+ self.application.set_normal_cursor()
return
# VLC behaves a bit differently on windows and linux when loading, which creates problems when trying to
# detect if we're dealing with a DVD or CD, so we use different loading approaches depending on the OS.
@@ -231,6 +232,7 @@ class MediaClipSelectorForm(QtGui.QDialog, Ui_MediaClipSelector):
critical_error_message_box(message=translate('MediaPlugin.MediaClipSelectorForm',
'An error happened during initialization of VLC player'))
self.toggle_disable_load_media(False)
+ self.application.set_normal_cursor()
return
# put the media in the media player
self.vlc_media_player.set_media(self.vlc_media)
@@ -241,6 +243,8 @@ class MediaClipSelectorForm(QtGui.QDialog, Ui_MediaClipSelector):
critical_error_message_box(message=translate('MediaPlugin.MediaClipSelectorForm',
'VLC player failed playing the media'))
self.toggle_disable_load_media(False)
+ self.application.set_normal_cursor()
+ self.vlc_media_player.audio_set_mute(False)
return
self.vlc_media_player.audio_set_mute(True)
if not self.media_state_wait(vlc.State.Playing):
@@ -249,23 +253,32 @@ class MediaClipSelectorForm(QtGui.QDialog, Ui_MediaClipSelector):
critical_error_message_box(message=translate('MediaPlugin.MediaClipSelectorForm',
'VLC player failed playing the media'))
self.toggle_disable_load_media(False)
+ self.application.set_normal_cursor()
+ self.vlc_media_player.audio_set_mute(False)
return
- self.vlc_media_player.audio_set_mute(True)
+ # pause
+ self.vlc_media_player.set_time(0)
+ self.vlc_media_player.set_pause(1)
+ self.media_state_wait(vlc.State.Paused)
+ self.toggle_disable_load_media(False)
+ self.application.set_normal_cursor()
+ self.vlc_media_player.audio_set_mute(False)
if not self.audio_cd:
+ # Temporarily disable signals
+ self.blockSignals(True)
# Get titles, insert in combobox
titles = self.vlc_media_player.video_get_title_description()
self.titles_combo_box.clear()
for title in titles:
self.titles_combo_box.addItem(title[1].decode(), title[0])
+ # Re-enable signals
+ self.blockSignals(False)
# Main title is usually title #1
if len(titles) > 1:
self.titles_combo_box.setCurrentIndex(1)
- else:
- self.titles_combo_box.setCurrentIndex(0)
# Enable audio track combobox if anything is in it
if len(titles) > 0:
self.titles_combo_box.setDisabled(False)
- self.toggle_disable_load_media(False)
log.debug('load_disc_button end - vlc_media_player state: %s' % self.vlc_media_player.get_state())
@QtCore.pyqtSlot(bool)
@@ -378,6 +391,7 @@ class MediaClipSelectorForm(QtGui.QDialog, Ui_MediaClipSelector):
if not self.vlc_media_player:
log.error('vlc_media_player was None')
return
+ self.application.set_busy_cursor()
if self.audio_cd:
self.vlc_media = self.audio_cd_tracks.item_at_index(index)
self.vlc_media_player.set_media(self.vlc_media)
@@ -385,14 +399,14 @@ class MediaClipSelectorForm(QtGui.QDialog, Ui_MediaClipSelector):
self.vlc_media_player.play()
if not self.media_state_wait(vlc.State.Playing):
log.error('Could not start playing audio cd, needed to get track info')
+ self.application.set_normal_cursor()
return
self.vlc_media_player.audio_set_mute(True)
- # Sleep 1 second to make sure VLC has the needed metadata
- sleep(1)
# pause
self.vlc_media_player.set_time(0)
self.vlc_media_player.set_pause(1)
self.vlc_media_player.audio_set_mute(False)
+ self.application.set_normal_cursor()
self.toggle_disable_player(False)
else:
self.vlc_media_player.set_title(index)
@@ -400,13 +414,13 @@ class MediaClipSelectorForm(QtGui.QDialog, Ui_MediaClipSelector):
self.vlc_media_player.play()
if not self.media_state_wait(vlc.State.Playing):
log.error('Could not start playing dvd, needed to get track info')
+ self.application.set_normal_cursor()
return
self.vlc_media_player.audio_set_mute(True)
- # Sleep 1 second to make sure VLC has the needed metadata
- sleep(1)
- self.vlc_media_player.set_time(0)
- # Get audio tracks, insert in combobox
+ # Get audio tracks
audio_tracks = self.vlc_media_player.audio_get_track_description()
+ log.debug('number of audio tracks: %d' % len(audio_tracks))
+ # Clear the audio track combobox, insert new tracks
self.audio_tracks_combobox.clear()
for audio_track in audio_tracks:
self.audio_tracks_combobox.addItem(audio_track[1].decode(), audio_track[0])
@@ -447,6 +461,7 @@ class MediaClipSelectorForm(QtGui.QDialog, Ui_MediaClipSelector):
self.vlc_media_player.set_pause(1)
loop_count += 1
log.debug('titles_combo_box end - vlc_media_player state: %s' % self.vlc_media_player.get_state())
+ self.application.set_normal_cursor()
@QtCore.pyqtSlot(int)
def on_audio_tracks_combobox_currentIndexChanged(self, index):
@@ -535,7 +550,7 @@ class MediaClipSelectorForm(QtGui.QDialog, Ui_MediaClipSelector):
"""
Saves the current media and trackinfo as a clip to the mediamanager
"""
- log.debug('in on_save_button_clicked')
+ log.debug('in MediaClipSelectorForm.accept')
start_time = self.start_position_edit.time()
start_time_ms = start_time.hour() * 60 * 60 * 1000 + \
start_time.minute() * 60 * 1000 + \
@@ -550,10 +565,23 @@ class MediaClipSelectorForm(QtGui.QDialog, Ui_MediaClipSelector):
path = self.media_path_combobox.currentText()
optical = ''
if self.audio_cd:
+ # Check for load problems
+ if start_time_ms is None or end_time_ms is None or title is None:
+ critical_error_message_box(translate('MediaPlugin.MediaClipSelectorForm', 'CD not loaded correctly'),
+ translate('MediaPlugin.MediaClipSelectorForm',
+ 'The CD was not loaded correctly, please re-load and try again.'))
+ return
optical = 'optical:%d:-1:-1:%d:%d:' % (title, start_time_ms, end_time_ms)
else:
audio_track = self.audio_tracks_combobox.itemData(self.audio_tracks_combobox.currentIndex())
subtitle_track = self.subtitle_tracks_combobox.itemData(self.subtitle_tracks_combobox.currentIndex())
+ # Check for load problems
+ if start_time_ms is None or end_time_ms is None or title is None or audio_track is None\
+ or subtitle_track is None:
+ critical_error_message_box(translate('MediaPlugin.MediaClipSelectorForm', 'DVD not loaded correctly'),
+ translate('MediaPlugin.MediaClipSelectorForm',
+ 'The DVD was not loaded correctly, please re-load and try again.'))
+ return
optical = 'optical:%d:%d:%d:%d:%d:' % (title, audio_track, subtitle_track, start_time_ms, end_time_ms)
# Ask for an alternative name for the mediaclip
while True:
@@ -595,7 +623,7 @@ class MediaClipSelectorForm(QtGui.QDialog, Ui_MediaClipSelector):
while media_state != self.vlc_media_player.get_state():
if self.vlc_media_player.get_state() == vlc.State.Error:
return False
- if (datetime.now() - start).seconds > 30:
+ if (datetime.now() - start).seconds > 15:
return False
return True
@@ -606,7 +634,7 @@ class MediaClipSelectorForm(QtGui.QDialog, Ui_MediaClipSelector):
"""
# Clear list first
self.media_path_combobox.clear()
- if os.name == 'nt':
+ if is_win():
# use win api to find optical drives
fso = Dispatch('scripting.filesystemobject')
for drive in fso.Drives:
@@ -614,7 +642,7 @@ class MediaClipSelectorForm(QtGui.QDialog, Ui_MediaClipSelector):
# if type is 4, it is a cd-rom drive
if drive.DriveType == 4:
self.media_path_combobox.addItem('%s:\\' % drive.DriveLetter)
- elif sys.platform.startswith('linux'):
+ elif is_linux():
# Get disc devices from dbus and find the ones that are optical
bus = dbus.SystemBus()
try:
@@ -646,7 +674,7 @@ class MediaClipSelectorForm(QtGui.QDialog, Ui_MediaClipSelector):
if chr(c) != '\x00':
block_file += chr(c)
self.media_path_combobox.addItem(block_file)
- elif sys.platform.startswith('darwin'):
+ elif is_macosx():
# Look for DVD folders in devices to find optical devices
volumes = os.listdir('/Volumes')
candidates = list()
diff --git a/openlp/plugins/presentations/lib/impresscontroller.py b/openlp/plugins/presentations/lib/impresscontroller.py
index d032b9161..a6e52411b 100644
--- a/openlp/plugins/presentations/lib/impresscontroller.py
+++ b/openlp/plugins/presentations/lib/impresscontroller.py
@@ -65,7 +65,7 @@ from PyQt4 import QtCore
from openlp.core.lib import ScreenList
from openlp.core.utils import delete_file, get_uno_command, get_uno_instance
-from .presentationcontroller import PresentationController, PresentationDocument
+from .presentationcontroller import PresentationController, PresentationDocument, TextType
log = logging.getLogger(__name__)
@@ -257,6 +257,7 @@ class ImpressDocument(PresentationDocument):
self.presentation.Display = ScreenList().current['number'] + 1
self.control = None
self.create_thumbnails()
+ self.create_titles_and_notes()
return True
def create_thumbnails(self):
@@ -450,22 +451,44 @@ class ImpressDocument(PresentationDocument):
:param slide_no: The slide the notes are required for, starting at 1
"""
- return self.__get_text_from_page(slide_no, True)
+ return self.__get_text_from_page(slide_no, TextType.Notes)
- def __get_text_from_page(self, slide_no, notes=False):
+ def __get_text_from_page(self, slide_no, text_type=TextType.SlideText):
"""
Return any text extracted from the presentation page.
:param slide_no: The slide the notes are required for, starting at 1
:param notes: A boolean. If set the method searches the notes of the slide.
+ :param text_type: A TextType. Enumeration of the types of supported text.
"""
text = ''
- pages = self.document.getDrawPages()
- page = pages.getByIndex(slide_no - 1)
- if notes:
- page = page.getNotesPage()
- for index in range(page.getCount()):
- shape = page.getByIndex(index)
- if shape.supportsService("com.sun.star.drawing.Text"):
- text += shape.getString() + '\n'
+ if TextType.Title <= text_type <= TextType.Notes:
+ pages = self.document.getDrawPages()
+ if 0 < slide_no <= pages.getCount():
+ page = pages.getByIndex(slide_no - 1)
+ if text_type == TextType.Notes:
+ page = page.getNotesPage()
+ for index in range(page.getCount()):
+ shape = page.getByIndex(index)
+ shape_type = shape.getShapeType()
+ if shape.supportsService("com.sun.star.drawing.Text"):
+ # if they requested title, make sure it is the title
+ if text_type != TextType.Title or shape_type == "com.sun.star.presentation.TitleTextShape":
+ text += shape.getString() + '\n'
return text
+
+ def create_titles_and_notes(self):
+ """
+ Writes the list of titles (one per slide) to 'titles.txt' and the notes to 'slideNotes[x].txt'
+ in the thumbnails directory
+ """
+ titles = []
+ notes = []
+ pages = self.document.getDrawPages()
+ for slide_no in range(1, pages.getCount() + 1):
+ titles.append(self.__get_text_from_page(slide_no, TextType.Title).replace('\n', ' ') + '\n')
+ note = self.__get_text_from_page(slide_no, TextType.Notes)
+ if len(note) == 0:
+ note = ' '
+ notes.append(note)
+ self.save_titles_and_notes(titles, notes)
diff --git a/openlp/plugins/presentations/lib/mediaitem.py b/openlp/plugins/presentations/lib/mediaitem.py
index 5b503d50f..5467d117d 100644
--- a/openlp/plugins/presentations/lib/mediaitem.py
+++ b/openlp/plugins/presentations/lib/mediaitem.py
@@ -288,13 +288,14 @@ class PresentationMediaItem(MediaManagerItem):
os.path.join(doc.get_temp_folder(), 'mainslide001.png')):
doc.load_presentation()
i = 1
- image_file = 'mainslide%03d.png' % i
- image = os.path.join(doc.get_temp_folder(), image_file)
+ image = os.path.join(doc.get_temp_folder(), 'mainslide%03d.png' % i)
+ thumbnail = os.path.join(doc.get_thumbnail_folder(), 'slide%d.png' % i)
while os.path.isfile(image):
- service_item.add_from_image(image, name)
+ service_item.add_from_image(image, name, thumbnail=thumbnail)
i += 1
- image_file = 'mainslide%03d.png' % i
- image = os.path.join(doc.get_temp_folder(), image_file)
+ image = os.path.join(doc.get_temp_folder(), 'mainslide%03d.png' % i)
+ thumbnail = os.path.join(doc.get_thumbnail_folder(), 'slide%d.png' % i)
+ service_item.add_capability(ItemCapabilities.HasThumbnails)
doc.close_presentation()
return True
else:
@@ -323,8 +324,21 @@ class PresentationMediaItem(MediaManagerItem):
i = 1
img = doc.get_thumbnail_path(i, True)
if img:
+ # Get titles and notes
+ titles, notes = doc.get_titles_and_notes()
+ service_item.add_capability(ItemCapabilities.HasDisplayTitle)
+ if notes.count('') != len(notes):
+ service_item.add_capability(ItemCapabilities.HasNotes)
+ service_item.add_capability(ItemCapabilities.HasThumbnails)
while img:
- service_item.add_from_command(path, name, img)
+ # Use title and note if available
+ title = ''
+ if titles and len(titles) >= i:
+ title = titles[i - 1]
+ note = ''
+ if notes and len(notes) >= i:
+ note = notes[i - 1]
+ service_item.add_from_command(path, name, img, title, note)
i += 1
img = doc.get_thumbnail_path(i, True)
doc.close_presentation()
diff --git a/openlp/plugins/presentations/lib/powerpointcontroller.py b/openlp/plugins/presentations/lib/powerpointcontroller.py
index f42e4f814..cf0f5bb99 100644
--- a/openlp/plugins/presentations/lib/powerpointcontroller.py
+++ b/openlp/plugins/presentations/lib/powerpointcontroller.py
@@ -27,7 +27,7 @@
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
-This modul is for controlling powerpiont. PPT API documentation:
+This module is for controlling powerpoint. PPT API documentation:
`http://msdn.microsoft.com/en-us/library/aa269321(office.10).aspx`_
"""
import os
@@ -37,16 +37,17 @@ from openlp.core.common import is_win
if is_win():
from win32com.client import Dispatch
+ import win32com
import winreg
import win32ui
import pywintypes
from openlp.core.lib import ScreenList
+from openlp.core.common import Registry
from openlp.core.lib.ui import UiStrings, critical_error_message_box, translate
from openlp.core.common import trace_error_handler
from .presentationcontroller import PresentationController, PresentationDocument
-
log = logging.getLogger(__name__)
@@ -136,6 +137,7 @@ class PowerpointDocument(PresentationDocument):
self.controller.process.Presentations.Open(self.file_path, False, False, True)
self.presentation = self.controller.process.Presentations(self.controller.process.Presentations.Count)
self.create_thumbnails()
+ self.create_titles_and_notes()
# Powerpoint 2013 pops up when loading a file, so we minimize it again
if self.presentation.Application.Version == u'15.0':
try:
@@ -392,6 +394,28 @@ class PowerpointDocument(PresentationDocument):
"""
return _get_text_from_shapes(self.presentation.Slides(slide_no).NotesPage.Shapes)
+ def create_titles_and_notes(self):
+ """
+ Writes the list of titles (one per slide)
+ to 'titles.txt'
+ and the notes to 'slideNotes[x].txt'
+ in the thumbnails directory
+ """
+ titles = []
+ notes = []
+ for slide in self.presentation.Slides:
+ try:
+ text = slide.Shapes.Title.TextFrame.TextRange.Text
+ except Exception as e:
+ log.exception(e)
+ text = ''
+ titles.append(text.replace('\n', ' ').replace('\x0b', ' ') + '\n')
+ note = _get_text_from_shapes(slide.NotesPage.Shapes)
+ if len(note) == 0:
+ note = ' '
+ notes.append(note)
+ self.save_titles_and_notes(titles, notes)
+
def show_error_msg(self):
"""
Stop presentation and display an error message.
@@ -410,8 +434,8 @@ def _get_text_from_shapes(shapes):
:param shapes: A set of shapes to search for text.
"""
text = ''
- for index in range(shapes.Count):
- shape = shapes(index + 1)
- if shape.HasTextFrame:
- text += shape.TextFrame.TextRange.Text + '\n'
+ for shape in shapes:
+ if shape.PlaceholderFormat.Type == 2: # 2 from is enum PpPlaceholderType.ppPlaceholderBody
+ if shape.HasTextFrame and shape.TextFrame.HasText:
+ text += shape.TextFrame.TextRange.Text + '\n'
return text
diff --git a/openlp/plugins/presentations/lib/pptviewcontroller.py b/openlp/plugins/presentations/lib/pptviewcontroller.py
index 7e03e322f..4aea501b4 100644
--- a/openlp/plugins/presentations/lib/pptviewcontroller.py
+++ b/openlp/plugins/presentations/lib/pptviewcontroller.py
@@ -29,6 +29,11 @@
import logging
import os
+import logging
+import zipfile
+import re
+from xml.etree import ElementTree
+
from openlp.core.common import is_win
@@ -127,14 +132,14 @@ class PptviewDocument(PresentationDocument):
temp_folder = self.get_temp_folder()
size = ScreenList().current['size']
rect = RECT(size.x(), size.y(), size.right(), size.bottom())
- file_path = os.path.normpath(self.file_path)
+ self.file_path = os.path.normpath(self.file_path)
preview_path = os.path.join(temp_folder, 'slide')
# Ensure that the paths are null terminated
- file_path = file_path.encode('utf-16-le') + b'\0'
+ self.file_path = self.file_path.encode('utf-16-le') + b'\0'
preview_path = preview_path.encode('utf-16-le') + b'\0'
if not os.path.isdir(temp_folder):
os.makedirs(temp_folder)
- self.ppt_id = self.controller.process.OpenPPT(file_path, None, rect, preview_path)
+ self.ppt_id = self.controller.process.OpenPPT(self.file_path, None, rect, preview_path)
if self.ppt_id >= 0:
self.create_thumbnails()
self.stop_presentation()
@@ -154,6 +159,68 @@ class PptviewDocument(PresentationDocument):
path = '%s\\slide%s.bmp' % (self.get_temp_folder(), str(idx + 1))
self.convert_thumbnail(path, idx + 1)
+ def create_titles_and_notes(self):
+ """
+ Extracts the titles and notes from the zipped file
+ and writes the list of titles (one per slide)
+ to 'titles.txt'
+ and the notes to 'slideNotes[x].txt'
+ in the thumbnails directory
+ """
+ titles = None
+ notes = None
+ filename = os.path.normpath(self.file_path)
+ # let's make sure we have a valid zipped presentation
+ if os.path.exists(filename) and zipfile.is_zipfile(filename):
+ namespaces = {"p": "http://schemas.openxmlformats.org/presentationml/2006/main",
+ "a": "http://schemas.openxmlformats.org/drawingml/2006/main"}
+ # open the file
+ with zipfile.ZipFile(filename) as zip_file:
+ # find the presentation.xml to get the slide count
+ with zip_file.open('ppt/presentation.xml') as pres:
+ tree = ElementTree.parse(pres)
+ nodes = tree.getroot().findall(".//p:sldIdLst/p:sldId", namespaces=namespaces)
+ # initialize the lists
+ titles = ['' for i in range(len(nodes))]
+ notes = ['' for i in range(len(nodes))]
+ # loop thru the file list to find slides and notes
+ for zip_info in zip_file.infolist():
+ node_type = ''
+ index = -1
+ list_to_add = None
+ # check if it is a slide
+ match = re.search("slides/slide(.+)\.xml", zip_info.filename)
+ if match:
+ index = int(match.group(1))-1
+ node_type = 'ctrTitle'
+ list_to_add = titles
+ # or a note
+ match = re.search("notesSlides/notesSlide(.+)\.xml", zip_info.filename)
+ if match:
+ index = int(match.group(1))-1
+ node_type = 'body'
+ list_to_add = notes
+ # if it is one of our files, index shouldn't be -1
+ if index >= 0:
+ with zip_file.open(zip_info) as zipped_file:
+ tree = ElementTree.parse(zipped_file)
+ text = ''
+ nodes = tree.getroot().findall(".//p:ph[@type='" + node_type + "']../../..//p:txBody//a:t",
+ namespaces=namespaces)
+ # if we found any content
+ if nodes and len(nodes) > 0:
+ for node in nodes:
+ if len(text) > 0:
+ text += '\n'
+ text += node.text
+ # Let's remove the \n from the titles and
+ # just add one at the end
+ if node_type == 'ctrTitle':
+ text = text.replace('\n', ' ').replace('\x0b', ' ') + '\n'
+ list_to_add[index] = text
+ # now let's write the files
+ self.save_titles_and_notes(titles, notes)
+
def close_presentation(self):
"""
Close presentation and clean up objects. Triggered by new object being added to SlideController or OpenLP being
diff --git a/openlp/plugins/presentations/lib/pptviewlib/test.pptx b/openlp/plugins/presentations/lib/pptviewlib/test.pptx
new file mode 100644
index 000000000..c8beab172
Binary files /dev/null and b/openlp/plugins/presentations/lib/pptviewlib/test.pptx differ
diff --git a/openlp/plugins/presentations/lib/presentationcontroller.py b/openlp/plugins/presentations/lib/presentationcontroller.py
index 3060bcdb0..c64d70016 100644
--- a/openlp/plugins/presentations/lib/presentationcontroller.py
+++ b/openlp/plugins/presentations/lib/presentationcontroller.py
@@ -293,6 +293,49 @@ class PresentationDocument(object):
"""
return ''
+ def get_titles_and_notes(self):
+ """
+ Reads the titles from the titles file and
+ the notes files and returns the content in two lists
+ """
+ titles = []
+ notes = []
+ titles_file = os.path.join(self.get_thumbnail_folder(), 'titles.txt')
+ if os.path.exists(titles_file):
+ try:
+ with open(titles_file) as fi:
+ titles = fi.read().splitlines()
+ except:
+ log.exception('Failed to open/read existing titles file')
+ titles = []
+ for slide_no, title in enumerate(titles, 1):
+ notes_file = os.path.join(self.get_thumbnail_folder(), 'slideNotes%d.txt' % slide_no)
+ note = ''
+ if os.path.exists(notes_file):
+ try:
+ with open(notes_file) as fn:
+ note = fn.read()
+ except:
+ log.exception('Failed to open/read notes file')
+ note = ''
+ notes.append(note)
+ return titles, notes
+
+ def save_titles_and_notes(self, titles, notes):
+ """
+ Performs the actual persisting of titles to the titles.txt
+ and notes to the slideNote%.txt
+ """
+ if titles:
+ titles_file = os.path.join(self.get_thumbnail_folder(), 'titles.txt')
+ with open(titles_file, mode='w') as fo:
+ fo.writelines(titles)
+ if notes:
+ for slide_no, note in enumerate(notes, 1):
+ notes_file = os.path.join(self.get_thumbnail_folder(), 'slideNotes%d.txt' % slide_no)
+ with open(notes_file, mode='w') as fn:
+ fn.write(note)
+
class PresentationController(object):
"""
@@ -427,3 +470,12 @@ class PresentationController(object):
def close_presentation(self):
pass
+
+
+class TextType(object):
+ """
+ Type Enumeration for Types of Text to request
+ """
+ Title = 0
+ SlideText = 1
+ Notes = 2
diff --git a/openlp/plugins/remotes/html/openlp.js b/openlp/plugins/remotes/html/openlp.js
index ffc9430c2..9f18c1552 100644
--- a/openlp/plugins/remotes/html/openlp.js
+++ b/openlp/plugins/remotes/html/openlp.js
@@ -87,16 +87,30 @@ window.OpenLP = {
var ul = $("#slide-controller > div[data-role=content] > ul[data-role=listview]");
ul.html("");
for (idx in data.results.slides) {
- var text = data.results.slides[idx]["tag"];
- if (text != "") text = text + ": ";
- text = text + data.results.slides[idx]["text"];
+ var indexInt = parseInt(idx,10);
+ var slide = data.results.slides[idx];
+ var text = slide["tag"];
+ if (text != "") {
+ text = text + ": ";
+ }
+ if (slide["title"]) {
+ text += slide["title"]
+ } else {
+ text += slide["text"];
+ }
+ if (slide["notes"]) {
+ text += ("