This commit is contained in:
Raoul Snyman 2014-03-04 23:51:54 +02:00
commit c607661aee
34 changed files with 1203 additions and 526 deletions

View File

@ -63,7 +63,7 @@ class ServiceNoteForm(QtGui.QDialog):
self.dialog_layout = QtGui.QVBoxLayout(self) self.dialog_layout = QtGui.QVBoxLayout(self)
self.dialog_layout.setContentsMargins(8, 8, 8, 8) self.dialog_layout.setContentsMargins(8, 8, 8, 8)
self.dialog_layout.setSpacing(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 = SpellTextEdit(self, False)
self.text_edit.setObjectName('textEdit') self.text_edit.setObjectName('textEdit')
self.dialog_layout.addWidget(self.text_edit) self.dialog_layout.addWidget(self.text_edit)

View File

@ -444,7 +444,7 @@ class SlideController(DisplayController):
# "V1" was the slide we wanted to go. # "V1" was the slide we wanted to go.
self.preview_widget.change_slide(self.slide_list[self.current_shortcut]) self.preview_widget.change_slide(self.slide_list[self.current_shortcut])
self.slide_selected() self.slide_selected()
# Reset the shortcut. # Reset the shortcut.
self.current_shortcut = '' self.current_shortcut = ''
def set_live_hot_keys(self, parent=None): def set_live_hot_keys(self, parent=None):
@ -774,7 +774,7 @@ class SlideController(DisplayController):
self._reset_blank() self._reset_blank()
if service_item.is_command(): if service_item.is_command():
Registry().execute( 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 = {} self.slide_list = {}
if self.is_live: if self.is_live:
self.song_menu.menu().clear() self.song_menu.menu().clear()
@ -1440,4 +1440,4 @@ class LiveController(RegistryMixin, OpenLPMixin, SlideController):
""" """
process the bootstrap post setup request process the bootstrap post setup request
""" """
self.post_set_up() self.post_set_up()

View File

@ -215,7 +215,7 @@ class OpenLPWizard(QtGui.QWizard):
""" """
if self.with_progress_page and self.page(pageId) == self.progress_page: if self.with_progress_page and self.page(pageId) == self.progress_page:
self.pre_wizard() self.pre_wizard()
self.performWizard() self.perform_wizard()
self.post_wizard() self.post_wizard()
else: else:
self.custom_page_changed(pageId) self.custom_page_changed(pageId)
@ -294,8 +294,8 @@ class OpenLPWizard(QtGui.QWizard):
if filters: if filters:
filters += ';;' filters += ';;'
filters += '%s (*)' % UiStrings().AllFiles filters += '%s (*)' % UiStrings().AllFiles
filename = QtGui.QFileDialog.getOpenFileName(self, title, filename = QtGui.QFileDialog.getOpenFileName(
os.path.dirname(Settings().value(self.plugin.settings_section + '/' + setting_name)), filters) self, title, os.path.dirname(Settings().value(self.plugin.settings_section + '/' + setting_name)), filters)
if filename: if filename:
editbox.setText(filename) editbox.setText(filename)
Settings().setValue(self.plugin.settings_section + '/' + setting_name, filename) Settings().setValue(self.plugin.settings_section + '/' + setting_name, filename)
@ -313,8 +313,9 @@ class OpenLPWizard(QtGui.QWizard):
``setting_name`` ``setting_name``
The place where to save the last opened directory. The place where to save the last opened directory.
""" """
folder = QtGui.QFileDialog.getExistingDirectory(self, title, folder = QtGui.QFileDialog.getExistingDirectory(
Settings().value(self.plugin.settings_section + '/' + setting_name), QtGui.QFileDialog.ShowDirsOnly) self, title, Settings().value(self.plugin.settings_section + '/' + setting_name),
QtGui.QFileDialog.ShowDirsOnly)
if folder: if folder:
editbox.setText(folder) editbox.setText(folder)
Settings().setValue(self.plugin.settings_section + '/' + setting_name, folder) Settings().setValue(self.plugin.settings_section + '/' + setting_name, folder)

View File

@ -524,7 +524,7 @@ class BibleImportForm(OpenLPWizard):
self.progress_label.setText(WizardStrings.StartingImport) self.progress_label.setText(WizardStrings.StartingImport)
self.application.process_events() self.application.process_events()
def performWizard(self): def perform_wizard(self):
""" """
Perform the actual import. Perform the actual import.
""" """

View File

@ -107,7 +107,7 @@ class BibleUpgradeForm(OpenLPWizard):
""" """
if self.page(pageId) == self.progress_page: if self.page(pageId) == self.progress_page:
self.pre_wizard() self.pre_wizard()
self.performWizard() self.perform_wizard()
self.post_wizard() self.post_wizard()
elif self.page(pageId) == self.selectPage and not self.files: elif self.page(pageId) == self.selectPage and not self.files:
self.next() self.next()
@ -312,7 +312,7 @@ class BibleUpgradeForm(OpenLPWizard):
""" """
Set default values for the wizard pages. Set default values for the wizard pages.
""" """
log.debug('BibleUpgrade setDefaults') log.debug('BibleUpgrade set_defaults')
settings = Settings() settings = Settings()
settings.beginGroup(self.plugin.settings_section) settings.beginGroup(self.plugin.settings_section)
self.stop_import_flag = False self.stop_import_flag = False
@ -338,7 +338,7 @@ class BibleUpgradeForm(OpenLPWizard):
self.progress_label.setText(translate('BiblesPlugin.UpgradeWizardForm', 'Starting upgrade...')) self.progress_label.setText(translate('BiblesPlugin.UpgradeWizardForm', 'Starting upgrade...'))
self.application.process_events() self.application.process_events()
def performWizard(self): def perform_wizard(self):
""" """
Perform the actual upgrade. Perform the actual upgrade.
""" """

View File

@ -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

View File

@ -116,7 +116,7 @@ class PresentationMediaItem(MediaManagerItem):
self.display_type_label = QtGui.QLabel(self.presentation_widget) self.display_type_label = QtGui.QLabel(self.presentation_widget)
self.display_type_label.setObjectName('display_type_label') self.display_type_label.setObjectName('display_type_label')
self.display_type_combo_box = create_horizontal_adjusting_combo_box(self.presentation_widget, 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_type_label.setBuddy(self.display_type_combo_box)
self.display_layout.addRow(self.display_type_label, 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. # Add the Presentation widget to the page layout.
@ -138,6 +138,9 @@ class PresentationMediaItem(MediaManagerItem):
""" """
self.display_type_combo_box.clear() self.display_type_combo_box.clear()
for item in self.controllers: 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 # load the drop down selection
if self.controllers[item].enabled(): if self.controllers[item].enabled():
self.display_type_combo_box.addItem(item) self.display_type_combo_box.addItem(item)
@ -177,9 +180,8 @@ class PresentationMediaItem(MediaManagerItem):
if titles.count(filename) > 0: if titles.count(filename) > 0:
if not initial_load: if not initial_load:
critical_error_message_box(translate('PresentationPlugin.MediaItem', 'File Exists'), critical_error_message_box(translate('PresentationPlugin.MediaItem', 'File Exists'),
translate('PresentationPlugin.MediaItem', translate('PresentationPlugin.MediaItem',
'A presentation with that filename already exists.') 'A presentation with that filename already exists.'))
)
continue continue
controller_name = self.findControllerByType(filename) controller_name = self.findControllerByType(filename)
if controller_name: if controller_name:
@ -203,7 +205,8 @@ class PresentationMediaItem(MediaManagerItem):
icon = build_icon(':/general/general_delete.png') icon = build_icon(':/general/general_delete.png')
else: else:
critical_error_message_box(UiStrings().UnsupportedFile, 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 continue
item_name = QtGui.QListWidgetItem(filename) item_name = QtGui.QListWidgetItem(filename)
item_name.setData(QtCore.Qt.UserRole, file) 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()) Settings().setValue(self.settings_section + '/presentations files', self.get_file_list())
def generate_slide_data(self, service_item, item=None, xml_version=False, 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 Load the relevant information for displaying the presentation in the slidecontroller. In the case of
powerpoints, an image for each slide. powerpoints, an image for each slide.
@ -249,45 +252,93 @@ class PresentationMediaItem(MediaManagerItem):
items = self.list_view.selectedItems() items = self.list_view.selectedItems()
if len(items) > 1: if len(items) > 1:
return False return False
service_item.processor = self.display_type_combo_box.currentText() filename = presentation_file
service_item.add_capability(ItemCapabilities.ProvidesOwnDisplay) 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(): if not self.display_type_combo_box.currentText():
return False return False
for bitem in items: if (file_type == 'pdf' or file_type == 'xps') and context != ServiceItemContext.Service:
filename = bitem.data(QtCore.Qt.UserRole) service_item.add_capability(ItemCapabilities.CanMaintain)
(path, name) = os.path.split(filename) service_item.add_capability(ItemCapabilities.CanPreview)
service_item.title = name service_item.add_capability(ItemCapabilities.CanLoop)
if os.path.exists(filename): service_item.add_capability(ItemCapabilities.CanAppend)
if service_item.processor == self.automatic: # force a nonexistent theme
service_item.processor = self.findControllerByType(filename) service_item.theme = -1
if not service_item.processor: 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 return False
controller = self.controllers[service_item.processor] controller = self.controllers[processor]
doc = controller.add_document(filename) service_item.processor = None
if doc.get_thumbnail_path(1, True) is None: doc = controller.add_document(filename)
doc.load_presentation() if doc.get_thumbnail_path(1, True) is None or not os.path.isfile(
i = 1 os.path.join(doc.get_temp_folder(), 'mainslide001.png')):
img = doc.get_thumbnail_path(i, True) doc.load_presentation()
if img: i = 1
while img: imagefile = 'mainslide%03d.png' % i
service_item.add_from_command(path, name, img) image = os.path.join(doc.get_temp_folder(), imagefile)
while os.path.isfile(image):
service_item.add_from_image(image, name)
i += 1 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() doc.close_presentation()
return True return True
else: else:
# File is no longer present # File is no longer present
if not remote: if not remote:
critical_error_message_box(translate('PresentationPlugin.MediaItem', 'Missing Presentation'), critical_error_message_box(translate('PresentationPlugin.MediaItem', 'Missing Presentation'),
translate('PresentationPlugin.MediaItem', translate('PresentationPlugin.MediaItem',
'The presentation %s is incomplete, please reload.') % filename) '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 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): def findControllerByType(self, filename):
""" """

View File

@ -28,11 +28,13 @@
############################################################################### ###############################################################################
import logging import logging
import copy
from PyQt4 import QtCore from PyQt4 import QtCore
from openlp.core.common import Registry from openlp.core.common import Registry
from openlp.core.ui import HideMode from openlp.core.ui import HideMode
from openlp.core.lib import ServiceItemContext, ServiceItem
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -69,6 +71,7 @@ class Controller(object):
return return
self.doc.slidenumber = slide_no self.doc.slidenumber = slide_no
self.hide_mode = hide_mode self.hide_mode = hide_mode
log.debug('add_handler, slidenumber: %d' % slide_no)
if self.is_live: if self.is_live:
if hide_mode == HideMode.Screen: if hide_mode == HideMode.Screen:
Registry().execute('live_display_hide', HideMode.Screen) Registry().execute('live_display_hide', HideMode.Screen)
@ -316,6 +319,28 @@ class MessageListener(object):
hide_mode = message[2] hide_mode = message[2]
file = item.get_frame_path() file = item.get_frame_path()
self.handler = item.processor 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: if self.handler == self.media_item.automatic:
self.handler = self.media_item.findControllerByType(file) self.handler = self.media_item.findControllerByType(file)
if not self.handler: if not self.handler:
@ -324,7 +349,12 @@ class MessageListener(object):
controller = self.live_handler controller = self.live_handler
else: else:
controller = self.preview_handler 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): def slide(self, message):
""" """

View File

@ -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: <width>, y: <height>"
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

View File

@ -30,7 +30,9 @@
from PyQt4 import QtGui from PyQt4 import QtGui
from openlp.core.common import Settings, UiStrings, translate 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): class PresentationTab(SettingsTab):
@ -64,6 +66,7 @@ class PresentationTab(SettingsTab):
self.presenter_check_boxes[controller.name] = checkbox self.presenter_check_boxes[controller.name] = checkbox
self.controllers_layout.addWidget(checkbox) self.controllers_layout.addWidget(checkbox)
self.left_layout.addWidget(self.controllers_group_box) self.left_layout.addWidget(self.controllers_group_box)
# Advanced
self.advanced_group_box = QtGui.QGroupBox(self.left_column) self.advanced_group_box = QtGui.QGroupBox(self.left_column)
self.advanced_group_box.setObjectName('advanced_group_box') self.advanced_group_box.setObjectName('advanced_group_box')
self.advanced_layout = QtGui.QVBoxLayout(self.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.override_app_check_box.setObjectName('override_app_check_box')
self.advanced_layout.addWidget(self.override_app_check_box) self.advanced_layout.addWidget(self.override_app_check_box)
self.left_layout.addWidget(self.advanced_group_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.left_layout.addStretch()
self.right_column.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Preferred)
self.right_layout.addStretch() 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): def retranslateUi(self):
""" """
@ -85,8 +114,11 @@ class PresentationTab(SettingsTab):
checkbox = self.presenter_check_boxes[controller.name] checkbox = self.presenter_check_boxes[controller.name]
self.set_controller_text(checkbox, controller) self.set_controller_text(checkbox, controller)
self.advanced_group_box.setTitle(UiStrings().Advanced) self.advanced_group_box.setTitle(UiStrings().Advanced)
self.pdf_group_box.setTitle(translate('PresentationPlugin.PresentationTab', 'PDF options'))
self.override_app_check_box.setText( self.override_app_check_box.setText(
translate('PresentationPlugin.PresentationTab', 'Allow presentation application to be overridden')) 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): def set_controller_text(self, checkbox, controller):
if checkbox.isEnabled(): if checkbox.isEnabled():
@ -103,6 +135,14 @@ class PresentationTab(SettingsTab):
checkbox = self.presenter_check_boxes[controller.name] checkbox = self.presenter_check_boxes[controller.name]
checkbox.setChecked(Settings().value(self.settings_section + '/' + controller.name)) checkbox.setChecked(Settings().value(self.settings_section + '/' + controller.name))
self.override_app_check_box.setChecked(Settings().value(self.settings_section + '/override app')) 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): def save(self):
""" """
@ -128,6 +168,18 @@ class PresentationTab(SettingsTab):
if Settings().value(setting_key) != self.override_app_check_box.checkState(): if Settings().value(setting_key) != self.override_app_check_box.checkState():
Settings().setValue(setting_key, self.override_app_check_box.checkState()) Settings().setValue(setting_key, self.override_app_check_box.checkState())
changed = True 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: if changed:
self.settings_form.register_post_process('mediaitem_suffix_reset') self.settings_form.register_post_process('mediaitem_suffix_reset')
self.settings_form.register_post_process('mediaitem_presentation_rebuild') self.settings_form.register_post_process('mediaitem_presentation_rebuild')
@ -143,3 +195,43 @@ class PresentationTab(SettingsTab):
checkbox = self.presenter_check_boxes[controller.name] checkbox = self.presenter_check_boxes[controller.name]
checkbox.setEnabled(controller.is_available()) checkbox.setEnabled(controller.is_available())
self.set_controller_text(checkbox, controller) 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

View File

@ -43,13 +43,15 @@ from openlp.plugins.presentations.lib import PresentationController, Presentatio
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
__default_settings__ = { __default_settings__ = {'presentations/override app': QtCore.Qt.Unchecked,
'presentations/override app': QtCore.Qt.Unchecked, 'presentations/enable_pdf_program': QtCore.Qt.Unchecked,
'presentations/Impress': QtCore.Qt.Checked, 'presentations/pdf_program': '',
'presentations/Powerpoint': QtCore.Qt.Checked, 'presentations/Impress': QtCore.Qt.Checked,
'presentations/Powerpoint Viewer': QtCore.Qt.Checked, 'presentations/Powerpoint': QtCore.Qt.Checked,
'presentations/presentations files': [] 'presentations/Powerpoint Viewer': QtCore.Qt.Checked,
} 'presentations/Pdf': QtCore.Qt.Checked,
'presentations/presentations files': []
}
class PresentationPlugin(Plugin): class PresentationPlugin(Plugin):
@ -144,10 +146,10 @@ class PresentationPlugin(Plugin):
Return information about this plugin. Return information about this plugin.
""" """
about_text = translate('PresentationPlugin', '<strong>Presentation ' about_text = translate('PresentationPlugin', '<strong>Presentation '
'Plugin</strong><br />The presentation plugin provides the ' 'Plugin</strong><br />The presentation plugin provides the '
'ability to show presentations using a number of different ' 'ability to show presentations using a number of different '
'programs. The choice of available presentation programs is ' 'programs. The choice of available presentation programs is '
'available to the user in a drop down box.') 'available to the user in a drop down box.')
return about_text return about_text
def set_plugin_text_strings(self): def set_plugin_text_strings(self):

View File

@ -111,8 +111,8 @@ class AuthorsForm(QtGui.QDialog, Ui_AuthorsDialog):
elif not self.display_edit.text(): elif not self.display_edit.text():
if critical_error_message_box( if critical_error_message_box(
message=translate('SongsPlugin.AuthorsForm', message=translate('SongsPlugin.AuthorsForm',
'You have not set a display name for the author, combine the first and last names?'), 'You have not set a display name for the author, combine the first and last names?'),
parent=self, question=True) == QtGui.QMessageBox.Yes: parent=self, question=True) == QtGui.QMessageBox.Yes:
self.display_edit.setText(self.first_name_edit.text() + ' ' + self.last_name_edit.text()) self.display_edit.setText(self.first_name_edit.text() + ' ' + self.last_name_edit.text())
return QtGui.QDialog.accept(self) return QtGui.QDialog.accept(self)
else: else:

View File

@ -47,8 +47,8 @@ log = logging.getLogger(__name__)
class DuplicateSongRemovalForm(OpenLPWizard): class DuplicateSongRemovalForm(OpenLPWizard):
""" """
This is the Duplicate Song Removal Wizard. It provides functionality to This is the Duplicate Song Removal Wizard. It provides functionality to search for and remove duplicate songs
search for and remove duplicate songs in the database. in the database.
""" """
log.info('DuplicateSongRemovalForm loaded') log.info('DuplicateSongRemovalForm loaded')
@ -56,19 +56,16 @@ class DuplicateSongRemovalForm(OpenLPWizard):
""" """
Instantiate the wizard, and run any extra setup we need to. Instantiate the wizard, and run any extra setup we need to.
``parent`` :param plugin: The songs plugin.
The QWidget-derived parent of the wizard.
``plugin``
The songs plugin.
""" """
self.duplicate_song_list = [] self.duplicate_song_list = []
self.review_current_count = 0 self.review_current_count = 0
self.review_total_count = 0 self.review_total_count = 0
# Used to interrupt ongoing searches when cancel is clicked. # Used to interrupt ongoing searches when cancel is clicked.
self.break_search = False self.break_search = False
super(DuplicateSongRemovalForm, self).__init__(Registry().get('main_window'), super(DuplicateSongRemovalForm, self).__init__(
plugin, 'duplicateSongRemovalWizard', ':/wizards/wizard_duplicateremoval.bmp', False) Registry().get('main_window'), plugin, 'duplicateSongRemovalWizard', ':/wizards/wizard_duplicateremoval.bmp'
, False)
self.setMinimumWidth(730) self.setMinimumWidth(730)
def custom_signals(self): def custom_signals(self):
@ -127,30 +124,31 @@ class DuplicateSongRemovalForm(OpenLPWizard):
""" """
self.setWindowTitle(translate('Wizard', 'Wizard')) self.setWindowTitle(translate('Wizard', 'Wizard'))
self.title_label.setText(WizardStrings.HeaderStyle % translate('OpenLP.Ui', self.title_label.setText(WizardStrings.HeaderStyle % translate('OpenLP.Ui',
'Welcome to the Duplicate Song Removal Wizard')) 'Welcome to the Duplicate Song Removal Wizard'))
self.information_label.setText(translate("Wizard", self.information_label.setText(
'This wizard will help you to remove duplicate songs from the song database. You will have a chance to ' translate("Wizard",
'review every potential duplicate song before it is deleted. So no songs will be deleted without your ' 'This wizard will help you to remove duplicate songs from the song database. You will have a '
'explicit approval.')) '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.setTitle(translate('Wizard', 'Searching for duplicate songs.'))
self.searching_page.setSubTitle(translate('Wizard', 'Please wait while your songs database is analyzed.')) self.searching_page.setSubTitle(translate('Wizard', 'Please wait while your songs database is analyzed.'))
self.update_review_counter_text() self.update_review_counter_text()
self.review_page.setSubTitle(translate('Wizard', 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): def update_review_counter_text(self):
""" """
Set the wizard review page header text. Set the wizard review page header text.
""" """
self.review_page.setTitle(translate('Wizard', 'Review duplicate songs (%s/%s)') % \ self.review_page.setTitle(
(self.review_current_count, self.review_total_count)) translate('Wizard', 'Review duplicate songs (%s/%s)') %
(self.review_current_count, self.review_total_count))
def custom_page_changed(self, page_id): def custom_page_changed(self, page_id):
""" """
Called when changing the wizard page. Called when changing the wizard page.
``page_id`` :param page_id: ID of the page the wizard changed to.
ID of the page the wizard changed to.
""" """
# Hide back button. # Hide back button.
self.button(QtGui.QWizard.BackButton).hide() self.button(QtGui.QWizard.BackButton).hide()
@ -172,11 +170,11 @@ class DuplicateSongRemovalForm(OpenLPWizard):
for outer_song_counter in range(max_songs - 1): for outer_song_counter in range(max_songs - 1):
for inner_song_counter in range(outer_song_counter + 1, max_songs): for inner_song_counter in range(outer_song_counter + 1, max_songs):
if songs_probably_equal(songs[outer_song_counter], songs[inner_song_counter]): if songs_probably_equal(songs[outer_song_counter], songs[inner_song_counter]):
duplicate_added = self.add_duplicates_to_song_list(songs[outer_song_counter], duplicate_added = self.add_duplicates_to_song_list(
songs[inner_song_counter]) songs[outer_song_counter], songs[inner_song_counter])
if duplicate_added: if duplicate_added:
self.found_duplicates_edit.appendPlainText(songs[outer_song_counter].title + " = " + self.found_duplicates_edit.appendPlainText(
songs[inner_song_counter].title) songs[outer_song_counter].title + " = " + songs[inner_song_counter].title)
self.duplicate_search_progress_bar.setValue(self.duplicate_search_progress_bar.value() + 1) self.duplicate_search_progress_bar.setValue(self.duplicate_search_progress_bar.value() + 1)
# The call to process_events() will keep the GUI responsive. # The call to process_events() will keep the GUI responsive.
self.application.process_events() self.application.process_events()
@ -200,23 +198,20 @@ class DuplicateSongRemovalForm(OpenLPWizard):
self.button(QtGui.QWizard.FinishButton).setEnabled(True) self.button(QtGui.QWizard.FinishButton).setEnabled(True)
self.button(QtGui.QWizard.NextButton).hide() self.button(QtGui.QWizard.NextButton).hide()
self.button(QtGui.QWizard.CancelButton).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.'), translate('Wizard', 'No duplicate songs have been found in the database.'),
QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Ok)) QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Ok))
def add_duplicates_to_song_list(self, search_song, duplicate_song): def add_duplicates_to_song_list(self, search_song, duplicate_song):
""" """
Inserts a song duplicate (two similar songs) to the duplicate song list. 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, If one of the two songs is already part of the duplicate song list, don't add another duplicate group but
don't add another duplicate group but add the other song to that group. add the other song to that group.
Returns True if at least one of the songs was added, False if both were already Returns True if at least one of the songs was added, False if both were already member of a group.
member of a group.
``search_song`` :param search_song: The song we searched the duplicate for.
The song we searched the duplicate for. :param duplicate_song: The duplicate song.
``duplicate_song``
The duplicate song.
""" """
duplicate_group_found = False duplicate_group_found = False
duplicate_added = False duplicate_added = False
@ -259,8 +254,8 @@ class DuplicateSongRemovalForm(OpenLPWizard):
def validateCurrentPage(self): def validateCurrentPage(self):
""" """
Controls whether we should switch to the next wizard page. This method loops Controls whether we should switch to the next wizard page. This method loops on the review page as long as
on the review page as long as there are more song duplicates to review. there are more song duplicates to review.
""" """
if self.currentId() == self.review_page_id: if self.currentId() == self.review_page_id:
# As long as it's not the last duplicate list entry we revisit the review page. # 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): def remove_button_clicked(self, song_review_widget):
""" """
Removes a song from the database, removes the GUI element representing the Removes a song from the database, removes the GUI element representing the song on the review page, and
song on the review page, and disable the remove button if only one duplicate disable the remove button if only one duplicate is left.
is left.
``song_review_widget`` :param song_review_widget: The SongReviewWidget whose song we should delete.
The SongReviewWidget whose song we should delete.
""" """
# Remove song from duplicate song list. # Remove song from duplicate song list.
self.duplicate_song_list[-1].remove(song_review_widget.song) self.duplicate_song_list[-1].remove(song_review_widget.song)
@ -315,9 +308,8 @@ class DuplicateSongRemovalForm(OpenLPWizard):
def process_current_duplicate_entry(self): def process_current_duplicate_entry(self):
""" """
Update the review counter in the wizard header, add song widgets for Update the review counter in the wizard header, add song widgets for the current duplicate group to review,
the current duplicate group to review, if it's the last if it's the last duplicate song group, hide the "next" button and show the "finish" button.
duplicate song group, hide the "next" button and show the "finish" button.
""" """
# Update the counter. # Update the counter.
self.review_current_count = self.review_total_count - (len(self.duplicate_song_list) - 1) self.review_current_count = self.review_total_count - (len(self.duplicate_song_list) - 1)

View File

@ -154,8 +154,8 @@ class Ui_EditSongDialog(object):
self.topics_layout.setObjectName('topics_layout') self.topics_layout.setObjectName('topics_layout')
self.topic_add_layout = QtGui.QHBoxLayout() self.topic_add_layout = QtGui.QHBoxLayout()
self.topic_add_layout.setObjectName('topic_add_layout') self.topic_add_layout.setObjectName('topic_add_layout')
self.topicsComboBox = create_combo_box(self.topics_group_box, 'topicsComboBox') self.topics_combo_box = create_combo_box(self.topics_group_box, 'topics_combo_box')
self.topic_add_layout.addWidget(self.topicsComboBox) self.topic_add_layout.addWidget(self.topics_combo_box)
self.topic_add_button = QtGui.QPushButton(self.topics_group_box) self.topic_add_button = QtGui.QPushButton(self.topics_group_box)
self.topic_add_button.setObjectName('topic_add_button') self.topic_add_button.setObjectName('topic_add_button')
self.topic_add_layout.addWidget(self.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_edit_all_button.setText(translate('SongsPlugin.EditSongForm', 'Ed&it All'))
self.verse_delete_button.setText(UiStrings().Delete) self.verse_delete_button.setText(UiStrings().Delete)
self.song_tab_widget.setTabText(self.song_tab_widget.indexOf(self.lyrics_tab), 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.authors_group_box.setTitle(SongStrings.Authors)
self.author_add_button.setText(translate('SongsPlugin.EditSongForm', '&Add to Song')) self.author_add_button.setText(translate('SongsPlugin.EditSongForm', '&Add to Song'))
self.author_remove_button.setText(translate('SongsPlugin.EditSongForm', '&Remove')) 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_name_label.setText(translate('SongsPlugin.EditSongForm', 'Book:'))
self.song_book_number_label.setText(translate('SongsPlugin.EditSongForm', 'Number:')) self.song_book_number_label.setText(translate('SongsPlugin.EditSongForm', 'Number:'))
self.song_tab_widget.setTabText(self.song_tab_widget.indexOf(self.authors_tab), 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_group_box.setTitle(UiStrings().Theme)
self.theme_add_button.setText(translate('SongsPlugin.EditSongForm', 'New &Theme')) self.theme_add_button.setText(translate('SongsPlugin.EditSongForm', 'New &Theme'))
self.rights_group_box.setTitle(translate('SongsPlugin.EditSongForm', 'Copyright Information')) 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.ccli_label.setText(UiStrings().CCLINumberLabel)
self.comments_group_box.setTitle(translate('SongsPlugin.EditSongForm', 'Comments')) self.comments_group_box.setTitle(translate('SongsPlugin.EditSongForm', 'Comments'))
self.song_tab_widget.setTabText(self.song_tab_widget.indexOf(self.theme_tab), 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), 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_file_button.setText(translate('SongsPlugin.EditSongForm', 'Add &File(s)'))
self.from_media_button.setText(translate('SongsPlugin.EditSongForm', 'Add &Media')) self.from_media_button.setText(translate('SongsPlugin.EditSongForm', 'Add &Media'))
self.audio_remove_button.setText(translate('SongsPlugin.EditSongForm', '&Remove')) self.audio_remove_button.setText(translate('SongsPlugin.EditSongForm', '&Remove'))

View File

@ -131,6 +131,12 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
self.authors_list_view.addItem(author_item) self.authors_list_view.addItem(author_item)
def _extract_verse_order(self, verse_order): def _extract_verse_order(self, verse_order):
"""
Split out the verse order
:param verse_order: The starting verse order
:return: revised order
"""
order = [] order = []
order_names = str(verse_order).split() order_names = str(verse_order).split()
for item in order_names: for item in order_names:
@ -153,6 +159,13 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
return order return order
def _validate_verse_list(self, verse_order, verse_count): 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 = [] verses = []
invalid_verses = [] invalid_verses = []
verse_names = [] verse_names = []
@ -171,12 +184,12 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
valid = create_separated_list(verse_names) valid = create_separated_list(verse_names)
if len(invalid_verses) > 1: if len(invalid_verses) > 1:
msg = translate('SongsPlugin.EditSongForm', 'There are no verses corresponding to "%(invalid)s".' msg = translate('SongsPlugin.EditSongForm', 'There are no verses corresponding to "%(invalid)s".'
'Valid entries are %(valid)s.\nPlease enter the verses seperated by spaces.') \ 'Valid entries are %(valid)s.\nPlease enter the verses separated by spaces.') % \
% {'invalid' : ', '.join(invalid_verses), 'valid' : valid} {'invalid': ', '.join(invalid_verses), 'valid' : valid}
else: else:
msg = translate('SongsPlugin.EditSongForm', 'There is no verse corresponding to "%(invalid)s".' msg = translate('SongsPlugin.EditSongForm', 'There is no verse corresponding to "%(invalid)s".'
'Valid entries are %(valid)s.\nPlease enter the verses seperated by spaces.') \ 'Valid entries are %(valid)s.\nPlease enter the verses separated by spaces.') % \
% {'invalid' : invalid_verses[0], 'valid' : valid} {'invalid': invalid_verses[0], 'valid' : valid}
critical_error_message_box(title=translate('SongsPlugin.EditSongForm', 'Invalid Verse Order'), critical_error_message_box(title=translate('SongsPlugin.EditSongForm', 'Invalid Verse Order'),
message=msg) message=msg)
return len(invalid_verses) == 0 return len(invalid_verses) == 0
@ -213,7 +226,8 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
return False return False
text = self.song_book_combo_box.currentText() text = self.song_book_combo_box.currentText()
if self.song_book_combo_box.findText(text, QtCore.Qt.MatchExactly) < 0: 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?'), 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: QtGui.QMessageBox.Yes | QtGui.QMessageBox.No, QtGui.QMessageBox.Yes) == QtGui.QMessageBox.Yes:
book = Book.populate(name=text, publisher='') book = Book.populate(name=text, publisher='')
@ -250,17 +264,16 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
def keyPressEvent(self, event): 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. dialog's default action be triggered but instead our own.
``event`` :param event: A QtGui.QKeyEvent event.
A QtGui.QKeyEvent event.
""" """
if event.key() in (QtCore.Qt.Key_Enter, QtCore.Qt.Key_Return): if event.key() in (QtCore.Qt.Key_Enter, QtCore.Qt.Key_Return):
if self.authors_combo_box.hasFocus() and self.authors_combo_box.currentText(): if self.authors_combo_box.hasFocus() and self.authors_combo_box.currentText():
self.on_author_add_button_clicked() self.on_author_add_button_clicked()
return 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() self.on_topic_add_button_clicked()
return return
QtGui.QDialog.keyPressEvent(self, event) QtGui.QDialog.keyPressEvent(self, event)
@ -294,7 +307,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
Load the topics into the combobox. Load the topics into the combobox.
""" """
self.topics = [] self.topics = []
self._load_objects(Topic, self.topicsComboBox, self.topics) self._load_objects(Topic, self.topics_combo_box, self.topics)
def load_books(self): def load_books(self):
""" """
@ -321,7 +334,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
for plugin in self.plugin_manager.plugins: for plugin in self.plugin_manager.plugins:
if plugin.name == 'media' and plugin.status == PluginStatus.Active: if plugin.name == 'media' and plugin.status == PluginStatus.Active:
self.from_media_button.setVisible(True) 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 break
def new_song(self): def new_song(self):
@ -358,11 +371,8 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
""" """
Loads a song. Loads a song.
``song_id`` :param song_id: The song id (int).
The song id (int). :param preview: Should be ``True`` if the song is also previewed (boolean).
``preview``
Should be ``True`` if the song is also previewed (boolean).
""" """
log.debug('Load Song') log.debug('Load Song')
self.initialise() self.initialise()
@ -396,8 +406,8 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
self.verse_list_widget.setRowCount(0) self.verse_list_widget.setRowCount(0)
verse_tags_translated = False verse_tags_translated = False
if self.song.lyrics.startswith('<?xml version='): if self.song.lyrics.startswith('<?xml version='):
songXML = SongXML() song_xml = SongXML()
verse_list = songXML.get_verses(self.song.lyrics) verse_list = song_xml.get_verses(self.song.lyrics)
for count, verse in enumerate(verse_list): for count, verse in enumerate(verse_list):
self.verse_list_widget.setRowCount(self.verse_list_widget.rowCount() + 1) self.verse_list_widget.setRowCount(self.verse_list_widget.rowCount() + 1)
# This silently migrates from localized verse type markup. # This silently migrates from localized verse type markup.
@ -487,20 +497,21 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
item = int(self.authors_combo_box.currentIndex()) item = int(self.authors_combo_box.currentIndex())
text = self.authors_combo_box.currentText().strip(' \r\n\t') text = self.authors_combo_box.currentText().strip(' \r\n\t')
# This if statement is for OS X, which doesn't seem to work well with # This if statement is for OS X, which doesn't seem to work well with
# the QCompleter autocompletion class. See bug #812628. # the QCompleter auto-completion class. See bug #812628.
if text in self.authors: if text in self.authors:
# Index 0 is a blank string, so add 1 # Index 0 is a blank string, so add 1
item = self.authors.index(text) + 1 item = self.authors.index(text) + 1
if item == 0 and text: if item == 0 and text:
if QtGui.QMessageBox.question(self, if QtGui.QMessageBox.question(
translate('SongsPlugin.EditSongForm', 'Add Author'), self,
translate('SongsPlugin.EditSongForm', 'This author does not exist, do you want to add them?'), translate('SongsPlugin.EditSongForm', 'Add Author'),
QtGui.QMessageBox.Yes | QtGui.QMessageBox.No, QtGui.QMessageBox.Yes) == QtGui.QMessageBox.Yes: translate('SongsPlugin.EditSongForm', 'This author does not exist, do you want to add them?'),
QtGui.QMessageBox.Yes | QtGui.QMessageBox.No, QtGui.QMessageBox.Yes) == QtGui.QMessageBox.Yes:
if text.find(' ') == -1: if text.find(' ') == -1:
author = Author.populate(first_name='', last_name='', display_name=text) author = Author.populate(first_name='', last_name='', display_name=text)
else: else:
author = Author.populate(first_name=text.rsplit(' ', 1)[0], author = Author.populate(first_name=text.rsplit(' ', 1)[0], last_name=text.rsplit(' ', 1)[1],
last_name=text.rsplit(' ', 1)[1], display_name=text) display_name=text)
self.manager.save_object(author) self.manager.save_object(author)
self._add_author_to_list(author) self._add_author_to_list(author)
self.load_authors() self.load_authors()
@ -510,18 +521,18 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
elif item > 0: elif item > 0:
item_id = (self.authors_combo_box.itemData(item)) item_id = (self.authors_combo_box.itemData(item))
author = self.manager.get_object(Author, item_id) author = self.manager.get_object(Author, item_id)
if self.authors_list_view.findItems(str(author.display_name), if self.authors_list_view.findItems(str(author.display_name), QtCore.Qt.MatchExactly):
QtCore.Qt.MatchExactly):
critical_error_message_box( critical_error_message_box(
message=translate('SongsPlugin.EditSongForm', 'This author is already in the list.')) message=translate('SongsPlugin.EditSongForm', 'This author is already in the list.'))
else: else:
self._add_author_to_list(author) self._add_author_to_list(author)
self.authors_combo_box.setCurrentIndex(0) self.authors_combo_box.setCurrentIndex(0)
else: 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 ' 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 ' 'from the list, or type in a new author and click the "Add Author to Song" button to add '
'the new author.')) 'the new author.'))
def on_authors_list_view_clicked(self): def on_authors_list_view_clicked(self):
""" """
@ -540,10 +551,11 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
self.authors_list_view.takeItem(row) self.authors_list_view.takeItem(row)
def on_topic_add_button_clicked(self): def on_topic_add_button_clicked(self):
item = int(self.topicsComboBox.currentIndex()) item = int(self.topics_combo_box.currentIndex())
text = self.topicsComboBox.currentText() text = self.topics_combo_box.currentText()
if item == 0 and text: 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?'), 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: QtGui.QMessageBox.Yes | QtGui.QMessageBox.No, QtGui.QMessageBox.Yes) == QtGui.QMessageBox.Yes:
topic = Topic.populate(name=text) topic = Topic.populate(name=text)
@ -552,25 +564,26 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
topic_item.setData(QtCore.Qt.UserRole, topic.id) topic_item.setData(QtCore.Qt.UserRole, topic.id)
self.topics_list_view.addItem(topic_item) self.topics_list_view.addItem(topic_item)
self.load_topics() self.load_topics()
self.topicsComboBox.setCurrentIndex(0) self.topics_combo_box.setCurrentIndex(0)
else: else:
return return
elif item > 0: 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) topic = self.manager.get_object(Topic, item_id)
if self.topics_list_view.findItems(str(topic.name), if self.topics_list_view.findItems(str(topic.name), QtCore.Qt.MatchExactly):
QtCore.Qt.MatchExactly):
critical_error_message_box( critical_error_message_box(
message=translate('SongsPlugin.EditSongForm', 'This topic is already in the list.')) message=translate('SongsPlugin.EditSongForm', 'This topic is already in the list.'))
else: else:
topic_item = QtGui.QListWidgetItem(str(topic.name)) topic_item = QtGui.QListWidgetItem(str(topic.name))
topic_item.setData(QtCore.Qt.UserRole, topic.id) topic_item.setData(QtCore.Qt.UserRole, topic.id)
self.topics_list_view.addItem(topic_item) self.topics_list_view.addItem(topic_item)
self.topicsComboBox.setCurrentIndex(0) self.topics_combo_box.setCurrentIndex(0)
else: 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 ' 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): def on_topic_list_view_clicked(self):
self.topic_remove_button.setEnabled(True) self.topic_remove_button.setEnabled(True)
@ -588,7 +601,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
def on_verse_add_button_clicked(self): def on_verse_add_button_clicked(self):
self.verse_form.set_verse('', True) self.verse_form.set_verse('', True)
if self.verse_form.exec_(): 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) verse_def = '%s%s' % (verse_tag, verse_num)
item = QtGui.QTableWidgetItem(after_text) item = QtGui.QTableWidgetItem(after_text)
item.setData(QtCore.Qt.UserRole, verse_def) item.setData(QtCore.Qt.UserRole, verse_def)
@ -606,7 +619,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
verse_id = item.data(QtCore.Qt.UserRole) verse_id = item.data(QtCore.Qt.UserRole)
self.verse_form.set_verse(temp_text, True, verse_id) self.verse_form.set_verse(temp_text, True, verse_id)
if self.verse_form.exec_(): 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) verse_def = '%s%s' % (verse_tag, verse_num)
item.setData(QtCore.Qt.UserRole, verse_def) item.setData(QtCore.Qt.UserRole, verse_def)
item.setText(after_text) 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()) self.on_verse_order_text_changed(self.verse_order_edit.text())
def on_verse_edit_all_button_clicked(self): def on_verse_edit_all_button_clicked(self):
"""
Verse edit all button (save) pressed
:return:
"""
verse_list = '' verse_list = ''
if self.verse_list_widget.rowCount() > 0: if self.verse_list_widget.rowCount() > 0:
for row in range(self.verse_list_widget.rowCount()): for row in range(self.verse_list_widget.rowCount()):
@ -643,7 +661,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
self.verse_form.set_verse('') self.verse_form.set_verse('')
if not self.verse_form.exec_(): if not self.verse_form.exec_():
return 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')) verse_list = str(verse_list.replace('\r\n', '\n'))
self.verse_list_widget.clear() self.verse_list_widget.clear()
self.verse_list_widget.setRowCount(0) 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()) self.on_verse_order_text_changed(self.verse_order_edit.text())
def on_verse_delete_button_clicked(self): def on_verse_delete_button_clicked(self):
"""
Verse Delete button pressed
"""
self.verse_list_widget.removeRow(self.verse_list_widget.currentRow()) self.verse_list_widget.removeRow(self.verse_list_widget.currentRow())
if not self.verse_list_widget.selectedItems(): if not self.verse_list_widget.selectedItems():
self.verse_edit_button.setEnabled(False) 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 Checks if the verse order is complete or missing. Shows a error message according to the state of the verse
order. order.
``text`` :param text: The text of the verse order edit (ignored).
The text of the verse order edit (ignored).
""" """
# Extract all verses which were used in the order. # Extract all verses which were used in the order.
verses_in_order = self._extract_verse_order(self.verse_order_edit.text()) 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) self.warning_label.setText(label_text)
def on_copyright_insert_button_triggered(self): def on_copyright_insert_button_triggered(self):
"""
Copyright insert button pressed
"""
text = self.copyright_edit.text() text = self.copyright_edit.text()
pos = self.copyright_edit.cursorPosition() pos = self.copyright_edit.cursorPosition()
sign = SongStrings.CopyrightSymbol sign = SongStrings.CopyrightSymbol
@ -728,6 +752,9 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
self.copyright_edit.setCursorPosition(pos + len(sign)) self.copyright_edit.setCursorPosition(pos + len(sign))
def on_maintenance_button_clicked(self): def on_maintenance_button_clicked(self):
"""
Maintenance button pressed
"""
temp_song_book = None temp_song_book = None
item = int(self.song_book_combo_box.currentIndex()) item = int(self.song_book_combo_box.currentIndex())
text = self.song_book_combo_box.currentText() text = self.song_book_combo_box.currentText()
@ -745,8 +772,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
Save and Preview button clicked. Save and Preview button clicked.
The Song is valid so as the plugin to add it to preview to see. The Song is valid so as the plugin to add it to preview to see.
``button`` :param button: A button (QPushButton).
A button (QPushButton).
""" """
log.debug('onPreview') log.debug('onPreview')
if button.objectName() == 'preview_button': if button.objectName() == 'preview_button':
@ -758,9 +784,9 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
Loads file(s) from the filesystem. Loads file(s) from the filesystem.
""" """
filters = '%s (*)' % UiStrings().AllFiles filters = '%s (*)' % UiStrings().AllFiles
filenames = FileDialog.getOpenFileNames(self, file_names = FileDialog.getOpenFileNames(self, translate('SongsPlugin.EditSongForm', 'Open File(s)'), '',
translate('SongsPlugin.EditSongForm', 'Open File(s)'), '', filters) filters)
for filename in filenames: for filename in file_names:
item = QtGui.QListWidgetItem(os.path.split(str(filename))[1]) item = QtGui.QListWidgetItem(os.path.split(str(filename))[1])
item.setData(QtCore.Qt.UserRole, filename) item.setData(QtCore.Qt.UserRole, filename)
self.audio_list_widget.addItem(item) self.audio_list_widget.addItem(item)
@ -770,7 +796,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
Loads file(s) from the media plugin. Loads file(s) from the media plugin.
""" """
if self.media_form.exec_(): 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 = QtGui.QListWidgetItem(os.path.split(str(filename))[1])
item.setData(QtCore.Qt.UserRole, filename) item.setData(QtCore.Qt.UserRole, filename)
self.audio_list_widget.addItem(item) self.audio_list_widget.addItem(item)
@ -814,7 +840,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
def clear_caches(self): def clear_caches(self):
""" """
Free up autocompletion memory on dialog exit Free up auto-completion memory on dialog exit
""" """
log.debug('SongEditForm.clearCaches') log.debug('SongEditForm.clearCaches')
self.authors = [] self.authors = []
@ -826,7 +852,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
""" """
Exit Dialog and do not save Exit Dialog and do not save
""" """
log.debug ('SongEditForm.reject') log.debug('SongEditForm.reject')
self.clear_caches() self.clear_caches()
QtGui.QDialog.reject(self) QtGui.QDialog.reject(self)
@ -843,13 +869,10 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
def save_song(self, preview=False): def save_song(self, preview=False):
""" """
Get all the data from the widgets on the form, and then save it to the Get all the data from the widgets on the form, and then save it to the database. The form has been validated
database. The form has been validated and all reference items and all reference items (Authors, Books and Topics) have been saved before this function is called.
(Authors, Books and Topics) have been saved before this function is
called.
``preview`` :param preview: Should be ``True`` if the song is also previewed (boolean).
Should be ``True`` if the song is also previewed (boolean).
""" """
# The Song() assignment. No database calls should be made while a # The Song() assignment. No database calls should be made while a
# Song() is in a partially complete state. # Song() is in a partially complete state.
@ -863,9 +886,9 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
self.song.search_lyrics = '' self.song.search_lyrics = ''
self.song.verse_order = '' self.song.verse_order = ''
self.song.comments = self.comments_edit.toPlainText() self.song.comments = self.comments_edit.toPlainText()
ordertext = self.verse_order_edit.text() order_text = self.verse_order_edit.text()
order = [] order = []
for item in ordertext.split(): for item in order_text.split():
verse_tag = VerseType.tags[VerseType.from_translated_tag(item[0])] verse_tag = VerseType.tags[VerseType.from_translated_tag(item[0])]
verse_num = item[1:].lower() verse_num = item[1:].lower()
order.append('%s%s' % (verse_tag, verse_num)) 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() self.song.song_number = self.song_book_number_edit.text()
book_name = self.song_book_combo_box.currentText() book_name = self.song_book_combo_box.currentText()
if book_name: if book_name:
self.song.book = self.manager.get_object_filtered(Book, self.song.book = self.manager.get_object_filtered(Book, Book.name == book_name)
Book.name == book_name)
else: else:
self.song.book = None self.song.book = None
theme_name = self.theme_combo_box.currentText() theme_name = self.theme_combo_box.currentText()
@ -887,15 +909,15 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
self.song.authors = [] self.song.authors = []
for row in range(self.authors_list_view.count()): for row in range(self.authors_list_view.count()):
item = self.authors_list_view.item(row) item = self.authors_list_view.item(row)
authorId = (item.data(QtCore.Qt.UserRole)) author_id = (item.data(QtCore.Qt.UserRole))
author = self.manager.get_object(Author, authorId) author = self.manager.get_object(Author, author_id)
if author is not None: if author is not None:
self.song.authors.append(author) self.song.authors.append(author)
self.song.topics = [] self.song.topics = []
for row in range(self.topics_list_view.count()): for row in range(self.topics_list_view.count()):
item = self.topics_list_view.item(row) item = self.topics_list_view.item(row)
topicId = (item.data(QtCore.Qt.UserRole)) topic_id = (item.data(QtCore.Qt.UserRole))
topic = self.manager.get_object(Topic, topicId) topic = self.manager.get_object(Topic, topic_id)
if topic is not None: if topic is not None:
self.song.topics.append(topic) self.song.topics.append(topic)
# Save the song here because we need a valid id for the audio files. # 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] audio_files = [a.file_name for a in self.song.media_files]
log.debug(audio_files) log.debug(audio_files)
save_path = os.path.join(AppLocation.get_section_data_path(self.media_item.plugin.name), 'audio', 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) check_directory_exists(save_path)
self.song.media_files = [] self.song.media_files = []
files = [] files = []
@ -912,8 +934,8 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
item = self.audio_list_widget.item(row) item = self.audio_list_widget.item(row)
filename = item.data(QtCore.Qt.UserRole) filename = item.data(QtCore.Qt.UserRole)
if not filename.startswith(save_path): if not filename.startswith(save_path):
oldfile, filename = filename, os.path.join(save_path, os.path.split(filename)[1]) old_file, filename = filename, os.path.join(save_path, os.path.split(filename)[1])
shutil.copyfile(oldfile, filename) shutil.copyfile(old_file, filename)
files.append(filename) files.append(filename)
media_file = MediaFile() media_file = MediaFile()
media_file.file_name = filename media_file.file_name = filename

View File

@ -86,4 +86,4 @@ class Ui_EditVerseDialog(object):
self.split_button.setToolTip(UiStrings().SplitToolTip) self.split_button.setToolTip(UiStrings().SplitToolTip)
self.insert_button.setText(translate('SongsPlugin.EditVerseForm', '&Insert')) self.insert_button.setText(translate('SongsPlugin.EditVerseForm', '&Insert'))
self.insert_button.setToolTip(translate('SongsPlugin.EditVerseForm', 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.'))

View File

@ -50,16 +50,18 @@ class EditVerseForm(QtGui.QDialog, Ui_EditVerseDialog):
""" """
super(EditVerseForm, self).__init__(parent) super(EditVerseForm, self).__init__(parent)
self.setupUi(self) self.setupUi(self)
self.verse_text_edit.customContextMenuRequested.connect(self.context_menu)
self.insert_button.clicked.connect(self.on_insert_button_clicked) self.insert_button.clicked.connect(self.on_insert_button_clicked)
self.split_button.clicked.connect(self.on_split_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_text_edit.cursorPositionChanged.connect(self.on_cursor_position_changed)
self.verse_type_combo_box.currentIndexChanged.connect(self.on_verse_type_combo_box_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): 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: if self.verse_text_edit.textCursor().columnNumber() != 0:
self.verse_text_edit.insertPlainText('\n') self.verse_text_edit.insertPlainText('\n')
verse_tag = VerseType.translated_name(verse_tag) verse_tag = VerseType.translated_name(verse_tag)
@ -67,24 +69,36 @@ class EditVerseForm(QtGui.QDialog, Ui_EditVerseDialog):
self.verse_text_edit.setFocus() self.verse_text_edit.setFocus()
def on_split_button_clicked(self): def on_split_button_clicked(self):
"""
The split button has been pressed
"""
text = self.verse_text_edit.toPlainText() text = self.verse_text_edit.toPlainText()
position = self.verse_text_edit.textCursor().position() position = self.verse_text_edit.textCursor().position()
insert_string = '[---]' insert_string = '[---]'
if position and text[position-1] != '\n': if position and text[position-1] != '\n':
insert_string = '\n' + insert_string insert_string = '\n' + insert_string
if position == len(text) or text[position] != '\n': if position == len(text) or text[position] != '\n':
insert_string += '\n' insert_string += '\n'
self.verse_text_edit.insertPlainText(insert_string) self.verse_text_edit.insertPlainText(insert_string)
self.verse_text_edit.setFocus() self.verse_text_edit.setFocus()
def on_insert_button_clicked(self): def on_insert_button_clicked(self):
"""
The insert button has been pressed
"""
verse_type_index = self.verse_type_combo_box.currentIndex() verse_type_index = self.verse_type_combo_box.currentIndex()
self.insert_verse(VerseType.tags[verse_type_index], self.verse_number_box.value()) self.insert_verse(VerseType.tags[verse_type_index], self.verse_number_box.value())
def on_verse_type_combo_box_changed(self): def on_verse_type_combo_box_changed(self):
"""
The verse type combo has been changed
"""
self.update_suggested_verse_number() self.update_suggested_verse_number()
def on_cursor_position_changed(self): def on_cursor_position_changed(self):
"""
The cursor position has been changed
"""
self.update_suggested_verse_number() self.update_suggested_verse_number()
def update_suggested_verse_number(self): def update_suggested_verse_number(self):
@ -117,6 +131,13 @@ class EditVerseForm(QtGui.QDialog, Ui_EditVerseDialog):
self.verse_number_box.setValue(verse_num) self.verse_number_box.setValue(verse_num)
def set_verse(self, text, single=False, tag='%s1' % VerseType.tags[VerseType.Verse]): 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 self.has_single_verse = single
if single: if single:
verse_type_index = VerseType.from_tag(tag[0], None) 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) self.verse_text_edit.moveCursor(QtGui.QTextCursor.End)
def get_verse(self): 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()], \ return self.verse_text_edit.toPlainText(), VerseType.tags[self.verse_type_combo_box.currentIndex()], \
str(self.verse_number_box.value()) str(self.verse_number_box.value())
def get_all_verses(self): def get_all_verses(self):
"""
Extract all the verses
:return: The text
"""
text = self.verse_text_edit.toPlainText() text = self.verse_text_edit.toPlainText()
if not text.startswith('---['): if not text.startswith('---['):
text = '---[%s:1]---\n%s' % (VerseType.translated_names[VerseType.Verse], text) text = '---[%s:1]---\n%s' % (VerseType.translated_names[VerseType.Verse], text)

View File

@ -69,5 +69,6 @@ class Ui_MediaFilesDialog(object):
""" """
media_files_dialog.setWindowTitle(translate('SongsPlugin.MediaFilesForm', 'Select Media File(s)')) media_files_dialog.setWindowTitle(translate('SongsPlugin.MediaFilesForm', 'Select Media File(s)'))
self.select_label.setText(translate('SongsPlugin.MediaFilesForm', 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.'))

View File

@ -47,13 +47,13 @@ class MediaFilesForm(QtGui.QDialog, Ui_MediaFilesDialog):
super(MediaFilesForm, self).__init__() super(MediaFilesForm, self).__init__()
self.setupUi(self) self.setupUi(self)
def populateFiles(self, files): def populate_files(self, files):
self.file_list_widget.clear() self.file_list_widget.clear()
for file in files: for file in files:
item = QtGui.QListWidgetItem(os.path.split(file)[1]) item = QtGui.QListWidgetItem(os.path.split(file)[1])
item.setData(QtCore.Qt.UserRole, file) item.setData(QtCore.Qt.UserRole, file)
self.file_list_widget.addItem(item) 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()] return [item.data(QtCore.Qt.UserRole) for item in self.file_list_widget.selectedItems()]

View File

@ -52,8 +52,7 @@ class SongBookForm(QtGui.QDialog, Ui_SongBookDialog):
""" """
Execute the song book form. Execute the song book form.
``clear`` :param clear: Clear the fields on the form before displaying it.
Clear the fields on the form before displaying it.
""" """
if clear: if clear:
self.name_edit.clear() self.name_edit.clear()

View File

@ -55,11 +55,8 @@ class SongExportForm(OpenLPWizard):
""" """
Instantiate the wizard, and run any extra setup we need to. Instantiate the wizard, and run any extra setup we need to.
``parent`` :param parent: The QWidget-derived parent of the wizard.
The QWidget-derived parent of the wizard. :param plugin: The songs plugin.
``plugin``
The songs plugin.
""" """
super(SongExportForm, self).__init__(parent, plugin, 'song_export_wizard', ':/wizards/wizard_exportsong.bmp') super(SongExportForm, self).__init__(parent, plugin, 'song_export_wizard', ':/wizards/wizard_exportsong.bmp')
self.stop_export_flag = False self.stop_export_flag = False
@ -82,71 +79,71 @@ class SongExportForm(OpenLPWizard):
""" """
Song wizard specific signals. Song wizard specific signals.
""" """
self.availableListWidget.itemActivated.connect(self.onItemActivated) self.available_list_widget.itemActivated.connect(self.on_item_activated)
self.searchLineEdit.textEdited.connect(self.onSearchLineEditChanged) self.search_line_edit.textEdited.connect(self.on_search_line_edit_changed)
self.uncheckButton.clicked.connect(self.onUncheckButtonClicked) self.uncheck_button.clicked.connect(self.on_uncheck_button_clicked)
self.checkButton.clicked.connect(self.onCheckButtonClicked) self.check_button.clicked.connect(self.on_check_button_clicked)
self.directoryButton.clicked.connect(self.onDirectoryButtonClicked) self.directory_button.clicked.connect(self.on_directory_button_clicked)
def add_custom_pages(self): def add_custom_pages(self):
""" """
Add song wizard specific pages. Add song wizard specific pages.
""" """
# The page with all available songs. # The page with all available songs.
self.availableSongsPage = QtGui.QWizardPage() self.available_songs_page = QtGui.QWizardPage()
self.availableSongsPage.setObjectName('availableSongsPage') self.available_songs_page.setObjectName('available_songs_page')
self.availableSongsLayout = QtGui.QHBoxLayout(self.availableSongsPage) self.available_songs_layout = QtGui.QHBoxLayout(self.available_songs_page)
self.availableSongsLayout.setObjectName('availableSongsLayout') self.available_songs_layout.setObjectName('available_songs_layout')
self.verticalLayout = QtGui.QVBoxLayout() self.vertical_layout = QtGui.QVBoxLayout()
self.verticalLayout.setObjectName('verticalLayout') self.vertical_layout.setObjectName('vertical_layout')
self.availableListWidget = QtGui.QListWidget(self.availableSongsPage) self.available_list_widget = QtGui.QListWidget(self.available_songs_page)
self.availableListWidget.setObjectName('availableListWidget') self.available_list_widget.setObjectName('available_list_widget')
self.verticalLayout.addWidget(self.availableListWidget) self.vertical_layout.addWidget(self.available_list_widget)
self.horizontalLayout = QtGui.QHBoxLayout() self.horizontal_layout = QtGui.QHBoxLayout()
self.horizontalLayout.setObjectName('horizontalLayout') self.horizontal_layout.setObjectName('horizontal_layout')
self.searchLabel = QtGui.QLabel(self.availableSongsPage) self.search_label = QtGui.QLabel(self.available_songs_page)
self.searchLabel.setObjectName('searchLabel') self.search_label.setObjectName('search_label')
self.horizontalLayout.addWidget(self.searchLabel) self.horizontal_layout.addWidget(self.search_label)
self.searchLineEdit = QtGui.QLineEdit(self.availableSongsPage) self.search_line_edit = QtGui.QLineEdit(self.available_songs_page)
self.searchLineEdit.setObjectName('searchLineEdit') self.search_line_edit.setObjectName('search_line_edit')
self.horizontalLayout.addWidget(self.searchLineEdit) self.horizontal_layout.addWidget(self.search_line_edit)
spacer_item = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) spacer_item = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
self.horizontalLayout.addItem(spacer_item) self.horizontal_layout.addItem(spacer_item)
self.uncheckButton = QtGui.QPushButton(self.availableSongsPage) self.uncheck_button = QtGui.QPushButton(self.available_songs_page)
self.uncheckButton.setObjectName('uncheckButton') self.uncheck_button.setObjectName('uncheck_button')
self.horizontalLayout.addWidget(self.uncheckButton) self.horizontal_layout.addWidget(self.uncheck_button)
self.checkButton = QtGui.QPushButton(self.availableSongsPage) self.check_button = QtGui.QPushButton(self.available_songs_page)
self.checkButton.setObjectName('selectButton') self.check_button.setObjectName('selectButton')
self.horizontalLayout.addWidget(self.checkButton) self.horizontal_layout.addWidget(self.check_button)
self.verticalLayout.addLayout(self.horizontalLayout) self.vertical_layout.addLayout(self.horizontal_layout)
self.availableSongsLayout.addLayout(self.verticalLayout) self.available_songs_layout.addLayout(self.vertical_layout)
self.addPage(self.availableSongsPage) self.addPage(self.available_songs_page)
# The page with the selected songs. # The page with the selected songs.
self.exportSongPage = QtGui.QWizardPage() self.export_song_page = QtGui.QWizardPage()
self.exportSongPage.setObjectName('availableSongsPage') self.export_song_page.setObjectName('available_songs_page')
self.exportSongLayout = QtGui.QHBoxLayout(self.exportSongPage) self.export_song_layout = QtGui.QHBoxLayout(self.export_song_page)
self.exportSongLayout.setObjectName('exportSongLayout') self.export_song_layout.setObjectName('export_song_layout')
self.gridLayout = QtGui.QGridLayout() self.grid_layout = QtGui.QGridLayout()
self.gridLayout.setObjectName('gridLayout') self.grid_layout.setObjectName('grid_layout')
self.selectedListWidget = QtGui.QListWidget(self.exportSongPage) self.selected_list_widget = QtGui.QListWidget(self.export_song_page)
self.selectedListWidget.setObjectName('selectedListWidget') self.selected_list_widget.setObjectName('selected_list_widget')
self.gridLayout.addWidget(self.selectedListWidget, 1, 0, 1, 1) self.grid_layout.addWidget(self.selected_list_widget, 1, 0, 1, 1)
# FIXME: self.horizontalLayout is already defined above?!?!? # FIXME: self.horizontal_layout is already defined above?!?!?
self.horizontalLayout = QtGui.QHBoxLayout() self.horizontal_layout = QtGui.QHBoxLayout()
self.horizontalLayout.setObjectName('horizontalLayout') self.horizontal_layout.setObjectName('horizontal_layout')
self.directoryLabel = QtGui.QLabel(self.exportSongPage) self.directory_label = QtGui.QLabel(self.export_song_page)
self.directoryLabel.setObjectName('directoryLabel') self.directory_label.setObjectName('directory_label')
self.horizontalLayout.addWidget(self.directoryLabel) self.horizontal_layout.addWidget(self.directory_label)
self.directoryLineEdit = QtGui.QLineEdit(self.exportSongPage) self.directory_line_edit = QtGui.QLineEdit(self.export_song_page)
self.directoryLineEdit.setObjectName('directoryLineEdit') self.directory_line_edit.setObjectName('directory_line_edit')
self.horizontalLayout.addWidget(self.directoryLineEdit) self.horizontal_layout.addWidget(self.directory_line_edit)
self.directoryButton = QtGui.QToolButton(self.exportSongPage) self.directory_button = QtGui.QToolButton(self.export_song_page)
self.directoryButton.setIcon(build_icon(':/exports/export_load.png')) self.directory_button.setIcon(build_icon(':/exports/export_load.png'))
self.directoryButton.setObjectName('directoryButton') self.directory_button.setObjectName('directory_button')
self.horizontalLayout.addWidget(self.directoryButton) self.horizontal_layout.addWidget(self.directory_button)
self.gridLayout.addLayout(self.horizontalLayout, 0, 0, 1, 1) self.grid_layout.addLayout(self.horizontal_layout, 0, 0, 1, 1)
self.exportSongLayout.addLayout(self.gridLayout) self.export_song_layout.addLayout(self.grid_layout)
self.addPage(self.exportSongPage) self.addPage(self.export_song_page)
def retranslateUi(self): def retranslateUi(self):
""" """
@ -154,22 +151,23 @@ class SongExportForm(OpenLPWizard):
""" """
self.setWindowTitle(translate('SongsPlugin.ExportWizardForm', 'Song Export Wizard')) self.setWindowTitle(translate('SongsPlugin.ExportWizardForm', 'Song Export Wizard'))
self.title_label.setText(WizardStrings.HeaderStyle % 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' self.information_label.setText(translate('SongsPlugin.ExportWizardForm', 'This wizard will help to'
' export your songs to the open and free <strong>OpenLyrics </strong> worship song format.')) ' export your songs to the open and free <strong>OpenLyrics </strong> worship '
self.availableSongsPage.setTitle(translate('SongsPlugin.ExportWizardForm', 'Select Songs')) 'song format.'))
self.availableSongsPage.setSubTitle(translate('SongsPlugin.ExportWizardForm', self.available_songs_page.setTitle(translate('SongsPlugin.ExportWizardForm', 'Select Songs'))
'Check the songs you want to export.')) self.available_songs_page.setSubTitle(translate('SongsPlugin.ExportWizardForm',
self.searchLabel.setText('%s:' % UiStrings().Search) 'Check the songs you want to export.'))
self.uncheckButton.setText(translate('SongsPlugin.ExportWizardForm', 'Uncheck All')) self.search_label.setText('%s:' % UiStrings().Search)
self.checkButton.setText(translate('SongsPlugin.ExportWizardForm', 'Check All')) self.uncheck_button.setText(translate('SongsPlugin.ExportWizardForm', 'Uncheck All'))
self.exportSongPage.setTitle(translate('SongsPlugin.ExportWizardForm', 'Select Directory')) self.check_button.setText(translate('SongsPlugin.ExportWizardForm', 'Check All'))
self.exportSongPage.setSubTitle(translate('SongsPlugin.ExportWizardForm', self.export_song_page.setTitle(translate('SongsPlugin.ExportWizardForm', 'Select Directory'))
'Select the directory where you want the songs to be saved.')) self.export_song_page.setSubTitle(translate('SongsPlugin.ExportWizardForm',
self.directoryLabel.setText(translate('SongsPlugin.ExportWizardForm', 'Directory:')) '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.setTitle(translate('SongsPlugin.ExportWizardForm', 'Exporting'))
self.progress_page.setSubTitle(translate('SongsPlugin.ExportWizardForm', 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_label.setText(WizardStrings.Ready)
self.progress_bar.setFormat(WizardStrings.PercentSymbolFormat) self.progress_bar.setFormat(WizardStrings.PercentSymbolFormat)
@ -179,46 +177,46 @@ class SongExportForm(OpenLPWizard):
""" """
if self.currentPage() == self.welcome_page: if self.currentPage() == self.welcome_page:
return True return True
elif self.currentPage() == self.availableSongsPage: elif self.currentPage() == self.available_songs_page:
items = [ items = [
item for item in self._findListWidgetItems( item for item in self._find_list_widget_items(self.available_list_widget) if item.checkState()
self.availableListWidget) if item.checkState()
] ]
if not items: 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.')) translate('SongsPlugin.ExportWizardForm', 'You need to add at least one Song to export.'))
return False return False
self.selectedListWidget.clear() self.selected_list_widget.clear()
# Add the songs to the list of selected songs. # Add the songs to the list of selected songs.
for item in items: for item in items:
song = QtGui.QListWidgetItem(item.text()) song = QtGui.QListWidgetItem(item.text())
song.setData(QtCore.Qt.UserRole, item.data(QtCore.Qt.UserRole)) song.setData(QtCore.Qt.UserRole, item.data(QtCore.Qt.UserRole))
song.setFlags(QtCore.Qt.ItemIsEnabled) song.setFlags(QtCore.Qt.ItemIsEnabled)
self.selectedListWidget.addItem(song) self.selected_list_widget.addItem(song)
return True return True
elif self.currentPage() == self.exportSongPage: elif self.currentPage() == self.export_song_page:
if not self.directoryLineEdit.text(): if not self.directory_line_edit.text():
critical_error_message_box( critical_error_message_box(
translate('SongsPlugin.ExportWizardForm', 'No Save Location specified'), translate('SongsPlugin.ExportWizardForm', 'No Save Location specified'),
translate('SongsPlugin.ExportWizardForm', 'You need to specify a directory.')) translate('SongsPlugin.ExportWizardForm', 'You need to specify a directory.'))
return False return False
return True return True
elif self.currentPage() == self.progress_page: elif self.currentPage() == self.progress_page:
self.availableListWidget.clear() self.available_list_widget.clear()
self.selectedListWidget.clear() self.selected_list_widget.clear()
return True return True
def setDefaults(self): def set_defaults(self):
""" """
Set default form values for the song export wizard. Set default form values for the song export wizard.
""" """
self.restart() self.restart()
self.finish_button.setVisible(False) self.finish_button.setVisible(False)
self.cancel_button.setVisible(True) self.cancel_button.setVisible(True)
self.availableListWidget.clear() self.available_list_widget.clear()
self.selectedListWidget.clear() self.selected_list_widget.clear()
self.directoryLineEdit.clear() self.directory_line_edit.clear()
self.searchLineEdit.clear() self.search_line_edit.clear()
# Load the list of songs. # Load the list of songs.
self.application.set_busy_cursor() self.application.set_busy_cursor()
songs = self.plugin.manager.get_all_objects(Song) songs = self.plugin.manager.get_all_objects(Song)
@ -233,7 +231,7 @@ class SongExportForm(OpenLPWizard):
item.setData(QtCore.Qt.UserRole, song) item.setData(QtCore.Qt.UserRole, song)
item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsUserCheckable | QtCore.Qt.ItemIsEnabled) item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsUserCheckable | QtCore.Qt.ItemIsEnabled)
item.setCheckState(QtCore.Qt.Unchecked) item.setCheckState(QtCore.Qt.Unchecked)
self.availableListWidget.addItem(item) self.available_list_widget.addItem(item)
self.application.set_normal_cursor() self.application.set_normal_cursor()
def pre_wizard(self): def pre_wizard(self):
@ -244,86 +242,81 @@ class SongExportForm(OpenLPWizard):
self.progress_label.setText(translate('SongsPlugin.ExportWizardForm', 'Starting export...')) self.progress_label.setText(translate('SongsPlugin.ExportWizardForm', 'Starting export...'))
self.application.process_events() self.application.process_events()
def performWizard(self): def perform_wizard(self):
""" """
Perform the actual export. This creates an *openlyricsexport* instance Perform the actual export. This creates an *openlyricsexport* instance and calls the *do_export* method.
and calls the *do_export* method.
""" """
songs = [ songs = [
song.data(QtCore.Qt.UserRole) 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(): if exporter.do_export():
self.progress_label.setText(translate('SongsPlugin.SongExportForm', self.progress_label.setText(
'Finished export. To import these files use the <strong>OpenLyrics</strong> importer.')) translate('SongsPlugin.SongExportForm',
'Finished export. To import these files use the <strong>OpenLyrics</strong> importer.'))
else: else:
self.progress_label.setText(translate('SongsPlugin.SongExportForm', 'Your song export failed.')) 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 Returns a list of *QListWidgetItem*s of the ``list_widget``. Note, that hidden items are included.
hidden items are included.
``listWidget`` :param list_widget: The widget to get all items from. (QListWidget)
The widget to get all items from. (QListWidget) :param text: The text to search for. (unicode string)
``text``
The text to search for. (unicode string)
""" """
return [ 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 The item is check if it was not checked, whereas it is unchecked when it
was checked. was checked.
``item`` :param item: The *QListWidgetItem* which was triggered.
The *QListWidgetItem* which was triggered.
""" """
item.setCheckState( item.setCheckState(
QtCore.Qt.Unchecked if item.checkState() else QtCore.Qt.Checked) 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`` available songs. Note that any song, which does not match the ``text``
will be hidden, but not unchecked! will be hidden, but not unchecked!
``text`` :param text: The text of the *search_line_edit*.
The text of the *searchLineEdit*.
""" """
search_result = [ 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) 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()): for row in range(self.available_list_widget.count()):
item = self.availableListWidget.item(row) item = self.available_list_widget.item(row)
if not item.isHidden(): if not item.isHidden():
item.setCheckState(QtCore.Qt.Unchecked) 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()): for row in range(self.available_list_widget.count()):
item = self.availableListWidget.item(row) item = self.available_list_widget.item(row)
if not item.isHidden(): if not item.isHidden():
item.setCheckState(QtCore.Qt.Checked) 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 Called when the *directory_button* was clicked. Opens a dialog and writes
the path to *directoryLineEdit*. the path to *directory_line_edit*.
""" """
self.get_folder(translate('SongsPlugin.ExportWizardForm', 'Select Destination Folder'), self.get_folder(
self.directoryLineEdit, 'last directory export') translate('SongsPlugin.ExportWizardForm', 'Select Destination Folder'),
self.directory_line_edit, 'last directory export')

View File

@ -55,11 +55,8 @@ class SongImportForm(OpenLPWizard):
""" """
Instantiate the wizard, and run any extra setup we need to. Instantiate the wizard, and run any extra setup we need to.
``parent`` :param parent: The QWidget-derived parent of the wizard.
The QWidget-derived parent of the wizard. :param plugin: The songs plugin.
``plugin``
The songs plugin.
""" """
super(SongImportForm, self).__init__(parent, plugin, 'songImportWizard', ':/wizards/wizard_importsong.bmp') super(SongImportForm, self).__init__(parent, plugin, 'songImportWizard', ':/wizards/wizard_importsong.bmp')
self.clipboard = self.main_window.clipboard self.clipboard = self.main_window.clipboard
@ -72,9 +69,9 @@ class SongImportForm(OpenLPWizard):
super(SongImportForm, self).setupUi(image) super(SongImportForm, self).setupUi(image)
self.current_format = SongFormat.OpenLyrics self.current_format = SongFormat.OpenLyrics
self.format_stack.setCurrentIndex(self.current_format) 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. Called when the format combo box's index changed.
""" """
@ -99,10 +96,10 @@ class SongImportForm(OpenLPWizard):
select_mode = SongFormat.get(song_format, 'selectMode') select_mode = SongFormat.get(song_format, 'selectMode')
if select_mode == SongFormatSelect.MultipleFiles: if select_mode == SongFormatSelect.MultipleFiles:
self.format_widgets[song_format]['addButton'].clicked.connect(self.on_add_button_clicked) 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: else:
self.format_widgets[song_format]['browseButton'].clicked.connect(self.on_browse_button_clicked) 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): def add_custom_pages(self):
""" """
@ -131,7 +128,7 @@ class SongImportForm(OpenLPWizard):
self.format_stack.setObjectName('format_stack') self.format_stack.setObjectName('format_stack')
self.disablable_formats = [] self.disablable_formats = []
for self.current_format in SongFormat.get_format_list(): for self.current_format in SongFormat.get_format_list():
self.addFileSelectItem() self.add_file_select_item()
self.source_layout.addLayout(self.format_stack) self.source_layout.addLayout(self.format_stack)
self.addPage(self.source_page) self.addPage(self.source_page)
@ -140,33 +137,35 @@ class SongImportForm(OpenLPWizard):
Song wizard localisation. Song wizard localisation.
""" """
self.setWindowTitle(translate('SongsPlugin.ImportWizardForm', 'Song Import Wizard')) self.setWindowTitle(translate('SongsPlugin.ImportWizardForm', 'Song Import Wizard'))
self.title_label.setText(WizardStrings.HeaderStyle % translate('OpenLP.Ui', 'Welcome to the Song Import Wizard')) self.title_label.setText(WizardStrings.HeaderStyle % translate('OpenLP.Ui',
self.information_label.setText(translate('SongsPlugin.ImportWizardForm', 'Welcome to the Song Import Wizard'))
'This wizard will help you to import songs from a variety of ' self.information_label.setText(
'formats. Click the next button below to start the process by selecting a format to import from.')) 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.setTitle(WizardStrings.ImportSelect)
self.source_page.setSubTitle(WizardStrings.ImportSelectLong) self.source_page.setSubTitle(WizardStrings.ImportSelectLong)
self.format_label.setText(WizardStrings.FormatLabel) 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 = \ 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) 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: 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: if select_mode == SongFormatSelect.MultipleFiles:
self.format_widgets[format]['addButton'].setText( self.format_widgets[format_list]['addButton'].setText(
translate('SongsPlugin.ImportWizardForm', 'Add Files...')) translate('SongsPlugin.ImportWizardForm', 'Add Files...'))
self.format_widgets[format]['removeButton'].setText( self.format_widgets[format_list]['removeButton'].setText(
translate('SongsPlugin.ImportWizardForm', 'Remove File(s)')) translate('SongsPlugin.ImportWizardForm', 'Remove File(s)'))
else: else:
self.format_widgets[format]['browseButton'].setText(UiStrings().Browse) self.format_widgets[format_list]['browseButton'].setText(UiStrings().Browse)
f_label = 'Filename:' f_label = 'Filename:'
if select_mode == SongFormatSelect.SingleFolder: if select_mode == SongFormatSelect.SingleFolder:
f_label = 'Folder:' f_label = 'Folder:'
self.format_widgets[format]['filepathLabel'].setText(translate('SongsPlugin.ImportWizardForm', f_label)) self.format_widgets[format_list]['filepathLabel'].setText(translate('SongsPlugin.ImportWizardForm', f_label))
for format in self.disablable_formats: for format_list in self.disablable_formats:
self.format_widgets[format]['disabled_label'].setText(SongFormat.get(format, 'disabledLabelText')) self.format_widgets[format_list]['disabled_label'].setText(SongFormat.get(format_list, 'disabledLabelText'))
self.progress_page.setTitle(WizardStrings.Importing) self.progress_page.setTitle(WizardStrings.Importing)
self.progress_page.setSubTitle( self.progress_page.setSubTitle(
translate('SongsPlugin.ImportWizardForm', 'Please wait while your songs are imported.')) 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] labels = [self.format_widgets[f]['filepathLabel'] for f in formats]
# Get max width of all labels # Get max width of all labels
max_label_width = max(self.format_label.minimumSizeHint().width(), 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) self.format_spacer.changeSize(max_label_width, 0, QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed)
spacers = [self.format_widgets[f]['filepathSpacer'] for f in formats] spacers = [self.format_widgets[f]['filepathSpacer'] for f in formats]
for index, spacer in enumerate(spacers): for index, spacer in enumerate(spacers):
@ -187,9 +186,9 @@ class SongImportForm(OpenLPWizard):
max_label_width - labels[index].minimumSizeHint().width(), 0, max_label_width - labels[index].minimumSizeHint().width(), 0,
QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed)
# Align descriptionLabels with rest of layout # Align descriptionLabels with rest of layout
for format in SongFormat.get_format_list(): for format_list in SongFormat.get_format_list():
if SongFormat.get(format, 'descriptionText') is not None: if SongFormat.get(format_list, 'descriptionText') is not None:
self.format_widgets[format]['descriptionSpacer'].changeSize( self.format_widgets[format_list]['descriptionSpacer'].changeSize(
max_label_width + self.format_h_spacing, 0, QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) max_label_width + self.format_h_spacing, 0, QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed)
def custom_page_changed(self, page_id): 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. Called when changing to a page other than the progress page.
""" """
if self.page(page_id) == self.source_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): 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(). Provide each song format class with a chance to validate its input by overriding isValidSource().
""" """
if self.currentPage() == self.welcome_page: if self.currentPage() == self.welcome_page:
@ -230,37 +229,36 @@ class SongImportForm(OpenLPWizard):
""" """
Opens a QFileDialog and writes the filenames to the given listbox. Opens a QFileDialog and writes the filenames to the given listbox.
``title`` :param title: The title of the dialog (unicode).
The title of the dialog (unicode). :param listbox: A listbox (QListWidget).
:param filters: The file extension filters. It should contain the file descriptions
``listbox``
A listbox (QListWidget).
``filters``
The file extension filters. It should contain the file descriptions
as well as the file extensions. For example:: as well as the file extensions. For example::
u'SongBeamer Files (*.sng)' u'SongBeamer Files (*.sng)'
""" """
if filters: if filters:
filters += ';;' filters += ';;'
filters += '%s (*)' % UiStrings().AllFiles 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) Settings().value(self.plugin.settings_section + '/last directory import'), filters)
if filenames: if file_names:
listbox.addItems(filenames) listbox.addItems(file_names)
Settings().setValue(self.plugin.settings_section + '/last directory import', 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): def remove_selected_items(self, list_box):
""" """
Remove selected list_box items Remove selected list_box items
:param list_box: the source list box
""" """
for item in list_box.selectedItems(): for item in list_box.selectedItems():
item = list_box.takeItem(list_box.row(item)) 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.get_files(title, self.format_widgets[this_format]['file_list_widget'], ext_filter)
self.source_page.emit(QtCore.SIGNAL('completeChanged()')) self.source_page.emit(QtCore.SIGNAL('completeChanged()'))
def onRemoveButtonClicked(self): def on_remove_button_clicked(self):
""" """
Remove a file from the list. Remove a file from the list.
""" """
self.remove_selected_items(self.format_widgets[self.current_format]['file_list_widget']) self.remove_selected_items(self.format_widgets[self.current_format]['file_list_widget'])
self.source_page.emit(QtCore.SIGNAL('completeChanged()')) 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. 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(): if last_import_type < 0 or last_import_type >= self.format_combo_box.count():
last_import_type = 0 last_import_type = 0
self.format_combo_box.setCurrentIndex(last_import_type) self.format_combo_box.setCurrentIndex(last_import_type)
for format in SongFormat.get_format_list(): for format_list in SongFormat.get_format_list():
select_mode = SongFormat.get(format, 'selectMode') select_mode = SongFormat.get(format_list, 'selectMode')
if select_mode == SongFormatSelect.MultipleFiles: if select_mode == SongFormatSelect.MultipleFiles:
self.format_widgets[format]['file_list_widget'].clear() self.format_widgets[format_list]['file_list_widget'].clear()
else: 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.clear()
self.error_report_text_edit.setHidden(True) self.error_report_text_edit.setHidden(True)
self.error_copy_to_button.setHidden(True) self.error_copy_to_button.setHidden(True)
@ -334,22 +332,22 @@ class SongImportForm(OpenLPWizard):
self.progress_label.setText(WizardStrings.StartingImport) self.progress_label.setText(WizardStrings.StartingImport)
self.application.process_events() self.application.process_events()
def performWizard(self): def perform_wizard(self):
""" """
Perform the actual import. This method pulls in the correct importer Perform the actual import. This method pulls in the correct importer class, and then runs the ``doImport``
class, and then runs the ``doImport`` method of the importer to do method of the importer to do the actual importing.
the actual importing.
""" """
source_format = self.current_format source_format = self.current_format
select_mode = SongFormat.get(source_format, 'selectMode') select_mode = SongFormat.get(source_format, 'selectMode')
if select_mode == SongFormatSelect.SingleFile: if select_mode == SongFormatSelect.SingleFile:
importer = self.plugin.importSongs(source_format, importer = self.plugin.import_songs(source_format,
filename=self.format_widgets[source_format]['file_path_edit'].text()) filename=self.format_widgets[source_format]['file_path_edit'].text())
elif select_mode == SongFormatSelect.SingleFolder: elif select_mode == SongFormatSelect.SingleFolder:
importer = self.plugin.importSongs(source_format, importer = self.plugin.import_songs(source_format,
folder=self.format_widgets[source_format]['file_path_edit'].text()) folder=self.format_widgets[source_format]['file_path_edit'].text())
else: 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'])) filenames=self.get_list_of_files(self.format_widgets[source_format]['file_list_widget']))
importer.doImport() importer.doImport()
self.progress_label.setText(WizardStrings.FinishedImport) self.progress_label.setText(WizardStrings.FinishedImport)
@ -364,15 +362,15 @@ class SongImportForm(OpenLPWizard):
""" """
Save the error report to a file. Save the error report to a file.
""" """
filename = QtGui.QFileDialog.getSaveFileName(self, filename = QtGui.QFileDialog.getSaveFileName(
Settings().value(self.plugin.settings_section + '/last directory import')) self, Settings().value(self.plugin.settings_section + '/last directory import'))
if not filename: if not filename:
return return
report_file = codecs.open(filename, 'w', 'utf-8') report_file = codecs.open(filename, 'w', 'utf-8')
report_file.write(self.error_report_text_edit.toPlainText()) report_file.write(self.error_report_text_edit.toPlainText())
report_file.close() report_file.close()
def addFileSelectItem(self): def add_file_select_item(self):
""" """
Add a file selection page. Add a file selection page.
""" """
@ -382,75 +380,75 @@ class SongImportForm(OpenLPWizard):
page = QtGui.QWidget() page = QtGui.QWidget()
page.setObjectName(prefix + 'Page') page.setObjectName(prefix + 'Page')
if can_disable: if can_disable:
importWidget = self.disablableWidget(page, prefix) import_widget = self.disablable_widget(page, prefix)
else: else:
importWidget = page import_widget = page
importLayout = QtGui.QVBoxLayout(importWidget) import_layout = QtGui.QVBoxLayout(import_widget)
importLayout.setMargin(0) import_layout.setMargin(0)
importLayout.setObjectName(prefix + 'ImportLayout') import_layout.setObjectName(prefix + 'ImportLayout')
if description_text is not None: if description_text is not None:
descriptionLayout = QtGui.QHBoxLayout() description_layout = QtGui.QHBoxLayout()
descriptionLayout.setObjectName(prefix + 'DescriptionLayout') description_layout.setObjectName(prefix + 'DescriptionLayout')
descriptionSpacer = QtGui.QSpacerItem(0, 0, QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) description_spacer = QtGui.QSpacerItem(0, 0, QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed)
descriptionLayout.addSpacerItem(descriptionSpacer) description_layout.addSpacerItem(description_spacer)
description_label = QtGui.QLabel(importWidget) description_label = QtGui.QLabel(import_widget)
description_label.setWordWrap(True) description_label.setWordWrap(True)
description_label.setOpenExternalLinks(True) description_label.setOpenExternalLinks(True)
description_label.setObjectName(prefix + '_description_label') description_label.setObjectName(prefix + '_description_label')
descriptionLayout.addWidget(description_label) description_layout.addWidget(description_label)
importLayout.addLayout(descriptionLayout) import_layout.addLayout(description_layout)
self.format_widgets[this_format]['description_label'] = description_label 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: if select_mode == SongFormatSelect.SingleFile or select_mode == SongFormatSelect.SingleFolder:
file_path_layout = QtGui.QHBoxLayout() file_path_layout = QtGui.QHBoxLayout()
file_path_layout.setObjectName(prefix + '_file_path_layout') file_path_layout.setObjectName(prefix + '_file_path_layout')
file_path_layout.setContentsMargins(0, self.format_v_spacing, 0, 0) file_path_layout.setContentsMargins(0, self.format_v_spacing, 0, 0)
filepathLabel = QtGui.QLabel(importWidget) file_path_label = QtGui.QLabel(import_widget)
filepathLabel.setObjectName(prefix + 'FilepathLabel') file_path_label.setObjectName(prefix + 'FilepathLabel')
file_path_layout.addWidget(filepathLabel) file_path_layout.addWidget(file_path_label)
filepathSpacer = QtGui.QSpacerItem(0, 0, QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) file_path_spacer = QtGui.QSpacerItem(0, 0, QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed)
file_path_layout.addSpacerItem(filepathSpacer) file_path_layout.addSpacerItem(file_path_spacer)
file_path_edit = QtGui.QLineEdit(importWidget) file_path_edit = QtGui.QLineEdit(import_widget)
file_path_edit.setObjectName(prefix + '_file_path_edit') file_path_edit.setObjectName(prefix + '_file_path_edit')
file_path_layout.addWidget(file_path_edit) file_path_layout.addWidget(file_path_edit)
browseButton = QtGui.QToolButton(importWidget) browse_button = QtGui.QToolButton(import_widget)
browseButton.setIcon(self.open_icon) browse_button.setIcon(self.open_icon)
browseButton.setObjectName(prefix + 'BrowseButton') browse_button.setObjectName(prefix + 'BrowseButton')
file_path_layout.addWidget(browseButton) file_path_layout.addWidget(browse_button)
importLayout.addLayout(file_path_layout) import_layout.addLayout(file_path_layout)
importLayout.addSpacerItem(self.stack_spacer) import_layout.addSpacerItem(self.stack_spacer)
self.format_widgets[this_format]['filepathLabel'] = filepathLabel self.format_widgets[this_format]['filepathLabel'] = file_path_label
self.format_widgets[this_format]['filepathSpacer'] = filepathSpacer 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_layout'] = file_path_layout
self.format_widgets[this_format]['file_path_edit'] = file_path_edit 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: elif select_mode == SongFormatSelect.MultipleFiles:
fileListWidget = QtGui.QListWidget(importWidget) file_list_widget = QtGui.QListWidget(import_widget)
fileListWidget.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection) file_list_widget.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection)
fileListWidget.setObjectName(prefix + 'FileListWidget') file_list_widget.setObjectName(prefix + 'FileListWidget')
importLayout.addWidget(fileListWidget) import_layout.addWidget(file_list_widget)
button_layout = QtGui.QHBoxLayout() button_layout = QtGui.QHBoxLayout()
button_layout.setObjectName(prefix + '_button_layout') button_layout.setObjectName(prefix + '_button_layout')
addButton = QtGui.QPushButton(importWidget) add_button = QtGui.QPushButton(import_widget)
addButton.setIcon(self.open_icon) add_button.setIcon(self.open_icon)
addButton.setObjectName(prefix + 'AddButton') add_button.setObjectName(prefix + 'AddButton')
button_layout.addWidget(addButton) button_layout.addWidget(add_button)
button_layout.addStretch() button_layout.addStretch()
removeButton = QtGui.QPushButton(importWidget) remove_button = QtGui.QPushButton(import_widget)
removeButton.setIcon(self.delete_icon) remove_button.setIcon(self.delete_icon)
removeButton.setObjectName(prefix + 'RemoveButton') remove_button.setObjectName(prefix + 'RemoveButton')
button_layout.addWidget(removeButton) button_layout.addWidget(remove_button)
importLayout.addLayout(button_layout) import_layout.addLayout(button_layout)
self.format_widgets[this_format]['file_list_widget'] = fileListWidget 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]['button_layout'] = button_layout
self.format_widgets[this_format]['addButton'] = addButton self.format_widgets[this_format]['addButton'] = add_button
self.format_widgets[this_format]['removeButton'] = removeButton self.format_widgets[this_format]['removeButton'] = remove_button
self.format_stack.addWidget(page) self.format_stack.addWidget(page)
self.format_widgets[this_format]['page'] = 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('') self.format_combo_box.addItem('')
def disablableWidget(self, page, prefix): def disablable_widget(self, page, prefix):
""" """
Disable a widget. Disable a widget.
""" """
@ -526,10 +524,10 @@ class SongImportSourcePage(QtGui.QWizardPage):
if wizard.format_widgets[this_format]['file_list_widget'].count() > 0: if wizard.format_widgets[this_format]['file_list_widget'].count() > 0:
return True return True
else: else:
filepath = str(wizard.format_widgets[this_format]['file_path_edit'].text()) file_path = str(wizard.format_widgets[this_format]['file_path_edit'].text())
if filepath: if file_path:
if select_mode == SongFormatSelect.SingleFile and os.path.isfile(filepath): if select_mode == SongFormatSelect.SingleFile and os.path.isfile(file_path):
return True 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 True
return False return False

View File

@ -161,6 +161,7 @@ class Ui_SongMaintenanceDialog(object):
self.add_book_button.setText(UiStrings().Add) self.add_book_button.setText(UiStrings().Add)
self.edit_book_button.setText(UiStrings().Edit) self.edit_book_button.setText(UiStrings().Edit)
self.delete_book_button.setText(UiStrings().Delete) self.delete_book_button.setText(UiStrings().Delete)
typeListWidth = max(self.fontMetrics().width(SongStrings.Authors), type_list_width = max(self.fontMetrics().width(SongStrings.Authors),
self.fontMetrics().width(SongStrings.Topics), self.fontMetrics().width(SongStrings.SongBooks)) self.fontMetrics().width(SongStrings.Topics),
self.type_list_widget.setFixedWidth(typeListWidth + self.type_list_widget.iconSize().width() + 32) self.fontMetrics().width(SongStrings.SongBooks))
self.type_list_widget.setFixedWidth(type_list_width + self.type_list_widget.iconSize().width() + 32)

View File

@ -98,8 +98,7 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog):
""" """
Get the ID of the currently selected item. Get the ID of the currently selected item.
``list_widget`` :param list_widget: The list widget to examine.
The list widget to examine.
""" """
item = list_widget.currentItem() item = list_widget.currentItem()
if item: if item:
@ -163,6 +162,9 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog):
def check_author_exists(self, new_author, edit=False): def check_author_exists(self, new_author, edit=False):
""" """
Returns *False* if the given Author already exists, otherwise *True*. 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( authors = self.manager.get_all_objects(
Author, Author,
@ -177,6 +179,9 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog):
def check_topic_exists(self, new_topic, edit=False): def check_topic_exists(self, new_topic, edit=False):
""" """
Returns *False* if the given Topic already exists, otherwise *True*. 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) topics = self.manager.get_all_objects(Topic, Topic.name == new_topic.name)
return self.__check_object_exists(topics, new_topic, edit) 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): def check_song_book_exists(self, new_book, edit=False):
""" """
Returns *False* if the given Topic already exists, otherwise *True*. 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, books = self.manager.get_all_objects(
and_(Book.name == new_book.name, Book.publisher == new_book.publisher)) Book, and_(Book.name == new_book.name, Book.publisher == new_book.publisher))
return self.__check_object_exists(books, new_book, edit) return self.__check_object_exists(books, new_book, edit)
def __check_object_exists(self, existing_objects, new_object, edit): def __check_object_exists(self, existing_objects, new_object, edit):
""" """
Utility method to check for an existing object. Utility method to check for an existing object.
``edit`` :param existing_objects: The objects reference
If we edit an item, this should be *True*. :param new_object: An individual object
:param edit: If we edit an item, this should be *True*.
""" """
if existing_objects: if existing_objects:
# If we edit an existing object, we need to make sure that we do # 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 %s use the existing author %s?') %
(author.display_name, temp_display_name, author.display_name), parent=self, question=True) == \ (author.display_name, temp_display_name, author.display_name), parent=self, question=True) == \
QtGui.QMessageBox.Yes: QtGui.QMessageBox.Yes:
self._merge_objects(author, self.merge_authors, self._merge_objects(author, self.merge_authors, self.reset_authors)
self.reset_authors)
else: else:
# We restore the author's old first and last name as well as # We restore the author's old first and last name as well as
# his display name. # his display name.
@ -330,15 +338,16 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog):
message=translate('SongsPlugin.SongMaintenanceForm', 'Could not save your changes.')) message=translate('SongsPlugin.SongMaintenanceForm', 'Could not save your changes.'))
elif critical_error_message_box( elif critical_error_message_box(
message=translate('SongsPlugin.SongMaintenanceForm', message=translate('SongsPlugin.SongMaintenanceForm',
'The topic %s already exists. Would you like to make songs with topic %s use the existing topic %s?') % 'The topic %s already exists. Would you like to make songs with topic %s use the '
(topic.name, temp_name, topic.name), parent=self, question=True) == QtGui.QMessageBox.Yes: '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) self._merge_objects(topic, self.merge_topics, self.reset_topics)
else: else:
# We restore the topics's old name. # We restore the topics's old name.
topic.name = temp_name topic.name = temp_name
critical_error_message_box( critical_error_message_box(
message=translate('SongsPlugin.SongMaintenanceForm', 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): 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.')) message=translate('SongsPlugin.SongMaintenanceForm', 'Could not save your changes.'))
elif critical_error_message_box( elif critical_error_message_box(
message=translate('SongsPlugin.SongMaintenanceForm', message=translate('SongsPlugin.SongMaintenanceForm',
'The book %s already exists. Would you like to make songs with book %s use the existing book %s?') % 'The book %s already exists. Would you like to make '
(book.name, temp_name, book.name), parent=self, question=True) == QtGui.QMessageBox.Yes: '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) self._merge_objects(book, self.merge_song_books, self.reset_song_books)
else: else:
# We restore the book's old name and publisher. # 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. Merges two authors into one author.
``old_author`` :param old_author: The object, which was edited, that will be deleted
The object, which was edited, that will be deleted
""" """
# Find the duplicate. # Find the duplicate.
existing_author = self.manager.get_object_filtered( existing_author = self.manager.get_object_filtered(
@ -418,15 +427,11 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog):
""" """
Merges two topics into one topic. Merges two topics into one topic.
``old_topic`` :param old_topic: The object, which was edited, that will be deleted
The object, which was edited, that will be deleted
""" """
# Find the duplicate. # Find the duplicate.
existing_topic = self.manager.get_object_filtered( existing_topic = self.manager.get_object_filtered(
Topic, Topic, and_(Topic.name == old_topic.name, Topic.id != old_topic.id)
and_(
Topic.name == old_topic.name, Topic.id != old_topic.id
)
) )
# Find the songs, which have the old_topic as topic. # Find the songs, which have the old_topic as topic.
songs = self.manager.get_all_objects(Song, Song.topics.contains(old_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. Delete the author if the author is not attached to any songs.
""" """
self._delete_item(Author, self.authors_list_widget, self.reset_authors, self._delete_item(Author, self.authors_list_widget, self.reset_authors,
translate('SongsPlugin.SongMaintenanceForm', 'Delete Author'), translate('SongsPlugin.SongMaintenanceForm', 'Delete Author'),
translate('SongsPlugin.SongMaintenanceForm', 'Are you sure you want to delete the selected author?'), translate('SongsPlugin.SongMaintenanceForm',
translate('SongsPlugin.SongMaintenanceForm', 'Are you sure you want to delete the selected author?'),
'This author cannot be deleted, they are currently assigned to at least one song.')) translate('SongsPlugin.SongMaintenanceForm',
'This author cannot be deleted, they are currently assigned to at least one song'
'.'))
def on_delete_topic_button_clicked(self): def on_delete_topic_button_clicked(self):
""" """
Delete the Book if the Book is not attached to any songs. Delete the Book if the Book is not attached to any songs.
""" """
self._delete_item(Topic, self.topics_list_widget, self.reset_topics, self._delete_item(Topic, self.topics_list_widget, self.reset_topics,
translate('SongsPlugin.SongMaintenanceForm', 'Delete Topic'), translate('SongsPlugin.SongMaintenanceForm', 'Delete Topic'),
translate('SongsPlugin.SongMaintenanceForm', 'Are you sure you want to delete the selected topic?'), translate('SongsPlugin.SongMaintenanceForm',
translate('SongsPlugin.SongMaintenanceForm', 'Are you sure you want to delete the selected topic?'),
'This topic cannot be deleted, it is currently assigned to at least one song.')) translate('SongsPlugin.SongMaintenanceForm',
'This topic cannot be deleted, it is currently assigned to at least one song.'))
def on_delete_book_button_clicked(self): def on_delete_book_button_clicked(self):
""" """
Delete the Book if the Book is not attached to any songs. 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, self._delete_item(Book, self.song_books_list_widget, self.reset_song_books,
translate('SongsPlugin.SongMaintenanceForm', 'Delete Book'), translate('SongsPlugin.SongMaintenanceForm', 'Delete Book'),
translate('SongsPlugin.SongMaintenanceForm', 'Are you sure you want to delete the selected book?'), translate('SongsPlugin.SongMaintenanceForm',
translate('SongsPlugin.SongMaintenanceForm', 'Are you sure you want to delete the selected book?'),
'This book cannot be deleted, it is currently assigned to at least one song.')) 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): def on_authors_list_row_changed(self, row):
""" """

View File

@ -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 # 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. # 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.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.addWidget(self.song_info_verse_list_widget)
self.song_group_box_layout.addStretch() self.song_group_box_layout.addStretch()
self.song_vertical_layout.addWidget(self.song_group_box) self.song_vertical_layout.addWidget(self.song_group_box)

View File

@ -62,8 +62,8 @@ class TopicsForm(QtGui.QDialog, Ui_TopicsDialog):
Override the inherited method to check before we close. Override the inherited method to check before we close.
""" """
if not self.name_edit.text(): if not self.name_edit.text():
critical_error_message_box(message=translate('SongsPlugin.TopicsForm', critical_error_message_box(
'You need to type in a topic name.')) message=translate('SongsPlugin.TopicsForm', 'You need to type in a topic name.'))
self.name_edit.setFocus() self.name_edit.setFocus()
return False return False
else: else:

View File

@ -107,7 +107,7 @@ class SongFormat(object):
``u'prefix'`` ``u'prefix'``
Prefix for Qt objects. Use mixedCase, e.g. ``u'openLyrics'`` 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: Optional attributes for each song format:

View File

@ -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. :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 # Main song import menu item - will eventually be the only one
self.song_import_item = create_action(import_menu, 'songImportItem', text=translate('SongsPlugin', '&Song'), self.song_import_item = create_action(
tooltip=translate('SongsPlugin', 'Import songs using the import wizard.'), import_menu, 'songImportItem',
triggers=self.on_song_import_item_clicked) 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) import_menu.addAction(self.song_import_item)
self.import_songselect_item = create_action( self.import_songselect_item = create_action(
import_menu, 'import_songselect_item', text=translate('SongsPlugin', 'CCLI SongSelect'), 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. :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 # 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'), text=translate('SongsPlugin', '&Song'),
tooltip=translate('SongsPlugin', 'Exports songs using the export wizard.'), tooltip=translate('SongsPlugin', 'Exports songs using the export wizard.'),
triggers=self.on_song_export_item_clicked) 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. :param tools_menu: The actual **Tools** menu item, so that your actions can use it as their parent.
""" """
log.info('add tools menu') 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'), text=translate('SongsPlugin', '&Re-index Songs'),
icon=':/plugins/plugin_songs.png', icon=':/plugins/plugin_songs.png',
statustip=translate('SongsPlugin', 'Re-index the songs database to improve searching and ordering.'), statustip=translate('SongsPlugin', 'Re-index the songs database to improve searching and ordering.'),
visible=False, triggers=self.on_tools_reindex_item_triggered) visible=False, triggers=self.on_tools_reindex_item_triggered)
tools_menu.addAction(self.tools_reindex_item) 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'), text=translate('SongsPlugin', 'Find &Duplicate Songs'),
statustip=translate('SongsPlugin', statustip=translate('SongsPlugin', 'Find and remove duplicate songs in the song database.'),
'Find and remove duplicate songs in the song database.'),
visible=False, triggers=self.on_tools_find_duplicates_triggered, can_shortcuts=True) visible=False, triggers=self.on_tools_find_duplicates_triggered, can_shortcuts=True)
tools_menu.addAction(self.tools_find_duplicates) tools_menu.addAction(self.tools_find_duplicates)
@ -170,8 +174,8 @@ class SongsPlugin(Plugin):
max_songs = self.manager.get_object_count(Song) max_songs = self.manager.get_object_count(Song)
if max_songs == 0: if max_songs == 0:
return return
progress_dialog = QtGui.QProgressDialog(translate('SongsPlugin', 'Reindexing songs...'), UiStrings().Cancel, progress_dialog = QtGui.QProgressDialog(
0, max_songs, self.main_window) translate('SongsPlugin', 'Reindexing songs...'), UiStrings().Cancel, 0, max_songs, self.main_window)
progress_dialog.setWindowTitle(translate('SongsPlugin', 'Reindexing songs')) progress_dialog.setWindowTitle(translate('SongsPlugin', 'Reindexing songs'))
progress_dialog.setWindowModality(QtCore.Qt.WindowModal) progress_dialog.setWindowModality(QtCore.Qt.WindowModal)
songs = self.manager.get_all_objects(Song) songs = self.manager.get_all_objects(Song)
@ -230,22 +234,25 @@ class SongsPlugin(Plugin):
def rename_theme(self, old_theme, new_theme): def rename_theme(self, old_theme, new_theme):
""" """
Renames a theme the song plugin is using making the plugin use the new Renames a theme the song plugin is using making the plugin use the new name.
name.
``old_theme`` :param old_theme: The name of the theme the plugin should stop using.
The name of the theme the plugin should stop using. :param new_theme: The new name the plugin should now use.
``new_theme``
The new name the plugin should now use.
""" """
songs_using_theme = self.manager.get_all_objects(Song, Song.theme_name == old_theme) songs_using_theme = self.manager.get_all_objects(Song, Song.theme_name == old_theme)
for song in songs_using_theme: for song in songs_using_theme:
song.theme_name = new_theme song.theme_name = new_theme
self.manager.save_object(song) self.manager.save_object(song)
def importSongs(self, format, **kwargs): def import_songs(self, import_format, **kwargs):
class_ = SongFormat.get(format, 'class') """
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 = class_(self.manager, **kwargs)
importer.register(self.media_item.import_wizard) importer.register(self.media_item.import_wizard)
return importer return importer
@ -278,8 +285,7 @@ class SongsPlugin(Plugin):
def first_time(self): def first_time(self):
""" """
If the first time wizard has run, this function is run to import all the If the first time wizard has run, this function is run to import all the new songs into the database.
new songs into the database.
""" """
self.application.process_events() self.application.process_events()
self.on_tools_reindex_item_triggered() self.on_tools_reindex_item_triggered()
@ -293,7 +299,7 @@ class SongsPlugin(Plugin):
if sfile.startswith('songs_') and sfile.endswith('.sqlite'): if sfile.startswith('songs_') and sfile.endswith('.sqlite'):
self.application.process_events() self.application.process_events()
song_dbs.append(os.path.join(db_dir, sfile)) 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: if not song_dbs:
return return
self.application.process_events() self.application.process_events()
@ -340,9 +346,11 @@ class SongsPlugin(Plugin):
for song in songs: for song in songs:
self.manager.delete_object(Song, song.id) 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 Provide a count of the songs in the database
:param db_file: the database name to count
""" """
connection = sqlite3.connect(db_file) connection = sqlite3.connect(db_file)
cursor = connection.cursor() cursor = connection.cursor()

View File

@ -29,4 +29,5 @@
hiddenimports = ['openlp.plugins.presentations.lib.impresscontroller', hiddenimports = ['openlp.plugins.presentations.lib.impresscontroller',
'openlp.plugins.presentations.lib.powerpointcontroller', 'openlp.plugins.presentations.lib.powerpointcontroller',
'openlp.plugins.presentations.lib.pptviewcontroller'] 'openlp.plugins.presentations.lib.pptviewcontroller',
'openlp.plugins.presentations.lib.pdfcontroller']

View File

@ -75,11 +75,16 @@ class TestMediaItem(TestCase):
presentation_controller.also_supports = [] presentation_controller.also_supports = []
presentation_viewer_controller = MagicMock() presentation_viewer_controller = MagicMock()
presentation_viewer_controller.enabled.return_value = False 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. # Mock the controllers.
self.media_item.controllers = { self.media_item.controllers = {
'Impress': impress_controller, 'Impress': impress_controller,
'Powerpoint': presentation_controller, 'Powerpoint': presentation_controller,
'Powerpoint Viewer': presentation_viewer_controller 'Powerpoint Viewer': presentation_viewer_controller,
'Pdf': pdf_controller
} }
# WHEN: Build the file mask. # WHEN: Build the file mask.
@ -88,7 +93,7 @@ class TestMediaItem(TestCase):
self.media_item.build_file_mask_string() self.media_item.build_file_mask_string()
# THEN: The file mask should be generated correctly # THEN: The file mask should be generated correctly
self.assertIn('*.odp', self.media_item.on_new_file_masks, self.assertIn('*.odp', self.media_item.on_new_file_masks, 'The file mask should contain the odp extension')
'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('*.ppt', self.media_item.on_new_file_masks, self.assertIn('*.pdf', self.media_item.on_new_file_masks, 'The file mask should contain the pdf extension')
'The file mask should contain the ppt extension') self.assertIn('*.xps', self.media_item.on_new_file_masks, 'The file mask should contain the xps extension')

View File

@ -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.')

View File

@ -29,7 +29,7 @@
""" """
This module contains tests for the pptviewcontroller module of the Presentations plugin. This module contains tests for the pptviewcontroller module of the Presentations plugin.
""" """
import os
from unittest import TestCase from unittest import TestCase
from tests.functional import MagicMock, patch from tests.functional import MagicMock, patch
@ -43,6 +43,7 @@ from openlp.plugins.presentations.lib.pptviewcontroller import PptviewDocument
# start_process(self) # start_process(self)
# kill # kill
class TestPptviewDocument(TestCase): class TestPptviewDocument(TestCase):
""" """
Test the PptviewDocument Class Test the PptviewDocument Class
@ -78,8 +79,8 @@ class TestPptviewDocument(TestCase):
'openlp.plugins.presentations.lib.pptviewcontroller.PresentationDocument.get_temp_folder') 'openlp.plugins.presentations.lib.pptviewcontroller.PresentationDocument.get_temp_folder')
self.presentation_document_setup_patcher = patch( self.presentation_document_setup_patcher = patch(
'openlp.plugins.presentations.lib.pptviewcontroller.PresentationDocument._setup') '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.screen_list_patcher = patch('openlp.plugins.presentations.lib.pptviewcontroller.ScreenList')
self.rect_patcher = MagicMock()
self.mock_os = self.os_patcher.start() self.mock_os = self.os_patcher.start()
self.mock_pptview_document_create_thumbnails = self.pptview_document_create_thumbnails_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 self.mock_controller.process.OpenPPT.return_value = 0
instance = PptviewDocument(self.mock_controller, self.mock_presentation) instance = PptviewDocument(self.mock_controller, self.mock_presentation)
instance.filepath = 'test\path.ppt' instance.filepath = 'test\path.ppt'
result = instance.load_presentation()
# THEN: PptviewDocument.load_presentation should return True if os.name == 'nt':
self.assertTrue(result) 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 Test the PptviewDocument.load_presentation() method when the temporary directory does not exist and the PPT is
not successfully opened not successfully opened
@ -136,8 +139,9 @@ class TestPptviewDocument(TestCase):
self.mock_controller.process.OpenPPT.return_value = -1 self.mock_controller.process.OpenPPT.return_value = -1
instance = PptviewDocument(self.mock_controller, self.mock_presentation) instance = PptviewDocument(self.mock_controller, self.mock_presentation)
instance.filepath = 'test\path.ppt' 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 # 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.mock_os.makedirs.assert_called_once_with('temp folder')
self.assertFalse(result) self.assertFalse(result)

View File

@ -34,6 +34,7 @@ from unittest import TestCase
from openlp.plugins.presentations.lib.presentationcontroller import PresentationController, PresentationDocument from openlp.plugins.presentations.lib.presentationcontroller import PresentationController, PresentationDocument
from tests.functional import MagicMock, patch from tests.functional import MagicMock, patch
class TestPresentationController(TestCase): class TestPresentationController(TestCase):
""" """
Test the PresentationController. Test the PresentationController.
@ -65,6 +66,7 @@ class TestPresentationController(TestCase):
self.assertEqual('PresentationController', controller.name, self.assertEqual('PresentationController', controller.name,
'The name of the presentation controller should be correct') 'The name of the presentation controller should be correct')
class TestPresentationDocument(TestCase): class TestPresentationDocument(TestCase):
""" """
Test the PresentationDocument Class Test the PresentationDocument Class
@ -101,12 +103,12 @@ class TestPresentationDocument(TestCase):
""" """
Set up the patches and mocks need for all tests. Set up the patches and mocks need for all tests.
""" """
self.check_directory_exists_patcher = patch( self.check_directory_exists_patcher = \
'openlp.plugins.presentations.lib.presentationcontroller.check_directory_exists') patch('openlp.plugins.presentations.lib.presentationcontroller.check_directory_exists')
self.get_thumbnail_folder_patcher = patch( self.get_thumbnail_folder_patcher = \
'openlp.plugins.presentations.lib.presentationcontroller.PresentationDocument.get_thumbnail_folder') patch('openlp.plugins.presentations.lib.presentationcontroller.PresentationDocument.get_thumbnail_folder')
self._setup_patcher = patch( self._setup_patcher = \
'openlp.plugins.presentations.lib.presentationcontroller.PresentationDocument._setup') patch('openlp.plugins.presentations.lib.presentationcontroller.PresentationDocument._setup')
self.mock_check_directory_exists = self.check_directory_exists_patcher.start() self.mock_check_directory_exists = self.check_directory_exists_patcher.start()
self.mock_get_thumbnail_folder = self.get_thumbnail_folder_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.get_thumbnail_folder_patcher.stop()
self._setup_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 # GIVEN: A reset mock_setup and mocked controller
self.mock_setup.reset() self.mock_setup.reset()
# WHEN: Creating an instance of PresentationDocument # 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 # THEN: PresentationDocument.__init__ should have been called with the correct arguments
self.mock_setup.assert_called_once_with('Name') self.mock_setup.assert_called_once_with('Name')
def presentation_document_setup_test(self): 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() self._setup_patcher.stop()
# GIVEN: A mocked controller, patched check_directory_exists_patcher and patched get_thumbnail_folder method # GIVEN: A mocked controller, patched check_directory_exists_patcher and patched get_thumbnail_folder method
# WHEN: Creating an instance of PresentationDocument # 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 # THEN: check_directory_exists should have been called with the correct arguments
self.mock_check_directory_exists.assert_called_once_with('returned/path/') self.mock_check_directory_exists.assert_called_once_with('returned/path/')

Binary file not shown.