From b23abd0378526ddd8a10d18b7e27dcc0138fb158 Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Mon, 15 Jul 2013 21:20:00 +0200 Subject: [PATCH 01/18] First attempt to support pdf using mupdf or ghostscript --- openlp/plugins/presentations/lib/mediaitem.py | 2 +- .../presentations/lib/pdfcontroller.py | 427 ++++++++++++++++++ .../presentations/presentationplugin.py | 1 + ...lugins.presentations.presentationplugin.py | 3 +- 4 files changed, 431 insertions(+), 2 deletions(-) create mode 100644 openlp/plugins/presentations/lib/pdfcontroller.py diff --git a/openlp/plugins/presentations/lib/mediaitem.py b/openlp/plugins/presentations/lib/mediaitem.py index cef30a498..0f1bd440d 100644 --- a/openlp/plugins/presentations/lib/mediaitem.py +++ b/openlp/plugins/presentations/lib/mediaitem.py @@ -85,7 +85,7 @@ class PresentationMediaItem(MediaManagerItem): if self.controllers[controller].enabled(): file_types = self.controllers[controller].supports + self.controllers[controller].also_supports for file_type in file_types: - if file_type.find(file_type) == -1: + if file_type_list.find(file_type) == -1: file_type_list += u'*.%s ' % file_type self.service_manager.supported_suffixes(file_type) self.on_new_file_masks = translate('PresentationPlugin.MediaItem', 'Presentations (%s)') % file_type_list diff --git a/openlp/plugins/presentations/lib/pdfcontroller.py b/openlp/plugins/presentations/lib/pdfcontroller.py new file mode 100644 index 000000000..c01ef2e41 --- /dev/null +++ b/openlp/plugins/presentations/lib/pdfcontroller.py @@ -0,0 +1,427 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2013 Raoul Snyman # +# Portions copyright (c) 2008-2013 Tim Bentley, Gerald Britton, Jonathan # +# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, # +# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. # +# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, # +# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, # +# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, # +# Frode Woldsund, Martin Zibricky, Patrick Zimmermann # +# --------------------------------------------------------------------------- # +# 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 # +############################################################################### + +import os +import logging +from tempfile import NamedTemporaryFile +import re +from subprocess import check_output, call +from PyQt4 import QtCore, QtGui + +from openlp.core.lib import ScreenList +from presentationcontroller import PresentationController, PresentationDocument + + +log = logging.getLogger(__name__) + + +class PdfController(PresentationController): + """ + Class to control PDF presentations + """ + log.info(u'PdfController loaded') + + def __init__(self, plugin): + """ + Initialise the class + """ + log.debug(u'Initialising') + self.process = None + PresentationController.__init__(self, plugin, u'Pdf', PdfDocument) + self.supports = [u'pdf', u'xps'] + self.mudrawbin = u'' + self.gsbin = u'' + self.viewer = None + + def check_available(self): + """ + PdfController is able to run on this machine. + """ + log.debug(u'check_available Pdf') + return self.check_installed() + + def check_installed(self): + """ + Check the viewer is installed. + """ + log.debug(u'check_installed Pdf') + # First try to find mupdf + try: + self.mudrawbin = check_output([u'which', u'mudraw']).rstrip('\n') + except CalledProcessError: + self.mudrawbin = u'' + + # if mupdf isn't installed, fallback to ghostscript + if self.mudrawbin == u'': + try: + self.gsbin = check_output([u'which', u'gs']).rstrip('\n') + except CalledProcessError: + self.gsbin = u'' + + if self.mudrawbin == u'' and self.gsbin == u'': + return False + else: + return True + + def start_process(self): + log.debug(u'start_process pdf') + # Setup viewer + try: + size = ScreenList().current[u'size'] + self.viewer = PdfViewer(size) + except Exception as e: + log.debug(e) + + + def kill(self): + """ + Called at system exit to clean up any running presentations + """ + log.debug(u'Kill pdfviewer') + self.viewer.close() + while self.docs: + self.docs[0].close_presentation() + + +class PdfDocument(PresentationDocument): + """ + Class which holds information and controls a single presentation. + """ + def __init__(self, controller, presentation): + """ + Constructor, store information about the file and initialise. + """ + log.debug(u'Init Presentation Pdf') + PresentationDocument.__init__(self, controller, presentation) + self.presentation = None + self.blanked = False + self.hidden = False + self.image_files = [] + self.num_pages = -1 + + # Only used when using ghostscript + # Ghostscript can't scale automaticly while keeping aspect like mupdf, so we need + # to get the ratio bewteen the screen size and the PDF to scale + def gs_get_resolution(self, size): + # Use a postscript script to get size of the pdf. It is assumed that all pages have same size + postscript = u'%!PS \n\ +() = \n\ +File dup (r) file runpdfbegin \n\ +1 pdfgetpage dup \n\ +/MediaBox pget { \n\ +aload pop exch 4 1 roll exch sub 3 1 roll sub \n\ +( Size: x: ) print =print (, y: ) print =print (\n) print \n\ +} if \n\ +flush \n\ +quit \n\ +' + # Put postscript into tempfile + tmpfile = NamedTemporaryFile(delete=False) + tmpfile.write(postscript) + tmpfile.close() + + # Run the script on the pdf to get the size + runlog = check_output([self.controller.gsbin, u'-dNOPAUSE', u'-dNODISPLAY', u'-dBATCH', u'-sFile=' + self.filepath, tmpfile.name]) + os.unlink(tmpfile.name) + + # Extract the pdf resolution from output, the format is " Size: x: , y: " + width = 0 + height = 0 + for line in runlog.splitlines(): + try: + width = re.search(u'.*Size: x: (\d+), y: \d+.*', line).group(1) + height = re.search(u'.*Size: x: \d+, y: (\d+).*', line).group(1) + break; + except AttributeError: + pass + + # Calculate the ratio from pdf to screen + if width > 0 and height > 0: + width_ratio = size.right() / float(width) + heigth_ratio = size.bottom() / float(height) + + # return the resolution that should be used. 72 is default. + if width_ratio > heigth_ratio: + return int(heigth_ratio * 72) + else: + return int(width_ratio * 72) + else: + return 72 + + def load_presentation(self): + """ + Called when a presentation is added to the SlideController. It generates images from the PDF. + """ + log.debug(u'load_presentation pdf') + + # Check if the images has already been created, and if yes load them + if os.path.isfile(self.get_temp_folder() + u'/mainslide001.png'): + created_files = sorted(os.listdir(self.get_temp_folder())) + for fn in created_files: + if os.path.isfile(self.get_temp_folder() + u'/' + fn): + self.image_files.append(self.get_temp_folder()+ u'/' + fn) + self.num_pages = len(self.image_files) + return True + + size = ScreenList().current[u'size'] + # Generate images from PDF that will fit the frame. + runlog = u'' + try: + if not os.path.isdir(self.get_temp_folder()): + os.makedirs(self.get_temp_folder()) + if self.controller.mudrawbin != u'': + runlog = check_output([self.controller.mudrawbin, u'-w', str(size.right()), u'-h', str(size.bottom()), u'-o', self.get_temp_folder() + u'/mainslide%03d.png', self.filepath]) + elif self.controller.gsbin != u'': + resolution = self.gs_get_resolution(size) + runlog = check_output([self.controller.gsbin, u'-dSAFER', u'-dNOPAUSE', u'-dBATCH', u'-sDEVICE=png16m', u'-r' + str(resolution), u'-dTextAlphaBits=4', u'-dGraphicsAlphaBits=4', u'-sOutputFile=' + self.get_temp_folder() + u'/mainslide%03d.png', self.filepath]) + created_files = sorted(os.listdir(self.get_temp_folder())) + for fn in created_files: + if os.path.isfile(self.get_temp_folder() + u'/' + fn): + self.image_files.append(self.get_temp_folder()+ u'/' + fn) + except Exception as e: + log.debug(e) + log.debug(runlog) + return False + self.num_pages = len(self.image_files) + + # Create thumbnails + self.create_thumbnails() + return True + + def create_thumbnails(self): + """ + Generates thumbnails + """ + log.debug(u'create_thumbnails pdf') + if self.check_thumbnails(): + return + log.debug(u'create_thumbnails proceeding') + + # use builtin function to create thumbnails from generated images + index = 1 + for image in self.image_files: + self.convert_thumbnail(image, index) + index += 1 + + def close_presentation(self): + """ + Close presentation and clean up objects. Triggered by new object being added to SlideController or OpenLP being + shut down. + """ + log.debug(u'close_presentation pdf') + self.controller.remove_doc(self) + # TODO + + def is_loaded(self): + """ + Returns true if a presentation is loaded. + """ + log.debug(u'is_loaded pdf') + if self.num_pages < 0: + return False + return True + + def is_active(self): + """ + Returns true if a presentation is currently active. + """ + log.debug(u'is_active pdf') + return self.is_loaded() and not self.hidden + + def blank_screen(self): + """ + Blanks the screen. + """ + log.debug(u'blank_screen pdf') + self.blanked = True + self.controller.viewer.blank() + + def unblank_screen(self): + """ + Unblanks (restores) the presentation. + """ + log.debug(u'unblank_screen pdf') + self.blanked = False + self.controller.viewer.unblank() + + def is_blank(self): + """ + Returns true if screen is blank. + """ + log.debug(u'is blank pdf') + return self.blanked + + def stop_presentation(self): + """ + Stops the current presentation and hides the output. + """ + log.debug(u'stop_presentation pdf') + self.hidden = True + self.controller.viewer.stop() + + def start_presentation(self): + """ + Starts a presentation from the beginning. + """ + log.debug(u'start_presentation pdf') + if self.hidden: + self.hidden = False + self.controller.viewer.start(self.image_files) + + def get_slide_number(self): + """ + Return the current slide number on the screen, from 1. + """ + log.debug(u'get_slide_number pdf') + return self.controller.viewer.get_current_page() + 1 + + def get_slide_count(self): + """ + Returns total number of slides. + """ + log.debug(u'get_slide_count pdf') + return self.num_pages + + def goto_slide(self, slideno): + """ + Moves to a specific slide in the presentation. + """ + log.debug(u'goto_slide pdf' + str(slideno)) + self.controller.viewer.show_page(slideno - 1) + # TODO + + def next_step(self): + """ + Triggers the next effect of slide on the running presentation. + """ + log.debug(u'next_step pdf') + self.controller.viewer.next_page() + + def previous_step(self): + """ + Triggers the previous slide on the running presentation. + """ + log.debug(u'previous_step pdf') + self.controller.viewer.previous_page() + + +class PdfViewer(QtGui.QWidget): + def __init__(self, rect): + log.debug(u'initialised pdf viewer') + QtGui.QWidget.__init__(self, None) + self.setWindowTitle("PDF Viewer") + p = QtGui.QPalette() + p.setColor(QtGui.QPalette.Background, QtCore.Qt.black); + self.setPalette(p) + self.setGeometry(rect) # QtGui.QApplication.desktop().screenGeometry()) + self.hide() + self.num_pages = 0 + self.pdf_images = [] + self.image_files = [] + self.is_blanked = False + self.current_page = 0 + + def keyPressEvent(self, event): + if event.key() == QtCore.Qt.Key_Down: + self.next_page() + elif event.key() == QtCore.Qt.Key_Up: + self.previous_page() + elif event.key() == QtCore.Qt.Key_Escape: + self.stop() + + def paintEvent(self, event): + if self.is_blanked: + return + img = self.get_image(self.current_page) + if img is None: + return + x = (self.frameSize().width() - img.width()) / 2 + y = (self.frameSize().height() - img.height()) / 2 + painter = QtGui.QPainter(self) + painter.drawImage(x, y, img, 0, 0, 0, 0) + + def display(self): + self.update() + self.cache_image(self.current_page + 1) + + def start(self, images): + log.debug(u'start pdfviewer') + self.image_files = images + self.num_pages = len(self.image_files) + self.pdf_images = [None for i in range(self.num_pages)] + self.showFullScreen() + self.show() + + def stop(self): + log.debug(u'stop pdfviewer') + self.hide() + + def close(self): + log.debug(u'close pdfviewer') + self.stop() + self.pdf_images = None + self.image_files = None + + def blank(self): + self.is_blanked = True + self.update() + + def unblank(self): + self.is_blanked = False + self.update() + + def next_page(self): + if self.current_page + 1 < self.num_pages: + self.current_page += 1 + self.display() + + def previous_page(self): + if self.current_page > 0: + self.current_page -= 1 + self.display() + + def show_page(self, idx): + if idx < self.num_pages: + self.current_page = idx + self.display() + + def cache_image(self, idx): + if idx >= self.num_pages: + return + if self.image_files[idx] is None: + return + img = QtGui.QImage(self.image_files[idx]) + self.pdf_images[idx] = img + + def get_image(self, idx): + self.cache_image(idx) + return self.pdf_images[idx] + + def get_current_page(self): + return self.current_page diff --git a/openlp/plugins/presentations/presentationplugin.py b/openlp/plugins/presentations/presentationplugin.py index cc1516b69..b11975591 100644 --- a/openlp/plugins/presentations/presentationplugin.py +++ b/openlp/plugins/presentations/presentationplugin.py @@ -48,6 +48,7 @@ __default_settings__ = { u'presentations/Impress': QtCore.Qt.Checked, u'presentations/Powerpoint': QtCore.Qt.Checked, u'presentations/Powerpoint Viewer': QtCore.Qt.Checked, + u'presentations/Pdf': QtCore.Qt.Checked, u'presentations/presentations files': [] } diff --git a/resources/pyinstaller/hook-openlp.plugins.presentations.presentationplugin.py b/resources/pyinstaller/hook-openlp.plugins.presentations.presentationplugin.py index 4948a641f..0e7190b32 100644 --- a/resources/pyinstaller/hook-openlp.plugins.presentations.presentationplugin.py +++ b/resources/pyinstaller/hook-openlp.plugins.presentations.presentationplugin.py @@ -29,4 +29,5 @@ hiddenimports = ['openlp.plugins.presentations.lib.impresscontroller', 'openlp.plugins.presentations.lib.powerpointcontroller', - 'openlp.plugins.presentations.lib.pptviewcontroller'] + 'openlp.plugins.presentations.lib.pptviewcontroller', + 'openlp.plugins.presentations.lib.pdfcontroller'] From fa537260f627851f9f6e3ab773c9e9ab9f305a2a Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Tue, 16 Jul 2013 22:59:52 +0200 Subject: [PATCH 02/18] Various fixes for ghostscript. Tried to support windows+mupdf. --- .../presentations/lib/pdfcontroller.py | 51 ++++++++++++------- 1 file changed, 34 insertions(+), 17 deletions(-) diff --git a/openlp/plugins/presentations/lib/pdfcontroller.py b/openlp/plugins/presentations/lib/pdfcontroller.py index c01ef2e41..cec86f84a 100644 --- a/openlp/plugins/presentations/lib/pdfcontroller.py +++ b/openlp/plugins/presentations/lib/pdfcontroller.py @@ -31,9 +31,10 @@ import os import logging from tempfile import NamedTemporaryFile import re -from subprocess import check_output, call +from subprocess import check_output, CalledProcessError from PyQt4 import QtCore, QtGui +from openlp.core.utils import AppLocation from openlp.core.lib import ScreenList from presentationcontroller import PresentationController, PresentationDocument @@ -54,9 +55,11 @@ class PdfController(PresentationController): log.debug(u'Initialising') self.process = None PresentationController.__init__(self, plugin, u'Pdf', PdfDocument) - self.supports = [u'pdf', u'xps'] + self.supports = [u'pdf'] self.mudrawbin = u'' self.gsbin = u'' + if self.check_installed() and self.mudrawbin != u'': + self.also_supports = [u'xps'] self.viewer = None def check_available(self): @@ -70,20 +73,34 @@ class PdfController(PresentationController): """ Check the viewer is installed. """ + application_path = AppLocation.get_directory(AppLocation.AppDir) + print application_path log.debug(u'check_installed Pdf') - # First try to find mupdf - try: - self.mudrawbin = check_output([u'which', u'mudraw']).rstrip('\n') - except CalledProcessError: - self.mudrawbin = u'' - - # if mupdf isn't installed, fallback to ghostscript - if self.mudrawbin == u'': + if os.name != u'nt': + # First try to find mupdf try: - self.gsbin = check_output([u'which', u'gs']).rstrip('\n') + self.mudrawbin = check_output([u'which', u'mudraw']).rstrip('\n') except CalledProcessError: - self.gsbin = u'' + self.mudrawbin = u'' + + # if mupdf isn't installed, fallback to ghostscript + if self.mudrawbin == u'': + try: + self.gsbin = check_output([u'which', u'gs']).rstrip('\n') + except CalledProcessError: + self.gsbin = u'' + # Last option: check if mudraw is placed in OpenLP base folder + if self.mudrawbin == u'' and self.gsbin == u'': + application_path = AppLocation.get_directory(AppLocation.AppDir) + if os.path.isfile(application_path + u'/../mudraw'): + self.mudrawbin = application_path + u'/../mudraw' + else: + # for windows we only accept mudraw.exe in the base folder + application_path = AppLocation.get_directory(AppLocation.AppDir) + if os.path.isfile(application_path + u'/../mudraw.exe'): + self.mudrawbin = application_path + u'/../mudraw.exe' + if self.mudrawbin == u'' and self.gsbin == u'': return False else: @@ -155,8 +172,8 @@ quit \n\ height = 0 for line in runlog.splitlines(): try: - width = re.search(u'.*Size: x: (\d+), y: \d+.*', line).group(1) - height = re.search(u'.*Size: x: \d+, y: (\d+).*', line).group(1) + width = re.search(u'.*Size: x: (\d+\.?\d*), y: \d+.*', line).group(1) + height = re.search(u'.*Size: x: \d+\.?\d*, y: (\d+\.?\d*).*', line).group(1) break; except AttributeError: pass @@ -164,11 +181,11 @@ quit \n\ # Calculate the ratio from pdf to screen if width > 0 and height > 0: width_ratio = size.right() / float(width) - heigth_ratio = size.bottom() / float(height) + height_ratio = size.bottom() / float(height) # return the resolution that should be used. 72 is default. - if width_ratio > heigth_ratio: - return int(heigth_ratio * 72) + if width_ratio > height_ratio: + return int(height_ratio * 72) else: return int(width_ratio * 72) else: From 308d5ed5ee56ddcb21f9156caf3f34cabca13941 Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Sat, 27 Jul 2013 11:43:04 +0200 Subject: [PATCH 03/18] Instead of using a special PDF-player, now we present the images from PDF using the image-presentation features. --- openlp/plugins/presentations/lib/mediaitem.py | 117 +++++++--- .../presentations/lib/pdfcontroller.py | 209 ++---------------- 2 files changed, 99 insertions(+), 227 deletions(-) diff --git a/openlp/plugins/presentations/lib/mediaitem.py b/openlp/plugins/presentations/lib/mediaitem.py index 0f1bd440d..577f0cf62 100644 --- a/openlp/plugins/presentations/lib/mediaitem.py +++ b/openlp/plugins/presentations/lib/mediaitem.py @@ -244,45 +244,100 @@ class PresentationMediaItem(MediaManagerItem): items = self.list_view.selectedItems() if len(items) > 1: return False - service_item.processor = self.display_type_combo_box.currentText() - service_item.add_capability(ItemCapabilities.ProvidesOwnDisplay) + + filename = items[0].data(QtCore.Qt.UserRole) + file_type = os.path.splitext(filename)[1][1:] if not self.display_type_combo_box.currentText(): return False - for bitem in items: - filename = bitem.data(QtCore.Qt.UserRole) - (path, name) = os.path.split(filename) - service_item.title = name - if os.path.exists(filename): - if service_item.processor == self.Automatic: - service_item.processor = self.findControllerByType(filename) - if not service_item.processor: + + service_item.processor = self.display_type_combo_box.currentText() + + if context == ServiceItemContext.Live and (file_type == u'pdf' or file_type == u'xps'): + service_item.add_capability(ItemCapabilities.CanMaintain) + service_item.add_capability(ItemCapabilities.CanPreview) + service_item.add_capability(ItemCapabilities.CanLoop) + service_item.add_capability(ItemCapabilities.CanAppend) + # force a nonexistent theme + service_item.theme = -1 + + # Why the loop when we above return False if len(items) is > 1? + for bitem in items: + filename = bitem.data(QtCore.Qt.UserRole) + (path, name) = os.path.split(filename) + service_item.title = name + if os.path.exists(filename): + if service_item.processor == self.Automatic: + service_item.processor = self.findControllerByType(filename) + if not service_item.processor: + return False + controller = self.controllers[service_item.processor] + #service_item.processor = None + doc = controller.add_document(filename) + if doc.get_thumbnail_path(1, True) is None: + doc.load_presentation() + i = 1 + imagefile = u'mainslide%03d.png' % i + img = os.path.join(doc.get_temp_folder(), imagefile) + if os.path.isfile(img): + while os.path.isfile(img): + service_item.add_from_image(img, name) + i += 1 + imagefile = u'mainslide%03d.png' % i + img = os.path.join(doc.get_temp_folder(), imagefile) + doc.close_presentation() + return True + else: + # File is no longer present + if not remote: + critical_error_message_box(translate('PresentationPlugin.MediaItem', 'Missing Presentation'), + translate('PresentationPlugin.MediaItem', + 'The presentation %s is incomplete, please reload.') % filename) return False - controller = self.controllers[service_item.processor] - doc = controller.add_document(filename) - if doc.get_thumbnail_path(1, True) is None: - doc.load_presentation() - i = 1 - img = doc.get_thumbnail_path(i, True) - if img: - while img: - service_item.add_from_command(path, name, img) - i += 1 - img = doc.get_thumbnail_path(i, True) - doc.close_presentation() - return True else: # File is no longer present if not remote: critical_error_message_box(translate('PresentationPlugin.MediaItem', 'Missing Presentation'), - translate('PresentationPlugin.MediaItem', - 'The presentation %s is incomplete, please reload.') % filename) + translate('PresentationPlugin.MediaItem', 'The presentation %s no longer exists.') % filename) + return False + else: + service_item.add_capability(ItemCapabilities.ProvidesOwnDisplay) + + # Why the loop when we above return False if len(items) is > 1? + for bitem in items: + filename = bitem.data(QtCore.Qt.UserRole) + (path, name) = os.path.split(filename) + service_item.title = name + if os.path.exists(filename): + if service_item.processor == self.Automatic: + service_item.processor = self.findControllerByType(filename) + if not service_item.processor: + return False + controller = self.controllers[service_item.processor] + doc = controller.add_document(filename) + if doc.get_thumbnail_path(1, True) is None: + doc.load_presentation() + i = 1 + img = doc.get_thumbnail_path(i, True) + if img: + while img: + service_item.add_from_command(path, name, img) + i += 1 + img = doc.get_thumbnail_path(i, True) + doc.close_presentation() + return True + else: + # File is no longer present + if not remote: + critical_error_message_box(translate('PresentationPlugin.MediaItem', 'Missing Presentation'), + translate('PresentationPlugin.MediaItem', + 'The presentation %s is incomplete, please reload.') % filename) + return False + else: + # File is no longer present + if not remote: + critical_error_message_box(translate('PresentationPlugin.MediaItem', 'Missing Presentation'), + translate('PresentationPlugin.MediaItem', 'The presentation %s no longer exists.') % filename) return False - else: - # File is no longer present - if not remote: - critical_error_message_box(translate('PresentationPlugin.MediaItem', 'Missing Presentation'), - translate('PresentationPlugin.MediaItem', 'The presentation %s no longer exists.') % filename) - return False def findControllerByType(self, filename): """ diff --git a/openlp/plugins/presentations/lib/pdfcontroller.py b/openlp/plugins/presentations/lib/pdfcontroller.py index cec86f84a..ff79c6231 100644 --- a/openlp/plugins/presentations/lib/pdfcontroller.py +++ b/openlp/plugins/presentations/lib/pdfcontroller.py @@ -60,7 +60,6 @@ class PdfController(PresentationController): self.gsbin = u'' if self.check_installed() and self.mudrawbin != u'': self.also_supports = [u'xps'] - self.viewer = None def check_available(self): """ @@ -74,7 +73,6 @@ class PdfController(PresentationController): Check the viewer is installed. """ application_path = AppLocation.get_directory(AppLocation.AppDir) - print application_path log.debug(u'check_installed Pdf') if os.name != u'nt': # First try to find mupdf @@ -105,30 +103,19 @@ class PdfController(PresentationController): return False else: return True - - def start_process(self): - log.debug(u'start_process pdf') - # Setup viewer - try: - size = ScreenList().current[u'size'] - self.viewer = PdfViewer(size) - except Exception as e: - log.debug(e) - def kill(self): """ Called at system exit to clean up any running presentations """ log.debug(u'Kill pdfviewer') - self.viewer.close() while self.docs: self.docs[0].close_presentation() class PdfDocument(PresentationDocument): """ - Class which holds information and controls a single presentation. + Class which holds information of a single presentation. """ def __init__(self, controller, presentation): """ @@ -142,10 +129,12 @@ class PdfDocument(PresentationDocument): self.image_files = [] self.num_pages = -1 - # Only used when using ghostscript - # Ghostscript can't scale automaticly while keeping aspect like mupdf, so we need - # to get the ratio bewteen the screen size and the PDF to scale def gs_get_resolution(self, size): + """ + Only used when using ghostscript + Ghostscript can't scale automaticly while keeping aspect like mupdf, so we need + to get the ratio bewteen the screen size and the PDF to scale + """ # Use a postscript script to get size of the pdf. It is assumed that all pages have same size postscript = u'%!PS \n\ () = \n\ @@ -198,11 +187,11 @@ quit \n\ log.debug(u'load_presentation pdf') # Check if the images has already been created, and if yes load them - if os.path.isfile(self.get_temp_folder() + u'/mainslide001.png'): + if os.path.isfile(os.path.join(self.get_temp_folder(), u'mainslide001.png')): created_files = sorted(os.listdir(self.get_temp_folder())) for fn in created_files: - if os.path.isfile(self.get_temp_folder() + u'/' + fn): - self.image_files.append(self.get_temp_folder()+ u'/' + fn) + if os.path.isfile(os.path.join(self.get_temp_folder(), fn)): + self.image_files.append(os.path.join(self.get_temp_folder(), fn)) self.num_pages = len(self.image_files) return True @@ -213,14 +202,14 @@ quit \n\ if not os.path.isdir(self.get_temp_folder()): os.makedirs(self.get_temp_folder()) if self.controller.mudrawbin != u'': - runlog = check_output([self.controller.mudrawbin, u'-w', str(size.right()), u'-h', str(size.bottom()), u'-o', self.get_temp_folder() + u'/mainslide%03d.png', self.filepath]) + runlog = check_output([self.controller.mudrawbin, u'-w', str(size.right()), u'-h', str(size.bottom()), u'-o', os.path.join(self.get_temp_folder(), u'mainslide%03d.png'), self.filepath]) elif self.controller.gsbin != u'': resolution = self.gs_get_resolution(size) - runlog = check_output([self.controller.gsbin, u'-dSAFER', u'-dNOPAUSE', u'-dBATCH', u'-sDEVICE=png16m', u'-r' + str(resolution), u'-dTextAlphaBits=4', u'-dGraphicsAlphaBits=4', u'-sOutputFile=' + self.get_temp_folder() + u'/mainslide%03d.png', self.filepath]) + runlog = check_output([self.controller.gsbin, u'-dSAFER', u'-dNOPAUSE', u'-dBATCH', u'-sDEVICE=png16m', u'-r' + str(resolution), u'-dTextAlphaBits=4', u'-dGraphicsAlphaBits=4', u'-sOutputFile=' + os.path.join(self.get_temp_folder(), u'mainslide%03d.png'), self.filepath]) created_files = sorted(os.listdir(self.get_temp_folder())) for fn in created_files: - if os.path.isfile(self.get_temp_folder() + u'/' + fn): - self.image_files.append(self.get_temp_folder()+ u'/' + fn) + if os.path.isfile(os.path.join(self.get_temp_folder(), fn)): + self.image_files.append(os.path.join(self.get_temp_folder(), fn)) except Exception as e: log.debug(e) log.debug(runlog) @@ -253,7 +242,6 @@ quit \n\ """ log.debug(u'close_presentation pdf') self.controller.remove_doc(self) - # TODO def is_loaded(self): """ @@ -271,174 +259,3 @@ quit \n\ log.debug(u'is_active pdf') return self.is_loaded() and not self.hidden - def blank_screen(self): - """ - Blanks the screen. - """ - log.debug(u'blank_screen pdf') - self.blanked = True - self.controller.viewer.blank() - - def unblank_screen(self): - """ - Unblanks (restores) the presentation. - """ - log.debug(u'unblank_screen pdf') - self.blanked = False - self.controller.viewer.unblank() - - def is_blank(self): - """ - Returns true if screen is blank. - """ - log.debug(u'is blank pdf') - return self.blanked - - def stop_presentation(self): - """ - Stops the current presentation and hides the output. - """ - log.debug(u'stop_presentation pdf') - self.hidden = True - self.controller.viewer.stop() - - def start_presentation(self): - """ - Starts a presentation from the beginning. - """ - log.debug(u'start_presentation pdf') - if self.hidden: - self.hidden = False - self.controller.viewer.start(self.image_files) - - def get_slide_number(self): - """ - Return the current slide number on the screen, from 1. - """ - log.debug(u'get_slide_number pdf') - return self.controller.viewer.get_current_page() + 1 - - def get_slide_count(self): - """ - Returns total number of slides. - """ - log.debug(u'get_slide_count pdf') - return self.num_pages - - def goto_slide(self, slideno): - """ - Moves to a specific slide in the presentation. - """ - log.debug(u'goto_slide pdf' + str(slideno)) - self.controller.viewer.show_page(slideno - 1) - # TODO - - def next_step(self): - """ - Triggers the next effect of slide on the running presentation. - """ - log.debug(u'next_step pdf') - self.controller.viewer.next_page() - - def previous_step(self): - """ - Triggers the previous slide on the running presentation. - """ - log.debug(u'previous_step pdf') - self.controller.viewer.previous_page() - - -class PdfViewer(QtGui.QWidget): - def __init__(self, rect): - log.debug(u'initialised pdf viewer') - QtGui.QWidget.__init__(self, None) - self.setWindowTitle("PDF Viewer") - p = QtGui.QPalette() - p.setColor(QtGui.QPalette.Background, QtCore.Qt.black); - self.setPalette(p) - self.setGeometry(rect) # QtGui.QApplication.desktop().screenGeometry()) - self.hide() - self.num_pages = 0 - self.pdf_images = [] - self.image_files = [] - self.is_blanked = False - self.current_page = 0 - - def keyPressEvent(self, event): - if event.key() == QtCore.Qt.Key_Down: - self.next_page() - elif event.key() == QtCore.Qt.Key_Up: - self.previous_page() - elif event.key() == QtCore.Qt.Key_Escape: - self.stop() - - def paintEvent(self, event): - if self.is_blanked: - return - img = self.get_image(self.current_page) - if img is None: - return - x = (self.frameSize().width() - img.width()) / 2 - y = (self.frameSize().height() - img.height()) / 2 - painter = QtGui.QPainter(self) - painter.drawImage(x, y, img, 0, 0, 0, 0) - - def display(self): - self.update() - self.cache_image(self.current_page + 1) - - def start(self, images): - log.debug(u'start pdfviewer') - self.image_files = images - self.num_pages = len(self.image_files) - self.pdf_images = [None for i in range(self.num_pages)] - self.showFullScreen() - self.show() - - def stop(self): - log.debug(u'stop pdfviewer') - self.hide() - - def close(self): - log.debug(u'close pdfviewer') - self.stop() - self.pdf_images = None - self.image_files = None - - def blank(self): - self.is_blanked = True - self.update() - - def unblank(self): - self.is_blanked = False - self.update() - - def next_page(self): - if self.current_page + 1 < self.num_pages: - self.current_page += 1 - self.display() - - def previous_page(self): - if self.current_page > 0: - self.current_page -= 1 - self.display() - - def show_page(self, idx): - if idx < self.num_pages: - self.current_page = idx - self.display() - - def cache_image(self, idx): - if idx >= self.num_pages: - return - if self.image_files[idx] is None: - return - img = QtGui.QImage(self.image_files[idx]) - self.pdf_images[idx] = img - - def get_image(self, idx): - self.cache_image(idx) - return self.pdf_images[idx] - - def get_current_page(self): - return self.current_page From d28d75a2ce8a6d77ab577212da9c8457b2da308f Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Sun, 4 Aug 2013 20:51:22 +0100 Subject: [PATCH 04/18] Added possible to point to a binary to use for pdf-processing. --- openlp/plugins/presentations/lib/mediaitem.py | 14 ++- .../presentations/lib/messagelistener.py | 7 +- .../presentations/lib/pdfcontroller.py | 49 ++++++++- .../presentations/lib/presentationtab.py | 101 +++++++++++++++++- .../presentations/presentationplugin.py | 2 + 5 files changed, 158 insertions(+), 15 deletions(-) diff --git a/openlp/plugins/presentations/lib/mediaitem.py b/openlp/plugins/presentations/lib/mediaitem.py index 577f0cf62..dabab5c04 100644 --- a/openlp/plugins/presentations/lib/mediaitem.py +++ b/openlp/plugins/presentations/lib/mediaitem.py @@ -249,8 +249,6 @@ class PresentationMediaItem(MediaManagerItem): file_type = os.path.splitext(filename)[1][1:] if not self.display_type_combo_box.currentText(): return False - - service_item.processor = self.display_type_combo_box.currentText() if context == ServiceItemContext.Live and (file_type == u'pdf' or file_type == u'xps'): service_item.add_capability(ItemCapabilities.CanMaintain) @@ -266,12 +264,11 @@ class PresentationMediaItem(MediaManagerItem): (path, name) = os.path.split(filename) service_item.title = name if os.path.exists(filename): - if service_item.processor == self.Automatic: - service_item.processor = self.findControllerByType(filename) - if not service_item.processor: - return False - controller = self.controllers[service_item.processor] - #service_item.processor = None + processor = self.findControllerByType(filename) + if not processor: + return False + controller = self.controllers[processor] + service_item.processor = None doc = controller.add_document(filename) if doc.get_thumbnail_path(1, True) is None: doc.load_presentation() @@ -300,6 +297,7 @@ class PresentationMediaItem(MediaManagerItem): translate('PresentationPlugin.MediaItem', 'The presentation %s no longer exists.') % filename) return False else: + service_item.processor = self.display_type_combo_box.currentText() service_item.add_capability(ItemCapabilities.ProvidesOwnDisplay) # Why the loop when we above return False if len(items) is > 1? diff --git a/openlp/plugins/presentations/lib/messagelistener.py b/openlp/plugins/presentations/lib/messagelistener.py index cd7c654a2..5df013c2e 100644 --- a/openlp/plugins/presentations/lib/messagelistener.py +++ b/openlp/plugins/presentations/lib/messagelistener.py @@ -324,7 +324,12 @@ class MessageListener(object): controller = self.live_handler else: controller = self.preview_handler - controller.add_handler(self.controllers[self.handler], file, hide_mode, message[3]) + # when presenting PDF, we're using the image presentation code, + # so handler & processor is set to None, and we skip adding the handler. + if self.handler == None: + self.controller = controller + else: + controller.add_handler(self.controllers[self.handler], file, hide_mode, message[3]) def slide(self, message): """ diff --git a/openlp/plugins/presentations/lib/pdfcontroller.py b/openlp/plugins/presentations/lib/pdfcontroller.py index ff79c6231..d2ba47bef 100644 --- a/openlp/plugins/presentations/lib/pdfcontroller.py +++ b/openlp/plugins/presentations/lib/pdfcontroller.py @@ -31,16 +31,40 @@ import os import logging from tempfile import NamedTemporaryFile import re -from subprocess import check_output, CalledProcessError +from subprocess import check_output, CalledProcessError, STDOUT from PyQt4 import QtCore, QtGui from openlp.core.utils import AppLocation -from openlp.core.lib import ScreenList +from openlp.core.lib import ScreenList, Settings from presentationcontroller import PresentationController, PresentationDocument log = logging.getLogger(__name__) +def check_binary(program_path): + """ + Function that checks whether a binary is either ghostscript or mudraw or neither. + """ + program_type = None + runlog = u'' + try: + runlog = check_output([program_path, u'--help'], stderr=STDOUT) + except CalledProcessError as e: + runlog = e.output + except Exception: + runlog = u'' + + # Analyse the output to see it the program is mudraw, ghostscript or neither + for line in runlog.splitlines(): + found_mudraw = re.search(u'usage: mudraw.*', line) + if found_mudraw: + program_type = u'mudraw' + break + found_gs = re.search(u'GPL Ghostscript.*', line) + if found_gs: + program_type = u'gs' + break + return program_type class PdfController(PresentationController): """ @@ -72,8 +96,20 @@ class PdfController(PresentationController): """ Check the viewer is installed. """ - application_path = AppLocation.get_directory(AppLocation.AppDir) log.debug(u'check_installed Pdf') + # Use the user defined program if given + if (Settings().value(u'presentations/enable_given_pdf_program')): + given_pdf_program = Settings().value(u'presentations/given_pdf_program') + type = check_binary(given_pdf_program) + if type == u'gs': + self.gsbin = given_pdf_program + return True + elif type == u'mudraw': + self.mudrawbin = given_pdf_program + return True + + # Fallback to autodetection + application_path = AppLocation.get_directory(AppLocation.AppDir) if os.name != u'nt': # First try to find mupdf try: @@ -153,7 +189,12 @@ quit \n\ tmpfile.close() # Run the script on the pdf to get the size - runlog = check_output([self.controller.gsbin, u'-dNOPAUSE', u'-dNODISPLAY', u'-dBATCH', u'-sFile=' + self.filepath, tmpfile.name]) + runlog = [] + try: + runlog = check_output([self.controller.gsbin, u'-dNOPAUSE', u'-dNODISPLAY', u'-dBATCH', u'-sFile=' + self.filepath, tmpfile.name]) + except CalledProcessError as e: + log.debug(u' '.join(e.cmd)) + log.debug(e.output) os.unlink(tmpfile.name) # Extract the pdf resolution from output, the format is " Size: x: , y: " diff --git a/openlp/plugins/presentations/lib/presentationtab.py b/openlp/plugins/presentations/lib/presentationtab.py index e46467403..6ada5d496 100644 --- a/openlp/plugins/presentations/lib/presentationtab.py +++ b/openlp/plugins/presentations/lib/presentationtab.py @@ -29,8 +29,9 @@ from PyQt4 import QtGui -from openlp.core.lib import Settings, SettingsTab, UiStrings, translate - +from openlp.core.lib import Settings, SettingsTab, UiStrings, translate, build_icon +from openlp.core.lib.ui import critical_error_message_box +from pdfcontroller import check_binary class PresentationTab(SettingsTab): """ @@ -63,6 +64,7 @@ class PresentationTab(SettingsTab): self.presenter_check_boxes[controller.name] = checkbox self.controllers_layout.addWidget(checkbox) self.left_layout.addWidget(self.controllers_group_box) + # Advanced self.advanced_group_box = QtGui.QGroupBox(self.left_column) self.advanced_group_box.setObjectName(u'advanced_group_box') self.advanced_layout = QtGui.QVBoxLayout(self.advanced_group_box) @@ -70,7 +72,33 @@ class PresentationTab(SettingsTab): self.override_app_check_box = QtGui.QCheckBox(self.advanced_group_box) self.override_app_check_box.setObjectName(u'override_app_check_box') self.advanced_layout.addWidget(self.override_app_check_box) + + # Pdf options + self.pdf_group_box = QtGui.QGroupBox(self.left_column) + self.pdf_group_box.setObjectName(u'pdf_group_box') + self.pdf_layout = QtGui.QFormLayout(self.pdf_group_box) + self.pdf_layout.setObjectName(u'pdf_layout') + self.pdf_program_check_box = QtGui.QCheckBox(self.pdf_group_box) + self.pdf_program_check_box.setObjectName(u'pdf_program_check_box') + self.pdf_layout.addWidget(self.pdf_program_check_box) + self.pdf_program_path_layout = QtGui.QHBoxLayout() + self.pdf_program_path_layout.setObjectName(u'pdf_program_path_layout') + self.pdf_program_path = QtGui.QLineEdit(self.pdf_group_box) + self.pdf_program_path.setObjectName(u'pdf_program_path') + self.pdf_program_path.setReadOnly(True) + self.pdf_program_path.setPalette(self.get_grey_text_palette(True)) + self.pdf_program_path_layout.addWidget(self.pdf_program_path) + self.pdf_program_browse_button = QtGui.QToolButton(self.pdf_group_box) + self.pdf_program_browse_button.setObjectName(u'pdf_program_browse_button') + self.pdf_program_browse_button.setIcon(build_icon(u':/general/general_open.png')) + self.pdf_program_browse_button.setEnabled(False) + self.pdf_program_path_layout.addWidget(self.pdf_program_browse_button) + self.pdf_layout.addRow(self.pdf_program_path_layout) + self.pdf_program_path.editingFinished.connect(self.on_pdf_program_path_edit_finished) + self.pdf_program_browse_button.clicked.connect(self.on_pdf_program_browse_button_clicked) + self.pdf_program_check_box.clicked.connect(self.on_pdf_program_check_box_clicked) self.left_layout.addWidget(self.advanced_group_box) + self.left_layout.addWidget(self.pdf_group_box) self.left_layout.addStretch() self.right_layout.addStretch() @@ -84,8 +112,12 @@ class PresentationTab(SettingsTab): checkbox = self.presenter_check_boxes[controller.name] self.set_controller_text(checkbox, controller) self.advanced_group_box.setTitle(UiStrings().Advanced) + self.pdf_group_box.setTitle(translate('PresentationPlugin.PresentationTab', 'PDF options')) self.override_app_check_box.setText( translate('PresentationPlugin.PresentationTab', 'Allow presentation application to be overridden')) + self.pdf_program_check_box.setText( + translate('PresentationPlugin.PresentationTab', 'Use given full path for mudraw or ghostscript binary:')) + def set_controller_text(self, checkbox, controller): if checkbox.isEnabled(): @@ -102,6 +134,15 @@ class PresentationTab(SettingsTab): checkbox = self.presenter_check_boxes[controller.name] checkbox.setChecked(Settings().value(self.settings_section + u'/' + controller.name)) self.override_app_check_box.setChecked(Settings().value(self.settings_section + u'/override app')) + # load pdf-program settings + enable_given_pdf_program = Settings().value(self.settings_section + u'/enable_given_pdf_program') + self.pdf_program_check_box.setChecked(enable_given_pdf_program) + self.pdf_program_path.setReadOnly(not enable_given_pdf_program) + self.pdf_program_path.setPalette(self.get_grey_text_palette(not enable_given_pdf_program)) + self.pdf_program_browse_button.setEnabled(enable_given_pdf_program) + pdf_program = Settings().value(self.settings_section + u'/given_pdf_program') + if pdf_program: + self.pdf_program_path.setText(pdf_program) def save(self): """ @@ -127,10 +168,25 @@ class PresentationTab(SettingsTab): if Settings().value(setting_key) != self.override_app_check_box.checkState(): Settings().setValue(setting_key, self.override_app_check_box.checkState()) changed = True + + # Save pdf-settings + pdf_program = self.pdf_program_path.text() + enable_given_pdf_program = self.pdf_program_check_box.checkState() + # If the given program is blank disable using the program + if pdf_program == u'': + enable_given_pdf_program = 0 + if pdf_program != Settings().value(self.settings_section + u'/given_pdf_program'): + Settings().setValue(self.settings_section + u'/given_pdf_program', pdf_program) + changed = True + if enable_given_pdf_program != Settings().value(self.settings_section + u'/enable_given_pdf_program'): + Settings().setValue(self.settings_section + u'/enable_given_pdf_program', enable_given_pdf_program) + changed = True + if changed: self.settings_form.register_post_process(u'mediaitem_suffix_reset') self.settings_form.register_post_process(u'mediaitem_presentation_rebuild') self.settings_form.register_post_process(u'mediaitem_suffixes') + def tab_visible(self): """ @@ -142,3 +198,44 @@ class PresentationTab(SettingsTab): checkbox = self.presenter_check_boxes[controller.name] checkbox.setEnabled(controller.is_available()) self.set_controller_text(checkbox, controller) + + def on_pdf_program_path_edit_finished(self): + """ + After selecting/typing in a program it is validated that it is a actually ghostscript or mudraw + """ + type = None + if self.pdf_program_path.text() != u'': + type = check_binary(self.pdf_program_path.text()) + if not type: + critical_error_message_box(UiStrings().Error, + translate('PresentationPlugin.PresentationTab', 'The program is not ghostscript or mudraw which is required.')) + self.pdf_program_path.setFocus() + + def on_pdf_program_browse_button_clicked(self): + """ + Select the mudraw or ghostscript binary that should be used. + """ + filename = QtGui.QFileDialog.getOpenFileName(self, translate('PresentationPlugin.PresentationTab', 'Select mudraw or ghostscript binary.')) + if filename: + self.pdf_program_path.setText(filename) + self.pdf_program_path.setFocus() + + def on_pdf_program_check_box_clicked(self, checked): + """ + When checkbox for manual entering pdf-program is clicked, + enable or disable the textbox for the programpath and the browse-button. + """ + self.pdf_program_path.setReadOnly(not checked) + self.pdf_program_path.setPalette(self.get_grey_text_palette(not checked)) + self.pdf_program_browse_button.setEnabled(checked) + + def get_grey_text_palette(self, greyed): + """ + Returns a QPalette with greyed out text as used for placeholderText. + """ + palette = QtGui.QPalette() + color = self.palette().color(QtGui.QPalette.Active, QtGui.QPalette.Text) + if greyed: + color.setAlpha(128) + palette.setColor(QtGui.QPalette.Active, QtGui.QPalette.Text, color) + return palette diff --git a/openlp/plugins/presentations/presentationplugin.py b/openlp/plugins/presentations/presentationplugin.py index b11975591..d6019a46b 100644 --- a/openlp/plugins/presentations/presentationplugin.py +++ b/openlp/plugins/presentations/presentationplugin.py @@ -45,6 +45,8 @@ log = logging.getLogger(__name__) __default_settings__ = { u'presentations/override app': QtCore.Qt.Unchecked, + u'presentations/enable_given_pdf_program': QtCore.Qt.Unchecked, + u'presentations/given_pdf_program': u'', u'presentations/Impress': QtCore.Qt.Checked, u'presentations/Powerpoint': QtCore.Qt.Checked, u'presentations/Powerpoint Viewer': QtCore.Qt.Checked, From 968e30837fe39375eae80d6de4436672d561df4d Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Tue, 6 Aug 2013 17:15:06 +0100 Subject: [PATCH 05/18] Reload pdf-presentation if images needed is missing instead of displaying an error. --- openlp/plugins/presentations/lib/mediaitem.py | 24 +++++++------------ 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/openlp/plugins/presentations/lib/mediaitem.py b/openlp/plugins/presentations/lib/mediaitem.py index dabab5c04..bb5f39a12 100644 --- a/openlp/plugins/presentations/lib/mediaitem.py +++ b/openlp/plugins/presentations/lib/mediaitem.py @@ -270,26 +270,18 @@ class PresentationMediaItem(MediaManagerItem): controller = self.controllers[processor] service_item.processor = None doc = controller.add_document(filename) - if doc.get_thumbnail_path(1, True) is None: + if doc.get_thumbnail_path(1, True) is None or not os.path.isfile(os.path.join(doc.get_temp_folder(), u'mainslide001.png')): doc.load_presentation() i = 1 imagefile = u'mainslide%03d.png' % i img = os.path.join(doc.get_temp_folder(), imagefile) - if os.path.isfile(img): - while os.path.isfile(img): - service_item.add_from_image(img, name) - i += 1 - imagefile = u'mainslide%03d.png' % i - img = os.path.join(doc.get_temp_folder(), imagefile) - doc.close_presentation() - return True - else: - # File is no longer present - if not remote: - critical_error_message_box(translate('PresentationPlugin.MediaItem', 'Missing Presentation'), - translate('PresentationPlugin.MediaItem', - 'The presentation %s is incomplete, please reload.') % filename) - return False + while os.path.isfile(img): + service_item.add_from_image(img, name) + i += 1 + imagefile = u'mainslide%03d.png' % i + img = os.path.join(doc.get_temp_folder(), imagefile) + doc.close_presentation() + return True else: # File is no longer present if not remote: From a45dfd2d1b436c7bcaf1a435628badf745d8e37c Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Fri, 4 Oct 2013 21:06:31 +0200 Subject: [PATCH 06/18] Made branch work with python3. --- openlp/plugins/presentations/lib/mediaitem.py | 8 +- .../presentations/lib/pdfcontroller.py | 151 +++++++++--------- .../presentations/lib/presentationtab.py | 39 ++--- .../presentations/presentationplugin.py | 2 +- 4 files changed, 100 insertions(+), 100 deletions(-) diff --git a/openlp/plugins/presentations/lib/mediaitem.py b/openlp/plugins/presentations/lib/mediaitem.py index 1f7f863bf..f1d4106b7 100644 --- a/openlp/plugins/presentations/lib/mediaitem.py +++ b/openlp/plugins/presentations/lib/mediaitem.py @@ -250,7 +250,7 @@ class PresentationMediaItem(MediaManagerItem): if not self.display_type_combo_box.currentText(): return False - if context == ServiceItemContext.Live and (file_type == u'pdf' or file_type == u'xps'): + if context == ServiceItemContext.Live and (file_type == 'pdf' or file_type == 'xps'): service_item.add_capability(ItemCapabilities.CanMaintain) service_item.add_capability(ItemCapabilities.CanPreview) service_item.add_capability(ItemCapabilities.CanLoop) @@ -270,15 +270,15 @@ class PresentationMediaItem(MediaManagerItem): controller = self.controllers[processor] service_item.processor = None doc = controller.add_document(filename) - if doc.get_thumbnail_path(1, True) is None or not os.path.isfile(os.path.join(doc.get_temp_folder(), u'mainslide001.png')): + if doc.get_thumbnail_path(1, True) is None or not os.path.isfile(os.path.join(doc.get_temp_folder(), 'mainslide001.png')): doc.load_presentation() i = 1 - imagefile = u'mainslide%03d.png' % i + imagefile = 'mainslide%03d.png' % i img = os.path.join(doc.get_temp_folder(), imagefile) while os.path.isfile(img): service_item.add_from_image(img, name) i += 1 - imagefile = u'mainslide%03d.png' % i + imagefile = 'mainslide%03d.png' % i img = os.path.join(doc.get_temp_folder(), imagefile) doc.close_presentation() return True diff --git a/openlp/plugins/presentations/lib/pdfcontroller.py b/openlp/plugins/presentations/lib/pdfcontroller.py index d2ba47bef..d907fa84b 100644 --- a/openlp/plugins/presentations/lib/pdfcontroller.py +++ b/openlp/plugins/presentations/lib/pdfcontroller.py @@ -32,110 +32,115 @@ import logging from tempfile import NamedTemporaryFile import re from subprocess import check_output, CalledProcessError, STDOUT -from PyQt4 import QtCore, QtGui +#from PyQt4 import QtCore, QtGui from openlp.core.utils import AppLocation from openlp.core.lib import ScreenList, Settings -from presentationcontroller import PresentationController, PresentationDocument +from .presentationcontroller import PresentationController, PresentationDocument log = logging.getLogger(__name__) -def check_binary(program_path): - """ - Function that checks whether a binary is either ghostscript or mudraw or neither. - """ - program_type = None - runlog = u'' - try: - runlog = check_output([program_path, u'--help'], stderr=STDOUT) - except CalledProcessError as e: - runlog = e.output - except Exception: - runlog = u'' - - # Analyse the output to see it the program is mudraw, ghostscript or neither - for line in runlog.splitlines(): - found_mudraw = re.search(u'usage: mudraw.*', line) - if found_mudraw: - program_type = u'mudraw' - break - found_gs = re.search(u'GPL Ghostscript.*', line) - if found_gs: - program_type = u'gs' - break - return program_type class PdfController(PresentationController): """ Class to control PDF presentations """ - log.info(u'PdfController loaded') + log.info('PdfController loaded') def __init__(self, plugin): """ Initialise the class """ - log.debug(u'Initialising') + log.debug('Initialising') self.process = None - PresentationController.__init__(self, plugin, u'Pdf', PdfDocument) - self.supports = [u'pdf'] - self.mudrawbin = u'' - self.gsbin = u'' - if self.check_installed() and self.mudrawbin != u'': - self.also_supports = [u'xps'] + PresentationController.__init__(self, plugin, 'Pdf', PdfDocument) + self.supports = ['pdf'] + self.mudrawbin = '' + self.gsbin = '' + if self.check_installed() and self.mudrawbin != '': + self.also_supports = ['xps'] + + def check_binary(program_path): + """ + Function that checks whether a binary is either ghostscript or mudraw or neither. + """ + + program_type = None + runlog = '' + try: + runlog = check_output([program_path, '--help'], stderr=STDOUT) + except CalledProcessError as e: + runlog = e.output + except Exception: + runlog = '' + + # Analyse the output to see it the program is mudraw, ghostscript or neither + for line in runlog.splitlines(): + found_mudraw = re.search('usage: mudraw.*', line) + if found_mudraw: + program_type = 'mudraw' + break + found_gs = re.search('GPL Ghostscript.*', line) + if found_gs: + program_type = 'gs' + break + log.info('in check_binary, found: ' + program_type) + return program_type + + def check_available(self): """ PdfController is able to run on this machine. """ - log.debug(u'check_available Pdf') + log.debug('check_available Pdf') return self.check_installed() def check_installed(self): """ Check the viewer is installed. """ - log.debug(u'check_installed Pdf') + log.debug('check_installed Pdf') # Use the user defined program if given - if (Settings().value(u'presentations/enable_given_pdf_program')): - given_pdf_program = Settings().value(u'presentations/given_pdf_program') - type = check_binary(given_pdf_program) - if type == u'gs': + if (Settings().value('presentations/enable_given_pdf_program')): + given_pdf_program = Settings().value('presentations/given_pdf_program') + type = self.check_binary(given_pdf_program) + if type == 'gs': self.gsbin = given_pdf_program return True - elif type == u'mudraw': + elif type == 'mudraw': self.mudrawbin = given_pdf_program return True # Fallback to autodetection application_path = AppLocation.get_directory(AppLocation.AppDir) - if os.name != u'nt': + if os.name != 'nt': # First try to find mupdf try: - self.mudrawbin = check_output([u'which', u'mudraw']).rstrip('\n') + self.mudrawbin = check_output(['which', 'mudraw']).decode(encoding='UTF-8').rstrip('\n') except CalledProcessError: - self.mudrawbin = u'' + self.mudrawbin = '' # if mupdf isn't installed, fallback to ghostscript - if self.mudrawbin == u'': + if self.mudrawbin == '': try: - self.gsbin = check_output([u'which', u'gs']).rstrip('\n') + self.gsbin = check_output(['which', 'gs']).rstrip('\n') except CalledProcessError: - self.gsbin = u'' + self.gsbin = '' # Last option: check if mudraw is placed in OpenLP base folder - if self.mudrawbin == u'' and self.gsbin == u'': + if self.mudrawbin == '' and self.gsbin == '': application_path = AppLocation.get_directory(AppLocation.AppDir) - if os.path.isfile(application_path + u'/../mudraw'): - self.mudrawbin = application_path + u'/../mudraw' + if os.path.isfile(application_path + '/../mudraw'): + self.mudrawbin = application_path + '/../mudraw' else: # for windows we only accept mudraw.exe in the base folder application_path = AppLocation.get_directory(AppLocation.AppDir) - if os.path.isfile(application_path + u'/../mudraw.exe'): - self.mudrawbin = application_path + u'/../mudraw.exe' + if os.path.isfile(application_path + '/../mudraw.exe'): + self.mudrawbin = application_path + '/../mudraw.exe' - if self.mudrawbin == u'' and self.gsbin == u'': + if self.mudrawbin == '' and self.gsbin == '': return False else: return True @@ -144,7 +149,7 @@ class PdfController(PresentationController): """ Called at system exit to clean up any running presentations """ - log.debug(u'Kill pdfviewer') + log.debug('Kill pdfviewer') while self.docs: self.docs[0].close_presentation() @@ -157,7 +162,7 @@ class PdfDocument(PresentationDocument): """ Constructor, store information about the file and initialise. """ - log.debug(u'Init Presentation Pdf') + log.debug('Init Presentation Pdf') PresentationDocument.__init__(self, controller, presentation) self.presentation = None self.blanked = False @@ -172,7 +177,7 @@ class PdfDocument(PresentationDocument): to get the ratio bewteen the screen size and the PDF to scale """ # Use a postscript script to get size of the pdf. It is assumed that all pages have same size - postscript = u'%!PS \n\ + postscript = '%!PS \n\ () = \n\ File dup (r) file runpdfbegin \n\ 1 pdfgetpage dup \n\ @@ -191,9 +196,9 @@ quit \n\ # Run the script on the pdf to get the size runlog = [] try: - runlog = check_output([self.controller.gsbin, u'-dNOPAUSE', u'-dNODISPLAY', u'-dBATCH', u'-sFile=' + self.filepath, tmpfile.name]) + runlog = check_output([self.controller.gsbin, '-dNOPAUSE', '-dNODISPLAY', '-dBATCH', '-sFile=' + self.filepath, tmpfile.name]) except CalledProcessError as e: - log.debug(u' '.join(e.cmd)) + log.debug(' '.join(e.cmd)) log.debug(e.output) os.unlink(tmpfile.name) @@ -202,8 +207,8 @@ quit \n\ height = 0 for line in runlog.splitlines(): try: - width = re.search(u'.*Size: x: (\d+\.?\d*), y: \d+.*', line).group(1) - height = re.search(u'.*Size: x: \d+\.?\d*, y: (\d+\.?\d*).*', line).group(1) + width = re.search('.*Size: x: (\d+\.?\d*), y: \d+.*', line).group(1) + height = re.search('.*Size: x: \d+\.?\d*, y: (\d+\.?\d*).*', line).group(1) break; except AttributeError: pass @@ -225,10 +230,10 @@ quit \n\ """ Called when a presentation is added to the SlideController. It generates images from the PDF. """ - log.debug(u'load_presentation pdf') + log.debug('load_presentation pdf') # Check if the images has already been created, and if yes load them - if os.path.isfile(os.path.join(self.get_temp_folder(), u'mainslide001.png')): + if os.path.isfile(os.path.join(self.get_temp_folder(), 'mainslide001.png')): created_files = sorted(os.listdir(self.get_temp_folder())) for fn in created_files: if os.path.isfile(os.path.join(self.get_temp_folder(), fn)): @@ -236,17 +241,17 @@ quit \n\ self.num_pages = len(self.image_files) return True - size = ScreenList().current[u'size'] + size = ScreenList().current['size'] # Generate images from PDF that will fit the frame. - runlog = u'' + runlog = '' try: if not os.path.isdir(self.get_temp_folder()): os.makedirs(self.get_temp_folder()) - if self.controller.mudrawbin != u'': - runlog = check_output([self.controller.mudrawbin, u'-w', str(size.right()), u'-h', str(size.bottom()), u'-o', os.path.join(self.get_temp_folder(), u'mainslide%03d.png'), self.filepath]) - elif self.controller.gsbin != u'': + if self.controller.mudrawbin != '': + runlog = check_output([self.controller.mudrawbin, '-w', str(size.right()), '-h', str(size.bottom()), '-o', os.path.join(self.get_temp_folder(), 'mainslide%03d.png'), self.filepath]) + elif self.controller.gsbin != '': resolution = self.gs_get_resolution(size) - runlog = check_output([self.controller.gsbin, u'-dSAFER', u'-dNOPAUSE', u'-dBATCH', u'-sDEVICE=png16m', u'-r' + str(resolution), u'-dTextAlphaBits=4', u'-dGraphicsAlphaBits=4', u'-sOutputFile=' + os.path.join(self.get_temp_folder(), u'mainslide%03d.png'), self.filepath]) + runlog = check_output([self.controller.gsbin, '-dSAFER', '-dNOPAUSE', '-dBATCH', '-sDEVICE=png16m', '-r' + str(resolution), '-dTextAlphaBits=4', '-dGraphicsAlphaBits=4', '-sOutputFile=' + os.path.join(self.get_temp_folder(), 'mainslide%03d.png'), self.filepath]) created_files = sorted(os.listdir(self.get_temp_folder())) for fn in created_files: if os.path.isfile(os.path.join(self.get_temp_folder(), fn)): @@ -265,10 +270,10 @@ quit \n\ """ Generates thumbnails """ - log.debug(u'create_thumbnails pdf') + log.debug('create_thumbnails pdf') if self.check_thumbnails(): return - log.debug(u'create_thumbnails proceeding') + log.debug('create_thumbnails proceeding') # use builtin function to create thumbnails from generated images index = 1 @@ -281,14 +286,14 @@ quit \n\ Close presentation and clean up objects. Triggered by new object being added to SlideController or OpenLP being shut down. """ - log.debug(u'close_presentation pdf') + log.debug('close_presentation pdf') self.controller.remove_doc(self) def is_loaded(self): """ Returns true if a presentation is loaded. """ - log.debug(u'is_loaded pdf') + log.debug('is_loaded pdf') if self.num_pages < 0: return False return True @@ -297,6 +302,6 @@ quit \n\ """ Returns true if a presentation is currently active. """ - log.debug(u'is_active pdf') + log.debug('is_active pdf') return self.is_loaded() and not self.hidden diff --git a/openlp/plugins/presentations/lib/presentationtab.py b/openlp/plugins/presentations/lib/presentationtab.py index e3cc46ae1..9ed2eb56e 100644 --- a/openlp/plugins/presentations/lib/presentationtab.py +++ b/openlp/plugins/presentations/lib/presentationtab.py @@ -31,7 +31,9 @@ from PyQt4 import QtGui from openlp.core.lib import Settings, SettingsTab, UiStrings, translate, build_icon from openlp.core.lib.ui import critical_error_message_box -from pdfcontroller import check_binary +from .pdfcontroller import PdfController +#from openlp.plugins.presentations.lib.pdfcontroller import PdfController +#from pdfcontroller import check_binary class PresentationTab(SettingsTab): """ @@ -75,22 +77,22 @@ class PresentationTab(SettingsTab): # Pdf options self.pdf_group_box = QtGui.QGroupBox(self.left_column) - self.pdf_group_box.setObjectName(u'pdf_group_box') + self.pdf_group_box.setObjectName('pdf_group_box') self.pdf_layout = QtGui.QFormLayout(self.pdf_group_box) - self.pdf_layout.setObjectName(u'pdf_layout') + self.pdf_layout.setObjectName('pdf_layout') self.pdf_program_check_box = QtGui.QCheckBox(self.pdf_group_box) - self.pdf_program_check_box.setObjectName(u'pdf_program_check_box') + self.pdf_program_check_box.setObjectName('pdf_program_check_box') self.pdf_layout.addWidget(self.pdf_program_check_box) self.pdf_program_path_layout = QtGui.QHBoxLayout() - self.pdf_program_path_layout.setObjectName(u'pdf_program_path_layout') + self.pdf_program_path_layout.setObjectName('pdf_program_path_layout') self.pdf_program_path = QtGui.QLineEdit(self.pdf_group_box) - self.pdf_program_path.setObjectName(u'pdf_program_path') + self.pdf_program_path.setObjectName('pdf_program_path') self.pdf_program_path.setReadOnly(True) self.pdf_program_path.setPalette(self.get_grey_text_palette(True)) self.pdf_program_path_layout.addWidget(self.pdf_program_path) self.pdf_program_browse_button = QtGui.QToolButton(self.pdf_group_box) - self.pdf_program_browse_button.setObjectName(u'pdf_program_browse_button') - self.pdf_program_browse_button.setIcon(build_icon(u':/general/general_open.png')) + self.pdf_program_browse_button.setObjectName('pdf_program_browse_button') + self.pdf_program_browse_button.setIcon(build_icon(':/general/general_open.png')) self.pdf_program_browse_button.setEnabled(False) self.pdf_program_path_layout.addWidget(self.pdf_program_browse_button) self.pdf_layout.addRow(self.pdf_program_path_layout) @@ -173,26 +175,19 @@ class PresentationTab(SettingsTab): pdf_program = self.pdf_program_path.text() enable_given_pdf_program = self.pdf_program_check_box.checkState() # If the given program is blank disable using the program - if pdf_program == u'': + if pdf_program == '': enable_given_pdf_program = 0 - if pdf_program != Settings().value(self.settings_section + u'/given_pdf_program'): - Settings().setValue(self.settings_section + u'/given_pdf_program', pdf_program) + if pdf_program != Settings().value(self.settings_section + '/given_pdf_program'): + Settings().setValue(self.settings_section + '/given_pdf_program', pdf_program) changed = True - if enable_given_pdf_program != Settings().value(self.settings_section + u'/enable_given_pdf_program'): - Settings().setValue(self.settings_section + u'/enable_given_pdf_program', enable_given_pdf_program) + if enable_given_pdf_program != Settings().value(self.settings_section + '/enable_given_pdf_program'): + Settings().setValue(self.settings_section + '/enable_given_pdf_program', enable_given_pdf_program) changed = True if changed: -<<<<<<< TREE - self.settings_form.register_post_process(u'mediaitem_suffix_reset') - self.settings_form.register_post_process(u'mediaitem_presentation_rebuild') - self.settings_form.register_post_process(u'mediaitem_suffixes') - -======= self.settings_form.register_post_process('mediaitem_suffix_reset') self.settings_form.register_post_process('mediaitem_presentation_rebuild') self.settings_form.register_post_process('mediaitem_suffixes') ->>>>>>> MERGE-SOURCE def tab_visible(self): """ @@ -210,8 +205,8 @@ class PresentationTab(SettingsTab): After selecting/typing in a program it is validated that it is a actually ghostscript or mudraw """ type = None - if self.pdf_program_path.text() != u'': - type = check_binary(self.pdf_program_path.text()) + if self.pdf_program_path.text() != '': + type = PdfController.check_binary(self.pdf_program_path.text()) if not type: critical_error_message_box(UiStrings().Error, translate('PresentationPlugin.PresentationTab', 'The program is not ghostscript or mudraw which is required.')) diff --git a/openlp/plugins/presentations/presentationplugin.py b/openlp/plugins/presentations/presentationplugin.py index 824eef2ba..a599cea4e 100644 --- a/openlp/plugins/presentations/presentationplugin.py +++ b/openlp/plugins/presentations/presentationplugin.py @@ -46,7 +46,7 @@ log = logging.getLogger(__name__) __default_settings__ = { 'presentations/override app': QtCore.Qt.Unchecked, 'presentations/enable_given_pdf_program': QtCore.Qt.Unchecked, - 'presentations/given_pdf_program': u'', + 'presentations/given_pdf_program': '', 'presentations/Impress': QtCore.Qt.Checked, 'presentations/Powerpoint': QtCore.Qt.Checked, 'presentations/Powerpoint Viewer': QtCore.Qt.Checked, From 3acd18c102c88595b3ac652d17b4687a6bb15839 Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Thu, 14 Nov 2013 17:42:21 +0100 Subject: [PATCH 07/18] Mostly code cleanup of PDF support code. --- openlp/plugins/presentations/lib/mediaitem.py | 19 +++---- .../presentations/lib/messagelistener.py | 13 ++++- .../presentations/lib/pdfcontroller.py | 51 +++++++------------ .../presentations/lib/presentationtab.py | 24 ++++----- .../presentations/presentationplugin.py | 4 +- 5 files changed, 50 insertions(+), 61 deletions(-) diff --git a/openlp/plugins/presentations/lib/mediaitem.py b/openlp/plugins/presentations/lib/mediaitem.py index f1d4106b7..9add8c8a4 100644 --- a/openlp/plugins/presentations/lib/mediaitem.py +++ b/openlp/plugins/presentations/lib/mediaitem.py @@ -244,21 +244,17 @@ class PresentationMediaItem(MediaManagerItem): items = self.list_view.selectedItems() if len(items) > 1: return False - filename = items[0].data(QtCore.Qt.UserRole) file_type = os.path.splitext(filename)[1][1:] if not self.display_type_combo_box.currentText(): return False - - if context == ServiceItemContext.Live and (file_type == 'pdf' or file_type == 'xps'): + if (file_type == 'pdf' or file_type == 'xps') and context != ServiceItemContext.Service: service_item.add_capability(ItemCapabilities.CanMaintain) service_item.add_capability(ItemCapabilities.CanPreview) service_item.add_capability(ItemCapabilities.CanLoop) service_item.add_capability(ItemCapabilities.CanAppend) # force a nonexistent theme service_item.theme = -1 - - # Why the loop when we above return False if len(items) is > 1? for bitem in items: filename = bitem.data(QtCore.Qt.UserRole) (path, name) = os.path.split(filename) @@ -270,16 +266,17 @@ class PresentationMediaItem(MediaManagerItem): controller = self.controllers[processor] service_item.processor = None doc = controller.add_document(filename) - if doc.get_thumbnail_path(1, True) is None or not os.path.isfile(os.path.join(doc.get_temp_folder(), 'mainslide001.png')): + if doc.get_thumbnail_path(1, True) is None or not os.path.isfile( + os.path.join(doc.get_temp_folder(), 'mainslide001.png')): doc.load_presentation() i = 1 imagefile = 'mainslide%03d.png' % i - img = os.path.join(doc.get_temp_folder(), imagefile) - while os.path.isfile(img): - service_item.add_from_image(img, name) + image = os.path.join(doc.get_temp_folder(), imagefile) + while os.path.isfile(image): + service_item.add_from_image(image, name) i += 1 imagefile = 'mainslide%03d.png' % i - img = os.path.join(doc.get_temp_folder(), imagefile) + image = os.path.join(doc.get_temp_folder(), imagefile) doc.close_presentation() return True else: @@ -291,8 +288,6 @@ class PresentationMediaItem(MediaManagerItem): else: service_item.processor = self.display_type_combo_box.currentText() service_item.add_capability(ItemCapabilities.ProvidesOwnDisplay) - - # Why the loop when we above return False if len(items) is > 1? for bitem in items: filename = bitem.data(QtCore.Qt.UserRole) (path, name) = os.path.split(filename) diff --git a/openlp/plugins/presentations/lib/messagelistener.py b/openlp/plugins/presentations/lib/messagelistener.py index e5667bda4..bd9311673 100644 --- a/openlp/plugins/presentations/lib/messagelistener.py +++ b/openlp/plugins/presentations/lib/messagelistener.py @@ -33,6 +33,7 @@ from PyQt4 import QtCore from openlp.core.lib import Registry from openlp.core.ui import HideMode +from openlp.core.lib import ServiceItemContext log = logging.getLogger(__name__) @@ -69,6 +70,7 @@ class Controller(object): return self.doc.slidenumber = slide_no self.hide_mode = hide_mode + log.debug('add_handler, slidenumber: %d' % slide_no) if self.is_live: if hide_mode == HideMode.Screen: Registry().execute('live_display_hide', HideMode.Screen) @@ -316,6 +318,14 @@ class MessageListener(object): hide_mode = message[2] file = item.get_frame_path() self.handler = item.processor + #self.media_item.generate_slide_data(self, service_item, item=None, xml_version=False,remote=False, context=ServiceItemContext.Service) + if file.endswith('.pdf') or file.endswith('.xps'): + log.debug('Trying to convert from pdf to images for service-item') + if is_live: + self.media_item.generate_slide_data(self, item, None, False, False, ServiceItemContext.Live) + else: + self.media_item.generate_slide_data(self, item, None, False, False, ServiceItemContext.Preview) + if self.handler == self.media_item.Automatic: self.handler = self.media_item.findControllerByType(file) if not self.handler: @@ -326,9 +336,10 @@ class MessageListener(object): controller = self.preview_handler # when presenting PDF, we're using the image presentation code, # so handler & processor is set to None, and we skip adding the handler. + log.debug('file: %s' % file) if self.handler == None: self.controller = controller - else: + else: controller.add_handler(self.controllers[self.handler], file, hide_mode, message[3]) def slide(self, message): diff --git a/openlp/plugins/presentations/lib/pdfcontroller.py b/openlp/plugins/presentations/lib/pdfcontroller.py index d907fa84b..c6fa7dc49 100644 --- a/openlp/plugins/presentations/lib/pdfcontroller.py +++ b/openlp/plugins/presentations/lib/pdfcontroller.py @@ -58,14 +58,13 @@ class PdfController(PresentationController): self.supports = ['pdf'] self.mudrawbin = '' self.gsbin = '' - if self.check_installed() and self.mudrawbin != '': + if self.check_installed() and self.mudrawbin: self.also_supports = ['xps'] def check_binary(program_path): """ Function that checks whether a binary is either ghostscript or mudraw or neither. """ - program_type = None runlog = '' try: @@ -74,7 +73,6 @@ class PdfController(PresentationController): runlog = e.output except Exception: runlog = '' - # Analyse the output to see it the program is mudraw, ghostscript or neither for line in runlog.splitlines(): found_mudraw = re.search('usage: mudraw.*', line) @@ -88,8 +86,6 @@ class PdfController(PresentationController): log.info('in check_binary, found: ' + program_type) return program_type - - def check_available(self): """ PdfController is able to run on this machine. @@ -103,44 +99,40 @@ class PdfController(PresentationController): """ log.debug('check_installed Pdf') # Use the user defined program if given - if (Settings().value('presentations/enable_given_pdf_program')): - given_pdf_program = Settings().value('presentations/given_pdf_program') - type = self.check_binary(given_pdf_program) + if (Settings().value('presentations/enable_pdf_program')): + pdf_program = Settings().value('presentations/pdf_program') + type = self.check_binary(pdf_program) if type == 'gs': - self.gsbin = given_pdf_program + self.gsbin = pdf_program return True elif type == 'mudraw': - self.mudrawbin = given_pdf_program + self.mudrawbin = pdf_program return True - # Fallback to autodetection application_path = AppLocation.get_directory(AppLocation.AppDir) - if os.name != 'nt': + if os.name == 'nt': + # for windows we only accept mudraw.exe in the base folder + application_path = AppLocation.get_directory(AppLocation.AppDir) + if os.path.isfile(application_path + '/../mudraw.exe'): + self.mudrawbin = application_path + '/../mudraw.exe' + else: # First try to find mupdf try: self.mudrawbin = check_output(['which', 'mudraw']).decode(encoding='UTF-8').rstrip('\n') except CalledProcessError: self.mudrawbin = '' - # if mupdf isn't installed, fallback to ghostscript - if self.mudrawbin == '': + if not self.mudrawbin: try: self.gsbin = check_output(['which', 'gs']).rstrip('\n') except CalledProcessError: self.gsbin = '' - # Last option: check if mudraw is placed in OpenLP base folder - if self.mudrawbin == '' and self.gsbin == '': + if not self.mudrawbin and not self.gsbin: application_path = AppLocation.get_directory(AppLocation.AppDir) if os.path.isfile(application_path + '/../mudraw'): self.mudrawbin = application_path + '/../mudraw' - else: - # for windows we only accept mudraw.exe in the base folder - application_path = AppLocation.get_directory(AppLocation.AppDir) - if os.path.isfile(application_path + '/../mudraw.exe'): - self.mudrawbin = application_path + '/../mudraw.exe' - - if self.mudrawbin == '' and self.gsbin == '': + if not self.mudrawbin and not self.gsbin: return False else: return True @@ -192,7 +184,6 @@ quit \n\ tmpfile = NamedTemporaryFile(delete=False) tmpfile.write(postscript) tmpfile.close() - # Run the script on the pdf to get the size runlog = [] try: @@ -201,7 +192,6 @@ quit \n\ log.debug(' '.join(e.cmd)) log.debug(e.output) os.unlink(tmpfile.name) - # Extract the pdf resolution from output, the format is " Size: x: , y: " width = 0 height = 0 @@ -212,12 +202,10 @@ quit \n\ break; except AttributeError: pass - # Calculate the ratio from pdf to screen if width > 0 and height > 0: width_ratio = size.right() / float(width) height_ratio = size.bottom() / float(height) - # return the resolution that should be used. 72 is default. if width_ratio > height_ratio: return int(height_ratio * 72) @@ -231,7 +219,6 @@ quit \n\ Called when a presentation is added to the SlideController. It generates images from the PDF. """ log.debug('load_presentation pdf') - # Check if the images has already been created, and if yes load them if os.path.isfile(os.path.join(self.get_temp_folder(), 'mainslide001.png')): created_files = sorted(os.listdir(self.get_temp_folder())) @@ -240,16 +227,15 @@ quit \n\ self.image_files.append(os.path.join(self.get_temp_folder(), fn)) self.num_pages = len(self.image_files) return True - size = ScreenList().current['size'] # Generate images from PDF that will fit the frame. runlog = '' try: if not os.path.isdir(self.get_temp_folder()): os.makedirs(self.get_temp_folder()) - if self.controller.mudrawbin != '': + if self.controller.mudrawbin: runlog = check_output([self.controller.mudrawbin, '-w', str(size.right()), '-h', str(size.bottom()), '-o', os.path.join(self.get_temp_folder(), 'mainslide%03d.png'), self.filepath]) - elif self.controller.gsbin != '': + elif self.controller.gsbin: resolution = self.gs_get_resolution(size) runlog = check_output([self.controller.gsbin, '-dSAFER', '-dNOPAUSE', '-dBATCH', '-sDEVICE=png16m', '-r' + str(resolution), '-dTextAlphaBits=4', '-dGraphicsAlphaBits=4', '-sOutputFile=' + os.path.join(self.get_temp_folder(), 'mainslide%03d.png'), self.filepath]) created_files = sorted(os.listdir(self.get_temp_folder())) @@ -261,7 +247,6 @@ quit \n\ log.debug(runlog) return False self.num_pages = len(self.image_files) - # Create thumbnails self.create_thumbnails() return True @@ -273,8 +258,6 @@ quit \n\ log.debug('create_thumbnails pdf') if self.check_thumbnails(): return - log.debug('create_thumbnails proceeding') - # use builtin function to create thumbnails from generated images index = 1 for image in self.image_files: diff --git a/openlp/plugins/presentations/lib/presentationtab.py b/openlp/plugins/presentations/lib/presentationtab.py index 9ed2eb56e..1d6d9a16e 100644 --- a/openlp/plugins/presentations/lib/presentationtab.py +++ b/openlp/plugins/presentations/lib/presentationtab.py @@ -137,12 +137,12 @@ class PresentationTab(SettingsTab): checkbox.setChecked(Settings().value(self.settings_section + '/' + controller.name)) self.override_app_check_box.setChecked(Settings().value(self.settings_section + '/override app')) # load pdf-program settings - enable_given_pdf_program = Settings().value(self.settings_section + '/enable_given_pdf_program') - self.pdf_program_check_box.setChecked(enable_given_pdf_program) - self.pdf_program_path.setReadOnly(not enable_given_pdf_program) - self.pdf_program_path.setPalette(self.get_grey_text_palette(not enable_given_pdf_program)) - self.pdf_program_browse_button.setEnabled(enable_given_pdf_program) - pdf_program = Settings().value(self.settings_section + '/given_pdf_program') + enable_pdf_program = Settings().value(self.settings_section + '/enable_pdf_program') + self.pdf_program_check_box.setChecked(enable_pdf_program) + self.pdf_program_path.setReadOnly(not enable_pdf_program) + self.pdf_program_path.setPalette(self.get_grey_text_palette(not enable_pdf_program)) + self.pdf_program_browse_button.setEnabled(enable_pdf_program) + pdf_program = Settings().value(self.settings_section + '/pdf_program') if pdf_program: self.pdf_program_path.setText(pdf_program) @@ -173,15 +173,15 @@ class PresentationTab(SettingsTab): # Save pdf-settings pdf_program = self.pdf_program_path.text() - enable_given_pdf_program = self.pdf_program_check_box.checkState() + enable_pdf_program = self.pdf_program_check_box.checkState() # If the given program is blank disable using the program if pdf_program == '': - enable_given_pdf_program = 0 - if pdf_program != Settings().value(self.settings_section + '/given_pdf_program'): - Settings().setValue(self.settings_section + '/given_pdf_program', pdf_program) + enable_pdf_program = 0 + if pdf_program != Settings().value(self.settings_section + '/pdf_program'): + Settings().setValue(self.settings_section + '/pdf_program', pdf_program) changed = True - if enable_given_pdf_program != Settings().value(self.settings_section + '/enable_given_pdf_program'): - Settings().setValue(self.settings_section + '/enable_given_pdf_program', enable_given_pdf_program) + if enable_pdf_program != Settings().value(self.settings_section + '/enable_pdf_program'): + Settings().setValue(self.settings_section + '/enable_pdf_program', enable_pdf_program) changed = True if changed: diff --git a/openlp/plugins/presentations/presentationplugin.py b/openlp/plugins/presentations/presentationplugin.py index a599cea4e..91f4d6096 100644 --- a/openlp/plugins/presentations/presentationplugin.py +++ b/openlp/plugins/presentations/presentationplugin.py @@ -45,8 +45,8 @@ log = logging.getLogger(__name__) __default_settings__ = { 'presentations/override app': QtCore.Qt.Unchecked, - 'presentations/enable_given_pdf_program': QtCore.Qt.Unchecked, - 'presentations/given_pdf_program': '', + 'presentations/enable_pdf_program': QtCore.Qt.Unchecked, + 'presentations/pdf_program': '', 'presentations/Impress': QtCore.Qt.Checked, 'presentations/Powerpoint': QtCore.Qt.Checked, 'presentations/Powerpoint Viewer': QtCore.Qt.Checked, From 685258b744d2fa1c3a469b9a16f4f93bb93a1b85 Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Fri, 27 Dec 2013 17:44:12 +0000 Subject: [PATCH 08/18] Made presenting PDF from the servicemanager work --- openlp/core/lib/serviceitem.py | 6 ++++- openlp/core/ui/slidecontroller.py | 4 +-- openlp/plugins/presentations/lib/mediaitem.py | 12 ++++++--- .../presentations/lib/messagelistener.py | 27 +++++++++++++------ 4 files changed, 34 insertions(+), 15 deletions(-) diff --git a/openlp/core/lib/serviceitem.py b/openlp/core/lib/serviceitem.py index 789854c78..8078b5868 100644 --- a/openlp/core/lib/serviceitem.py +++ b/openlp/core/lib/serviceitem.py @@ -420,7 +420,11 @@ class ServiceItem(object): self._raw_frames.append(slide) elif self.service_item_type == ServiceItemType.Image: settings_section = serviceitem['serviceitem']['header']['name'] - background = QtGui.QColor(Settings().value(settings_section + '/background color')) + background = None + try: + background = QtGui.QColor(Settings().value(settings_section + '/background color')) + except Exception: + pass if path: self.has_original_files = False for text_image in serviceitem['serviceitem']['data']: diff --git a/openlp/core/ui/slidecontroller.py b/openlp/core/ui/slidecontroller.py index 043838f36..dbf095e48 100644 --- a/openlp/core/ui/slidecontroller.py +++ b/openlp/core/ui/slidecontroller.py @@ -438,7 +438,7 @@ class SlideController(DisplayController): # "V1" was the slide we wanted to go. self.preview_widget.change_slide(self.slideList[self.current_shortcut]) self.slide_selected() - # Reset the shortcut. + # Reset the shortcut. self.current_shortcut = '' def set_live_hotkeys(self, parent=None): @@ -733,7 +733,7 @@ class SlideController(DisplayController): if old_item and self.is_live and old_item.is_capable(ItemCapabilities.ProvidesOwnDisplay): self._reset_blank() Registry().execute( - '%s_start' % service_item.name.lower(), [service_item, self.is_live, self.hide_mode(), slideno]) + '%s_start' % service_item.name.lower(), [self.service_item, self.is_live, self.hide_mode(), slideno]) self.slideList = {} if self.is_live: self.song_menu.menu().clear() diff --git a/openlp/plugins/presentations/lib/mediaitem.py b/openlp/plugins/presentations/lib/mediaitem.py index a89877ac6..be48c10f6 100644 --- a/openlp/plugins/presentations/lib/mediaitem.py +++ b/openlp/plugins/presentations/lib/mediaitem.py @@ -238,7 +238,7 @@ class PresentationMediaItem(MediaManagerItem): Settings().setValue(self.settings_section + '/presentations files', self.get_file_list()) def generate_slide_data(self, service_item, item=None, xml_version=False, - remote=False, context=ServiceItemContext.Service): + remote=False, context=ServiceItemContext.Service, presentation_file=None): """ Load the relevant information for displaying the presentation in the slidecontroller. In the case of powerpoints, an image for each slide. @@ -249,7 +249,9 @@ class PresentationMediaItem(MediaManagerItem): items = self.list_view.selectedItems() if len(items) > 1: return False - filename = items[0].data(QtCore.Qt.UserRole) + filename = presentation_file + if filename is None: + filename = items[0].data(QtCore.Qt.UserRole) file_type = os.path.splitext(filename)[1][1:] if not self.display_type_combo_box.currentText(): return False @@ -261,7 +263,9 @@ class PresentationMediaItem(MediaManagerItem): # force a nonexistent theme service_item.theme = -1 for bitem in items: - filename = bitem.data(QtCore.Qt.UserRole) + filename = presentation_file + if filename is None: + filename = bitem.data(QtCore.Qt.UserRole) (path, name) = os.path.split(filename) service_item.title = name if os.path.exists(filename): @@ -298,7 +302,7 @@ class PresentationMediaItem(MediaManagerItem): (path, name) = os.path.split(filename) service_item.title = name if os.path.exists(filename): - if service_item.processor == self.Automatic: + if service_item.processor == self.automatic: service_item.processor = self.findControllerByType(filename) if not service_item.processor: return False diff --git a/openlp/plugins/presentations/lib/messagelistener.py b/openlp/plugins/presentations/lib/messagelistener.py index fba9c8b4d..68b7641cc 100644 --- a/openlp/plugins/presentations/lib/messagelistener.py +++ b/openlp/plugins/presentations/lib/messagelistener.py @@ -33,7 +33,7 @@ from PyQt4 import QtCore from openlp.core.lib import Registry from openlp.core.ui import HideMode -from openlp.core.lib import ServiceItemContext +from openlp.core.lib import ServiceItemContext, ServiceItem log = logging.getLogger(__name__) @@ -318,14 +318,26 @@ class MessageListener(object): hide_mode = message[2] file = item.get_frame_path() self.handler = item.processor - #self.media_item.generate_slide_data(self, service_item, item=None, xml_version=False,remote=False, context=ServiceItemContext.Service) + # When starting presentation from the servicemanager/slidecontroller we convert + # PDF/XPS-serviceitems into image-serviceitems. When started from the mediamanager + # the conversion has already been done. if file.endswith('.pdf') or file.endswith('.xps'): - log.debug('Trying to convert from pdf to images for service-item') + log.debug('Converting from pdf/xps to images for serviceitem with file %s', file) + # Create a new image-serviceitem which will overwrite the old one + new_item = ServiceItem() + new_item.name = 'images' if is_live: - self.media_item.generate_slide_data(self, item, None, False, False, ServiceItemContext.Live) + self.media_item.generate_slide_data(new_item, item, False, False, ServiceItemContext.Live, file) else: - self.media_item.generate_slide_data(self, item, None, False, False, ServiceItemContext.Preview) - + self.media_item.generate_slide_data(new_item, item, False, False, ServiceItemContext.Preview, file) + # We need to overwrite the current serviceitem to make the slidecontroller + # present the images, so we do some copying + service_repr = {'serviceitem': new_item.get_service_repr(True) } + item.set_from_service(service_repr) + item._raw_frames = new_item._raw_frames + # When presenting PDF or XPS, we are using the image presentation code, + # so handler & processor is set to None, and we skip adding the handler. + self.handler = None if self.handler == self.media_item.automatic: self.handler = self.media_item.findControllerByType(file) if not self.handler: @@ -334,9 +346,8 @@ class MessageListener(object): controller = self.live_handler else: controller = self.preview_handler - # when presenting PDF, we're using the image presentation code, + # When presenting PDF or XPS, we are using the image presentation code, # so handler & processor is set to None, and we skip adding the handler. - log.debug('file: %s' % file) if self.handler == None: self.controller = controller else: From 82aaf11860e785c258200c6905b42766f03a3158 Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Sat, 28 Dec 2013 18:36:33 +0000 Subject: [PATCH 09/18] Fixes for the presentation setting tab --- .../presentations/lib/pdfcontroller.py | 17 ++++++++++------- .../presentations/lib/presentationtab.py | 19 ++++++++++--------- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/openlp/plugins/presentations/lib/pdfcontroller.py b/openlp/plugins/presentations/lib/pdfcontroller.py index 6b2dccfa1..781bd0af4 100644 --- a/openlp/plugins/presentations/lib/pdfcontroller.py +++ b/openlp/plugins/presentations/lib/pdfcontroller.py @@ -41,7 +41,6 @@ from .presentationcontroller import PresentationController, PresentationDocument log = logging.getLogger(__name__) - class PdfController(PresentationController): """ Class to control PDF presentations @@ -61,12 +60,15 @@ class PdfController(PresentationController): if self.check_installed() and self.mudrawbin: self.also_supports = ['xps'] + @staticmethod def check_binary(program_path): """ Function that checks whether a binary is either ghostscript or mudraw or neither. + Is also used from presentationtab.py """ program_type = None runlog = '' + log.debug('testing program_path: %s', program_path) try: runlog = check_output([program_path, '--help'], stderr=STDOUT) except CalledProcessError as e: @@ -75,15 +77,16 @@ class PdfController(PresentationController): runlog = '' # Analyse the output to see it the program is mudraw, ghostscript or neither for line in runlog.splitlines(): - found_mudraw = re.search('usage: mudraw.*', line) + decoded_line = line.decode() + found_mudraw = re.search('usage: mudraw.*', decoded_line) if found_mudraw: program_type = 'mudraw' break - found_gs = re.search('GPL Ghostscript.*', line) + found_gs = re.search('GPL Ghostscript.*', decoded_line) if found_gs: program_type = 'gs' break - log.info('in check_binary, found: ' + program_type) + log.debug('in check_binary, found: %s', program_type) return program_type def check_available(self): @@ -101,11 +104,11 @@ class PdfController(PresentationController): # Use the user defined program if given if (Settings().value('presentations/enable_pdf_program')): pdf_program = Settings().value('presentations/pdf_program') - type = self.check_binary(pdf_program) - if type == 'gs': + program_type = check_binary(pdf_program) + if program_type == 'gs': self.gsbin = pdf_program return True - elif type == 'mudraw': + elif program_type == 'mudraw': self.mudrawbin = pdf_program return True # Fallback to autodetection diff --git a/openlp/plugins/presentations/lib/presentationtab.py b/openlp/plugins/presentations/lib/presentationtab.py index cc0cf81e9..5b93bac10 100644 --- a/openlp/plugins/presentations/lib/presentationtab.py +++ b/openlp/plugins/presentations/lib/presentationtab.py @@ -73,7 +73,7 @@ class PresentationTab(SettingsTab): self.override_app_check_box = QtGui.QCheckBox(self.advanced_group_box) self.override_app_check_box.setObjectName('override_app_check_box') self.advanced_layout.addWidget(self.override_app_check_box) - + self.left_layout.addWidget(self.advanced_group_box) # Pdf options self.pdf_group_box = QtGui.QGroupBox(self.left_column) self.pdf_group_box.setObjectName('pdf_group_box') @@ -81,7 +81,7 @@ class PresentationTab(SettingsTab): self.pdf_layout.setObjectName('pdf_layout') self.pdf_program_check_box = QtGui.QCheckBox(self.pdf_group_box) self.pdf_program_check_box.setObjectName('pdf_program_check_box') - self.pdf_layout.addWidget(self.pdf_program_check_box) + self.pdf_layout.addRow(self.pdf_program_check_box) self.pdf_program_path_layout = QtGui.QHBoxLayout() self.pdf_program_path_layout.setObjectName('pdf_program_path_layout') self.pdf_program_path = QtGui.QLineEdit(self.pdf_group_box) @@ -95,13 +95,14 @@ class PresentationTab(SettingsTab): self.pdf_program_browse_button.setEnabled(False) self.pdf_program_path_layout.addWidget(self.pdf_program_browse_button) self.pdf_layout.addRow(self.pdf_program_path_layout) + self.left_layout.addWidget(self.pdf_group_box) + self.left_layout.addStretch() + self.right_column.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Preferred) + self.right_layout.addStretch() + # Signals and slots self.pdf_program_path.editingFinished.connect(self.on_pdf_program_path_edit_finished) self.pdf_program_browse_button.clicked.connect(self.on_pdf_program_browse_button_clicked) self.pdf_program_check_box.clicked.connect(self.on_pdf_program_check_box_clicked) - self.left_layout.addWidget(self.advanced_group_box) - self.left_layout.addWidget(self.pdf_group_box) - self.left_layout.addStretch() - self.right_layout.addStretch() def retranslateUi(self): """ @@ -203,10 +204,10 @@ class PresentationTab(SettingsTab): """ After selecting/typing in a program it is validated that it is a actually ghostscript or mudraw """ - type = None + program_type = None if self.pdf_program_path.text() != '': - type = PdfController.check_binary(self.pdf_program_path.text()) - if not type: + program_type = PdfController.check_binary(self.pdf_program_path.text()) + if not program_type: critical_error_message_box(UiStrings().Error, translate('PresentationPlugin.PresentationTab', 'The program is not ghostscript or mudraw which is required.')) self.pdf_program_path.setFocus() From 7dd341721712e96ac503e86e27dc847b0794f0e8 Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Sun, 29 Dec 2013 19:46:04 +0000 Subject: [PATCH 10/18] Added tests for the PdfController. --- .../presentations/lib/pdfcontroller.py | 14 ++- .../presentations/test_mediaitem.py | 11 +- .../presentations/test_pdfcontroller.py | 108 ++++++++++++++++++ tests/resources/presentations/pdf_test1.pdf | Bin 0 -> 12471 bytes 4 files changed, 129 insertions(+), 4 deletions(-) create mode 100644 tests/functional/openlp_plugins/presentations/test_pdfcontroller.py create mode 100644 tests/resources/presentations/pdf_test1.pdf diff --git a/openlp/plugins/presentations/lib/pdfcontroller.py b/openlp/plugins/presentations/lib/pdfcontroller.py index 781bd0af4..ef7e0dd1c 100644 --- a/openlp/plugins/presentations/lib/pdfcontroller.py +++ b/openlp/plugins/presentations/lib/pdfcontroller.py @@ -4,8 +4,8 @@ ############################################################################### # OpenLP - Open Source Lyrics Projection # # --------------------------------------------------------------------------- # -# Copyright (c) 2008-2013 Raoul Snyman # -# Portions copyright (c) 2008-2013 Tim Bentley, Gerald Britton, Jonathan # +# Copyright (c) 2008-2014 Raoul Snyman # +# Portions copyright (c) 2008-2014 Tim Bentley, Gerald Britton, Jonathan # # Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, # # Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. # # Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, # @@ -104,7 +104,7 @@ class PdfController(PresentationController): # Use the user defined program if given if (Settings().value('presentations/enable_pdf_program')): pdf_program = Settings().value('presentations/pdf_program') - program_type = check_binary(pdf_program) + program_type = self.check_binary(pdf_program) if program_type == 'gs': self.gsbin = pdf_program return True @@ -152,6 +152,9 @@ class PdfController(PresentationController): class PdfDocument(PresentationDocument): """ Class which holds information of a single presentation. + This class is not actually used to present the PDF, instead we convert to + image-serviceitem on the fly and present as such. Therefore some of the 'playback' + functions is not implemented. """ def __init__(self, controller, presentation): """ @@ -291,3 +294,8 @@ quit \n\ log.debug('is_active pdf') return self.is_loaded() and not self.hidden + def get_slide_count(self): + """ + Returns total number of slides + """ + return self.num_pages \ No newline at end of file diff --git a/tests/functional/openlp_plugins/presentations/test_mediaitem.py b/tests/functional/openlp_plugins/presentations/test_mediaitem.py index 5b5c99e78..c9c8fbbf6 100644 --- a/tests/functional/openlp_plugins/presentations/test_mediaitem.py +++ b/tests/functional/openlp_plugins/presentations/test_mediaitem.py @@ -75,11 +75,16 @@ class TestMediaItem(TestCase): presentation_controller.also_supports = [] presentation_viewer_controller = MagicMock() presentation_viewer_controller.enabled.return_value = False + pdf_controller = MagicMock() + pdf_controller.enabled.return_value = True + pdf_controller.supports = ['pdf'] + pdf_controller.also_supports = ['xps'] # Mock the controllers. self.media_item.controllers = { 'Impress': impress_controller, 'Powerpoint': presentation_controller, - 'Powerpoint Viewer': presentation_viewer_controller + 'Powerpoint Viewer': presentation_viewer_controller, + 'Pdf': pdf_controller } # WHEN: Build the file mask. @@ -92,3 +97,7 @@ class TestMediaItem(TestCase): 'The file mask should contain the odp extension') self.assertIn('*.ppt', self.media_item.on_new_file_masks, 'The file mask should contain the ppt extension') + self.assertIn('*.pdf', self.media_item.on_new_file_masks, + 'The file mask should contain the pdf extension') + self.assertIn('*.xps', self.media_item.on_new_file_masks, + 'The file mask should contain the xps extension') diff --git a/tests/functional/openlp_plugins/presentations/test_pdfcontroller.py b/tests/functional/openlp_plugins/presentations/test_pdfcontroller.py new file mode 100644 index 000000000..7739af3c8 --- /dev/null +++ b/tests/functional/openlp_plugins/presentations/test_pdfcontroller.py @@ -0,0 +1,108 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2014 Raoul Snyman # +# Portions copyright (c) 2008-2014 Tim Bentley, Gerald Britton, Jonathan # +# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, # +# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. # +# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, # +# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, # +# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, # +# Frode Woldsund, Martin Zibricky, Patrick Zimmermann # +# --------------------------------------------------------------------------- # +# 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 module contains tests for the PdfController +""" +import os +import shutil +from unittest import TestCase, SkipTest +from tempfile import mkstemp, mkdtemp + +from PyQt4 import QtGui + +from openlp.plugins.presentations.lib.pdfcontroller import PdfController, PdfDocument +from tests.functional import MagicMock +from openlp.core.common import Settings +from openlp.core.lib import ScreenList +from tests.utils.constants import TEST_RESOURCES_PATH + +__default_settings__ = { + 'presentations/enable_pdf_program': False +} + +class TestPdfController(TestCase): + """ + Test the PdfController. + """ + def setUp(self): + """ + Set up the components need for all tests. + """ + self.fd, self.ini_file = mkstemp('.ini') + Settings().set_filename(self.ini_file) + self.application = QtGui.QApplication.instance() + ScreenList.create(self.application.desktop()) + Settings().extend_default_settings(__default_settings__) + self.temp_folder = mkdtemp() + self.thumbnail_folder = mkdtemp() + + def tearDown(self): + """ + Delete all the C++ objects at the end so that we don't have a segfault + """ + del self.application + try: + os.unlink(self.ini_file) + shutil.rmtree(self.thumbnail_folder) + shutil.rmtree(self.temp_folder) + except OSError: + pass + + def constructor_test(self): + """ + Test the Constructor + """ + # GIVEN: No presentation controller + controller = None + + # WHEN: The presentation controller object is created + controller = PdfController(plugin = MagicMock()) + + # THEN: The name of the presentation controller should be correct + self.assertEqual('Pdf', controller.name, 'The name of the presentation controller should be correct') + + def load_pdf_test(self): + """ + Test loading of a Pdf + """ + # GIVEN: A Pdf-file + test_file = os.path.join(TEST_RESOURCES_PATH, 'presentations', 'pdf_test1.pdf') + + # WHEN: The Pdf is loaded + controller = PdfController(plugin = MagicMock()) + if not controller.check_available(): + raise SkipTest('Could not detect mudraw or ghostscript, so skipping PDF test') + controller.temp_folder = self.temp_folder + controller.thumbnail_folder = self.thumbnail_folder + document = PdfDocument(controller, test_file) + loaded = document.load_presentation() + + # THEN: The load should succeed and we should be able to get a pagecount + self.assertTrue(loaded, 'The loading of the PDF should succeed.') + self.assertEqual(3, document.get_slide_count(), 'The pagecount of the PDF should be 3.') diff --git a/tests/resources/presentations/pdf_test1.pdf b/tests/resources/presentations/pdf_test1.pdf new file mode 100644 index 0000000000000000000000000000000000000000..012dc95483300fd0338cb0fb303e7b0e7270d346 GIT binary patch literal 12471 zcmc(FWmp_bw>2Sw1PBlU1ef5>41;TMclQi1NN^Yk?h-t>I|L6B2p-&mySuv+G<-wO zIq!Sy`R>1a`=RNoT~&Lp-g{N`k6DxoB4Ug{CRSw1ww$(xwy$mJ$SkA)Qfq^E$h^GF zG7u{hs3|E3W>I1mGqZp~?3l$Yz)*+?#L(IZ!q1Ov54D4UEs>p4KtmuEnoT+tfC2!n zLWWWHJQRyT=J`1TRH_{+rh53ub23t&U{{tR~Z zrR2YWeZcWIu$=z@%k{qj_Ai;;d@1!WU>|V&4J;7&546AsYWy1!ekTf$lm!T22mF~9 z`);moD&tl6Gn;+nrIJ)gYl2=}J%ASp4bPS_LXCuod0WX(Wb%__jH!B>DL{R`xJ8IX zYN%Q@Qtp+=`5ofJERyYrmHyYet1~z=Git6SOW4{s8M$hfjqf%*e(+N>zcD{N{CRxi z=FtbFKxWgGB0%>Af#G;+b%3w8Es@&!=36aDMO7h91*0EWG=4ZyoZBo)+~>S z*EIXe^swP{c~_r2_#Qu!f9zn_to~-Eci?5N5yrO1?6PfLAWl+r5S8E)#w|GTl}!Z3 zzNqQo*4A_V(LP=zQ~e;vsKG-!;&`k+j)KSgesY^^-UNMDE|qPj^tuCbgPR@+CoD6r zLBV8?JZbhm;Q_ODC>BVbxxjm-k6#eaAQYmt+Z>}`Cwf;?CjM;txz+SUL-bX%iZk>2 zi;e}jlt&GG4J;Rt9t4_!)tz#^+iyLj9+MHw3*VR_rW)FBJ97)tZz~pNAB^aR2Q3Va7BQsu#}bummgkn=2)c(ZMDC6JfRLY+ zjUvDM#IyZcV>KQcuwWQu!SDK!sP+8i&6l6pTWazcb+24^1vL(VE>YnXwHBXaXd$gy ziLA-&S=Jmx?YF*XWJM;jdrxk{-4yg$bnud~k_m5g8N}@KG4K!a4&R2q*F5KJ*4YWv$+D@idW(%=5+`z?iN4_$Pl#8Jhy-B5&9gQ`J(?hzuaOr z&Xf^*G`4|gQ~TwQ+}<2~od^l)+{tzAwfVY;GkrpSzT2*4bVGL|kr+l_BC<0Fjb&zr z$J~9!+L|94k@|l~$oKYCXny^Ex=!cpJ4dldbr^fE`?G*4pC{+0%EN!5K)?u!_8zqf zt0-b#hF2OlW0HX`gGKAZrI|3RrLV^$9l^SAs~rC8mn-W`+;0NQ4pz_L?|eLjJ$;^j zSQj#3Ha-*?n{WN*x9f31af5b4?CRavvalH+6G*SGMXD+_u9P~)H1A_xdX{x2y>01S zxEJNRo?3I!GuU0aBat`Sah6*XxBGsZ1a#Y-V69_i5q5JdZX%F&YCGChiavT2vpk5p z2)a#OUhH7+aLH&2LoRrhw1CWbDE^$d{oFxh1$5@VcA$@>y9jI7o80huVo~Sai(2+S{p-f;zqPRO3e^5^d>}O_cvxG;~rTuZ+23&FiPySBzI62?C?^ zTgoewYj#IDwW(6g^u5^X4&un&9M5i#uaevYCq^M2<#S^16+(%k7CtV-dgLYdu}d_W zT-A9RF2>y2RT;f-KHseCsY^by#*}Qz>KHlS^E>0aqPqGtRc%`CLBf-}NZlwBd{7x9 zmseI~9edD`kuIfYhQqe={fs0E$I(;B6Lqgqq%N#KQh#DUyV|JejKeW}-naM|^Q#hv zaGe&NN#I9Y0prnahFQ5s7ZAHLlWFUz7r={}TU>2Q$`)HR_L`lq5H z7q0^r!r}Xri3*uHGn%%W7we_N?ajheqYCO`Nyt3btZ09EpncM%WNMlbKQ`asnajV`zx9B^}1)(sGkkk`o-nLE6t0f<&Tg} z#RR3G*?djy+E(Bq%1-GkR#6ZIdn=i%W(gFO(+OMjVuv*!7MHWr4u65yXFTGA=Xf;O z-vO7)UX(2sRPid8U8_1fyYy==;>tB9W?{*AnqMpYj=aQ^q-P9CW~gUh`Y2^cm5ldS zJc3qa6zI4+w3G?@zfRboW>u6-Y=^V!3~OjdM<>s!XuN=oGnt#m6ia8*I<^Rn8zkjt za=*hGrP2F_Ka+cgld@HB*ez4SctKG;zTZ#*q~l4xBrI_3)5h#?xR9C9z-%2YB(Nr0 zOeX&^ZtRS`u(w;U8bn-^W$R|m#GIT&N=`SuqjkLlI3@ys?_3b$att3YC8(lb#n)6PCCmzz_NEF` zD>lPm<_*aota@f~$qEb&u3JX8X8l%|JJ^5=1(A@9&-SV19`gCzmzdnMMy`y}ASPe& ztx^_CoNd zbx^eLwX3p_>h8Y_^$jrCyrfYKLmi#K(zQsR`eq^I>f<8m&Vn`3AG5<@vArvuFr*%6 zG(M?rqPlafb)6zyo?4>TVX@Cc$MHSP-Fqeu;|F?2`2P2xiTPnaQD|9*Kj%cP^;yl4 zkwwn<-Ah-sYxVRN*;1{S2%mt=?G`q9U{fT}G0nUv_8aq1#s5z(2A@!^A%$sGT zU5z@zbql>C+M_2l>*=8lAG@NYZKQQoLd)4wssKeuf~N4cP*h7R)Qj2E`3yI%)zor6 z#eNyHb2;1--{Y%yXBzPMdRGXYZ5Wfp!%K{|-(-&5$cyVw;u=jd;q6k}_@u7G#ZblY zT>>!kX)12V<7gollJ92N7}@?u*ND>ZS&FxNMFtiGT&K; zXrf_FH%KI=MFM9vz&c>JR#O|A^ij*88=Z}~VoTfQF`JpkLFiPA>DNXp(LKyt`rWrb ziq%2-{oEyP88@W%Cr0eD@g;+aDXEdET#UWA>4^!koFxpdniu%zslGB& zN&M{x(1h-zbo0^V(Yx)&?Ygtq#?Ty|vV7Vc&Y0BTxhz5j3d$SJi$`Q#?S$cy{WaJJ zv5wMR$2Rs-*CANc5bf~ga&I##efLi*d-$iSvx`md&g<1v+0y;D2R+=zFM_{ERL>6P ze1gQGaOA|sOK+?NYxiI8HYMNA*HT)z9?@O4Sye};_SBvRZN#@=AZ9zX_&DdfnIUEy z^)c#}z0GvXzseq!2>bqx>BrB+uY1w@e3!{lu@N3+epT1(ld|CJWpqmm+WNyF0+KKb zf$O{YAC=VtZbp}LWT$gN3>NNtmd=Zy>m`3GpCTmMU|XY-7JN44cyZX zjChmzT!$^hHKnI}Dn=om@&F~gqTs_SJ?*n|JaH33dY8l}c*Z#lQ3S^?Kq=Up zTSth&;7c<=QhQXu6!8$s`&Uw~$mm#7{uv}@>=y?6!Xw(@PsH(f6dbc;=-TGDqO7?` zBJ!lSr%%JnxA*oHPf=#m8{gg3H*osiNyVD%oA}0>lyWDl>A5*tNjgLr3Mw9-B_h#s z3+sJmdoT8xQQc%{Hp*l`MnOxn{Bv2Xto)<6WX_^k%61$)DXqi~){@vgM|=NNo*t`o zdvbn*rwC^C^JxR~6hZ)DPI?aOjawGM@7;X~>cIq!nxLzRiWA*VKJz_|InPt_Y@R`R{-NY|Wos6T+f1hn?aM+J)yxHJCz1j+k znCweZdy!YlN^x*nQlihw*!0e&b)2Kr^3Yv{mjW&){%7Ut2ik0|+?B?fJeM4t}9(UasJ*-Yr_kFIXd-4y+Lr zu@QYLp*M~r_Y{;Ut^vyNJ=z~k^vLuHjvuTsr2>Fv<4%=LsewW2ZBawBMhgnmg zsDTl7d$9g`@dRwBP!?t^IDZ&-e^}L^`7ktJx_qRlCj;wG`@+jF?IuN&9f^F4AH2tpt6OPPS%>U8TzuLZ!zZqYFLC)C&ae54~m(EAef3K`OzcPzMae8C)bD zu10jIUOM*O@N*lO4|jGqMPSxwS@C4i;>J6k0iWQRADm?Z zeXRhtLM>ZE0viMC=7svu z1}|84N6a+;jXZyv=jp(vc9>m8JP)gxD9*5C*lU4}b#8>_+0!5$ zVvPEfQf2OqNq*v~F@&u}x!1)wO*zqS2u(Xu z3dGHwfwX^@dxsqrtGM5JK{-iIQ=Ipq>PT!LgNQ=3B4o>J;p1!GgBPnpUr)56hS`pZ<8Un4FR)yov;VT8XT0`SjCF3$?{$ zaGZ2|s`%D;?zrVDS(f(JlCK3*k@8+d8Pzx?17_Z|LgT*|hMb?4Ip`WRmy-~zaAGMfYyOg`un8Pyx?kZ1-05m&x~Lm%ybDeHPcY6a!pP9JqOX`u`_^ad<6206V zK24Y-A&LQ_R7)<$Y9EsPEgU8bu7I2_Vz0u&Zx>U`wVezosKfg!5bKQ|JGv8P;QwGJ z)JLIPI4%Z zDx&ExI0Ks#ERqU)x80_lrsKSzpL-2Xh-s#@=M1#~I;NEAqJ2e#IQ!t6qw;#JJWgs} zGG<*jE33P(8N7ltT0Wdhq4UvpS0Oe`Zhx^yV9nAvQT~uYY{pmOCr&pt&dvumDAGln z+S2(U8JVWxdw$Jm`|3o*uWy*)YZ9sPO#|2H2LV z$q>p{sJEJ3uiP6|csH(#J+xxTkSrIPDX%@O;a2>@ylKdu+$7V*1P8ww|2)SQsnjub z(|5`Ff;hTwauer=oz*_W@(-dN%1bk`mN>^ZL2}v5%9ClsS-~^N#I5q$SPk-d9RIU4)#3g$+BT>3l6N;O zEs($kK8-!EH=8%pV(W;e=uY=1KtifY0Qcx=D!8;s8tGC;R4NR#cS|kPp_5Rta2R4E zC80U&Z)@M%D~+re^NLoLKfPjLb^!y8c9-|HyiH(NW6SI|60JG!{C958yf5add3-DO zh~iWlZhF;O=gbRhNCB5QxuPLbvA5%T>O&+a)&3^wT>@#c&`fbCYq5UgGVelHEUIAM z?CAD$E9G0u^g21pEwdj1t!WuuB~JA*01b}M>i8*O$fpW$RmInm5(!(!TI>kswe15q z?csb1jqXKaC?dB@XEd*r?9lZ7{<}@XMjNrr3jV#l6K9tU;UR*P_t-Sa4WG*d@{8>d z=hHp|`nXG>!hsx5bZfp3sHKY|MO;?&6Fm3FHX&WIeaCI*sCNn}Kj#x z!LmW*ZGq241wr3C=@JEV6hnE0gJ&^MzP@*!6Ai8)D~5W#46Y2|oh2V?nar+q(fKJC zv2u7~<2?6b{%6J@krMZv0x6blEpMiRc009-vW{Rx8Ti>q4Vi{keRc1)^4!jjuoOpV zF0>U@DyMfJDb3nnQ=yyStj3RA%XOd{i4ai7uL?FDL&(%6M7bv4CprpQsioT6Ms|Mh zR3?G`gRbn&(4eY+kmIF0PYv}9nP4c|34-Nn9KkUTwB|z>U|z}O+1CsEt1?RHmQad$ zaGRY~2ZFu04j3RR~&33!*!|!9{Jt&$Mcy&ti%M@wmTk zdTZ}0{3!j4IAz^?Cm%4gzPX|}C!vOp&fNUG?pOg{#j{N1IgEod?Dqjz_hL*3&)EWN zcV9#Zej^WY+B6}Y+CM&)z!IJ)o-fO?5_K34z&4+xG2e|*r7tv}EG|_oj4LTB(bcUt z_N8r!A-x#b3Rrdu`~Y8r_DUF_Z`2#C>RfK55fZ2$Az&Tsfo4y~ zk6VPYH*k^B)rP8;!wS|2#2h%O4oEEnG;j#wrS;U6+hRwki)7%R*eDV7v3$ZQCc3D0 z9V1;&BO-cA?dPlK_e?}s*7AKg%3E35y(mTHP&C8?6-Q2Yj7X)F%#YN(9?-}N44#)f zxh6)~8qsk`7=Ch;SVuJt&mvLOKQ{;ScFuNa&AhI#V>z1QP7p@#(JQrG|;v^-%* zZzbr#Y^5W1E%xo!zDR$rM?dmr-MXjik@&jdNp!qkv5rf*-FC${*7aRU^Vfm@q^2zyKN#r#`5Y+-F97Voy$DS{5nl9bzign5w)Zy z`Ni`=zMUN&jTkq57p{NjexYaEE?=F&vM`TL?- z#NFw-blixaWEk(_ZUfFJ29QvmJ9KM zV_tXQn()vm>3!)QO~@mUW-G67_HO;B?gY&1UF#(=f#vVJUCmf_bWi59e&X-Y=93`0 zbMFKWZJc|Q+^LQ=+|%RVc~pWF7W2k`_@wz>6CXciU^t1dx-4X>_9DbjumY;f ziZ{X2JnC+~fS*Mf-Csu&>sd=SN>3;taiHCis|u+7OwwJ2Ork50kf`edN7YFDsfjqQ z9`=_8-Kw+l+vJYTQ}~9)JL$d2eWdfWD5m>$AI^#Z7oy*ISIr27&U(S>XtFIT1$Yd*7iZUnj1X2$XJh7YO6C} z)+7iwy}clEi;d{wBqV~~zC5)a_Fd^iMVr*0@oh#ZM z-<5i&F=JaBgTM0Zbs`mx5(2KVb`t$mYux3V+X1I(!qWEmcAgc2m2;0;^##V>SG5|S zVP0@}hB4r&3L0xEIg z15yL{0v-ok@&)jT6;J{{)_@e*sp*0!gTmVbL>fD~(NyNis!!5cGFTtOd-1^$SHAFgJp5K$F_|%a(=XTlQH{G8Y|Pkra_L&B(||Qc3^UjoS@h?xu0;)6E<7 zoe8bUnTd^3zWky}wHdlodi0&(l8&z{w=2HfA>#&L*0j-f980+yb}kE}i=h2Qw?!`M zJt)g{EstC!3-V7b8u(d0^5sIoQse6*V1_)rb&qCRFBw`IMlXXml+EZv(t!x)v9ojSl1+7Zh4~5LvoSiqhNOvr+?$?@EoXv~WUhlI%zIbfZ?eX$X9@z?; zhha-GCJ({Aq|@`w5LY|`fF+!|g^_IDvW0z4DAvpP4O~WE^ocPyf)^CFbKqcGD7t!S zhQT^6t@S+TEJtSv(4T6N8Ty=%#H_$}gYHX=p9Y}3OVl}?#u^8bOJl+rrJl5)Z+3kl zHHxF2Wc(=d#I`JPN>6cv`(=@gyn5n^EbuAV@D$b~BD>`~ zqEFTMXg{Uf&?r>W7F8r)#vu4A9qK}6+e zZQY7TQhRv4%RY|1%4)-|L&17AE}o*9>qQfcaW|Hx=o(fGPT@s@sT#*rP!4Q{^5Q~H zqk=D}KqwC#Ad$SKVhX=9#<#)rLkTXgQ6i7whax3-yuh^bzLro>iBN19N`&NTPd8aO zyy4W;yE&tVR;NVUwDO^R+&sKIeCm$ej>Ofd)u@%1D=RC{ZR)7|x2lD3_0Ozoj6)gAKt=wl>VpbS zQr8|$kI!S_8&tEQ@*6z$DbS5&d#wCM?Qv-{m24iK(8O~yZWCeN{8pt-6lt$siv&qVaUxV+1< zG7lR$gzDzZcGRa%Bzf44Tbb^?=i+61EEo7?VyD;)->tH`&ZAYQI$}U|-W3DfsH4G_ zzB_y!0-ZcP5Wmtncl+$ZKbaja-l2288&`21(D{>7YevOAR|{P#2x$tPk*ii#8r0`a zX<)Atm+T-s03=$TlB(nojfVX)M+J9CS4>yDqC1t^K%aQegeTx|61u*$&_w+(~6Bh|4TD?u6?J49^L}3!Su=GY94FPg=OByhcy+)c;X#D8=BU7 zFhnp>R+Y8MH$pQy1s0x*3*8DQkUmk!`y#$K9;WKN7IlTb0*Tp%qkl{MmhX-H{nPcq zN7nE^5r>9lX$}#2;c?fnY89A@`%fm0@RIjz6j`LBhqw z#twKG|0RzD{vnxziTPBVZ6M5I)>cpvh`phmnGMt$Cgy{6<-nE@W+B0ci(bXd5@Ih0 zd9P$`3AR#DmQ_K1(EFL#ld{4Zu(?7))=t`tZ0ww*j359zDGPvwgA@n=uJWrm2KO@0T?8HZj>Qwx6ktidz;;+3~pNy+RN))%ILy(z zI1`hp)Fz6H3%{y`CO_uDtA1)F-Km>8dQvCA*kU0vS2mZU(wm#owfLpu#h0Q6>#0jS zgn#40pHz|msXDW=g8}q`0xEV6kY83Ius!4<=l{Rro7vezg-yYB4>gqm|K0{6Gpm~! zK~3$oIXE~-*#Ln5+`7M2@xKfs{l!2fWM&m>RV%Xx(I_eKp_G3u?jKr77)!-~q`(LG zf1SYb-|9agCI$Xdc_K3_*jXDYL!jEsFc~Ye3d9Mj`xnyuch#>KRsfqom=(Zw4`c)a z9)d6{LF}y^>!0xaDXae{ zcpmhyN`J!ht0oWU)*q+UL&5Bk|4}65|5gd+;Xh>}g|ScC%*bAw^g+6+MEZ+oq`%7g zFeGg40INIb&++~w$o-W7VKD3zvo?Wo1Qz#qiXbz~+d!-Y4WVY%R@%QgX4cTuBDHd` zu=wpC0Oni8{}9%~-bl~^YHDrAD<&!?1^}?J!cI-tlU)RM!%k2R z5CFge003BFPj*gNAJ$>#`pwSqFc-GV*hPQq|JLS*y%9`%4LimEaLMtfWxp@sV1Z48 zZ4x5~L&zWVScQLc%?=CkD-6sv>jM<_-)~r0_6Ha+`vW8p%ohtRGzj=dNLVIBU?OlD z5pEU$5Cmjl;bZ}VH~}15)U%pIN(yZKiDI0)dr?S;^WOb|yaT2iRFHX=Q9p`tahvIwGXnq`a(x z9HOF}un#{6o2am;FeewMh>$QFCzr4oJFB1&2M0g$|2*Yk6N*?H3Y$U<&Fvj5Ntro0 zIfcYPKp`;}Q8q4C5m7Et5iU*`dLdx|mkhl1^(zo-NRaR literal 0 HcmV?d00001 From 3cc69cc9fde5b0d3fde639c642af4ade15c4f3fb Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Tue, 31 Dec 2013 12:15:18 +0000 Subject: [PATCH 11/18] Changed the way a serviceitem from the servicemanager is copied and converted. Also fixed for PEP8. --- openlp/core/lib/serviceitem.py | 6 +-- openlp/plugins/presentations/lib/mediaitem.py | 28 ++++++++------ .../presentations/lib/messagelistener.py | 33 +++++++++-------- .../presentations/lib/pdfcontroller.py | 37 +++++++++++-------- .../presentations/lib/presentationtab.py | 16 ++++---- .../presentations/test_pdfcontroller.py | 9 +++-- 6 files changed, 69 insertions(+), 60 deletions(-) diff --git a/openlp/core/lib/serviceitem.py b/openlp/core/lib/serviceitem.py index b2569f0e5..83df4d338 100644 --- a/openlp/core/lib/serviceitem.py +++ b/openlp/core/lib/serviceitem.py @@ -420,11 +420,7 @@ class ServiceItem(object): self._raw_frames.append(slide) elif self.service_item_type == ServiceItemType.Image: settings_section = serviceitem['serviceitem']['header']['name'] - background = None - try: - background = QtGui.QColor(Settings().value(settings_section + '/background color')) - except Exception: - pass + background = QtGui.QColor(Settings().value(settings_section + '/background color')) if path: self.has_original_files = False for text_image in serviceitem['serviceitem']['data']: diff --git a/openlp/plugins/presentations/lib/mediaitem.py b/openlp/plugins/presentations/lib/mediaitem.py index fcaf5319e..93768594f 100644 --- a/openlp/plugins/presentations/lib/mediaitem.py +++ b/openlp/plugins/presentations/lib/mediaitem.py @@ -116,7 +116,7 @@ class PresentationMediaItem(MediaManagerItem): self.display_type_label = QtGui.QLabel(self.presentation_widget) self.display_type_label.setObjectName('display_type_label') self.display_type_combo_box = create_horizontal_adjusting_combo_box(self.presentation_widget, - 'display_type_combo_box') + 'display_type_combo_box') self.display_type_label.setBuddy(self.display_type_combo_box) self.display_layout.addRow(self.display_type_label, self.display_type_combo_box) # Add the Presentation widget to the page layout. @@ -177,9 +177,8 @@ class PresentationMediaItem(MediaManagerItem): if titles.count(filename) > 0: if not initial_load: critical_error_message_box(translate('PresentationPlugin.MediaItem', 'File Exists'), - translate('PresentationPlugin.MediaItem', - 'A presentation with that filename already exists.') - ) + translate('PresentationPlugin.MediaItem', + 'A presentation with that filename already exists.')) continue controller_name = self.findControllerByType(filename) if controller_name: @@ -203,7 +202,8 @@ class PresentationMediaItem(MediaManagerItem): icon = build_icon(':/general/general_delete.png') else: critical_error_message_box(UiStrings().UnsupportedFile, - translate('PresentationPlugin.MediaItem', 'This type of presentation is not supported.')) + translate('PresentationPlugin.MediaItem', + 'This type of presentation is not supported.')) continue item_name = QtGui.QListWidgetItem(filename) item_name.setData(QtCore.Qt.UserRole, file) @@ -238,7 +238,7 @@ class PresentationMediaItem(MediaManagerItem): Settings().setValue(self.settings_section + '/presentations files', self.get_file_list()) def generate_slide_data(self, service_item, item=None, xml_version=False, - remote=False, context=ServiceItemContext.Service, presentation_file=None): + remote=False, context=ServiceItemContext.Service, presentation_file=None): """ Load the relevant information for displaying the presentation in the slidecontroller. In the case of powerpoints, an image for each slide. @@ -251,7 +251,7 @@ class PresentationMediaItem(MediaManagerItem): return False filename = presentation_file if filename is None: - filename = items[0].data(QtCore.Qt.UserRole) + filename = items[0].data(QtCore.Qt.UserRole) file_type = os.path.splitext(filename)[1][1:] if not self.display_type_combo_box.currentText(): return False @@ -292,7 +292,8 @@ class PresentationMediaItem(MediaManagerItem): # File is no longer present if not remote: critical_error_message_box(translate('PresentationPlugin.MediaItem', 'Missing Presentation'), - translate('PresentationPlugin.MediaItem', 'The presentation %s no longer exists.') % filename) + translate('PresentationPlugin.MediaItem', + 'The presentation %s no longer exists.') % filename) return False else: service_item.processor = self.display_type_combo_box.currentText() @@ -322,15 +323,18 @@ class PresentationMediaItem(MediaManagerItem): else: # File is no longer present if not remote: - critical_error_message_box(translate('PresentationPlugin.MediaItem', 'Missing Presentation'), - translate('PresentationPlugin.MediaItem', - 'The presentation %s is incomplete, please reload.') % filename) + critical_error_message_box(translate('PresentationPlugin.MediaItem', + 'Missing Presentation'), + translate('PresentationPlugin.MediaItem', + 'The presentation %s is incomplete, please reload.') + % filename) return False else: # File is no longer present if not remote: critical_error_message_box(translate('PresentationPlugin.MediaItem', 'Missing Presentation'), - translate('PresentationPlugin.MediaItem', 'The presentation %s no longer exists.') % filename) + translate('PresentationPlugin.MediaItem', + 'The presentation %s no longer exists.') % filename) return False def findControllerByType(self, filename): diff --git a/openlp/plugins/presentations/lib/messagelistener.py b/openlp/plugins/presentations/lib/messagelistener.py index 6640fa9bb..1ad7ae860 100644 --- a/openlp/plugins/presentations/lib/messagelistener.py +++ b/openlp/plugins/presentations/lib/messagelistener.py @@ -28,6 +28,7 @@ ############################################################################### import logging +import copy from PyQt4 import QtCore @@ -318,24 +319,26 @@ class MessageListener(object): hide_mode = message[2] file = item.get_frame_path() self.handler = item.processor - # When starting presentation from the servicemanager/slidecontroller we convert + # When starting presentation from the servicemanager we convert # PDF/XPS-serviceitems into image-serviceitems. When started from the mediamanager - # the conversion has already been done. + # the conversion has already been done at this point. if file.endswith('.pdf') or file.endswith('.xps'): log.debug('Converting from pdf/xps to images for serviceitem with file %s', file) - # Create a new image-serviceitem which will overwrite the old one - new_item = ServiceItem() - new_item.name = 'images' + # Create a copy of the original item, and then clear the original item so it can be filled with images + item_cpy = copy.copy(item) + item.__init__(None) if is_live: - self.media_item.generate_slide_data(new_item, item, False, False, ServiceItemContext.Live, file) + self.media_item.generate_slide_data(item, item_cpy, False, False, ServiceItemContext.Live, file) else: - self.media_item.generate_slide_data(new_item, item, False, False, ServiceItemContext.Preview, file) - # We need to overwrite the current serviceitem to make the slidecontroller - # present the images, so we do some copying - service_repr = {'serviceitem': new_item.get_service_repr(True) } - item.set_from_service(service_repr) - item._raw_frames = new_item._raw_frames - # When presenting PDF or XPS, we are using the image presentation code, + self.media_item.generate_slide_data(item, item_cpy, False, False, ServiceItemContext.Preview, file) + # Some of the original serviceitem attributes is needed in the new serviceitem + item.footer = item_cpy.footer + item.from_service = item_cpy.from_service + item.iconic_representation = item_cpy.iconic_representation + item.image_border = item_cpy.image_border + item.main = item_cpy.main + item.theme_data = item_cpy.theme_data + # When presenting PDF or XPS, we are using the image presentation code, # so handler & processor is set to None, and we skip adding the handler. self.handler = None if self.handler == self.media_item.automatic: @@ -346,9 +349,9 @@ class MessageListener(object): controller = self.live_handler else: controller = self.preview_handler - # When presenting PDF or XPS, we are using the image presentation code, + # When presenting PDF or XPS, we are using the image presentation code, # so handler & processor is set to None, and we skip adding the handler. - if self.handler == None: + if self.handler is None: self.controller = controller else: controller.add_handler(self.controllers[self.handler], file, hide_mode, message[3]) diff --git a/openlp/plugins/presentations/lib/pdfcontroller.py b/openlp/plugins/presentations/lib/pdfcontroller.py index ef7e0dd1c..d403d2edd 100644 --- a/openlp/plugins/presentations/lib/pdfcontroller.py +++ b/openlp/plugins/presentations/lib/pdfcontroller.py @@ -38,9 +38,9 @@ from openlp.core.common import Settings from openlp.core.lib import ScreenList from .presentationcontroller import PresentationController, PresentationDocument - log = logging.getLogger(__name__) + class PdfController(PresentationController): """ Class to control PDF presentations @@ -139,7 +139,7 @@ class PdfController(PresentationController): return False else: return True - + def kill(self): """ Called at system exit to clean up any running presentations @@ -153,7 +153,7 @@ class PdfDocument(PresentationDocument): """ Class which holds information of a single presentation. This class is not actually used to present the PDF, instead we convert to - image-serviceitem on the fly and present as such. Therefore some of the 'playback' + image-serviceitem on the fly and present as such. Therefore some of the 'playback' functions is not implemented. """ def __init__(self, controller, presentation): @@ -169,7 +169,7 @@ class PdfDocument(PresentationDocument): self.num_pages = -1 def gs_get_resolution(self, size): - """ + """ Only used when using ghostscript Ghostscript can't scale automaticly while keeping aspect like mupdf, so we need to get the ratio bewteen the screen size and the PDF to scale @@ -187,17 +187,18 @@ flush \n\ quit \n\ ' # Put postscript into tempfile - tmpfile = NamedTemporaryFile(delete=False) - tmpfile.write(postscript) - tmpfile.close() + tmp_file = NamedTemporaryFile(delete=False) + tmp_file.write(postscript) + tmp_file.close() # Run the script on the pdf to get the size runlog = [] try: - runlog = check_output([self.controller.gsbin, '-dNOPAUSE', '-dNODISPLAY', '-dBATCH', '-sFile=' + self.filepath, tmpfile.name]) + runlog = check_output([self.controller.gsbin, '-dNOPAUSE', '-dNODISPLAY', '-dBATCH', + '-sFile=' + self.filepath, tmp_file.name]) except CalledProcessError as e: log.debug(' '.join(e.cmd)) log.debug(e.output) - os.unlink(tmpfile.name) + os.unlink(tmp_file.name) # Extract the pdf resolution from output, the format is " Size: x: , y: " width = 0 height = 0 @@ -205,7 +206,7 @@ quit \n\ try: width = re.search('.*Size: x: (\d+\.?\d*), y: \d+.*', line).group(1) height = re.search('.*Size: x: \d+\.?\d*, y: (\d+\.?\d*).*', line).group(1) - break; + break except AttributeError: pass # Calculate the ratio from pdf to screen @@ -240,18 +241,22 @@ quit \n\ if not os.path.isdir(self.get_temp_folder()): os.makedirs(self.get_temp_folder()) if self.controller.mudrawbin: - runlog = check_output([self.controller.mudrawbin, '-w', str(size.right()), '-h', str(size.bottom()), '-o', os.path.join(self.get_temp_folder(), 'mainslide%03d.png'), self.filepath]) + runlog = check_output([self.controller.mudrawbin, '-w', str(size.right()), '-h', str(size.bottom()), + '-o', os.path.join(self.get_temp_folder(), 'mainslide%03d.png'), self.filepath]) elif self.controller.gsbin: resolution = self.gs_get_resolution(size) - runlog = check_output([self.controller.gsbin, '-dSAFER', '-dNOPAUSE', '-dBATCH', '-sDEVICE=png16m', '-r' + str(resolution), '-dTextAlphaBits=4', '-dGraphicsAlphaBits=4', '-sOutputFile=' + os.path.join(self.get_temp_folder(), 'mainslide%03d.png'), self.filepath]) + runlog = check_output([self.controller.gsbin, '-dSAFER', '-dNOPAUSE', '-dBATCH', '-sDEVICE=png16m', + '-r' + str(resolution), '-dTextAlphaBits=4', '-dGraphicsAlphaBits=4', + '-sOutputFile=' + os.path.join(self.get_temp_folder(), 'mainslide%03d.png'), + self.filepath]) created_files = sorted(os.listdir(self.get_temp_folder())) for fn in created_files: if os.path.isfile(os.path.join(self.get_temp_folder(), fn)): self.image_files.append(os.path.join(self.get_temp_folder(), fn)) - except Exception as e: + except Exception as e: log.debug(e) log.debug(runlog) - return False + return False self.num_pages = len(self.image_files) # Create thumbnails self.create_thumbnails() @@ -277,7 +282,7 @@ quit \n\ """ log.debug('close_presentation pdf') self.controller.remove_doc(self) - + def is_loaded(self): """ Returns true if a presentation is loaded. @@ -298,4 +303,4 @@ quit \n\ """ Returns total number of slides """ - return self.num_pages \ No newline at end of file + return self.num_pages diff --git a/openlp/plugins/presentations/lib/presentationtab.py b/openlp/plugins/presentations/lib/presentationtab.py index 4c35de1a8..5b93c10a9 100644 --- a/openlp/plugins/presentations/lib/presentationtab.py +++ b/openlp/plugins/presentations/lib/presentationtab.py @@ -34,6 +34,7 @@ from openlp.core.lib import SettingsTab, build_icon from openlp.core.lib.ui import critical_error_message_box from .pdfcontroller import PdfController + class PresentationTab(SettingsTab): """ PresentationsTab is the Presentations settings tab in the settings dialog. @@ -120,7 +121,6 @@ class PresentationTab(SettingsTab): self.pdf_program_check_box.setText( translate('PresentationPlugin.PresentationTab', 'Use given full path for mudraw or ghostscript binary:')) - def set_controller_text(self, checkbox, controller): if checkbox.isEnabled(): checkbox.setText(controller.name) @@ -170,7 +170,6 @@ class PresentationTab(SettingsTab): if Settings().value(setting_key) != self.override_app_check_box.checkState(): Settings().setValue(setting_key, self.override_app_check_box.checkState()) changed = True - # Save pdf-settings pdf_program = self.pdf_program_path.text() enable_pdf_program = self.pdf_program_check_box.checkState() @@ -183,7 +182,6 @@ class PresentationTab(SettingsTab): if enable_pdf_program != Settings().value(self.settings_section + '/enable_pdf_program'): Settings().setValue(self.settings_section + '/enable_pdf_program', enable_pdf_program) changed = True - if changed: self.settings_form.register_post_process('mediaitem_suffix_reset') self.settings_form.register_post_process('mediaitem_presentation_rebuild') @@ -199,24 +197,26 @@ class PresentationTab(SettingsTab): checkbox = self.presenter_check_boxes[controller.name] checkbox.setEnabled(controller.is_available()) self.set_controller_text(checkbox, controller) - + def on_pdf_program_path_edit_finished(self): """ After selecting/typing in a program it is validated that it is a actually ghostscript or mudraw """ - program_type = None + program_type = None if self.pdf_program_path.text() != '': program_type = PdfController.check_binary(self.pdf_program_path.text()) if not program_type: - critical_error_message_box(UiStrings().Error, - translate('PresentationPlugin.PresentationTab', 'The program is not ghostscript or mudraw which is required.')) + critical_error_message_box(UiStrings().Error, + translate('PresentationPlugin.PresentationTab', + 'The program is not ghostscript or mudraw which is required.')) self.pdf_program_path.setFocus() def on_pdf_program_browse_button_clicked(self): """ Select the mudraw or ghostscript binary that should be used. """ - filename = QtGui.QFileDialog.getOpenFileName(self, translate('PresentationPlugin.PresentationTab', 'Select mudraw or ghostscript binary.')) + filename = QtGui.QFileDialog.getOpenFileName(self, translate('PresentationPlugin.PresentationTab', + 'Select mudraw or ghostscript binary.')) if filename: self.pdf_program_path.setText(filename) self.pdf_program_path.setFocus() diff --git a/tests/functional/openlp_plugins/presentations/test_pdfcontroller.py b/tests/functional/openlp_plugins/presentations/test_pdfcontroller.py index 7739af3c8..cd2e4f744 100644 --- a/tests/functional/openlp_plugins/presentations/test_pdfcontroller.py +++ b/tests/functional/openlp_plugins/presentations/test_pdfcontroller.py @@ -46,6 +46,7 @@ __default_settings__ = { 'presentations/enable_pdf_program': False } + class TestPdfController(TestCase): """ Test the PdfController. @@ -82,7 +83,7 @@ class TestPdfController(TestCase): controller = None # WHEN: The presentation controller object is created - controller = PdfController(plugin = MagicMock()) + controller = PdfController(plugin=MagicMock()) # THEN: The name of the presentation controller should be correct self.assertEqual('Pdf', controller.name, 'The name of the presentation controller should be correct') @@ -93,16 +94,16 @@ class TestPdfController(TestCase): """ # GIVEN: A Pdf-file test_file = os.path.join(TEST_RESOURCES_PATH, 'presentations', 'pdf_test1.pdf') - + # WHEN: The Pdf is loaded - controller = PdfController(plugin = MagicMock()) + controller = PdfController(plugin=MagicMock()) if not controller.check_available(): raise SkipTest('Could not detect mudraw or ghostscript, so skipping PDF test') controller.temp_folder = self.temp_folder controller.thumbnail_folder = self.thumbnail_folder document = PdfDocument(controller, test_file) loaded = document.load_presentation() - + # THEN: The load should succeed and we should be able to get a pagecount self.assertTrue(loaded, 'The loading of the PDF should succeed.') self.assertEqual(3, document.get_slide_count(), 'The pagecount of the PDF should be 3.') From b242343d5996d6b57fb492fdec019e25497bc1c7 Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Wed, 1 Jan 2014 15:31:06 +0000 Subject: [PATCH 12/18] Added some docstring documentation. --- .../presentations/lib/pdfcontroller.py | 24 +++++++++++++++++-- .../presentations/lib/presentationtab.py | 5 ++++ 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/openlp/plugins/presentations/lib/pdfcontroller.py b/openlp/plugins/presentations/lib/pdfcontroller.py index d403d2edd..be6a03c35 100644 --- a/openlp/plugins/presentations/lib/pdfcontroller.py +++ b/openlp/plugins/presentations/lib/pdfcontroller.py @@ -50,6 +50,8 @@ class PdfController(PresentationController): def __init__(self, plugin): """ Initialise the class + + :param plugin: The plugin that creates the controller. """ log.debug('Initialising') self.process = None @@ -65,6 +67,9 @@ class PdfController(PresentationController): """ Function that checks whether a binary is either ghostscript or mudraw or neither. Is also used from presentationtab.py + + :param program_path:The full path to the binary to check. + :return: Type of the binary, 'gs' if ghostscript, 'mudraw' if mudraw, None if invalid. """ program_type = None runlog = '' @@ -92,6 +97,8 @@ class PdfController(PresentationController): def check_available(self): """ PdfController is able to run on this machine. + + :return: True if program to open PDF-files was found, otherwise False. """ log.debug('check_available Pdf') return self.check_installed() @@ -99,6 +106,8 @@ class PdfController(PresentationController): def check_installed(self): """ Check the viewer is installed. + + :return: True if program to open PDF-files was found, otherwise False. """ log.debug('check_installed Pdf') # Use the user defined program if given @@ -171,8 +180,11 @@ class PdfDocument(PresentationDocument): def gs_get_resolution(self, size): """ Only used when using ghostscript - Ghostscript can't scale automaticly while keeping aspect like mupdf, so we need - to get the ratio bewteen the screen size and the PDF to scale + Ghostscript can't scale automatically while keeping aspect like mupdf, so we need + to get the ratio between the screen size and the PDF to scale + + :param size: Size struct containing the screen size. + :return: The resolution dpi to be used. """ # Use a postscript script to get size of the pdf. It is assumed that all pages have same size postscript = '%!PS \n\ @@ -224,6 +236,8 @@ quit \n\ def load_presentation(self): """ Called when a presentation is added to the SlideController. It generates images from the PDF. + + :return: True is loading succeeded, otherwise False. """ log.debug('load_presentation pdf') # Check if the images has already been created, and if yes load them @@ -286,6 +300,8 @@ quit \n\ def is_loaded(self): """ Returns true if a presentation is loaded. + + :return: True if loaded, False if not. """ log.debug('is_loaded pdf') if self.num_pages < 0: @@ -295,6 +311,8 @@ quit \n\ def is_active(self): """ Returns true if a presentation is currently active. + + :return: True if active, False if not. """ log.debug('is_active pdf') return self.is_loaded() and not self.hidden @@ -302,5 +320,7 @@ quit \n\ def get_slide_count(self): """ Returns total number of slides + + :return: The number of pages in the presentation.. """ return self.num_pages diff --git a/openlp/plugins/presentations/lib/presentationtab.py b/openlp/plugins/presentations/lib/presentationtab.py index 5b93c10a9..8c5997a61 100644 --- a/openlp/plugins/presentations/lib/presentationtab.py +++ b/openlp/plugins/presentations/lib/presentationtab.py @@ -225,6 +225,8 @@ class PresentationTab(SettingsTab): """ When checkbox for manual entering pdf-program is clicked, enable or disable the textbox for the programpath and the browse-button. + + :param checked: If the box is checked or not. """ self.pdf_program_path.setReadOnly(not checked) self.pdf_program_path.setPalette(self.get_grey_text_palette(not checked)) @@ -233,6 +235,9 @@ class PresentationTab(SettingsTab): def get_grey_text_palette(self, greyed): """ Returns a QPalette with greyed out text as used for placeholderText. + + :param greyed: Determines whether the palette should be grayed. + :return: The created palette. """ palette = QtGui.QPalette() color = self.palette().color(QtGui.QPalette.Active, QtGui.QPalette.Text) From d02377f74f1a1df58f067c82c822192a6039918f Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Mon, 6 Jan 2014 10:51:53 +0000 Subject: [PATCH 13/18] Moved PS script into separate file. --- .../lib/ghostscript_get_resolution.ps | 10 ++++++++ .../presentations/lib/pdfcontroller.py | 23 ++++--------------- 2 files changed, 14 insertions(+), 19 deletions(-) create mode 100644 openlp/plugins/presentations/lib/ghostscript_get_resolution.ps diff --git a/openlp/plugins/presentations/lib/ghostscript_get_resolution.ps b/openlp/plugins/presentations/lib/ghostscript_get_resolution.ps new file mode 100644 index 000000000..67c14e1b2 --- /dev/null +++ b/openlp/plugins/presentations/lib/ghostscript_get_resolution.ps @@ -0,0 +1,10 @@ +%!PS +() = +File dup (r) file runpdfbegin +1 pdfgetpage dup +/MediaBox pget { +aload pop exch 4 1 roll exch sub 3 1 roll sub +( Size: x: ) print =print (, y: ) print =print (\n) print +} if +flush +quit diff --git a/openlp/plugins/presentations/lib/pdfcontroller.py b/openlp/plugins/presentations/lib/pdfcontroller.py index be6a03c35..4b8cf5aab 100644 --- a/openlp/plugins/presentations/lib/pdfcontroller.py +++ b/openlp/plugins/presentations/lib/pdfcontroller.py @@ -187,37 +187,22 @@ class PdfDocument(PresentationDocument): :return: The resolution dpi to be used. """ # Use a postscript script to get size of the pdf. It is assumed that all pages have same size - postscript = '%!PS \n\ -() = \n\ -File dup (r) file runpdfbegin \n\ -1 pdfgetpage dup \n\ -/MediaBox pget { \n\ -aload pop exch 4 1 roll exch sub 3 1 roll sub \n\ -( Size: x: ) print =print (, y: ) print =print (\n) print \n\ -} if \n\ -flush \n\ -quit \n\ -' - # Put postscript into tempfile - tmp_file = NamedTemporaryFile(delete=False) - tmp_file.write(postscript) - tmp_file.close() + gs_resolution_script = AppLocation.get_directory(AppLocation.PluginsDir) + '/presentations/lib/ghostscript_get_resolution.ps' # Run the script on the pdf to get the size runlog = [] try: runlog = check_output([self.controller.gsbin, '-dNOPAUSE', '-dNODISPLAY', '-dBATCH', - '-sFile=' + self.filepath, tmp_file.name]) + '-sFile=' + self.filepath, gs_resolution_script]) except CalledProcessError as e: log.debug(' '.join(e.cmd)) log.debug(e.output) - os.unlink(tmp_file.name) # Extract the pdf resolution from output, the format is " Size: x: , y: " width = 0 height = 0 for line in runlog.splitlines(): try: - width = re.search('.*Size: x: (\d+\.?\d*), y: \d+.*', line).group(1) - height = re.search('.*Size: x: \d+\.?\d*, y: (\d+\.?\d*).*', line).group(1) + width = int(re.search('.*Size: x: (\d+\.?\d*), y: \d+.*', line.decode()).group(1)) + height = int(re.search('.*Size: x: \d+\.?\d*, y: (\d+\.?\d*).*', line.decode()).group(1)) break except AttributeError: pass From e958c0465948a7c34bcdc6af9e05bdb14cf29f83 Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Mon, 6 Jan 2014 12:06:22 +0000 Subject: [PATCH 14/18] Make the PdfController reload backend if setting changes. --- openlp/plugins/presentations/lib/mediaitem.py | 3 + .../presentations/lib/pdfcontroller.py | 66 ++++++++++--------- 2 files changed, 38 insertions(+), 31 deletions(-) diff --git a/openlp/plugins/presentations/lib/mediaitem.py b/openlp/plugins/presentations/lib/mediaitem.py index 5eb041ba8..b5bc492c1 100644 --- a/openlp/plugins/presentations/lib/mediaitem.py +++ b/openlp/plugins/presentations/lib/mediaitem.py @@ -138,6 +138,9 @@ class PresentationMediaItem(MediaManagerItem): """ self.display_type_combo_box.clear() for item in self.controllers: + # For PDF reload backend, since it can have changed + if self.controllers[item].name == 'Pdf': + self.controllers[item].check_available() # load the drop down selection if self.controllers[item].enabled(): self.display_type_combo_box.addItem(item) diff --git a/openlp/plugins/presentations/lib/pdfcontroller.py b/openlp/plugins/presentations/lib/pdfcontroller.py index 4b8cf5aab..42dd5960d 100644 --- a/openlp/plugins/presentations/lib/pdfcontroller.py +++ b/openlp/plugins/presentations/lib/pdfcontroller.py @@ -57,10 +57,9 @@ class PdfController(PresentationController): self.process = None PresentationController.__init__(self, plugin, 'Pdf', PdfDocument) self.supports = ['pdf'] - self.mudrawbin = '' - self.gsbin = '' - if self.check_installed() and self.mudrawbin: - self.also_supports = ['xps'] + self.also_supports = [] + # Determine whether mudraw or ghostscript is used + self.check_installed() @staticmethod def check_binary(program_path): @@ -110,44 +109,49 @@ class PdfController(PresentationController): :return: True if program to open PDF-files was found, otherwise False. """ log.debug('check_installed Pdf') + self.mudrawbin = '' + self.gsbin = '' + self.also_supports = [] # Use the user defined program if given if (Settings().value('presentations/enable_pdf_program')): pdf_program = Settings().value('presentations/pdf_program') program_type = self.check_binary(pdf_program) if program_type == 'gs': self.gsbin = pdf_program - return True elif program_type == 'mudraw': self.mudrawbin = pdf_program - return True - # Fallback to autodetection - application_path = AppLocation.get_directory(AppLocation.AppDir) - if os.name == 'nt': - # for windows we only accept mudraw.exe in the base folder + else: + # Fallback to autodetection application_path = AppLocation.get_directory(AppLocation.AppDir) - if os.path.isfile(application_path + '/../mudraw.exe'): - self.mudrawbin = application_path + '/../mudraw.exe' - else: - # First try to find mupdf - try: - self.mudrawbin = check_output(['which', 'mudraw']).decode(encoding='UTF-8').rstrip('\n') - except CalledProcessError: - self.mudrawbin = '' - # if mupdf isn't installed, fallback to ghostscript - if not self.mudrawbin: - try: - self.gsbin = check_output(['which', 'gs']).rstrip('\n') - except CalledProcessError: - self.gsbin = '' - # Last option: check if mudraw is placed in OpenLP base folder - if not self.mudrawbin and not self.gsbin: + if os.name == 'nt': + # for windows we only accept mudraw.exe in the base folder application_path = AppLocation.get_directory(AppLocation.AppDir) - if os.path.isfile(application_path + '/../mudraw'): - self.mudrawbin = application_path + '/../mudraw' - if not self.mudrawbin and not self.gsbin: - return False - else: + if os.path.isfile(application_path + '/../mudraw.exe'): + self.mudrawbin = application_path + '/../mudraw.exe' + else: + # First try to find mupdf + try: + self.mudrawbin = check_output(['which', 'mudraw']).decode(encoding='UTF-8').rstrip('\n') + except CalledProcessError: + self.mudrawbin = '' + # if mupdf isn't installed, fallback to ghostscript + if not self.mudrawbin: + try: + self.gsbin = check_output(['which', 'gs']).rstrip('\n') + except CalledProcessError: + self.gsbin = '' + # Last option: check if mudraw is placed in OpenLP base folder + if not self.mudrawbin and not self.gsbin: + application_path = AppLocation.get_directory(AppLocation.AppDir) + if os.path.isfile(application_path + '/../mudraw'): + self.mudrawbin = application_path + '/../mudraw' + if self.mudrawbin: + self.also_supports = ['xps'] return True + elif self.gsbin: + return True + else: + return False def kill(self): """ From c3af8d5d14a5eddff03c099429ddcf390b227074 Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Tue, 14 Jan 2014 21:57:43 +0000 Subject: [PATCH 15/18] Only allow pdf-program selection using filedialog. --- .../presentations/lib/presentationtab.py | 28 ++++++------------- 1 file changed, 9 insertions(+), 19 deletions(-) diff --git a/openlp/plugins/presentations/lib/presentationtab.py b/openlp/plugins/presentations/lib/presentationtab.py index 8c5997a61..7d5c81366 100644 --- a/openlp/plugins/presentations/lib/presentationtab.py +++ b/openlp/plugins/presentations/lib/presentationtab.py @@ -101,7 +101,6 @@ class PresentationTab(SettingsTab): self.right_column.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Preferred) self.right_layout.addStretch() # Signals and slots - self.pdf_program_path.editingFinished.connect(self.on_pdf_program_path_edit_finished) self.pdf_program_browse_button.clicked.connect(self.on_pdf_program_browse_button_clicked) self.pdf_program_check_box.clicked.connect(self.on_pdf_program_check_box_clicked) @@ -139,7 +138,6 @@ class PresentationTab(SettingsTab): # load pdf-program settings enable_pdf_program = Settings().value(self.settings_section + '/enable_pdf_program') self.pdf_program_check_box.setChecked(enable_pdf_program) - self.pdf_program_path.setReadOnly(not enable_pdf_program) self.pdf_program_path.setPalette(self.get_grey_text_palette(not enable_pdf_program)) self.pdf_program_browse_button.setEnabled(enable_pdf_program) pdf_program = Settings().value(self.settings_section + '/pdf_program') @@ -198,28 +196,21 @@ class PresentationTab(SettingsTab): checkbox.setEnabled(controller.is_available()) self.set_controller_text(checkbox, controller) - def on_pdf_program_path_edit_finished(self): - """ - After selecting/typing in a program it is validated that it is a actually ghostscript or mudraw - """ - program_type = None - if self.pdf_program_path.text() != '': - program_type = PdfController.check_binary(self.pdf_program_path.text()) - if not program_type: - critical_error_message_box(UiStrings().Error, - translate('PresentationPlugin.PresentationTab', - 'The program is not ghostscript or mudraw which is required.')) - self.pdf_program_path.setFocus() - def on_pdf_program_browse_button_clicked(self): """ Select the mudraw or ghostscript binary that should be used. """ filename = QtGui.QFileDialog.getOpenFileName(self, translate('PresentationPlugin.PresentationTab', - 'Select mudraw or ghostscript binary.')) + 'Select mudraw or ghostscript binary.'), + self.pdf_program_path.text()) if filename: - self.pdf_program_path.setText(filename) - self.pdf_program_path.setFocus() + program_type = PdfController.check_binary(filename) + if not program_type: + critical_error_message_box(UiStrings().Error, + translate('PresentationPlugin.PresentationTab', + 'The program is not ghostscript or mudraw which is required.')) + else: + self.pdf_program_path.setText(filename) def on_pdf_program_check_box_clicked(self, checked): """ @@ -228,7 +219,6 @@ class PresentationTab(SettingsTab): :param checked: If the box is checked or not. """ - self.pdf_program_path.setReadOnly(not checked) self.pdf_program_path.setPalette(self.get_grey_text_palette(not checked)) self.pdf_program_browse_button.setEnabled(checked) From 21eeac70ebea2a4e72ccd65f441558799fa65e2e Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Fri, 21 Feb 2014 23:27:00 +0100 Subject: [PATCH 16/18] fixed an exception --- openlp/plugins/presentations/lib/pdfcontroller.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openlp/plugins/presentations/lib/pdfcontroller.py b/openlp/plugins/presentations/lib/pdfcontroller.py index 42dd5960d..36eddac2f 100644 --- a/openlp/plugins/presentations/lib/pdfcontroller.py +++ b/openlp/plugins/presentations/lib/pdfcontroller.py @@ -129,15 +129,16 @@ class PdfController(PresentationController): if os.path.isfile(application_path + '/../mudraw.exe'): self.mudrawbin = application_path + '/../mudraw.exe' else: + DEVNULL = open(os.devnull, 'wb') # First try to find mupdf try: - self.mudrawbin = check_output(['which', 'mudraw']).decode(encoding='UTF-8').rstrip('\n') + self.mudrawbin = check_output(['which', 'mudraw'], stderr=DEVNULL).decode(encoding='UTF-8').rstrip('\n') except CalledProcessError: self.mudrawbin = '' # if mupdf isn't installed, fallback to ghostscript if not self.mudrawbin: try: - self.gsbin = check_output(['which', 'gs']).rstrip('\n') + self.gsbin = check_output(['which', 'gs'], stderr=DEVNULL).decode(encoding='UTF-8').rstrip('\n') except CalledProcessError: self.gsbin = '' # Last option: check if mudraw is placed in OpenLP base folder From 43f156d9fe9deebedba600b112de3f43288b1b04 Mon Sep 17 00:00:00 2001 From: Tim Bentley Date: Tue, 4 Mar 2014 18:49:30 +0000 Subject: [PATCH 17/18] Update Songs forms --- openlp/core/ui/servicenoteform.py | 2 +- openlp/core/ui/wizard.py | 11 +- .../plugins/bibles/forms/bibleimportform.py | 2 +- .../plugins/bibles/forms/bibleupgradeform.py | 6 +- .../presentations/presentationplugin.py | 27 +- openlp/plugins/songs/forms/authorsform.py | 4 +- .../songs/forms/duplicatesongremovalform.py | 78 +++--- openlp/plugins/songs/forms/editsongdialog.py | 12 +- openlp/plugins/songs/forms/editsongform.py | 162 ++++++----- openlp/plugins/songs/forms/editversedialog.py | 2 +- openlp/plugins/songs/forms/editverseform.py | 41 ++- .../plugins/songs/forms/mediafilesdialog.py | 3 +- openlp/plugins/songs/forms/mediafilesform.py | 4 +- openlp/plugins/songs/forms/songbookform.py | 3 +- openlp/plugins/songs/forms/songexportform.py | 253 +++++++++--------- openlp/plugins/songs/forms/songimportform.py | 226 ++++++++-------- .../songs/forms/songmaintenancedialog.py | 7 +- .../songs/forms/songmaintenanceform.py | 75 +++--- .../plugins/songs/forms/songreviewwidget.py | 2 +- openlp/plugins/songs/forms/topicsform.py | 4 +- openlp/plugins/songs/lib/importer.py | 2 +- openlp/plugins/songs/songsplugin.py | 122 +++++---- 22 files changed, 549 insertions(+), 499 deletions(-) diff --git a/openlp/core/ui/servicenoteform.py b/openlp/core/ui/servicenoteform.py index 3128b9a9c..0e5867772 100644 --- a/openlp/core/ui/servicenoteform.py +++ b/openlp/core/ui/servicenoteform.py @@ -63,7 +63,7 @@ class ServiceNoteForm(QtGui.QDialog): self.dialog_layout = QtGui.QVBoxLayout(self) self.dialog_layout.setContentsMargins(8, 8, 8, 8) self.dialog_layout.setSpacing(8) - self.dialog_layout.setObjectName('verticalLayout') + self.dialog_layout.setObjectName('vertical_layout') self.text_edit = SpellTextEdit(self, False) self.text_edit.setObjectName('textEdit') self.dialog_layout.addWidget(self.text_edit) diff --git a/openlp/core/ui/wizard.py b/openlp/core/ui/wizard.py index 4046e08f3..87fed2a54 100644 --- a/openlp/core/ui/wizard.py +++ b/openlp/core/ui/wizard.py @@ -215,7 +215,7 @@ class OpenLPWizard(QtGui.QWizard): """ if self.with_progress_page and self.page(pageId) == self.progress_page: self.pre_wizard() - self.performWizard() + self.perform_wizard() self.post_wizard() else: self.custom_page_changed(pageId) @@ -294,8 +294,8 @@ class OpenLPWizard(QtGui.QWizard): if filters: filters += ';;' filters += '%s (*)' % UiStrings().AllFiles - filename = QtGui.QFileDialog.getOpenFileName(self, title, - os.path.dirname(Settings().value(self.plugin.settings_section + '/' + setting_name)), filters) + filename = QtGui.QFileDialog.getOpenFileName( + self, title, os.path.dirname(Settings().value(self.plugin.settings_section + '/' + setting_name)), filters) if filename: editbox.setText(filename) Settings().setValue(self.plugin.settings_section + '/' + setting_name, filename) @@ -313,8 +313,9 @@ class OpenLPWizard(QtGui.QWizard): ``setting_name`` The place where to save the last opened directory. """ - folder = QtGui.QFileDialog.getExistingDirectory(self, title, - Settings().value(self.plugin.settings_section + '/' + setting_name), QtGui.QFileDialog.ShowDirsOnly) + folder = QtGui.QFileDialog.getExistingDirectory( + self, title, Settings().value(self.plugin.settings_section + '/' + setting_name), + QtGui.QFileDialog.ShowDirsOnly) if folder: editbox.setText(folder) Settings().setValue(self.plugin.settings_section + '/' + setting_name, folder) diff --git a/openlp/plugins/bibles/forms/bibleimportform.py b/openlp/plugins/bibles/forms/bibleimportform.py index 4e99f4a08..5cde95fef 100644 --- a/openlp/plugins/bibles/forms/bibleimportform.py +++ b/openlp/plugins/bibles/forms/bibleimportform.py @@ -524,7 +524,7 @@ class BibleImportForm(OpenLPWizard): self.progress_label.setText(WizardStrings.StartingImport) self.application.process_events() - def performWizard(self): + def perform_wizard(self): """ Perform the actual import. """ diff --git a/openlp/plugins/bibles/forms/bibleupgradeform.py b/openlp/plugins/bibles/forms/bibleupgradeform.py index 22a6dbe58..3bda5edfc 100644 --- a/openlp/plugins/bibles/forms/bibleupgradeform.py +++ b/openlp/plugins/bibles/forms/bibleupgradeform.py @@ -107,7 +107,7 @@ class BibleUpgradeForm(OpenLPWizard): """ if self.page(pageId) == self.progress_page: self.pre_wizard() - self.performWizard() + self.perform_wizard() self.post_wizard() elif self.page(pageId) == self.selectPage and not self.files: self.next() @@ -312,7 +312,7 @@ class BibleUpgradeForm(OpenLPWizard): """ Set default values for the wizard pages. """ - log.debug('BibleUpgrade setDefaults') + log.debug('BibleUpgrade set_defaults') settings = Settings() settings.beginGroup(self.plugin.settings_section) self.stop_import_flag = False @@ -338,7 +338,7 @@ class BibleUpgradeForm(OpenLPWizard): self.progress_label.setText(translate('BiblesPlugin.UpgradeWizardForm', 'Starting upgrade...')) self.application.process_events() - def performWizard(self): + def perform_wizard(self): """ Perform the actual upgrade. """ diff --git a/openlp/plugins/presentations/presentationplugin.py b/openlp/plugins/presentations/presentationplugin.py index c101f0437..de0a0c5a5 100644 --- a/openlp/plugins/presentations/presentationplugin.py +++ b/openlp/plugins/presentations/presentationplugin.py @@ -43,16 +43,15 @@ from openlp.plugins.presentations.lib import PresentationController, Presentatio log = logging.getLogger(__name__) -__default_settings__ = { - 'presentations/override app': QtCore.Qt.Unchecked, - 'presentations/enable_pdf_program': QtCore.Qt.Unchecked, - 'presentations/pdf_program': '', - 'presentations/Impress': QtCore.Qt.Checked, - 'presentations/Powerpoint': QtCore.Qt.Checked, - 'presentations/Powerpoint Viewer': QtCore.Qt.Checked, - 'presentations/Pdf': QtCore.Qt.Checked, - 'presentations/presentations files': [] -} +__default_settings__ = {'presentations/override app': QtCore.Qt.Unchecked, + 'presentations/enable_pdf_program': QtCore.Qt.Unchecked, + 'presentations/pdf_program': '', + 'presentations/Impress': QtCore.Qt.Checked, + 'presentations/Powerpoint': QtCore.Qt.Checked, + 'presentations/Powerpoint Viewer': QtCore.Qt.Checked, + 'presentations/Pdf': QtCore.Qt.Checked, + 'presentations/presentations files': [] + } class PresentationPlugin(Plugin): @@ -147,10 +146,10 @@ class PresentationPlugin(Plugin): Return information about this plugin. """ about_text = translate('PresentationPlugin', 'Presentation ' - 'Plugin
The presentation plugin provides the ' - 'ability to show presentations using a number of different ' - 'programs. The choice of available presentation programs is ' - 'available to the user in a drop down box.') + 'Plugin
The presentation plugin provides the ' + 'ability to show presentations using a number of different ' + 'programs. The choice of available presentation programs is ' + 'available to the user in a drop down box.') return about_text def set_plugin_text_strings(self): diff --git a/openlp/plugins/songs/forms/authorsform.py b/openlp/plugins/songs/forms/authorsform.py index 0375bf1de..5a3ee175d 100644 --- a/openlp/plugins/songs/forms/authorsform.py +++ b/openlp/plugins/songs/forms/authorsform.py @@ -111,8 +111,8 @@ class AuthorsForm(QtGui.QDialog, Ui_AuthorsDialog): elif not self.display_edit.text(): if critical_error_message_box( message=translate('SongsPlugin.AuthorsForm', - 'You have not set a display name for the author, combine the first and last names?'), - parent=self, question=True) == QtGui.QMessageBox.Yes: + 'You have not set a display name for the author, combine the first and last names?'), + parent=self, question=True) == QtGui.QMessageBox.Yes: self.display_edit.setText(self.first_name_edit.text() + ' ' + self.last_name_edit.text()) return QtGui.QDialog.accept(self) else: diff --git a/openlp/plugins/songs/forms/duplicatesongremovalform.py b/openlp/plugins/songs/forms/duplicatesongremovalform.py index fe54e159f..2da9113d6 100644 --- a/openlp/plugins/songs/forms/duplicatesongremovalform.py +++ b/openlp/plugins/songs/forms/duplicatesongremovalform.py @@ -47,8 +47,8 @@ log = logging.getLogger(__name__) class DuplicateSongRemovalForm(OpenLPWizard): """ - This is the Duplicate Song Removal Wizard. It provides functionality to - search for and remove duplicate songs in the database. + This is the Duplicate Song Removal Wizard. It provides functionality to search for and remove duplicate songs + in the database. """ log.info('DuplicateSongRemovalForm loaded') @@ -56,19 +56,16 @@ class DuplicateSongRemovalForm(OpenLPWizard): """ Instantiate the wizard, and run any extra setup we need to. - ``parent`` - The QWidget-derived parent of the wizard. - - ``plugin`` - The songs plugin. + :param plugin: The songs plugin. """ self.duplicate_song_list = [] self.review_current_count = 0 self.review_total_count = 0 # Used to interrupt ongoing searches when cancel is clicked. self.break_search = False - super(DuplicateSongRemovalForm, self).__init__(Registry().get('main_window'), - plugin, 'duplicateSongRemovalWizard', ':/wizards/wizard_duplicateremoval.bmp', False) + super(DuplicateSongRemovalForm, self).__init__( + Registry().get('main_window'), plugin, 'duplicateSongRemovalWizard', ':/wizards/wizard_duplicateremoval.bmp' + , False) self.setMinimumWidth(730) def custom_signals(self): @@ -127,30 +124,31 @@ class DuplicateSongRemovalForm(OpenLPWizard): """ self.setWindowTitle(translate('Wizard', 'Wizard')) self.title_label.setText(WizardStrings.HeaderStyle % translate('OpenLP.Ui', - 'Welcome to the Duplicate Song Removal Wizard')) - self.information_label.setText(translate("Wizard", - 'This wizard will help you to remove duplicate songs from the song database. You will have a chance to ' - 'review every potential duplicate song before it is deleted. So no songs will be deleted without your ' - 'explicit approval.')) + 'Welcome to the Duplicate Song Removal Wizard')) + self.information_label.setText( + translate("Wizard", + 'This wizard will help you to remove duplicate songs from the song database. You will have a ' + 'chance to review every potential duplicate song before it is deleted. So no songs will be ' + 'deleted without your explicit approval.')) self.searching_page.setTitle(translate('Wizard', 'Searching for duplicate songs.')) self.searching_page.setSubTitle(translate('Wizard', 'Please wait while your songs database is analyzed.')) self.update_review_counter_text() self.review_page.setSubTitle(translate('Wizard', - 'Here you can decide which songs to remove and which ones to keep.')) + 'Here you can decide which songs to remove and which ones to keep.')) def update_review_counter_text(self): """ Set the wizard review page header text. """ - self.review_page.setTitle(translate('Wizard', 'Review duplicate songs (%s/%s)') % \ - (self.review_current_count, self.review_total_count)) + self.review_page.setTitle( + translate('Wizard', 'Review duplicate songs (%s/%s)') % + (self.review_current_count, self.review_total_count)) def custom_page_changed(self, page_id): """ Called when changing the wizard page. - ``page_id`` - ID of the page the wizard changed to. + :param page_id: ID of the page the wizard changed to. """ # Hide back button. self.button(QtGui.QWizard.BackButton).hide() @@ -172,11 +170,11 @@ class DuplicateSongRemovalForm(OpenLPWizard): for outer_song_counter in range(max_songs - 1): for inner_song_counter in range(outer_song_counter + 1, max_songs): if songs_probably_equal(songs[outer_song_counter], songs[inner_song_counter]): - duplicate_added = self.add_duplicates_to_song_list(songs[outer_song_counter], - songs[inner_song_counter]) + duplicate_added = self.add_duplicates_to_song_list( + songs[outer_song_counter], songs[inner_song_counter]) if duplicate_added: - self.found_duplicates_edit.appendPlainText(songs[outer_song_counter].title + " = " + - songs[inner_song_counter].title) + self.found_duplicates_edit.appendPlainText( + songs[outer_song_counter].title + " = " + songs[inner_song_counter].title) self.duplicate_search_progress_bar.setValue(self.duplicate_search_progress_bar.value() + 1) # The call to process_events() will keep the GUI responsive. self.application.process_events() @@ -200,23 +198,20 @@ class DuplicateSongRemovalForm(OpenLPWizard): self.button(QtGui.QWizard.FinishButton).setEnabled(True) self.button(QtGui.QWizard.NextButton).hide() self.button(QtGui.QWizard.CancelButton).hide() - QtGui.QMessageBox.information(self, translate('Wizard', 'Information'), + QtGui.QMessageBox.information( + self, translate('Wizard', 'Information'), translate('Wizard', 'No duplicate songs have been found in the database.'), QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Ok)) def add_duplicates_to_song_list(self, search_song, duplicate_song): """ Inserts a song duplicate (two similar songs) to the duplicate song list. - If one of the two songs is already part of the duplicate song list, - don't add another duplicate group but add the other song to that group. - Returns True if at least one of the songs was added, False if both were already - member of a group. + If one of the two songs is already part of the duplicate song list, don't add another duplicate group but + add the other song to that group. + Returns True if at least one of the songs was added, False if both were already member of a group. - ``search_song`` - The song we searched the duplicate for. - - ``duplicate_song`` - The duplicate song. + :param search_song: The song we searched the duplicate for. + :param duplicate_song: The duplicate song. """ duplicate_group_found = False duplicate_added = False @@ -259,8 +254,8 @@ class DuplicateSongRemovalForm(OpenLPWizard): def validateCurrentPage(self): """ - Controls whether we should switch to the next wizard page. This method loops - on the review page as long as there are more song duplicates to review. + Controls whether we should switch to the next wizard page. This method loops on the review page as long as + there are more song duplicates to review. """ if self.currentId() == self.review_page_id: # As long as it's not the last duplicate list entry we revisit the review page. @@ -273,12 +268,10 @@ class DuplicateSongRemovalForm(OpenLPWizard): def remove_button_clicked(self, song_review_widget): """ - Removes a song from the database, removes the GUI element representing the - song on the review page, and disable the remove button if only one duplicate - is left. + Removes a song from the database, removes the GUI element representing the song on the review page, and + disable the remove button if only one duplicate is left. - ``song_review_widget`` - The SongReviewWidget whose song we should delete. + :param song_review_widget: The SongReviewWidget whose song we should delete. """ # Remove song from duplicate song list. self.duplicate_song_list[-1].remove(song_review_widget.song) @@ -315,9 +308,8 @@ class DuplicateSongRemovalForm(OpenLPWizard): def process_current_duplicate_entry(self): """ - Update the review counter in the wizard header, add song widgets for - the current duplicate group to review, if it's the last - duplicate song group, hide the "next" button and show the "finish" button. + Update the review counter in the wizard header, add song widgets for the current duplicate group to review, + if it's the last duplicate song group, hide the "next" button and show the "finish" button. """ # Update the counter. self.review_current_count = self.review_total_count - (len(self.duplicate_song_list) - 1) diff --git a/openlp/plugins/songs/forms/editsongdialog.py b/openlp/plugins/songs/forms/editsongdialog.py index d0fda031e..4cf6fc435 100644 --- a/openlp/plugins/songs/forms/editsongdialog.py +++ b/openlp/plugins/songs/forms/editsongdialog.py @@ -153,8 +153,8 @@ class Ui_EditSongDialog(object): self.topics_layout.setObjectName('topics_layout') self.topic_add_layout = QtGui.QHBoxLayout() self.topic_add_layout.setObjectName('topic_add_layout') - self.topicsComboBox = create_combo_box(self.topics_group_box, 'topicsComboBox') - self.topic_add_layout.addWidget(self.topicsComboBox) + self.topics_combo_box = create_combo_box(self.topics_group_box, 'topics_combo_box') + self.topic_add_layout.addWidget(self.topics_combo_box) self.topic_add_button = QtGui.QPushButton(self.topics_group_box) self.topic_add_button.setObjectName('topic_add_button') self.topic_add_layout.addWidget(self.topic_add_button) @@ -296,7 +296,7 @@ class Ui_EditSongDialog(object): self.verse_edit_all_button.setText(translate('SongsPlugin.EditSongForm', 'Ed&it All')) self.verse_delete_button.setText(UiStrings().Delete) self.song_tab_widget.setTabText(self.song_tab_widget.indexOf(self.lyrics_tab), - translate('SongsPlugin.EditSongForm', 'Title && Lyrics')) + translate('SongsPlugin.EditSongForm', 'Title && Lyrics')) self.authors_group_box.setTitle(SongStrings.Authors) self.author_add_button.setText(translate('SongsPlugin.EditSongForm', '&Add to Song')) self.author_remove_button.setText(translate('SongsPlugin.EditSongForm', '&Remove')) @@ -308,7 +308,7 @@ class Ui_EditSongDialog(object): self.song_book_name_label.setText(translate('SongsPlugin.EditSongForm', 'Book:')) self.song_book_number_label.setText(translate('SongsPlugin.EditSongForm', 'Number:')) self.song_tab_widget.setTabText(self.song_tab_widget.indexOf(self.authors_tab), - translate('SongsPlugin.EditSongForm', 'Authors, Topics && Song Book')) + translate('SongsPlugin.EditSongForm', 'Authors, Topics && Song Book')) self.theme_group_box.setTitle(UiStrings().Theme) self.theme_add_button.setText(translate('SongsPlugin.EditSongForm', 'New &Theme')) self.rights_group_box.setTitle(translate('SongsPlugin.EditSongForm', 'Copyright Information')) @@ -316,9 +316,9 @@ class Ui_EditSongDialog(object): self.ccli_label.setText(UiStrings().CCLINumberLabel) self.comments_group_box.setTitle(translate('SongsPlugin.EditSongForm', 'Comments')) self.song_tab_widget.setTabText(self.song_tab_widget.indexOf(self.theme_tab), - translate('SongsPlugin.EditSongForm', 'Theme, Copyright Info && Comments')) + translate('SongsPlugin.EditSongForm', 'Theme, Copyright Info && Comments')) self.song_tab_widget.setTabText(self.song_tab_widget.indexOf(self.audio_tab), - translate('SongsPlugin.EditSongForm', 'Linked Audio')) + translate('SongsPlugin.EditSongForm', 'Linked Audio')) self.from_file_button.setText(translate('SongsPlugin.EditSongForm', 'Add &File(s)')) self.from_media_button.setText(translate('SongsPlugin.EditSongForm', 'Add &Media')) self.audio_remove_button.setText(translate('SongsPlugin.EditSongForm', '&Remove')) diff --git a/openlp/plugins/songs/forms/editsongform.py b/openlp/plugins/songs/forms/editsongform.py index 1620a1285..4a4b772bc 100644 --- a/openlp/plugins/songs/forms/editsongform.py +++ b/openlp/plugins/songs/forms/editsongform.py @@ -131,6 +131,12 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog): self.authors_list_view.addItem(author_item) def _extract_verse_order(self, verse_order): + """ + Split out the verse order + + :param verse_order: The starting verse order + :return: revised order + """ order = [] order_names = str(verse_order).split() for item in order_names: @@ -153,6 +159,13 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog): return order def _validate_verse_list(self, verse_order, verse_count): + """ + Check the verse order list has valid verses + + :param verse_order: Verse order + :param verse_count: number of verses + :return: Count of invalid verses + """ verses = [] invalid_verses = [] verse_names = [] @@ -171,12 +184,12 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog): valid = create_separated_list(verse_names) if len(invalid_verses) > 1: msg = translate('SongsPlugin.EditSongForm', 'There are no verses corresponding to "%(invalid)s".' - 'Valid entries are %(valid)s.\nPlease enter the verses seperated by spaces.') \ - % {'invalid' : ', '.join(invalid_verses), 'valid' : valid} + 'Valid entries are %(valid)s.\nPlease enter the verses separated by spaces.') % \ + {'invalid': ', '.join(invalid_verses), 'valid' : valid} else: msg = translate('SongsPlugin.EditSongForm', 'There is no verse corresponding to "%(invalid)s".' - 'Valid entries are %(valid)s.\nPlease enter the verses seperated by spaces.') \ - % {'invalid' : invalid_verses[0], 'valid' : valid} + 'Valid entries are %(valid)s.\nPlease enter the verses separated by spaces.') % \ + {'invalid': invalid_verses[0], 'valid' : valid} critical_error_message_box(title=translate('SongsPlugin.EditSongForm', 'Invalid Verse Order'), message=msg) return len(invalid_verses) == 0 @@ -213,7 +226,8 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog): return False text = self.song_book_combo_box.currentText() if self.song_book_combo_box.findText(text, QtCore.Qt.MatchExactly) < 0: - if QtGui.QMessageBox.question(self, translate('SongsPlugin.EditSongForm', 'Add Book'), + if QtGui.QMessageBox.question( + self, translate('SongsPlugin.EditSongForm', 'Add Book'), translate('SongsPlugin.EditSongForm', 'This song book does not exist, do you want to add it?'), QtGui.QMessageBox.Yes | QtGui.QMessageBox.No, QtGui.QMessageBox.Yes) == QtGui.QMessageBox.Yes: book = Book.populate(name=text, publisher='') @@ -250,17 +264,16 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog): def keyPressEvent(self, event): """ - Reimplement the keyPressEvent to react on Return/Enter keys. When some combo boxes have focus we do not want + Re-implement the keyPressEvent to react on Return/Enter keys. When some combo boxes have focus we do not want dialog's default action be triggered but instead our own. - ``event`` - A QtGui.QKeyEvent event. + :param event: A QtGui.QKeyEvent event. """ if event.key() in (QtCore.Qt.Key_Enter, QtCore.Qt.Key_Return): if self.authors_combo_box.hasFocus() and self.authors_combo_box.currentText(): self.on_author_add_button_clicked() return - if self.topicsComboBox.hasFocus() and self.topicsComboBox.currentText(): + if self.topics_combo_box.hasFocus() and self.topics_combo_box.currentText(): self.on_topic_add_button_clicked() return QtGui.QDialog.keyPressEvent(self, event) @@ -294,7 +307,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog): Load the topics into the combobox. """ self.topics = [] - self._load_objects(Topic, self.topicsComboBox, self.topics) + self._load_objects(Topic, self.topics_combo_box, self.topics) def load_books(self): """ @@ -321,7 +334,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog): for plugin in self.plugin_manager.plugins: if plugin.name == 'media' and plugin.status == PluginStatus.Active: self.from_media_button.setVisible(True) - self.media_form.populateFiles(plugin.media_item.get_list(MediaType.Audio)) + self.media_form.populate_files(plugin.media_item.get_list(MediaType.Audio)) break def new_song(self): @@ -358,11 +371,8 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog): """ Loads a song. - ``song_id`` - The song id (int). - - ``preview`` - Should be ``True`` if the song is also previewed (boolean). + :param song_id: The song id (int). + :param preview: Should be ``True`` if the song is also previewed (boolean). """ log.debug('Load Song') self.initialise() @@ -396,8 +406,8 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog): self.verse_list_widget.setRowCount(0) verse_tags_translated = False if self.song.lyrics.startswith(' 0: item_id = (self.authors_combo_box.itemData(item)) author = self.manager.get_object(Author, item_id) - if self.authors_list_view.findItems(str(author.display_name), - QtCore.Qt.MatchExactly): + if self.authors_list_view.findItems(str(author.display_name), QtCore.Qt.MatchExactly): critical_error_message_box( message=translate('SongsPlugin.EditSongForm', 'This author is already in the list.')) else: self._add_author_to_list(author) self.authors_combo_box.setCurrentIndex(0) else: - QtGui.QMessageBox.warning(self, UiStrings().NISs, + QtGui.QMessageBox.warning( + self, UiStrings().NISs, translate('SongsPlugin.EditSongForm', 'You have not selected a valid author. Either select an author ' - 'from the list, or type in a new author and click the "Add Author to Song" button to add ' - 'the new author.')) + 'from the list, or type in a new author and click the "Add Author to Song" button to add ' + 'the new author.')) def on_authors_list_view_clicked(self): """ @@ -540,10 +551,11 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog): self.authors_list_view.takeItem(row) def on_topic_add_button_clicked(self): - item = int(self.topicsComboBox.currentIndex()) - text = self.topicsComboBox.currentText() + item = int(self.topics_combo_box.currentIndex()) + text = self.topics_combo_box.currentText() if item == 0 and text: - if QtGui.QMessageBox.question(self, translate('SongsPlugin.EditSongForm', 'Add Topic'), + if QtGui.QMessageBox.question( + self, translate('SongsPlugin.EditSongForm', 'Add Topic'), translate('SongsPlugin.EditSongForm', 'This topic does not exist, do you want to add it?'), QtGui.QMessageBox.Yes | QtGui.QMessageBox.No, QtGui.QMessageBox.Yes) == QtGui.QMessageBox.Yes: topic = Topic.populate(name=text) @@ -552,25 +564,26 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog): topic_item.setData(QtCore.Qt.UserRole, topic.id) self.topics_list_view.addItem(topic_item) self.load_topics() - self.topicsComboBox.setCurrentIndex(0) + self.topics_combo_box.setCurrentIndex(0) else: return elif item > 0: - item_id = (self.topicsComboBox.itemData(item)) + item_id = (self.topics_combo_box.itemData(item)) topic = self.manager.get_object(Topic, item_id) - if self.topics_list_view.findItems(str(topic.name), - QtCore.Qt.MatchExactly): + if self.topics_list_view.findItems(str(topic.name), QtCore.Qt.MatchExactly): critical_error_message_box( message=translate('SongsPlugin.EditSongForm', 'This topic is already in the list.')) else: topic_item = QtGui.QListWidgetItem(str(topic.name)) topic_item.setData(QtCore.Qt.UserRole, topic.id) self.topics_list_view.addItem(topic_item) - self.topicsComboBox.setCurrentIndex(0) + self.topics_combo_box.setCurrentIndex(0) else: - QtGui.QMessageBox.warning(self, UiStrings().NISs, + QtGui.QMessageBox.warning( + self, UiStrings().NISs, translate('SongsPlugin.EditSongForm', 'You have not selected a valid topic. Either select a topic ' - 'from the list, or type in a new topic and click the "Add Topic to Song" button to add the new topic.')) + 'from the list, or type in a new topic and click the "Add Topic to Song" button to add the ' + 'new topic.')) def on_topic_list_view_clicked(self): self.topic_remove_button.setEnabled(True) @@ -588,7 +601,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog): def on_verse_add_button_clicked(self): self.verse_form.set_verse('', True) if self.verse_form.exec_(): - after_text, verse_tag, verse_num = self.verse_form.get_verse() + after_text, verse_tag, verse_num = self.verse_form.get_verse verse_def = '%s%s' % (verse_tag, verse_num) item = QtGui.QTableWidgetItem(after_text) item.setData(QtCore.Qt.UserRole, verse_def) @@ -606,7 +619,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog): verse_id = item.data(QtCore.Qt.UserRole) self.verse_form.set_verse(temp_text, True, verse_id) if self.verse_form.exec_(): - after_text, verse_tag, verse_num = self.verse_form.get_verse() + after_text, verse_tag, verse_num = self.verse_form.get_verse verse_def = '%s%s' % (verse_tag, verse_num) item.setData(QtCore.Qt.UserRole, verse_def) item.setText(after_text) @@ -628,6 +641,11 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog): self.on_verse_order_text_changed(self.verse_order_edit.text()) def on_verse_edit_all_button_clicked(self): + """ + Verse edit all button (save) pressed + + :return: + """ verse_list = '' if self.verse_list_widget.rowCount() > 0: for row in range(self.verse_list_widget.rowCount()): @@ -643,7 +661,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog): self.verse_form.set_verse('') if not self.verse_form.exec_(): return - verse_list = self.verse_form.get_all_verses() + verse_list = self.verse_form.get_all_verses verse_list = str(verse_list.replace('\r\n', '\n')) self.verse_list_widget.clear() self.verse_list_widget.setRowCount(0) @@ -686,6 +704,10 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog): self.on_verse_order_text_changed(self.verse_order_edit.text()) def on_verse_delete_button_clicked(self): + """ + Verse Delete button pressed + + """ self.verse_list_widget.removeRow(self.verse_list_widget.currentRow()) if not self.verse_list_widget.selectedItems(): self.verse_edit_button.setEnabled(False) @@ -696,8 +718,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog): Checks if the verse order is complete or missing. Shows a error message according to the state of the verse order. - ``text`` - The text of the verse order edit (ignored). + :param text: The text of the verse order edit (ignored). """ # Extract all verses which were used in the order. verses_in_order = self._extract_verse_order(self.verse_order_edit.text()) @@ -719,6 +740,9 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog): self.warning_label.setText(label_text) def on_copyright_insert_button_triggered(self): + """ + Copyright insert button pressed + """ text = self.copyright_edit.text() pos = self.copyright_edit.cursorPosition() sign = SongStrings.CopyrightSymbol @@ -728,6 +752,9 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog): self.copyright_edit.setCursorPosition(pos + len(sign)) def on_maintenance_button_clicked(self): + """ + Maintenance button pressed + """ temp_song_book = None item = int(self.song_book_combo_box.currentIndex()) text = self.song_book_combo_box.currentText() @@ -745,8 +772,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog): Save and Preview button clicked. The Song is valid so as the plugin to add it to preview to see. - ``button`` - A button (QPushButton). + :param button: A button (QPushButton). """ log.debug('onPreview') if button.objectName() == 'preview_button': @@ -758,9 +784,9 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog): Loads file(s) from the filesystem. """ filters = '%s (*)' % UiStrings().AllFiles - filenames = FileDialog.getOpenFileNames(self, - translate('SongsPlugin.EditSongForm', 'Open File(s)'), '', filters) - for filename in filenames: + file_names = FileDialog.getOpenFileNames(self, translate('SongsPlugin.EditSongForm', 'Open File(s)'), '', + filters) + for filename in file_names: item = QtGui.QListWidgetItem(os.path.split(str(filename))[1]) item.setData(QtCore.Qt.UserRole, filename) self.audio_list_widget.addItem(item) @@ -770,7 +796,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog): Loads file(s) from the media plugin. """ if self.media_form.exec_(): - for filename in self.media_form.getSelectedFiles(): + for filename in self.media_form.get_selected_files(): item = QtGui.QListWidgetItem(os.path.split(str(filename))[1]) item.setData(QtCore.Qt.UserRole, filename) self.audio_list_widget.addItem(item) @@ -814,7 +840,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog): def clear_caches(self): """ - Free up autocompletion memory on dialog exit + Free up auto-completion memory on dialog exit """ log.debug('SongEditForm.clearCaches') self.authors = [] @@ -826,7 +852,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog): """ Exit Dialog and do not save """ - log.debug ('SongEditForm.reject') + log.debug('SongEditForm.reject') self.clear_caches() QtGui.QDialog.reject(self) @@ -843,13 +869,10 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog): def save_song(self, preview=False): """ - Get all the data from the widgets on the form, and then save it to the - database. The form has been validated and all reference items - (Authors, Books and Topics) have been saved before this function is - called. + Get all the data from the widgets on the form, and then save it to the database. The form has been validated + and all reference items (Authors, Books and Topics) have been saved before this function is called. - ``preview`` - Should be ``True`` if the song is also previewed (boolean). + :param preview: Should be ``True`` if the song is also previewed (boolean). """ # The Song() assignment. No database calls should be made while a # Song() is in a partially complete state. @@ -863,9 +886,9 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog): self.song.search_lyrics = '' self.song.verse_order = '' self.song.comments = self.comments_edit.toPlainText() - ordertext = self.verse_order_edit.text() + order_text = self.verse_order_edit.text() order = [] - for item in ordertext.split(): + for item in order_text.split(): verse_tag = VerseType.tags[VerseType.from_translated_tag(item[0])] verse_num = item[1:].lower() order.append('%s%s' % (verse_tag, verse_num)) @@ -874,8 +897,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog): self.song.song_number = self.song_book_number_edit.text() book_name = self.song_book_combo_box.currentText() if book_name: - self.song.book = self.manager.get_object_filtered(Book, - Book.name == book_name) + self.song.book = self.manager.get_object_filtered(Book, Book.name == book_name) else: self.song.book = None theme_name = self.theme_combo_box.currentText() @@ -887,15 +909,15 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog): self.song.authors = [] for row in range(self.authors_list_view.count()): item = self.authors_list_view.item(row) - authorId = (item.data(QtCore.Qt.UserRole)) - author = self.manager.get_object(Author, authorId) + author_id = (item.data(QtCore.Qt.UserRole)) + author = self.manager.get_object(Author, author_id) if author is not None: self.song.authors.append(author) self.song.topics = [] for row in range(self.topics_list_view.count()): item = self.topics_list_view.item(row) - topicId = (item.data(QtCore.Qt.UserRole)) - topic = self.manager.get_object(Topic, topicId) + topic_id = (item.data(QtCore.Qt.UserRole)) + topic = self.manager.get_object(Topic, topic_id) if topic is not None: self.song.topics.append(topic) # Save the song here because we need a valid id for the audio files. @@ -904,7 +926,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog): audio_files = [a.file_name for a in self.song.media_files] log.debug(audio_files) save_path = os.path.join(AppLocation.get_section_data_path(self.media_item.plugin.name), 'audio', - str(self.song.id)) + str(self.song.id)) check_directory_exists(save_path) self.song.media_files = [] files = [] @@ -912,8 +934,8 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog): item = self.audio_list_widget.item(row) filename = item.data(QtCore.Qt.UserRole) if not filename.startswith(save_path): - oldfile, filename = filename, os.path.join(save_path, os.path.split(filename)[1]) - shutil.copyfile(oldfile, filename) + old_file, filename = filename, os.path.join(save_path, os.path.split(filename)[1]) + shutil.copyfile(old_file, filename) files.append(filename) media_file = MediaFile() media_file.file_name = filename diff --git a/openlp/plugins/songs/forms/editversedialog.py b/openlp/plugins/songs/forms/editversedialog.py index d6944433d..f1901b203 100644 --- a/openlp/plugins/songs/forms/editversedialog.py +++ b/openlp/plugins/songs/forms/editversedialog.py @@ -86,4 +86,4 @@ class Ui_EditVerseDialog(object): self.split_button.setToolTip(UiStrings().SplitToolTip) self.insert_button.setText(translate('SongsPlugin.EditVerseForm', '&Insert')) self.insert_button.setToolTip(translate('SongsPlugin.EditVerseForm', - 'Split a slide into two by inserting a verse splitter.')) + 'Split a slide into two by inserting a verse splitter.')) diff --git a/openlp/plugins/songs/forms/editverseform.py b/openlp/plugins/songs/forms/editverseform.py index 847527eb9..038cff539 100644 --- a/openlp/plugins/songs/forms/editverseform.py +++ b/openlp/plugins/songs/forms/editverseform.py @@ -50,16 +50,18 @@ class EditVerseForm(QtGui.QDialog, Ui_EditVerseDialog): """ super(EditVerseForm, self).__init__(parent) self.setupUi(self) - self.verse_text_edit.customContextMenuRequested.connect(self.context_menu) self.insert_button.clicked.connect(self.on_insert_button_clicked) self.split_button.clicked.connect(self.on_split_button_clicked) self.verse_text_edit.cursorPositionChanged.connect(self.on_cursor_position_changed) self.verse_type_combo_box.currentIndexChanged.connect(self.on_verse_type_combo_box_changed) - def context_menu(self, point): - item = self.serviceManagerList.itemAt(point) - def insert_verse(self, verse_tag, verse_num=1): + """ + Insert a verse + + :param verse_tag: The verse tag + :param verse_num: The verse number + """ if self.verse_text_edit.textCursor().columnNumber() != 0: self.verse_text_edit.insertPlainText('\n') verse_tag = VerseType.translated_name(verse_tag) @@ -67,24 +69,36 @@ class EditVerseForm(QtGui.QDialog, Ui_EditVerseDialog): self.verse_text_edit.setFocus() def on_split_button_clicked(self): + """ + The split button has been pressed + """ text = self.verse_text_edit.toPlainText() position = self.verse_text_edit.textCursor().position() insert_string = '[---]' if position and text[position-1] != '\n': insert_string = '\n' + insert_string - if position == len(text) or text[position] != '\n': + if position == len(text) or text[position] != '\n': insert_string += '\n' self.verse_text_edit.insertPlainText(insert_string) self.verse_text_edit.setFocus() def on_insert_button_clicked(self): + """ + The insert button has been pressed + """ verse_type_index = self.verse_type_combo_box.currentIndex() self.insert_verse(VerseType.tags[verse_type_index], self.verse_number_box.value()) def on_verse_type_combo_box_changed(self): + """ + The verse type combo has been changed + """ self.update_suggested_verse_number() def on_cursor_position_changed(self): + """ + The cursor position has been changed + """ self.update_suggested_verse_number() def update_suggested_verse_number(self): @@ -117,6 +131,13 @@ class EditVerseForm(QtGui.QDialog, Ui_EditVerseDialog): self.verse_number_box.setValue(verse_num) def set_verse(self, text, single=False, tag='%s1' % VerseType.tags[VerseType.Verse]): + """ + Save the verse + + :param text: The text + :param single: is this a single verse + :param tag: The tag + """ self.has_single_verse = single if single: verse_type_index = VerseType.from_tag(tag[0], None) @@ -136,10 +157,20 @@ class EditVerseForm(QtGui.QDialog, Ui_EditVerseDialog): self.verse_text_edit.moveCursor(QtGui.QTextCursor.End) def get_verse(self): + """ + Extract the verse text + + :return: The text + """ return self.verse_text_edit.toPlainText(), VerseType.tags[self.verse_type_combo_box.currentIndex()], \ str(self.verse_number_box.value()) def get_all_verses(self): + """ + Extract all the verses + + :return: The text + """ text = self.verse_text_edit.toPlainText() if not text.startswith('---['): text = '---[%s:1]---\n%s' % (VerseType.translated_names[VerseType.Verse], text) diff --git a/openlp/plugins/songs/forms/mediafilesdialog.py b/openlp/plugins/songs/forms/mediafilesdialog.py index f785640a7..09fbd96f5 100644 --- a/openlp/plugins/songs/forms/mediafilesdialog.py +++ b/openlp/plugins/songs/forms/mediafilesdialog.py @@ -69,5 +69,6 @@ class Ui_MediaFilesDialog(object): """ media_files_dialog.setWindowTitle(translate('SongsPlugin.MediaFilesForm', 'Select Media File(s)')) self.select_label.setText(translate('SongsPlugin.MediaFilesForm', - 'Select one or more audio files from the list below, and click OK to import them into this song.')) + 'Select one or more audio files from the list below, and click OK to import them ' + 'into this song.')) diff --git a/openlp/plugins/songs/forms/mediafilesform.py b/openlp/plugins/songs/forms/mediafilesform.py index c9636fdff..660a9f72d 100644 --- a/openlp/plugins/songs/forms/mediafilesform.py +++ b/openlp/plugins/songs/forms/mediafilesform.py @@ -47,13 +47,13 @@ class MediaFilesForm(QtGui.QDialog, Ui_MediaFilesDialog): super(MediaFilesForm, self).__init__() self.setupUi(self) - def populateFiles(self, files): + def populate_files(self, files): self.file_list_widget.clear() for file in files: item = QtGui.QListWidgetItem(os.path.split(file)[1]) item.setData(QtCore.Qt.UserRole, file) self.file_list_widget.addItem(item) - def getSelectedFiles(self): + def get_selected_files(self): return [item.data(QtCore.Qt.UserRole) for item in self.file_list_widget.selectedItems()] diff --git a/openlp/plugins/songs/forms/songbookform.py b/openlp/plugins/songs/forms/songbookform.py index 9392c8a9a..adced7946 100644 --- a/openlp/plugins/songs/forms/songbookform.py +++ b/openlp/plugins/songs/forms/songbookform.py @@ -52,8 +52,7 @@ class SongBookForm(QtGui.QDialog, Ui_SongBookDialog): """ Execute the song book form. - ``clear`` - Clear the fields on the form before displaying it. + :param clear: Clear the fields on the form before displaying it. """ if clear: self.name_edit.clear() diff --git a/openlp/plugins/songs/forms/songexportform.py b/openlp/plugins/songs/forms/songexportform.py index 1970d6332..4e345b750 100644 --- a/openlp/plugins/songs/forms/songexportform.py +++ b/openlp/plugins/songs/forms/songexportform.py @@ -55,11 +55,8 @@ class SongExportForm(OpenLPWizard): """ Instantiate the wizard, and run any extra setup we need to. - ``parent`` - The QWidget-derived parent of the wizard. - - ``plugin`` - The songs plugin. + :param parent: The QWidget-derived parent of the wizard. + :param plugin: The songs plugin. """ super(SongExportForm, self).__init__(parent, plugin, 'song_export_wizard', ':/wizards/wizard_exportsong.bmp') self.stop_export_flag = False @@ -82,71 +79,71 @@ class SongExportForm(OpenLPWizard): """ Song wizard specific signals. """ - self.availableListWidget.itemActivated.connect(self.onItemActivated) - self.searchLineEdit.textEdited.connect(self.onSearchLineEditChanged) - self.uncheckButton.clicked.connect(self.onUncheckButtonClicked) - self.checkButton.clicked.connect(self.onCheckButtonClicked) - self.directoryButton.clicked.connect(self.onDirectoryButtonClicked) + self.available_list_widget.itemActivated.connect(self.on_item_activated) + self.search_line_edit.textEdited.connect(self.on_search_line_edit_changed) + self.uncheck_button.clicked.connect(self.on_uncheck_button_clicked) + self.check_button.clicked.connect(self.on_check_button_clicked) + self.directory_button.clicked.connect(self.on_directory_button_clicked) def add_custom_pages(self): """ Add song wizard specific pages. """ # The page with all available songs. - self.availableSongsPage = QtGui.QWizardPage() - self.availableSongsPage.setObjectName('availableSongsPage') - self.availableSongsLayout = QtGui.QHBoxLayout(self.availableSongsPage) - self.availableSongsLayout.setObjectName('availableSongsLayout') - self.verticalLayout = QtGui.QVBoxLayout() - self.verticalLayout.setObjectName('verticalLayout') - self.availableListWidget = QtGui.QListWidget(self.availableSongsPage) - self.availableListWidget.setObjectName('availableListWidget') - self.verticalLayout.addWidget(self.availableListWidget) - self.horizontalLayout = QtGui.QHBoxLayout() - self.horizontalLayout.setObjectName('horizontalLayout') - self.searchLabel = QtGui.QLabel(self.availableSongsPage) - self.searchLabel.setObjectName('searchLabel') - self.horizontalLayout.addWidget(self.searchLabel) - self.searchLineEdit = QtGui.QLineEdit(self.availableSongsPage) - self.searchLineEdit.setObjectName('searchLineEdit') - self.horizontalLayout.addWidget(self.searchLineEdit) + self.available_songs_page = QtGui.QWizardPage() + self.available_songs_page.setObjectName('available_songs_page') + self.available_songs_layout = QtGui.QHBoxLayout(self.available_songs_page) + self.available_songs_layout.setObjectName('available_songs_layout') + self.vertical_layout = QtGui.QVBoxLayout() + self.vertical_layout.setObjectName('vertical_layout') + self.available_list_widget = QtGui.QListWidget(self.available_songs_page) + self.available_list_widget.setObjectName('available_list_widget') + self.vertical_layout.addWidget(self.available_list_widget) + self.horizontal_layout = QtGui.QHBoxLayout() + self.horizontal_layout.setObjectName('horizontal_layout') + self.search_label = QtGui.QLabel(self.available_songs_page) + self.search_label.setObjectName('search_label') + self.horizontal_layout.addWidget(self.search_label) + self.search_line_edit = QtGui.QLineEdit(self.available_songs_page) + self.search_line_edit.setObjectName('search_line_edit') + self.horizontal_layout.addWidget(self.search_line_edit) spacer_item = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) - self.horizontalLayout.addItem(spacer_item) - self.uncheckButton = QtGui.QPushButton(self.availableSongsPage) - self.uncheckButton.setObjectName('uncheckButton') - self.horizontalLayout.addWidget(self.uncheckButton) - self.checkButton = QtGui.QPushButton(self.availableSongsPage) - self.checkButton.setObjectName('selectButton') - self.horizontalLayout.addWidget(self.checkButton) - self.verticalLayout.addLayout(self.horizontalLayout) - self.availableSongsLayout.addLayout(self.verticalLayout) - self.addPage(self.availableSongsPage) + self.horizontal_layout.addItem(spacer_item) + self.uncheck_button = QtGui.QPushButton(self.available_songs_page) + self.uncheck_button.setObjectName('uncheck_button') + self.horizontal_layout.addWidget(self.uncheck_button) + self.check_button = QtGui.QPushButton(self.available_songs_page) + self.check_button.setObjectName('selectButton') + self.horizontal_layout.addWidget(self.check_button) + self.vertical_layout.addLayout(self.horizontal_layout) + self.available_songs_layout.addLayout(self.vertical_layout) + self.addPage(self.available_songs_page) # The page with the selected songs. - self.exportSongPage = QtGui.QWizardPage() - self.exportSongPage.setObjectName('availableSongsPage') - self.exportSongLayout = QtGui.QHBoxLayout(self.exportSongPage) - self.exportSongLayout.setObjectName('exportSongLayout') - self.gridLayout = QtGui.QGridLayout() - self.gridLayout.setObjectName('gridLayout') - self.selectedListWidget = QtGui.QListWidget(self.exportSongPage) - self.selectedListWidget.setObjectName('selectedListWidget') - self.gridLayout.addWidget(self.selectedListWidget, 1, 0, 1, 1) - # FIXME: self.horizontalLayout is already defined above?!?!? - self.horizontalLayout = QtGui.QHBoxLayout() - self.horizontalLayout.setObjectName('horizontalLayout') - self.directoryLabel = QtGui.QLabel(self.exportSongPage) - self.directoryLabel.setObjectName('directoryLabel') - self.horizontalLayout.addWidget(self.directoryLabel) - self.directoryLineEdit = QtGui.QLineEdit(self.exportSongPage) - self.directoryLineEdit.setObjectName('directoryLineEdit') - self.horizontalLayout.addWidget(self.directoryLineEdit) - self.directoryButton = QtGui.QToolButton(self.exportSongPage) - self.directoryButton.setIcon(build_icon(':/exports/export_load.png')) - self.directoryButton.setObjectName('directoryButton') - self.horizontalLayout.addWidget(self.directoryButton) - self.gridLayout.addLayout(self.horizontalLayout, 0, 0, 1, 1) - self.exportSongLayout.addLayout(self.gridLayout) - self.addPage(self.exportSongPage) + self.export_song_page = QtGui.QWizardPage() + self.export_song_page.setObjectName('available_songs_page') + self.export_song_layout = QtGui.QHBoxLayout(self.export_song_page) + self.export_song_layout.setObjectName('export_song_layout') + self.grid_layout = QtGui.QGridLayout() + self.grid_layout.setObjectName('grid_layout') + self.selected_list_widget = QtGui.QListWidget(self.export_song_page) + self.selected_list_widget.setObjectName('selected_list_widget') + self.grid_layout.addWidget(self.selected_list_widget, 1, 0, 1, 1) + # FIXME: self.horizontal_layout is already defined above?!?!? + self.horizontal_layout = QtGui.QHBoxLayout() + self.horizontal_layout.setObjectName('horizontal_layout') + self.directory_label = QtGui.QLabel(self.export_song_page) + self.directory_label.setObjectName('directory_label') + self.horizontal_layout.addWidget(self.directory_label) + self.directory_line_edit = QtGui.QLineEdit(self.export_song_page) + self.directory_line_edit.setObjectName('directory_line_edit') + self.horizontal_layout.addWidget(self.directory_line_edit) + self.directory_button = QtGui.QToolButton(self.export_song_page) + self.directory_button.setIcon(build_icon(':/exports/export_load.png')) + self.directory_button.setObjectName('directory_button') + self.horizontal_layout.addWidget(self.directory_button) + self.grid_layout.addLayout(self.horizontal_layout, 0, 0, 1, 1) + self.export_song_layout.addLayout(self.grid_layout) + self.addPage(self.export_song_page) def retranslateUi(self): """ @@ -154,22 +151,23 @@ class SongExportForm(OpenLPWizard): """ self.setWindowTitle(translate('SongsPlugin.ExportWizardForm', 'Song Export Wizard')) self.title_label.setText(WizardStrings.HeaderStyle % - translate('OpenLP.Ui', 'Welcome to the Song Export Wizard')) + translate('OpenLP.Ui', 'Welcome to the Song Export Wizard')) self.information_label.setText(translate('SongsPlugin.ExportWizardForm', 'This wizard will help to' - ' export your songs to the open and free OpenLyrics worship song format.')) - self.availableSongsPage.setTitle(translate('SongsPlugin.ExportWizardForm', 'Select Songs')) - self.availableSongsPage.setSubTitle(translate('SongsPlugin.ExportWizardForm', - 'Check the songs you want to export.')) - self.searchLabel.setText('%s:' % UiStrings().Search) - self.uncheckButton.setText(translate('SongsPlugin.ExportWizardForm', 'Uncheck All')) - self.checkButton.setText(translate('SongsPlugin.ExportWizardForm', 'Check All')) - self.exportSongPage.setTitle(translate('SongsPlugin.ExportWizardForm', 'Select Directory')) - self.exportSongPage.setSubTitle(translate('SongsPlugin.ExportWizardForm', - 'Select the directory where you want the songs to be saved.')) - self.directoryLabel.setText(translate('SongsPlugin.ExportWizardForm', 'Directory:')) + ' export your songs to the open and free OpenLyrics worship ' + 'song format.')) + self.available_songs_page.setTitle(translate('SongsPlugin.ExportWizardForm', 'Select Songs')) + self.available_songs_page.setSubTitle(translate('SongsPlugin.ExportWizardForm', + 'Check the songs you want to export.')) + self.search_label.setText('%s:' % UiStrings().Search) + self.uncheck_button.setText(translate('SongsPlugin.ExportWizardForm', 'Uncheck All')) + self.check_button.setText(translate('SongsPlugin.ExportWizardForm', 'Check All')) + self.export_song_page.setTitle(translate('SongsPlugin.ExportWizardForm', 'Select Directory')) + self.export_song_page.setSubTitle(translate('SongsPlugin.ExportWizardForm', + 'Select the directory where you want the songs to be saved.')) + self.directory_label.setText(translate('SongsPlugin.ExportWizardForm', 'Directory:')) self.progress_page.setTitle(translate('SongsPlugin.ExportWizardForm', 'Exporting')) self.progress_page.setSubTitle(translate('SongsPlugin.ExportWizardForm', - 'Please wait while your songs are exported.')) + 'Please wait while your songs are exported.')) self.progress_label.setText(WizardStrings.Ready) self.progress_bar.setFormat(WizardStrings.PercentSymbolFormat) @@ -179,46 +177,46 @@ class SongExportForm(OpenLPWizard): """ if self.currentPage() == self.welcome_page: return True - elif self.currentPage() == self.availableSongsPage: + elif self.currentPage() == self.available_songs_page: items = [ - item for item in self._findListWidgetItems( - self.availableListWidget) if item.checkState() + item for item in self._find_list_widget_items(self.available_list_widget) if item.checkState() ] if not items: - critical_error_message_box(UiStrings().NISp, + critical_error_message_box( + UiStrings().NISp, translate('SongsPlugin.ExportWizardForm', 'You need to add at least one Song to export.')) return False - self.selectedListWidget.clear() + self.selected_list_widget.clear() # Add the songs to the list of selected songs. for item in items: song = QtGui.QListWidgetItem(item.text()) song.setData(QtCore.Qt.UserRole, item.data(QtCore.Qt.UserRole)) song.setFlags(QtCore.Qt.ItemIsEnabled) - self.selectedListWidget.addItem(song) + self.selected_list_widget.addItem(song) return True - elif self.currentPage() == self.exportSongPage: - if not self.directoryLineEdit.text(): + elif self.currentPage() == self.export_song_page: + if not self.directory_line_edit.text(): critical_error_message_box( translate('SongsPlugin.ExportWizardForm', 'No Save Location specified'), translate('SongsPlugin.ExportWizardForm', 'You need to specify a directory.')) return False return True elif self.currentPage() == self.progress_page: - self.availableListWidget.clear() - self.selectedListWidget.clear() + self.available_list_widget.clear() + self.selected_list_widget.clear() return True - def setDefaults(self): + def set_defaults(self): """ Set default form values for the song export wizard. """ self.restart() self.finish_button.setVisible(False) self.cancel_button.setVisible(True) - self.availableListWidget.clear() - self.selectedListWidget.clear() - self.directoryLineEdit.clear() - self.searchLineEdit.clear() + self.available_list_widget.clear() + self.selected_list_widget.clear() + self.directory_line_edit.clear() + self.search_line_edit.clear() # Load the list of songs. self.application.set_busy_cursor() songs = self.plugin.manager.get_all_objects(Song) @@ -233,7 +231,7 @@ class SongExportForm(OpenLPWizard): item.setData(QtCore.Qt.UserRole, song) item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsUserCheckable | QtCore.Qt.ItemIsEnabled) item.setCheckState(QtCore.Qt.Unchecked) - self.availableListWidget.addItem(item) + self.available_list_widget.addItem(item) self.application.set_normal_cursor() def pre_wizard(self): @@ -244,86 +242,81 @@ class SongExportForm(OpenLPWizard): self.progress_label.setText(translate('SongsPlugin.ExportWizardForm', 'Starting export...')) self.application.process_events() - def performWizard(self): + def perform_wizard(self): """ - Perform the actual export. This creates an *openlyricsexport* instance - and calls the *do_export* method. + Perform the actual export. This creates an *openlyricsexport* instance and calls the *do_export* method. """ songs = [ song.data(QtCore.Qt.UserRole) - for song in self._findListWidgetItems(self.selectedListWidget) + for song in self._find_list_widget_items(self.selected_list_widget) ] - exporter = OpenLyricsExport(self, songs, self.directoryLineEdit.text()) + exporter = OpenLyricsExport(self, songs, self.directory_line_edit.text()) if exporter.do_export(): - self.progress_label.setText(translate('SongsPlugin.SongExportForm', - 'Finished export. To import these files use the OpenLyrics importer.')) + self.progress_label.setText( + translate('SongsPlugin.SongExportForm', + 'Finished export. To import these files use the OpenLyrics importer.')) else: self.progress_label.setText(translate('SongsPlugin.SongExportForm', 'Your song export failed.')) - def _findListWidgetItems(self, listWidget, text=''): + def _find_list_widget_items(self, list_widget, text=''): """ - Returns a list of *QListWidgetItem*s of the ``listWidget``. Note, that - hidden items are included. + Returns a list of *QListWidgetItem*s of the ``list_widget``. Note, that hidden items are included. - ``listWidget`` - The widget to get all items from. (QListWidget) - - ``text`` - The text to search for. (unicode string) + :param list_widget: The widget to get all items from. (QListWidget) + :param text: The text to search for. (unicode string) """ return [ - item for item in listWidget.findItems(text, QtCore.Qt.MatchContains) + item for item in list_widget.findItems(text, QtCore.Qt.MatchContains) ] - def onItemActivated(self, item): + def on_item_activated(self, item): """ - Called, when an item in the *availableListWidget* has been triggered. + Called, when an item in the *available_list_widget* has been triggered. The item is check if it was not checked, whereas it is unchecked when it was checked. - ``item`` - The *QListWidgetItem* which was triggered. + :param item: The *QListWidgetItem* which was triggered. """ item.setCheckState( QtCore.Qt.Unchecked if item.checkState() else QtCore.Qt.Checked) - def onSearchLineEditChanged(self, text): + def on_search_line_edit_changed(self, text): """ - The *searchLineEdit*'s text has been changed. Update the list of + The *search_line_edit*'s text has been changed. Update the list of available songs. Note that any song, which does not match the ``text`` will be hidden, but not unchecked! - ``text`` - The text of the *searchLineEdit*. + :param text: The text of the *search_line_edit*. """ search_result = [ - song for song in self._findListWidgetItems(self.availableListWidget, text) + song for song in self._find_list_widget_items(self.available_list_widget, text) ] - for item in self._findListWidgetItems(self.availableListWidget): + for item in self._find_list_widget_items(self.available_list_widget): item.setHidden(item not in search_result) - def onUncheckButtonClicked(self): + def on_uncheck_button_clicked(self): """ - The *uncheckButton* has been clicked. Set all visible songs unchecked. + The *uncheck_button* has been clicked. Set all visible songs unchecked. """ - for row in range(self.availableListWidget.count()): - item = self.availableListWidget.item(row) + for row in range(self.available_list_widget.count()): + item = self.available_list_widget.item(row) if not item.isHidden(): item.setCheckState(QtCore.Qt.Unchecked) - def onCheckButtonClicked(self): + def on_check_button_clicked(self): """ - The *checkButton* has been clicked. Set all visible songs checked. + The *check_button* has been clicked. Set all visible songs checked. """ - for row in range(self.availableListWidget.count()): - item = self.availableListWidget.item(row) + for row in range(self.available_list_widget.count()): + item = self.available_list_widget.item(row) if not item.isHidden(): item.setCheckState(QtCore.Qt.Checked) - def onDirectoryButtonClicked(self): + def on_directory_button_clicked(self): """ - Called when the *directoryButton* was clicked. Opens a dialog and writes - the path to *directoryLineEdit*. + Called when the *directory_button* was clicked. Opens a dialog and writes + the path to *directory_line_edit*. """ - self.get_folder(translate('SongsPlugin.ExportWizardForm', 'Select Destination Folder'), - self.directoryLineEdit, 'last directory export') + self.get_folder( + translate('SongsPlugin.ExportWizardForm', 'Select Destination Folder'), + self.directory_line_edit, 'last directory export') diff --git a/openlp/plugins/songs/forms/songimportform.py b/openlp/plugins/songs/forms/songimportform.py index e39fab8fb..c2d9a3110 100644 --- a/openlp/plugins/songs/forms/songimportform.py +++ b/openlp/plugins/songs/forms/songimportform.py @@ -55,11 +55,8 @@ class SongImportForm(OpenLPWizard): """ Instantiate the wizard, and run any extra setup we need to. - ``parent`` - The QWidget-derived parent of the wizard. - - ``plugin`` - The songs plugin. + :param parent: The QWidget-derived parent of the wizard. + :param plugin: The songs plugin. """ super(SongImportForm, self).__init__(parent, plugin, 'songImportWizard', ':/wizards/wizard_importsong.bmp') self.clipboard = self.main_window.clipboard @@ -72,9 +69,9 @@ class SongImportForm(OpenLPWizard): super(SongImportForm, self).setupUi(image) self.current_format = SongFormat.OpenLyrics self.format_stack.setCurrentIndex(self.current_format) - self.format_combo_box.currentIndexChanged.connect(self.onCurrentIndexChanged) + self.format_combo_box.currentIndexChanged.connect(self.on_current_index_changed) - def onCurrentIndexChanged(self, index): + def on_current_index_changed(self, index): """ Called when the format combo box's index changed. """ @@ -99,10 +96,10 @@ class SongImportForm(OpenLPWizard): select_mode = SongFormat.get(song_format, 'selectMode') if select_mode == SongFormatSelect.MultipleFiles: self.format_widgets[song_format]['addButton'].clicked.connect(self.on_add_button_clicked) - self.format_widgets[song_format]['removeButton'].clicked.connect(self.onRemoveButtonClicked) + self.format_widgets[song_format]['removeButton'].clicked.connect(self.on_remove_button_clicked) else: self.format_widgets[song_format]['browseButton'].clicked.connect(self.on_browse_button_clicked) - self.format_widgets[song_format]['file_path_edit'].textChanged.connect(self.onFilepathEditTextChanged) + self.format_widgets[song_format]['file_path_edit'].textChanged.connect(self.on_filepath_edit_text_changed) def add_custom_pages(self): """ @@ -131,7 +128,7 @@ class SongImportForm(OpenLPWizard): self.format_stack.setObjectName('format_stack') self.disablable_formats = [] for self.current_format in SongFormat.get_format_list(): - self.addFileSelectItem() + self.add_file_select_item() self.source_layout.addLayout(self.format_stack) self.addPage(self.source_page) @@ -140,33 +137,35 @@ class SongImportForm(OpenLPWizard): Song wizard localisation. """ self.setWindowTitle(translate('SongsPlugin.ImportWizardForm', 'Song Import Wizard')) - self.title_label.setText(WizardStrings.HeaderStyle % translate('OpenLP.Ui', 'Welcome to the Song Import Wizard')) - self.information_label.setText(translate('SongsPlugin.ImportWizardForm', - 'This wizard will help you to import songs from a variety of ' - 'formats. Click the next button below to start the process by selecting a format to import from.')) + self.title_label.setText(WizardStrings.HeaderStyle % translate('OpenLP.Ui', + 'Welcome to the Song Import Wizard')) + self.information_label.setText( + translate('SongsPlugin.ImportWizardForm', + 'This wizard will help you to import songs from a variety of formats. Click the next button ' + 'below to start the process by selecting a format to import from.')) self.source_page.setTitle(WizardStrings.ImportSelect) self.source_page.setSubTitle(WizardStrings.ImportSelectLong) self.format_label.setText(WizardStrings.FormatLabel) - for format in SongFormat.get_format_list(): + for format_list in SongFormat.get_format_list(): format_name, custom_combo_text, description_text, select_mode = \ - SongFormat.get(format, 'name', 'comboBoxText', 'descriptionText', 'selectMode') + SongFormat.get(format_list, 'name', 'comboBoxText', 'descriptionText', 'selectMode') combo_box_text = (custom_combo_text if custom_combo_text else format_name) - self.format_combo_box.setItemText(format, combo_box_text) + self.format_combo_box.setItemText(format_list, combo_box_text) if description_text is not None: - self.format_widgets[format]['description_label'].setText(description_text) + self.format_widgets[format_list]['description_label'].setText(description_text) if select_mode == SongFormatSelect.MultipleFiles: - self.format_widgets[format]['addButton'].setText( + self.format_widgets[format_list]['addButton'].setText( translate('SongsPlugin.ImportWizardForm', 'Add Files...')) - self.format_widgets[format]['removeButton'].setText( + self.format_widgets[format_list]['removeButton'].setText( translate('SongsPlugin.ImportWizardForm', 'Remove File(s)')) else: - self.format_widgets[format]['browseButton'].setText(UiStrings().Browse) + self.format_widgets[format_list]['browseButton'].setText(UiStrings().Browse) f_label = 'Filename:' if select_mode == SongFormatSelect.SingleFolder: f_label = 'Folder:' - self.format_widgets[format]['filepathLabel'].setText(translate('SongsPlugin.ImportWizardForm', f_label)) - for format in self.disablable_formats: - self.format_widgets[format]['disabled_label'].setText(SongFormat.get(format, 'disabledLabelText')) + self.format_widgets[format_list]['filepathLabel'].setText(translate('SongsPlugin.ImportWizardForm', f_label)) + for format_list in self.disablable_formats: + self.format_widgets[format_list]['disabled_label'].setText(SongFormat.get(format_list, 'disabledLabelText')) self.progress_page.setTitle(WizardStrings.Importing) self.progress_page.setSubTitle( translate('SongsPlugin.ImportWizardForm', 'Please wait while your songs are imported.')) @@ -179,7 +178,7 @@ class SongImportForm(OpenLPWizard): labels = [self.format_widgets[f]['filepathLabel'] for f in formats] # Get max width of all labels max_label_width = max(self.format_label.minimumSizeHint().width(), - max([label.minimumSizeHint().width() for label in labels])) + max([label.minimumSizeHint().width() for label in labels])) self.format_spacer.changeSize(max_label_width, 0, QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) spacers = [self.format_widgets[f]['filepathSpacer'] for f in formats] for index, spacer in enumerate(spacers): @@ -187,9 +186,9 @@ class SongImportForm(OpenLPWizard): max_label_width - labels[index].minimumSizeHint().width(), 0, QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) # Align descriptionLabels with rest of layout - for format in SongFormat.get_format_list(): - if SongFormat.get(format, 'descriptionText') is not None: - self.format_widgets[format]['descriptionSpacer'].changeSize( + for format_list in SongFormat.get_format_list(): + if SongFormat.get(format_list, 'descriptionText') is not None: + self.format_widgets[format_list]['descriptionSpacer'].changeSize( max_label_width + self.format_h_spacing, 0, QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) def custom_page_changed(self, page_id): @@ -197,11 +196,11 @@ class SongImportForm(OpenLPWizard): Called when changing to a page other than the progress page. """ if self.page(page_id) == self.source_page: - self.onCurrentIndexChanged(self.format_stack.currentIndex()) + self.on_current_index_changed(self.format_stack.currentIndex()) def validateCurrentPage(self): """ - Re-implement te validateCurrentPage() method. Validate the current page before moving on to the next page. + Re-implement the validateCurrentPage() method. Validate the current page before moving on to the next page. Provide each song format class with a chance to validate its input by overriding isValidSource(). """ if self.currentPage() == self.welcome_page: @@ -230,37 +229,36 @@ class SongImportForm(OpenLPWizard): """ Opens a QFileDialog and writes the filenames to the given listbox. - ``title`` - The title of the dialog (unicode). - - ``listbox`` - A listbox (QListWidget). - - ``filters`` - The file extension filters. It should contain the file descriptions + :param title: The title of the dialog (unicode). + :param listbox: A listbox (QListWidget). + :param filters: The file extension filters. It should contain the file descriptions as well as the file extensions. For example:: - u'SongBeamer Files (*.sng)' """ if filters: filters += ';;' filters += '%s (*)' % UiStrings().AllFiles - filenames = FileDialog.getOpenFileNames(self, title, + file_names = FileDialog.getOpenFileNames( + self, title, Settings().value(self.plugin.settings_section + '/last directory import'), filters) - if filenames: - listbox.addItems(filenames) + if file_names: + listbox.addItems(file_names) Settings().setValue(self.plugin.settings_section + '/last directory import', - os.path.split(str(filenames[0]))[0]) + os.path.split(str(file_names[0]))[0]) - def get_list_of_files(self, listbox): + def get_list_of_files(self, list_box): """ - Return a list of file from the listbox + Return a list of file from the list_box + + :param list_box: The source list box """ - return [listbox.item(i).text() for i in range(listbox.count())] + return [list_box.item(i).text() for i in range(list_box.count())] def remove_selected_items(self, list_box): """ Remove selected list_box items + + :param list_box: the source list box """ for item in list_box.selectedItems(): item = list_box.takeItem(list_box.row(item)) @@ -291,14 +289,14 @@ class SongImportForm(OpenLPWizard): self.get_files(title, self.format_widgets[this_format]['file_list_widget'], ext_filter) self.source_page.emit(QtCore.SIGNAL('completeChanged()')) - def onRemoveButtonClicked(self): + def on_remove_button_clicked(self): """ Remove a file from the list. """ self.remove_selected_items(self.format_widgets[self.current_format]['file_list_widget']) self.source_page.emit(QtCore.SIGNAL('completeChanged()')) - def onFilepathEditTextChanged(self): + def on_filepath_edit_text_changed(self): """ Called when the content of the Filename/Folder edit box changes. """ @@ -315,12 +313,12 @@ class SongImportForm(OpenLPWizard): if last_import_type < 0 or last_import_type >= self.format_combo_box.count(): last_import_type = 0 self.format_combo_box.setCurrentIndex(last_import_type) - for format in SongFormat.get_format_list(): - select_mode = SongFormat.get(format, 'selectMode') + for format_list in SongFormat.get_format_list(): + select_mode = SongFormat.get(format_list, 'selectMode') if select_mode == SongFormatSelect.MultipleFiles: - self.format_widgets[format]['file_list_widget'].clear() + self.format_widgets[format_list]['file_list_widget'].clear() else: - self.format_widgets[format]['file_path_edit'].setText('') + self.format_widgets[format_list]['file_path_edit'].setText('') self.error_report_text_edit.clear() self.error_report_text_edit.setHidden(True) self.error_copy_to_button.setHidden(True) @@ -334,22 +332,22 @@ class SongImportForm(OpenLPWizard): self.progress_label.setText(WizardStrings.StartingImport) self.application.process_events() - def performWizard(self): + def perform_wizard(self): """ - Perform the actual import. This method pulls in the correct importer - class, and then runs the ``doImport`` method of the importer to do - the actual importing. + Perform the actual import. This method pulls in the correct importer class, and then runs the ``doImport`` + method of the importer to do the actual importing. """ source_format = self.current_format select_mode = SongFormat.get(source_format, 'selectMode') if select_mode == SongFormatSelect.SingleFile: - importer = self.plugin.importSongs(source_format, - filename=self.format_widgets[source_format]['file_path_edit'].text()) + importer = self.plugin.import_songs(source_format, + filename=self.format_widgets[source_format]['file_path_edit'].text()) elif select_mode == SongFormatSelect.SingleFolder: - importer = self.plugin.importSongs(source_format, - folder=self.format_widgets[source_format]['file_path_edit'].text()) + importer = self.plugin.import_songs(source_format, + folder=self.format_widgets[source_format]['file_path_edit'].text()) else: - importer = self.plugin.importSongs(source_format, + importer = self.plugin.import_songs( + source_format, filenames=self.get_list_of_files(self.format_widgets[source_format]['file_list_widget'])) importer.doImport() self.progress_label.setText(WizardStrings.FinishedImport) @@ -364,15 +362,15 @@ class SongImportForm(OpenLPWizard): """ Save the error report to a file. """ - filename = QtGui.QFileDialog.getSaveFileName(self, - Settings().value(self.plugin.settings_section + '/last directory import')) + filename = QtGui.QFileDialog.getSaveFileName( + self, Settings().value(self.plugin.settings_section + '/last directory import')) if not filename: return report_file = codecs.open(filename, 'w', 'utf-8') report_file.write(self.error_report_text_edit.toPlainText()) report_file.close() - def addFileSelectItem(self): + def add_file_select_item(self): """ Add a file selection page. """ @@ -382,75 +380,75 @@ class SongImportForm(OpenLPWizard): page = QtGui.QWidget() page.setObjectName(prefix + 'Page') if can_disable: - importWidget = self.disablableWidget(page, prefix) + import_widget = self.disablable_widget(page, prefix) else: - importWidget = page - importLayout = QtGui.QVBoxLayout(importWidget) - importLayout.setMargin(0) - importLayout.setObjectName(prefix + 'ImportLayout') + import_widget = page + import_layout = QtGui.QVBoxLayout(import_widget) + import_layout.setMargin(0) + import_layout.setObjectName(prefix + 'ImportLayout') if description_text is not None: - descriptionLayout = QtGui.QHBoxLayout() - descriptionLayout.setObjectName(prefix + 'DescriptionLayout') - descriptionSpacer = QtGui.QSpacerItem(0, 0, QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) - descriptionLayout.addSpacerItem(descriptionSpacer) - description_label = QtGui.QLabel(importWidget) + description_layout = QtGui.QHBoxLayout() + description_layout.setObjectName(prefix + 'DescriptionLayout') + description_spacer = QtGui.QSpacerItem(0, 0, QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) + description_layout.addSpacerItem(description_spacer) + description_label = QtGui.QLabel(import_widget) description_label.setWordWrap(True) description_label.setOpenExternalLinks(True) description_label.setObjectName(prefix + '_description_label') - descriptionLayout.addWidget(description_label) - importLayout.addLayout(descriptionLayout) + description_layout.addWidget(description_label) + import_layout.addLayout(description_layout) self.format_widgets[this_format]['description_label'] = description_label - self.format_widgets[this_format]['descriptionSpacer'] = descriptionSpacer + self.format_widgets[this_format]['descriptionSpacer'] = description_spacer if select_mode == SongFormatSelect.SingleFile or select_mode == SongFormatSelect.SingleFolder: file_path_layout = QtGui.QHBoxLayout() file_path_layout.setObjectName(prefix + '_file_path_layout') file_path_layout.setContentsMargins(0, self.format_v_spacing, 0, 0) - filepathLabel = QtGui.QLabel(importWidget) - filepathLabel.setObjectName(prefix + 'FilepathLabel') - file_path_layout.addWidget(filepathLabel) - filepathSpacer = QtGui.QSpacerItem(0, 0, QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) - file_path_layout.addSpacerItem(filepathSpacer) - file_path_edit = QtGui.QLineEdit(importWidget) + file_path_label = QtGui.QLabel(import_widget) + file_path_label.setObjectName(prefix + 'FilepathLabel') + file_path_layout.addWidget(file_path_label) + file_path_spacer = QtGui.QSpacerItem(0, 0, QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) + file_path_layout.addSpacerItem(file_path_spacer) + file_path_edit = QtGui.QLineEdit(import_widget) file_path_edit.setObjectName(prefix + '_file_path_edit') file_path_layout.addWidget(file_path_edit) - browseButton = QtGui.QToolButton(importWidget) - browseButton.setIcon(self.open_icon) - browseButton.setObjectName(prefix + 'BrowseButton') - file_path_layout.addWidget(browseButton) - importLayout.addLayout(file_path_layout) - importLayout.addSpacerItem(self.stack_spacer) - self.format_widgets[this_format]['filepathLabel'] = filepathLabel - self.format_widgets[this_format]['filepathSpacer'] = filepathSpacer + browse_button = QtGui.QToolButton(import_widget) + browse_button.setIcon(self.open_icon) + browse_button.setObjectName(prefix + 'BrowseButton') + file_path_layout.addWidget(browse_button) + import_layout.addLayout(file_path_layout) + import_layout.addSpacerItem(self.stack_spacer) + self.format_widgets[this_format]['filepathLabel'] = file_path_label + self.format_widgets[this_format]['filepathSpacer'] = file_path_spacer self.format_widgets[this_format]['file_path_layout'] = file_path_layout self.format_widgets[this_format]['file_path_edit'] = file_path_edit - self.format_widgets[this_format]['browseButton'] = browseButton + self.format_widgets[this_format]['browseButton'] = browse_button elif select_mode == SongFormatSelect.MultipleFiles: - fileListWidget = QtGui.QListWidget(importWidget) - fileListWidget.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection) - fileListWidget.setObjectName(prefix + 'FileListWidget') - importLayout.addWidget(fileListWidget) + file_list_widget = QtGui.QListWidget(import_widget) + file_list_widget.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection) + file_list_widget.setObjectName(prefix + 'FileListWidget') + import_layout.addWidget(file_list_widget) button_layout = QtGui.QHBoxLayout() button_layout.setObjectName(prefix + '_button_layout') - addButton = QtGui.QPushButton(importWidget) - addButton.setIcon(self.open_icon) - addButton.setObjectName(prefix + 'AddButton') - button_layout.addWidget(addButton) + add_button = QtGui.QPushButton(import_widget) + add_button.setIcon(self.open_icon) + add_button.setObjectName(prefix + 'AddButton') + button_layout.addWidget(add_button) button_layout.addStretch() - removeButton = QtGui.QPushButton(importWidget) - removeButton.setIcon(self.delete_icon) - removeButton.setObjectName(prefix + 'RemoveButton') - button_layout.addWidget(removeButton) - importLayout.addLayout(button_layout) - self.format_widgets[this_format]['file_list_widget'] = fileListWidget + remove_button = QtGui.QPushButton(import_widget) + remove_button.setIcon(self.delete_icon) + remove_button.setObjectName(prefix + 'RemoveButton') + button_layout.addWidget(remove_button) + import_layout.addLayout(button_layout) + self.format_widgets[this_format]['file_list_widget'] = file_list_widget self.format_widgets[this_format]['button_layout'] = button_layout - self.format_widgets[this_format]['addButton'] = addButton - self.format_widgets[this_format]['removeButton'] = removeButton + self.format_widgets[this_format]['addButton'] = add_button + self.format_widgets[this_format]['removeButton'] = remove_button self.format_stack.addWidget(page) self.format_widgets[this_format]['page'] = page - self.format_widgets[this_format]['importLayout'] = importLayout + self.format_widgets[this_format]['importLayout'] = import_layout self.format_combo_box.addItem('') - def disablableWidget(self, page, prefix): + def disablable_widget(self, page, prefix): """ Disable a widget. """ @@ -526,10 +524,10 @@ class SongImportSourcePage(QtGui.QWizardPage): if wizard.format_widgets[this_format]['file_list_widget'].count() > 0: return True else: - filepath = str(wizard.format_widgets[this_format]['file_path_edit'].text()) - if filepath: - if select_mode == SongFormatSelect.SingleFile and os.path.isfile(filepath): + file_path = str(wizard.format_widgets[this_format]['file_path_edit'].text()) + if file_path: + if select_mode == SongFormatSelect.SingleFile and os.path.isfile(file_path): return True - elif select_mode == SongFormatSelect.SingleFolder and os.path.isdir(filepath): + elif select_mode == SongFormatSelect.SingleFolder and os.path.isdir(file_path): return True return False diff --git a/openlp/plugins/songs/forms/songmaintenancedialog.py b/openlp/plugins/songs/forms/songmaintenancedialog.py index 95374c9a8..200b4b1ff 100644 --- a/openlp/plugins/songs/forms/songmaintenancedialog.py +++ b/openlp/plugins/songs/forms/songmaintenancedialog.py @@ -161,6 +161,7 @@ class Ui_SongMaintenanceDialog(object): self.add_book_button.setText(UiStrings().Add) self.edit_book_button.setText(UiStrings().Edit) self.delete_book_button.setText(UiStrings().Delete) - typeListWidth = max(self.fontMetrics().width(SongStrings.Authors), - self.fontMetrics().width(SongStrings.Topics), self.fontMetrics().width(SongStrings.SongBooks)) - self.type_list_widget.setFixedWidth(typeListWidth + self.type_list_widget.iconSize().width() + 32) + type_list_width = max(self.fontMetrics().width(SongStrings.Authors), + self.fontMetrics().width(SongStrings.Topics), + self.fontMetrics().width(SongStrings.SongBooks)) + self.type_list_widget.setFixedWidth(type_list_width + self.type_list_widget.iconSize().width() + 32) diff --git a/openlp/plugins/songs/forms/songmaintenanceform.py b/openlp/plugins/songs/forms/songmaintenanceform.py index 8bd57b433..c4e158727 100644 --- a/openlp/plugins/songs/forms/songmaintenanceform.py +++ b/openlp/plugins/songs/forms/songmaintenanceform.py @@ -98,8 +98,7 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog): """ Get the ID of the currently selected item. - ``list_widget`` - The list widget to examine. + :param list_widget: The list widget to examine. """ item = list_widget.currentItem() if item: @@ -163,6 +162,9 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog): def check_author_exists(self, new_author, edit=False): """ Returns *False* if the given Author already exists, otherwise *True*. + + :param new_author: The new Author. + :param edit: Are we editing the song? """ authors = self.manager.get_all_objects( Author, @@ -177,6 +179,9 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog): def check_topic_exists(self, new_topic, edit=False): """ Returns *False* if the given Topic already exists, otherwise *True*. + + :param new_topic: The new Topic. + :param edit: Are we editing the song? """ topics = self.manager.get_all_objects(Topic, Topic.name == new_topic.name) return self.__check_object_exists(topics, new_topic, edit) @@ -184,17 +189,21 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog): def check_song_book_exists(self, new_book, edit=False): """ Returns *False* if the given Topic already exists, otherwise *True*. + + :param new_book: The new Book. + :param edit: Are we editing the song? """ - books = self.manager.get_all_objects(Book, - and_(Book.name == new_book.name, Book.publisher == new_book.publisher)) + books = self.manager.get_all_objects( + Book, and_(Book.name == new_book.name, Book.publisher == new_book.publisher)) return self.__check_object_exists(books, new_book, edit) def __check_object_exists(self, existing_objects, new_object, edit): """ Utility method to check for an existing object. - ``edit`` - If we edit an item, this should be *True*. + :param existing_objects: The objects reference + :param new_object: An individual object + :param edit: If we edit an item, this should be *True*. """ if existing_objects: # If we edit an existing object, we need to make sure that we do @@ -297,8 +306,7 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog): 'author %s use the existing author %s?') % (author.display_name, temp_display_name, author.display_name), parent=self, question=True) == \ QtGui.QMessageBox.Yes: - self._merge_objects(author, self.merge_authors, - self.reset_authors) + self._merge_objects(author, self.merge_authors, self.reset_authors) else: # We restore the author's old first and last name as well as # his display name. @@ -330,15 +338,16 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog): message=translate('SongsPlugin.SongMaintenanceForm', 'Could not save your changes.')) elif critical_error_message_box( message=translate('SongsPlugin.SongMaintenanceForm', - 'The topic %s already exists. Would you like to make songs with topic %s use the existing topic %s?') % - (topic.name, temp_name, topic.name), parent=self, question=True) == QtGui.QMessageBox.Yes: + 'The topic %s already exists. Would you like to make songs with topic %s use the ' + 'existing topic %s?') % (topic.name, temp_name, topic.name), + parent=self, question=True) == QtGui.QMessageBox.Yes: self._merge_objects(topic, self.merge_topics, self.reset_topics) else: # We restore the topics's old name. topic.name = temp_name critical_error_message_box( message=translate('SongsPlugin.SongMaintenanceForm', - 'Could not save your modified topic, because it already exists.')) + 'Could not save your modified topic, because it already exists.')) def on_edit_book_button_clicked(self): """ @@ -367,8 +376,9 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog): message=translate('SongsPlugin.SongMaintenanceForm', 'Could not save your changes.')) elif critical_error_message_box( message=translate('SongsPlugin.SongMaintenanceForm', - 'The book %s already exists. Would you like to make songs with book %s use the existing book %s?') % - (book.name, temp_name, book.name), parent=self, question=True) == QtGui.QMessageBox.Yes: + 'The book %s already exists. Would you like to make ' + 'songs with book %s use the existing book %s?') % (book.name, temp_name, book.name), + parent=self, question=True) == QtGui.QMessageBox.Yes: self._merge_objects(book, self.merge_song_books, self.reset_song_books) else: # We restore the book's old name and publisher. @@ -390,8 +400,7 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog): """ Merges two authors into one author. - ``old_author`` - The object, which was edited, that will be deleted + :param old_author: The object, which was edited, that will be deleted """ # Find the duplicate. existing_author = self.manager.get_object_filtered( @@ -418,15 +427,11 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog): """ Merges two topics into one topic. - ``old_topic`` - The object, which was edited, that will be deleted + :param old_topic: The object, which was edited, that will be deleted """ # Find the duplicate. existing_topic = self.manager.get_object_filtered( - Topic, - and_( - Topic.name == old_topic.name, Topic.id != old_topic.id - ) + Topic, and_(Topic.name == old_topic.name, Topic.id != old_topic.id) ) # Find the songs, which have the old_topic as topic. songs = self.manager.get_all_objects(Song, Song.topics.contains(old_topic)) @@ -467,30 +472,34 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog): Delete the author if the author is not attached to any songs. """ self._delete_item(Author, self.authors_list_widget, self.reset_authors, - translate('SongsPlugin.SongMaintenanceForm', 'Delete Author'), - translate('SongsPlugin.SongMaintenanceForm', 'Are you sure you want to delete the selected author?'), - translate('SongsPlugin.SongMaintenanceForm', - 'This author cannot be deleted, they are currently assigned to at least one song.')) + translate('SongsPlugin.SongMaintenanceForm', 'Delete Author'), + translate('SongsPlugin.SongMaintenanceForm', + 'Are you sure you want to delete the selected author?'), + translate('SongsPlugin.SongMaintenanceForm', + 'This author cannot be deleted, they are currently assigned to at least one song' + '.')) def on_delete_topic_button_clicked(self): """ Delete the Book if the Book is not attached to any songs. """ self._delete_item(Topic, self.topics_list_widget, self.reset_topics, - translate('SongsPlugin.SongMaintenanceForm', 'Delete Topic'), - translate('SongsPlugin.SongMaintenanceForm', 'Are you sure you want to delete the selected topic?'), - translate('SongsPlugin.SongMaintenanceForm', - 'This topic cannot be deleted, it is currently assigned to at least one song.')) + translate('SongsPlugin.SongMaintenanceForm', 'Delete Topic'), + translate('SongsPlugin.SongMaintenanceForm', + 'Are you sure you want to delete the selected topic?'), + translate('SongsPlugin.SongMaintenanceForm', + 'This topic cannot be deleted, it is currently assigned to at least one song.')) def on_delete_book_button_clicked(self): """ Delete the Book if the Book is not attached to any songs. """ self._delete_item(Book, self.song_books_list_widget, self.reset_song_books, - translate('SongsPlugin.SongMaintenanceForm', 'Delete Book'), - translate('SongsPlugin.SongMaintenanceForm', 'Are you sure you want to delete the selected book?'), - translate('SongsPlugin.SongMaintenanceForm', - 'This book cannot be deleted, it is currently assigned to at least one song.')) + translate('SongsPlugin.SongMaintenanceForm', 'Delete Book'), + translate('SongsPlugin.SongMaintenanceForm', + 'Are you sure you want to delete the selected book?'), + translate('SongsPlugin.SongMaintenanceForm', + 'This book cannot be deleted, it is currently assigned to at least one song.')) def on_authors_list_row_changed(self, row): """ diff --git a/openlp/plugins/songs/forms/songreviewwidget.py b/openlp/plugins/songs/forms/songreviewwidget.py index 3f9f8719b..3689e2d49 100644 --- a/openlp/plugins/songs/forms/songreviewwidget.py +++ b/openlp/plugins/songs/forms/songreviewwidget.py @@ -186,7 +186,7 @@ class SongReviewWidget(QtGui.QWidget): # Some pixels are missing at the bottom of the table, but all themes I tried still allowed # to read the last verse line, so I'll just leave it at that. self.song_info_verse_list_widget.setFixedHeight(self.song_info_verse_list_widget.verticalHeader().length() + - self.song_info_verse_list_widget.verticalHeader().offset() + 6) + self.song_info_verse_list_widget.verticalHeader().offset() + 6) self.song_group_box_layout.addWidget(self.song_info_verse_list_widget) self.song_group_box_layout.addStretch() self.song_vertical_layout.addWidget(self.song_group_box) diff --git a/openlp/plugins/songs/forms/topicsform.py b/openlp/plugins/songs/forms/topicsform.py index 27540a673..a8c77a668 100644 --- a/openlp/plugins/songs/forms/topicsform.py +++ b/openlp/plugins/songs/forms/topicsform.py @@ -62,8 +62,8 @@ class TopicsForm(QtGui.QDialog, Ui_TopicsDialog): Override the inherited method to check before we close. """ if not self.name_edit.text(): - critical_error_message_box(message=translate('SongsPlugin.TopicsForm', - 'You need to type in a topic name.')) + critical_error_message_box( + message=translate('SongsPlugin.TopicsForm', 'You need to type in a topic name.')) self.name_edit.setFocus() return False else: diff --git a/openlp/plugins/songs/lib/importer.py b/openlp/plugins/songs/lib/importer.py index 31b26de59..37a147ba9 100644 --- a/openlp/plugins/songs/lib/importer.py +++ b/openlp/plugins/songs/lib/importer.py @@ -107,7 +107,7 @@ class SongFormat(object): ``u'prefix'`` Prefix for Qt objects. Use mixedCase, e.g. ``u'openLyrics'`` - See ``SongImportForm.addFileSelectItem()`` + See ``SongImportForm.add_file_select_item()`` Optional attributes for each song format: diff --git a/openlp/plugins/songs/songsplugin.py b/openlp/plugins/songs/songsplugin.py index 36fd92237..9c8f3bfa9 100644 --- a/openlp/plugins/songs/songsplugin.py +++ b/openlp/plugins/songs/songsplugin.py @@ -54,26 +54,23 @@ from openlp.plugins.songs.forms.duplicatesongremovalform import DuplicateSongRem log = logging.getLogger(__name__) -__default_settings__ = { - 'songs/db type': 'sqlite', - 'songs/last search type': SongSearch.Entire, - 'songs/last import type': SongFormat.OpenLyrics, - 'songs/update service on edit': False, - 'songs/search as type': False, - 'songs/add song from service': True, - 'songs/display songbar': True, - 'songs/last directory import': '', - 'songs/last directory export': '' - } +__default_settings__ = {'songs/db type': 'sqlite', + 'songs/last search type': SongSearch.Entire, + 'songs/last import type': SongFormat.OpenLyrics, + 'songs/update service on edit': False, + 'songs/search as type': False, + 'songs/add song from service': True, + 'songs/display songbar': True, + 'songs/last directory import': '', + 'songs/last directory export': '' + } class SongsPlugin(Plugin): """ - This is the number 1 plugin, if importance were placed on any - plugins. This plugin enables the user to create, edit and display - songs. Songs are divided into verses, and the verse order can be - specified. Authors, topics and song books can be assigned to songs - as well. + This is the number 1 plugin, if importance were placed on any plugins. This plugin enables the user to create, + edit and display songs. Songs are divided into verses, and the verse order can be specified. Authors, topics and + song books can be assigned to songs as well. """ log.info('Song Plugin loaded') @@ -94,6 +91,9 @@ class SongsPlugin(Plugin): return self.manager.session is not None def initialise(self): + """ + Lets Initialise the plugin + """ log.info('Songs Initialising') super(SongsPlugin, self).initialise() self.song_import_item.setVisible(True) @@ -108,15 +108,13 @@ class SongsPlugin(Plugin): def add_import_menu_item(self, import_menu): """ - Give the Songs plugin the opportunity to add items to the - **Import** menu. + Give the Songs plugin the opportunity to add items to the **Import** menu. - ``import_menu`` - The actual **Import** menu item, so that your actions can - use it as their parent. + :param import_menu: The actual **Import** menu item, so that your actions can use it as their parent. """ # Main song import menu item - will eventually be the only one - self.song_import_item = create_action(import_menu, 'songImportItem', + self.song_import_item = create_action( + import_menu, 'songImportItem', text=translate('SongsPlugin', '&Song'), tooltip=translate('SongsPlugin', 'Import songs using the import wizard.'), triggers=self.on_song_import_item_clicked) @@ -124,15 +122,13 @@ class SongsPlugin(Plugin): def add_export_menu_Item(self, export_menu): """ - Give the Songs plugin the opportunity to add items to the - **Export** menu. + Give the Songs plugin the opportunity to add items to the **Export** menu. - ``export_menu`` - The actual **Export** menu item, so that your actions can - use it as their parent. + :param export_menu: The actual **Export** menu item, so that your actions can use it as their parent. """ # Main song import menu item - will eventually be the only one - self.song_export_item = create_action(export_menu, 'songExportItem', + self.song_export_item = create_action( + export_menu, 'songExportItem', text=translate('SongsPlugin', '&Song'), tooltip=translate('SongsPlugin', 'Exports songs using the export wizard.'), triggers=self.on_song_export_item_clicked) @@ -140,24 +136,22 @@ class SongsPlugin(Plugin): def add_tools_menu_item(self, tools_menu): """ - Give the Songs plugin the opportunity to add items to the - **Tools** menu. + Give the Songs plugin the opportunity to add items to the **Tools** menu. - ``tools_menu`` - The actual **Tools** menu item, so that your actions can - use it as their parent. + :param tools_menu: The actual **Tools** menu item, so that your actions can use it as their parent. """ log.info('add tools menu') - self.tools_reindex_item = create_action(tools_menu, 'toolsReindexItem', + self.tools_reindex_item = create_action( + tools_menu, 'toolsReindexItem', text=translate('SongsPlugin', '&Re-index Songs'), icon=':/plugins/plugin_songs.png', statustip=translate('SongsPlugin', 'Re-index the songs database to improve searching and ordering.'), visible=False, triggers=self.on_tools_reindex_item_triggered) tools_menu.addAction(self.tools_reindex_item) - self.tools_find_duplicates = create_action(tools_menu, 'toolsFindDuplicates', + self.tools_find_duplicates = create_action( + tools_menu, 'toolsFindDuplicates', text=translate('SongsPlugin', 'Find &Duplicate Songs'), - statustip=translate('SongsPlugin', - 'Find and remove duplicate songs in the song database.'), + statustip=translate('SongsPlugin', 'Find and remove duplicate songs in the song database.'), visible=False, triggers=self.on_tools_find_duplicates_triggered, can_shortcuts=True) tools_menu.addAction(self.tools_find_duplicates) @@ -165,11 +159,11 @@ class SongsPlugin(Plugin): """ Rebuild each song. """ - maxSongs = self.manager.get_object_count(Song) - if maxSongs == 0: + max_songs = self.manager.get_object_count(Song) + if max_songs == 0: return - progress_dialog = QtGui.QProgressDialog(translate('SongsPlugin', 'Reindexing songs...'), UiStrings().Cancel, - 0, maxSongs, self.main_window) + progress_dialog = QtGui.QProgressDialog( + translate('SongsPlugin', 'Reindexing songs...'), UiStrings().Cancel, 0, max_songs, self.main_window) progress_dialog.setWindowTitle(translate('SongsPlugin', 'Reindexing songs')) progress_dialog.setWindowModality(QtCore.Qt.WindowModal) songs = self.manager.get_all_objects(Song) @@ -186,16 +180,22 @@ class SongsPlugin(Plugin): DuplicateSongRemovalForm(self).exec_() def on_song_import_item_clicked(self): + """ + The song import option has been selected + """ if self.media_item: self.media_item.on_import_click() def on_song_export_item_clicked(self): + """ + The song export option has been selected + """ if self.media_item: self.media_item.on_export_click() def about(self): return translate('SongsPlugin', 'Songs Plugin' - '
The songs plugin provides the ability to display and manage songs.') + '
The songs plugin provides the ability to display and manage songs.') def uses_theme(self, theme): """ @@ -207,24 +207,27 @@ class SongsPlugin(Plugin): return True return False - def rename_theme(self, oldTheme, newTheme): + def rename_theme(self, old_theme, new_theme): """ - Renames a theme the song plugin is using making the plugin use the new - name. + Renames a theme the song plugin is using making the plugin use the new name. - ``oldTheme`` - The name of the theme the plugin should stop using. - - ``newTheme`` - The new name the plugin should now use. + :param old_theme: The name of the theme the plugin should stop using. + :param new_theme: The new name the plugin should now use. """ - songsUsingTheme = self.manager.get_all_objects(Song, Song.theme_name == oldTheme) - for song in songsUsingTheme: - song.theme_name = newTheme + songs_using_theme = self.manager.get_all_objects(Song, Song.theme_name == old_theme) + for song in songs_using_theme: + song.theme_name = new_theme self.manager.save_object(song) - def importSongs(self, format, **kwargs): - class_ = SongFormat.get(format, 'class') + def import_songs(self, import_format, **kwargs): + """ + Add the correct importer class + + :param import_format: The import_format to be used + :param kwargs: The arguments + :return: the correct importer + """ + class_ = SongFormat.get(import_format, 'class') importer = class_(self.manager, **kwargs) importer.register(self.media_item.import_wizard) return importer @@ -257,8 +260,7 @@ class SongsPlugin(Plugin): def first_time(self): """ - If the first time wizard has run, this function is run to import all the - new songs into the database. + If the first time wizard has run, this function is run to import all the new songs into the database. """ self.application.process_events() self.on_tools_reindex_item_triggered() @@ -272,7 +274,7 @@ class SongsPlugin(Plugin): if sfile.startswith('songs_') and sfile.endswith('.sqlite'): self.application.process_events() song_dbs.append(os.path.join(db_dir, sfile)) - song_count += self._countSongs(os.path.join(db_dir, sfile)) + song_count += self._count_songs(os.path.join(db_dir, sfile)) if not song_dbs: return self.application.process_events() @@ -319,9 +321,11 @@ class SongsPlugin(Plugin): for song in songs: self.manager.delete_object(Song, song.id) - def _countSongs(self, db_file): + def _count_songs(self, db_file): """ Provide a count of the songs in the database + + :param db_file: the database name to count """ connection = sqlite3.connect(db_file) cursor = connection.cursor() From 1301b4cfa332aaa0458d36a4d2e5999aaf018022 Mon Sep 17 00:00:00 2001 From: Tim Bentley Date: Tue, 4 Mar 2014 20:18:14 +0000 Subject: [PATCH 18/18] Fix up presentation tests --- .../presentations/test_mediaitem.py | 12 ++++------ .../presentations/test_pdfcontroller.py | 4 ++-- .../presentations/test_pptviewcontroller.py | 24 +++++++++++-------- .../test_presentationcontroller.py | 24 ++++++++++--------- 4 files changed, 33 insertions(+), 31 deletions(-) diff --git a/tests/functional/openlp_plugins/presentations/test_mediaitem.py b/tests/functional/openlp_plugins/presentations/test_mediaitem.py index fb2c17e05..0b2cf184c 100644 --- a/tests/functional/openlp_plugins/presentations/test_mediaitem.py +++ b/tests/functional/openlp_plugins/presentations/test_mediaitem.py @@ -93,11 +93,7 @@ class TestMediaItem(TestCase): self.media_item.build_file_mask_string() # THEN: The file mask should be generated correctly - self.assertIn('*.odp', self.media_item.on_new_file_masks, - 'The file mask should contain the odp extension') - self.assertIn('*.ppt', self.media_item.on_new_file_masks, - 'The file mask should contain the ppt extension') - self.assertIn('*.pdf', self.media_item.on_new_file_masks, - 'The file mask should contain the pdf extension') - self.assertIn('*.xps', self.media_item.on_new_file_masks, - 'The file mask should contain the xps extension') + self.assertIn('*.odp', self.media_item.on_new_file_masks, 'The file mask should contain the odp extension') + self.assertIn('*.ppt', self.media_item.on_new_file_masks, 'The file mask should contain the ppt extension') + self.assertIn('*.pdf', self.media_item.on_new_file_masks, 'The file mask should contain the pdf extension') + self.assertIn('*.xps', self.media_item.on_new_file_masks, 'The file mask should contain the xps extension') diff --git a/tests/functional/openlp_plugins/presentations/test_pdfcontroller.py b/tests/functional/openlp_plugins/presentations/test_pdfcontroller.py index cd2e4f744..6dba4ac90 100644 --- a/tests/functional/openlp_plugins/presentations/test_pdfcontroller.py +++ b/tests/functional/openlp_plugins/presentations/test_pdfcontroller.py @@ -77,7 +77,7 @@ class TestPdfController(TestCase): def constructor_test(self): """ - Test the Constructor + Test the Constructor from the PdfController """ # GIVEN: No presentation controller controller = None @@ -90,7 +90,7 @@ class TestPdfController(TestCase): def load_pdf_test(self): """ - Test loading of a Pdf + Test loading of a Pdf using the PdfController """ # GIVEN: A Pdf-file test_file = os.path.join(TEST_RESOURCES_PATH, 'presentations', 'pdf_test1.pdf') diff --git a/tests/functional/openlp_plugins/presentations/test_pptviewcontroller.py b/tests/functional/openlp_plugins/presentations/test_pptviewcontroller.py index 62a76966d..d22372377 100644 --- a/tests/functional/openlp_plugins/presentations/test_pptviewcontroller.py +++ b/tests/functional/openlp_plugins/presentations/test_pptviewcontroller.py @@ -29,7 +29,7 @@ """ This module contains tests for the pptviewcontroller module of the Presentations plugin. """ - +import os from unittest import TestCase from tests.functional import MagicMock, patch @@ -43,6 +43,7 @@ from openlp.plugins.presentations.lib.pptviewcontroller import PptviewDocument # start_process(self) # kill + class TestPptviewDocument(TestCase): """ Test the PptviewDocument Class @@ -78,8 +79,8 @@ class TestPptviewDocument(TestCase): 'openlp.plugins.presentations.lib.pptviewcontroller.PresentationDocument.get_temp_folder') self.presentation_document_setup_patcher = patch( 'openlp.plugins.presentations.lib.pptviewcontroller.PresentationDocument._setup') - self.rect_patcher = patch('openlp.plugins.presentations.lib.pptviewcontroller.RECT') self.screen_list_patcher = patch('openlp.plugins.presentations.lib.pptviewcontroller.ScreenList') + self.rect_patcher = MagicMock() self.mock_os = self.os_patcher.start() self.mock_pptview_document_create_thumbnails = self.pptview_document_create_thumbnails_patcher.start() @@ -118,12 +119,14 @@ class TestPptviewDocument(TestCase): self.mock_controller.process.OpenPPT.return_value = 0 instance = PptviewDocument(self.mock_controller, self.mock_presentation) instance.filepath = 'test\path.ppt' - result = instance.load_presentation() - # THEN: PptviewDocument.load_presentation should return True - self.assertTrue(result) + if os.name == 'nt': + result = instance.load_presentation() - def load_presentation_unsuccesfull_test(self): + # THEN: PptviewDocument.load_presentation should return True + self.assertTrue(result) + + def load_presentation_un_succesfull_test(self): """ Test the PptviewDocument.load_presentation() method when the temporary directory does not exist and the PPT is not successfully opened @@ -136,8 +139,9 @@ class TestPptviewDocument(TestCase): self.mock_controller.process.OpenPPT.return_value = -1 instance = PptviewDocument(self.mock_controller, self.mock_presentation) instance.filepath = 'test\path.ppt' - result = instance.load_presentation() + if os.name == 'nt': + result = instance.load_presentation() - # THEN: The temporary directory should be created and PptviewDocument.load_presentation should return False - self.mock_os.makedirs.assert_called_once_with('temp folder') - self.assertFalse(result) + # THEN: The temporary directory should be created and PptviewDocument.load_presentation should return False + self.mock_os.makedirs.assert_called_once_with('temp folder') + self.assertFalse(result) diff --git a/tests/functional/openlp_plugins/presentations/test_presentationcontroller.py b/tests/functional/openlp_plugins/presentations/test_presentationcontroller.py index 151fc5495..f3d666962 100644 --- a/tests/functional/openlp_plugins/presentations/test_presentationcontroller.py +++ b/tests/functional/openlp_plugins/presentations/test_presentationcontroller.py @@ -34,6 +34,7 @@ from unittest import TestCase from openlp.plugins.presentations.lib.presentationcontroller import PresentationController, PresentationDocument from tests.functional import MagicMock, patch + class TestPresentationController(TestCase): """ Test the PresentationController. @@ -65,6 +66,7 @@ class TestPresentationController(TestCase): self.assertEqual('PresentationController', controller.name, 'The name of the presentation controller should be correct') + class TestPresentationDocument(TestCase): """ Test the PresentationDocument Class @@ -101,12 +103,12 @@ class TestPresentationDocument(TestCase): """ Set up the patches and mocks need for all tests. """ - self.check_directory_exists_patcher = patch( - 'openlp.plugins.presentations.lib.presentationcontroller.check_directory_exists') - self.get_thumbnail_folder_patcher = patch( - 'openlp.plugins.presentations.lib.presentationcontroller.PresentationDocument.get_thumbnail_folder') - self._setup_patcher = patch( - 'openlp.plugins.presentations.lib.presentationcontroller.PresentationDocument._setup') + self.check_directory_exists_patcher = \ + patch('openlp.plugins.presentations.lib.presentationcontroller.check_directory_exists') + self.get_thumbnail_folder_patcher = \ + patch('openlp.plugins.presentations.lib.presentationcontroller.PresentationDocument.get_thumbnail_folder') + self._setup_patcher = \ + patch('openlp.plugins.presentations.lib.presentationcontroller.PresentationDocument._setup') self.mock_check_directory_exists = self.check_directory_exists_patcher.start() self.mock_get_thumbnail_folder = self.get_thumbnail_folder_patcher.start() @@ -124,29 +126,29 @@ class TestPresentationDocument(TestCase): self.get_thumbnail_folder_patcher.stop() self._setup_patcher.stop() - def initalise_presentation_document_test(self): + def initialise_presentation_document_test(self): """ - Test the PresentationDocument __init__ method when initalising the PresentationDocument Class + Test the PresentationDocument __init__ method when initialising the PresentationDocument Class """ # GIVEN: A reset mock_setup and mocked controller self.mock_setup.reset() # WHEN: Creating an instance of PresentationDocument - instance = PresentationDocument(self.mock_controller, 'Name') + PresentationDocument(self.mock_controller, 'Name') # THEN: PresentationDocument.__init__ should have been called with the correct arguments self.mock_setup.assert_called_once_with('Name') def presentation_document_setup_test(self): """ - Test the PresentationDocument _setup method when initalising the PresentationDocument Class + Test the PresentationDocument _setup method when initialising the PresentationDocument Class """ self._setup_patcher.stop() # GIVEN: A mocked controller, patched check_directory_exists_patcher and patched get_thumbnail_folder method # WHEN: Creating an instance of PresentationDocument - instance = PresentationDocument(self.mock_controller, 'Name') + PresentationDocument(self.mock_controller, 'Name') # THEN: check_directory_exists should have been called with the correct arguments self.mock_check_directory_exists.assert_called_once_with('returned/path/')