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/slidecontroller.py b/openlp/core/ui/slidecontroller.py index 956eedf6a..34a2df909 100644 --- a/openlp/core/ui/slidecontroller.py +++ b/openlp/core/ui/slidecontroller.py @@ -444,7 +444,7 @@ class SlideController(DisplayController): # "V1" was the slide we wanted to go. self.preview_widget.change_slide(self.slide_list[self.current_shortcut]) self.slide_selected() - # Reset the shortcut. + # Reset the shortcut. self.current_shortcut = '' def set_live_hot_keys(self, parent=None): @@ -774,7 +774,7 @@ class SlideController(DisplayController): self._reset_blank() if service_item.is_command(): Registry().execute( - '%s_start' % service_item.name.lower(), [service_item, self.is_live, self.hide_mode(), slide_no]) + '%s_start' % service_item.name.lower(), [self.service_item, self.is_live, self.hide_mode(), slide_no]) self.slide_list = {} if self.is_live: self.song_menu.menu().clear() @@ -1440,4 +1440,4 @@ class LiveController(RegistryMixin, OpenLPMixin, SlideController): """ process the bootstrap post setup request """ - self.post_set_up() \ No newline at end of file + self.post_set_up() 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/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/mediaitem.py b/openlp/plugins/presentations/lib/mediaitem.py index 2e3b6c964..931a01bfd 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. @@ -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) @@ -177,9 +180,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 +205,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 +241,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,45 +252,93 @@ 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 = 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 - 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: + 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 + for bitem in items: + 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): + processor = self.findControllerByType(filename) + if not 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) + 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')): + doc.load_presentation() + i = 1 + imagefile = 'mainslide%03d.png' % i + image = os.path.join(doc.get_temp_folder(), imagefile) + while os.path.isfile(image): + service_item.add_from_image(image, name) i += 1 - img = doc.get_thumbnail_path(i, True) + imagefile = 'mainslide%03d.png' % i + image = 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) + 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) + 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/messagelistener.py b/openlp/plugins/presentations/lib/messagelistener.py index fdb495c30..d734add72 100644 --- a/openlp/plugins/presentations/lib/messagelistener.py +++ b/openlp/plugins/presentations/lib/messagelistener.py @@ -28,11 +28,13 @@ ############################################################################### import logging +import copy from PyQt4 import QtCore from openlp.core.common import Registry from openlp.core.ui import HideMode +from openlp.core.lib import ServiceItemContext, ServiceItem log = logging.getLogger(__name__) @@ -69,6 +71,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 +319,28 @@ class MessageListener(object): hide_mode = message[2] file = item.get_frame_path() self.handler = item.processor + # 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 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 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(item, item_cpy, False, False, ServiceItemContext.Live, file) + else: + 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: self.handler = self.media_item.findControllerByType(file) if not self.handler: @@ -324,7 +349,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 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 is 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 new file mode 100644 index 000000000..36eddac2f --- /dev/null +++ b/openlp/plugins/presentations/lib/pdfcontroller.py @@ -0,0 +1,316 @@ +# -*- 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 # +############################################################################### + +import os +import logging +from tempfile import NamedTemporaryFile +import re +from subprocess import check_output, CalledProcessError, STDOUT + +from openlp.core.utils import AppLocation +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 + """ + log.info('PdfController loaded') + + def __init__(self, plugin): + """ + Initialise the class + + :param plugin: The plugin that creates the controller. + """ + log.debug('Initialising') + self.process = None + PresentationController.__init__(self, plugin, 'Pdf', PdfDocument) + self.supports = ['pdf'] + self.also_supports = [] + # Determine whether mudraw or ghostscript is used + self.check_installed() + + @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 + + :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 = '' + log.debug('testing program_path: %s', program_path) + 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(): + 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.*', decoded_line) + if found_gs: + program_type = 'gs' + break + log.debug('in check_binary, found: %s', program_type) + return program_type + + 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() + + 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') + 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 + elif program_type == 'mudraw': + self.mudrawbin = pdf_program + else: + # 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 + application_path = AppLocation.get_directory(AppLocation.AppDir) + 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'], 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'], stderr=DEVNULL).decode(encoding='UTF-8').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): + """ + Called at system exit to clean up any running presentations + """ + log.debug('Kill pdfviewer') + while self.docs: + self.docs[0].close_presentation() + + +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): + """ + Constructor, store information about the file and initialise. + """ + log.debug('Init Presentation Pdf') + PresentationDocument.__init__(self, controller, presentation) + self.presentation = None + self.blanked = False + self.hidden = False + self.image_files = [] + self.num_pages = -1 + + def gs_get_resolution(self, size): + """ + Only used when using ghostscript + 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 + 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, gs_resolution_script]) + except CalledProcessError as e: + log.debug(' '.join(e.cmd)) + log.debug(e.output) + # Extract the pdf resolution from output, the format is " Size: x: , y: " + width = 0 + height = 0 + for line in runlog.splitlines(): + try: + 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 + # 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) + 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. + + :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 + 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)): + 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: + 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]) + 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: + 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('create_thumbnails pdf') + if self.check_thumbnails(): + return + # 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('close_presentation pdf') + self.controller.remove_doc(self) + + 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: + return False + return True + + 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 + + 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 ba490f11b..7d5c81366 100644 --- a/openlp/plugins/presentations/lib/presentationtab.py +++ b/openlp/plugins/presentations/lib/presentationtab.py @@ -30,7 +30,9 @@ from PyQt4 import QtGui from openlp.core.common import Settings, UiStrings, translate -from openlp.core.lib import SettingsTab +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): @@ -64,6 +66,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('advanced_group_box') self.advanced_layout = QtGui.QVBoxLayout(self.advanced_group_box) @@ -72,8 +75,34 @@ class PresentationTab(SettingsTab): 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') + self.pdf_layout = QtGui.QFormLayout(self.pdf_group_box) + 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.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) + 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('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) + 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_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) def retranslateUi(self): """ @@ -85,8 +114,11 @@ 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(): @@ -103,6 +135,14 @@ class PresentationTab(SettingsTab): checkbox = self.presenter_check_boxes[controller.name] 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_pdf_program = Settings().value(self.settings_section + '/enable_pdf_program') + self.pdf_program_check_box.setChecked(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) def save(self): """ @@ -128,6 +168,18 @@ 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() + # If the given program is blank disable using the program + if 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_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') @@ -143,3 +195,43 @@ 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_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.'), + self.pdf_program_path.text()) + if filename: + 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): + """ + 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.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. + + :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) + 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 ca583244c..de0a0c5a5 100644 --- a/openlp/plugins/presentations/presentationplugin.py +++ b/openlp/plugins/presentations/presentationplugin.py @@ -43,13 +43,15 @@ from openlp.plugins.presentations.lib import PresentationController, Presentatio log = logging.getLogger(__name__) -__default_settings__ = { - 'presentations/override app': QtCore.Qt.Unchecked, - 'presentations/Impress': QtCore.Qt.Checked, - 'presentations/Powerpoint': QtCore.Qt.Checked, - 'presentations/Powerpoint Viewer': 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): @@ -144,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 71d7df8c0..88576ca95 100644 --- a/openlp/plugins/songs/forms/editsongdialog.py +++ b/openlp/plugins/songs/forms/editsongdialog.py @@ -154,8 +154,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) @@ -297,7 +297,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')) @@ -309,7 +309,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')) @@ -317,9 +317,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 4c6dc8106..00bb72b9a 100644 --- a/openlp/plugins/songs/songsplugin.py +++ b/openlp/plugins/songs/songsplugin.py @@ -119,9 +119,11 @@ class SongsPlugin(Plugin): :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', text=translate('SongsPlugin', '&Song'), - tooltip=translate('SongsPlugin', 'Import songs using the import wizard.'), - triggers=self.on_song_import_item_clicked) + 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) import_menu.addAction(self.song_import_item) self.import_songselect_item = create_action( import_menu, 'import_songselect_item', text=translate('SongsPlugin', 'CCLI SongSelect'), @@ -137,7 +139,8 @@ class SongsPlugin(Plugin): :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) @@ -150,16 +153,17 @@ class SongsPlugin(Plugin): :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) @@ -170,8 +174,8 @@ class SongsPlugin(Plugin): max_songs = self.manager.get_object_count(Song) if max_songs == 0: return - progress_dialog = QtGui.QProgressDialog(translate('SongsPlugin', 'Reindexing songs...'), UiStrings().Cancel, - 0, max_songs, 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) @@ -230,22 +234,25 @@ class SongsPlugin(Plugin): 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. - ``old_theme`` - The name of the theme the plugin should stop using. - - ``new_theme`` - 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. """ 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 @@ -278,8 +285,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() @@ -293,7 +299,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() @@ -340,9 +346,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() diff --git a/resources/pyinstaller/hook-openlp.plugins.presentations.presentationplugin.py b/resources/pyinstaller/hook-openlp.plugins.presentations.presentationplugin.py index 16f96cff9..6ffb416fa 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'] diff --git a/tests/functional/openlp_plugins/presentations/test_mediaitem.py b/tests/functional/openlp_plugins/presentations/test_mediaitem.py index c6bc274bc..0b2cf184c 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. @@ -88,7 +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('*.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 new file mode 100644 index 000000000..6dba4ac90 --- /dev/null +++ b/tests/functional/openlp_plugins/presentations/test_pdfcontroller.py @@ -0,0 +1,109 @@ +# -*- 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 from the PdfController + """ + # 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 using the PdfController + """ + # 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/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/') diff --git a/tests/resources/presentations/pdf_test1.pdf b/tests/resources/presentations/pdf_test1.pdf new file mode 100644 index 000000000..012dc9548 Binary files /dev/null and b/tests/resources/presentations/pdf_test1.pdf differ