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.setContentsMargins(8, 8, 8, 8)
self.dialog_layout.setSpacing(8)
self.dialog_layout.setObjectName('verticalLayout')
self.dialog_layout.setObjectName('vertical_layout')
self.text_edit = SpellTextEdit(self, False)
self.text_edit.setObjectName('textEdit')
self.dialog_layout.addWidget(self.text_edit)

View File

@ -774,7 +774,7 @@ class SlideController(DisplayController):
self._reset_blank()
if service_item.is_command():
Registry().execute(
'%s_start' % service_item.name.lower(), [service_item, self.is_live, self.hide_mode(), slide_no])
'%s_start' % service_item.name.lower(), [self.service_item, self.is_live, self.hide_mode(), slide_no])
self.slide_list = {}
if self.is_live:
self.song_menu.menu().clear()

View File

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

View File

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

View File

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

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

@ -138,6 +138,9 @@ class PresentationMediaItem(MediaManagerItem):
"""
self.display_type_combo_box.clear()
for item in self.controllers:
# For PDF reload backend, since it can have changed
if self.controllers[item].name == 'Pdf':
self.controllers[item].check_available()
# load the drop down selection
if self.controllers[item].enabled():
self.display_type_combo_box.addItem(item)
@ -178,8 +181,7 @@ class PresentationMediaItem(MediaManagerItem):
if not initial_load:
critical_error_message_box(translate('PresentationPlugin.MediaItem', 'File Exists'),
translate('PresentationPlugin.MediaItem',
'A presentation with that filename already exists.')
)
'A presentation with that filename already exists.'))
continue
controller_name = self.findControllerByType(filename)
if controller_name:
@ -203,7 +205,8 @@ class PresentationMediaItem(MediaManagerItem):
icon = build_icon(':/general/general_delete.png')
else:
critical_error_message_box(UiStrings().UnsupportedFile,
translate('PresentationPlugin.MediaItem', 'This type of presentation is not supported.'))
translate('PresentationPlugin.MediaItem',
'This type of presentation is not supported.'))
continue
item_name = QtGui.QListWidgetItem(filename)
item_name.setData(QtCore.Qt.UserRole, file)
@ -238,7 +241,7 @@ class PresentationMediaItem(MediaManagerItem):
Settings().setValue(self.settings_section + '/presentations files', self.get_file_list())
def generate_slide_data(self, service_item, item=None, xml_version=False,
remote=False, context=ServiceItemContext.Service):
remote=False, context=ServiceItemContext.Service, presentation_file=None):
"""
Load the relevant information for displaying the presentation in the slidecontroller. In the case of
powerpoints, an image for each slide.
@ -249,10 +252,55 @@ class PresentationMediaItem(MediaManagerItem):
items = self.list_view.selectedItems()
if len(items) > 1:
return False
service_item.processor = self.display_type_combo_box.currentText()
service_item.add_capability(ItemCapabilities.ProvidesOwnDisplay)
filename = presentation_file
if filename is None:
filename = items[0].data(QtCore.Qt.UserRole)
file_type = os.path.splitext(filename)[1][1:]
if not self.display_type_combo_box.currentText():
return False
if (file_type == 'pdf' or file_type == 'xps') and context != ServiceItemContext.Service:
service_item.add_capability(ItemCapabilities.CanMaintain)
service_item.add_capability(ItemCapabilities.CanPreview)
service_item.add_capability(ItemCapabilities.CanLoop)
service_item.add_capability(ItemCapabilities.CanAppend)
# force a nonexistent theme
service_item.theme = -1
for bitem in items:
filename = presentation_file
if filename is None:
filename = bitem.data(QtCore.Qt.UserRole)
(path, name) = os.path.split(filename)
service_item.title = name
if os.path.exists(filename):
processor = self.findControllerByType(filename)
if not processor:
return False
controller = self.controllers[processor]
service_item.processor = None
doc = controller.add_document(filename)
if doc.get_thumbnail_path(1, True) is None or not os.path.isfile(
os.path.join(doc.get_temp_folder(), 'mainslide001.png')):
doc.load_presentation()
i = 1
imagefile = 'mainslide%03d.png' % i
image = os.path.join(doc.get_temp_folder(), imagefile)
while os.path.isfile(image):
service_item.add_from_image(image, name)
i += 1
imagefile = 'mainslide%03d.png' % i
image = os.path.join(doc.get_temp_folder(), imagefile)
doc.close_presentation()
return True
else:
# File is no longer present
if not remote:
critical_error_message_box(translate('PresentationPlugin.MediaItem', 'Missing Presentation'),
translate('PresentationPlugin.MediaItem',
'The presentation %s 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)
@ -278,15 +326,18 @@ class PresentationMediaItem(MediaManagerItem):
else:
# File is no longer present
if not remote:
critical_error_message_box(translate('PresentationPlugin.MediaItem', 'Missing Presentation'),
critical_error_message_box(translate('PresentationPlugin.MediaItem',
'Missing Presentation'),
translate('PresentationPlugin.MediaItem',
'The presentation %s is incomplete, please reload.') % filename)
'The presentation %s is incomplete, please reload.')
% filename)
return False
else:
# File is no longer present
if not remote:
critical_error_message_box(translate('PresentationPlugin.MediaItem', 'Missing Presentation'),
translate('PresentationPlugin.MediaItem', 'The presentation %s no longer exists.') % filename)
translate('PresentationPlugin.MediaItem',
'The presentation %s no longer exists.') % filename)
return False
def findControllerByType(self, filename):

View File

@ -28,11 +28,13 @@
###############################################################################
import logging
import copy
from PyQt4 import QtCore
from openlp.core.common import Registry
from openlp.core.ui import HideMode
from openlp.core.lib import ServiceItemContext, ServiceItem
log = logging.getLogger(__name__)
@ -69,6 +71,7 @@ class Controller(object):
return
self.doc.slidenumber = slide_no
self.hide_mode = hide_mode
log.debug('add_handler, slidenumber: %d' % slide_no)
if self.is_live:
if hide_mode == HideMode.Screen:
Registry().execute('live_display_hide', HideMode.Screen)
@ -316,6 +319,28 @@ class MessageListener(object):
hide_mode = message[2]
file = item.get_frame_path()
self.handler = item.processor
# When starting presentation from the servicemanager we convert
# PDF/XPS-serviceitems into image-serviceitems. When started from the mediamanager
# the conversion has already been done at this point.
if file.endswith('.pdf') or file.endswith('.xps'):
log.debug('Converting from pdf/xps to images for serviceitem with file %s', file)
# Create a copy of the original item, and then clear the original item so it can be filled with images
item_cpy = copy.copy(item)
item.__init__(None)
if is_live:
self.media_item.generate_slide_data(item, item_cpy, False, False, ServiceItemContext.Live, file)
else:
self.media_item.generate_slide_data(item, item_cpy, False, False, ServiceItemContext.Preview, file)
# Some of the original serviceitem attributes is needed in the new serviceitem
item.footer = item_cpy.footer
item.from_service = item_cpy.from_service
item.iconic_representation = item_cpy.iconic_representation
item.image_border = item_cpy.image_border
item.main = item_cpy.main
item.theme_data = item_cpy.theme_data
# When presenting PDF or XPS, we are using the image presentation code,
# so handler & processor is set to None, and we skip adding the handler.
self.handler = None
if self.handler == self.media_item.automatic:
self.handler = self.media_item.findControllerByType(file)
if not self.handler:
@ -324,6 +349,11 @@ class MessageListener(object):
controller = self.live_handler
else:
controller = self.preview_handler
# 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):

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 openlp.core.common import Settings, UiStrings, translate
from openlp.core.lib import SettingsTab
from openlp.core.lib import SettingsTab, build_icon
from openlp.core.lib.ui import critical_error_message_box
from .pdfcontroller import PdfController
class PresentationTab(SettingsTab):
@ -64,6 +66,7 @@ class PresentationTab(SettingsTab):
self.presenter_check_boxes[controller.name] = checkbox
self.controllers_layout.addWidget(checkbox)
self.left_layout.addWidget(self.controllers_group_box)
# Advanced
self.advanced_group_box = QtGui.QGroupBox(self.left_column)
self.advanced_group_box.setObjectName('advanced_group_box')
self.advanced_layout = QtGui.QVBoxLayout(self.advanced_group_box)
@ -72,8 +75,34 @@ class PresentationTab(SettingsTab):
self.override_app_check_box.setObjectName('override_app_check_box')
self.advanced_layout.addWidget(self.override_app_check_box)
self.left_layout.addWidget(self.advanced_group_box)
# Pdf options
self.pdf_group_box = QtGui.QGroupBox(self.left_column)
self.pdf_group_box.setObjectName('pdf_group_box')
self.pdf_layout = QtGui.QFormLayout(self.pdf_group_box)
self.pdf_layout.setObjectName('pdf_layout')
self.pdf_program_check_box = QtGui.QCheckBox(self.pdf_group_box)
self.pdf_program_check_box.setObjectName('pdf_program_check_box')
self.pdf_layout.addRow(self.pdf_program_check_box)
self.pdf_program_path_layout = QtGui.QHBoxLayout()
self.pdf_program_path_layout.setObjectName('pdf_program_path_layout')
self.pdf_program_path = QtGui.QLineEdit(self.pdf_group_box)
self.pdf_program_path.setObjectName('pdf_program_path')
self.pdf_program_path.setReadOnly(True)
self.pdf_program_path.setPalette(self.get_grey_text_palette(True))
self.pdf_program_path_layout.addWidget(self.pdf_program_path)
self.pdf_program_browse_button = QtGui.QToolButton(self.pdf_group_box)
self.pdf_program_browse_button.setObjectName('pdf_program_browse_button')
self.pdf_program_browse_button.setIcon(build_icon(':/general/general_open.png'))
self.pdf_program_browse_button.setEnabled(False)
self.pdf_program_path_layout.addWidget(self.pdf_program_browse_button)
self.pdf_layout.addRow(self.pdf_program_path_layout)
self.left_layout.addWidget(self.pdf_group_box)
self.left_layout.addStretch()
self.right_column.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Preferred)
self.right_layout.addStretch()
# Signals and slots
self.pdf_program_browse_button.clicked.connect(self.on_pdf_program_browse_button_clicked)
self.pdf_program_check_box.clicked.connect(self.on_pdf_program_check_box_clicked)
def retranslateUi(self):
"""
@ -85,8 +114,11 @@ class PresentationTab(SettingsTab):
checkbox = self.presenter_check_boxes[controller.name]
self.set_controller_text(checkbox, controller)
self.advanced_group_box.setTitle(UiStrings().Advanced)
self.pdf_group_box.setTitle(translate('PresentationPlugin.PresentationTab', 'PDF options'))
self.override_app_check_box.setText(
translate('PresentationPlugin.PresentationTab', 'Allow presentation application to be overridden'))
self.pdf_program_check_box.setText(
translate('PresentationPlugin.PresentationTab', 'Use given full path for mudraw or ghostscript binary:'))
def set_controller_text(self, checkbox, controller):
if checkbox.isEnabled():
@ -103,6 +135,14 @@ class PresentationTab(SettingsTab):
checkbox = self.presenter_check_boxes[controller.name]
checkbox.setChecked(Settings().value(self.settings_section + '/' + controller.name))
self.override_app_check_box.setChecked(Settings().value(self.settings_section + '/override app'))
# load pdf-program settings
enable_pdf_program = Settings().value(self.settings_section + '/enable_pdf_program')
self.pdf_program_check_box.setChecked(enable_pdf_program)
self.pdf_program_path.setPalette(self.get_grey_text_palette(not enable_pdf_program))
self.pdf_program_browse_button.setEnabled(enable_pdf_program)
pdf_program = Settings().value(self.settings_section + '/pdf_program')
if pdf_program:
self.pdf_program_path.setText(pdf_program)
def save(self):
"""
@ -128,6 +168,18 @@ class PresentationTab(SettingsTab):
if Settings().value(setting_key) != self.override_app_check_box.checkState():
Settings().setValue(setting_key, self.override_app_check_box.checkState())
changed = True
# Save pdf-settings
pdf_program = self.pdf_program_path.text()
enable_pdf_program = self.pdf_program_check_box.checkState()
# If the given program is blank disable using the program
if pdf_program == '':
enable_pdf_program = 0
if pdf_program != Settings().value(self.settings_section + '/pdf_program'):
Settings().setValue(self.settings_section + '/pdf_program', pdf_program)
changed = True
if enable_pdf_program != Settings().value(self.settings_section + '/enable_pdf_program'):
Settings().setValue(self.settings_section + '/enable_pdf_program', enable_pdf_program)
changed = True
if changed:
self.settings_form.register_post_process('mediaitem_suffix_reset')
self.settings_form.register_post_process('mediaitem_presentation_rebuild')
@ -143,3 +195,43 @@ class PresentationTab(SettingsTab):
checkbox = self.presenter_check_boxes[controller.name]
checkbox.setEnabled(controller.is_available())
self.set_controller_text(checkbox, controller)
def on_pdf_program_browse_button_clicked(self):
"""
Select the mudraw or ghostscript binary that should be used.
"""
filename = QtGui.QFileDialog.getOpenFileName(self, translate('PresentationPlugin.PresentationTab',
'Select mudraw or ghostscript binary.'),
self.pdf_program_path.text())
if filename:
program_type = PdfController.check_binary(filename)
if not program_type:
critical_error_message_box(UiStrings().Error,
translate('PresentationPlugin.PresentationTab',
'The program is not ghostscript or mudraw which is required.'))
else:
self.pdf_program_path.setText(filename)
def on_pdf_program_check_box_clicked(self, checked):
"""
When checkbox for manual entering pdf-program is clicked,
enable or disable the textbox for the programpath and the browse-button.
:param checked: If the box is checked or not.
"""
self.pdf_program_path.setPalette(self.get_grey_text_palette(not checked))
self.pdf_program_browse_button.setEnabled(checked)
def get_grey_text_palette(self, greyed):
"""
Returns a QPalette with greyed out text as used for placeholderText.
:param greyed: Determines whether the palette should be grayed.
:return: The created palette.
"""
palette = QtGui.QPalette()
color = self.palette().color(QtGui.QPalette.Active, QtGui.QPalette.Text)
if greyed:
color.setAlpha(128)
palette.setColor(QtGui.QPalette.Active, QtGui.QPalette.Text, color)
return palette

View File

@ -43,11 +43,13 @@ from openlp.plugins.presentations.lib import PresentationController, Presentatio
log = logging.getLogger(__name__)
__default_settings__ = {
'presentations/override app': QtCore.Qt.Unchecked,
__default_settings__ = {'presentations/override app': QtCore.Qt.Unchecked,
'presentations/enable_pdf_program': QtCore.Qt.Unchecked,
'presentations/pdf_program': '',
'presentations/Impress': QtCore.Qt.Checked,
'presentations/Powerpoint': QtCore.Qt.Checked,
'presentations/Powerpoint Viewer': QtCore.Qt.Checked,
'presentations/Pdf': QtCore.Qt.Checked,
'presentations/presentations files': []
}

View File

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

View File

@ -154,8 +154,8 @@ class Ui_EditSongDialog(object):
self.topics_layout.setObjectName('topics_layout')
self.topic_add_layout = QtGui.QHBoxLayout()
self.topic_add_layout.setObjectName('topic_add_layout')
self.topicsComboBox = create_combo_box(self.topics_group_box, 'topicsComboBox')
self.topic_add_layout.addWidget(self.topicsComboBox)
self.topics_combo_box = create_combo_box(self.topics_group_box, 'topics_combo_box')
self.topic_add_layout.addWidget(self.topics_combo_box)
self.topic_add_button = QtGui.QPushButton(self.topics_group_box)
self.topic_add_button.setObjectName('topic_add_button')
self.topic_add_layout.addWidget(self.topic_add_button)

View File

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

View File

@ -50,16 +50,18 @@ class EditVerseForm(QtGui.QDialog, Ui_EditVerseDialog):
"""
super(EditVerseForm, self).__init__(parent)
self.setupUi(self)
self.verse_text_edit.customContextMenuRequested.connect(self.context_menu)
self.insert_button.clicked.connect(self.on_insert_button_clicked)
self.split_button.clicked.connect(self.on_split_button_clicked)
self.verse_text_edit.cursorPositionChanged.connect(self.on_cursor_position_changed)
self.verse_type_combo_box.currentIndexChanged.connect(self.on_verse_type_combo_box_changed)
def context_menu(self, point):
item = self.serviceManagerList.itemAt(point)
def insert_verse(self, verse_tag, verse_num=1):
"""
Insert a verse
:param verse_tag: The verse tag
:param verse_num: The verse number
"""
if self.verse_text_edit.textCursor().columnNumber() != 0:
self.verse_text_edit.insertPlainText('\n')
verse_tag = VerseType.translated_name(verse_tag)
@ -67,6 +69,9 @@ class EditVerseForm(QtGui.QDialog, Ui_EditVerseDialog):
self.verse_text_edit.setFocus()
def on_split_button_clicked(self):
"""
The split button has been pressed
"""
text = self.verse_text_edit.toPlainText()
position = self.verse_text_edit.textCursor().position()
insert_string = '[---]'
@ -78,13 +83,22 @@ class EditVerseForm(QtGui.QDialog, Ui_EditVerseDialog):
self.verse_text_edit.setFocus()
def on_insert_button_clicked(self):
"""
The insert button has been pressed
"""
verse_type_index = self.verse_type_combo_box.currentIndex()
self.insert_verse(VerseType.tags[verse_type_index], self.verse_number_box.value())
def on_verse_type_combo_box_changed(self):
"""
The verse type combo has been changed
"""
self.update_suggested_verse_number()
def on_cursor_position_changed(self):
"""
The cursor position has been changed
"""
self.update_suggested_verse_number()
def update_suggested_verse_number(self):
@ -117,6 +131,13 @@ class EditVerseForm(QtGui.QDialog, Ui_EditVerseDialog):
self.verse_number_box.setValue(verse_num)
def set_verse(self, text, single=False, tag='%s1' % VerseType.tags[VerseType.Verse]):
"""
Save the verse
:param text: The text
:param single: is this a single verse
:param tag: The tag
"""
self.has_single_verse = single
if single:
verse_type_index = VerseType.from_tag(tag[0], None)
@ -136,10 +157,20 @@ class EditVerseForm(QtGui.QDialog, Ui_EditVerseDialog):
self.verse_text_edit.moveCursor(QtGui.QTextCursor.End)
def get_verse(self):
"""
Extract the verse text
:return: The text
"""
return self.verse_text_edit.toPlainText(), VerseType.tags[self.verse_type_combo_box.currentIndex()], \
str(self.verse_number_box.value())
def get_all_verses(self):
"""
Extract all the verses
:return: The text
"""
text = self.verse_text_edit.toPlainText()
if not text.startswith('---['):
text = '---[%s:1]---\n%s' % (VerseType.translated_names[VerseType.Verse], text)

View File

@ -69,5 +69,6 @@ class Ui_MediaFilesDialog(object):
"""
media_files_dialog.setWindowTitle(translate('SongsPlugin.MediaFilesForm', 'Select Media File(s)'))
self.select_label.setText(translate('SongsPlugin.MediaFilesForm',
'Select one or more audio files from the list below, and click OK to import them into this song.'))
'Select one or more audio files from the list below, and click OK to import them '
'into this song.'))

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -107,7 +107,7 @@ class SongFormat(object):
``u'prefix'``
Prefix for Qt objects. Use mixedCase, e.g. ``u'openLyrics'``
See ``SongImportForm.addFileSelectItem()``
See ``SongImportForm.add_file_select_item()``
Optional attributes for each song format:

View File

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

View File

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

View File

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

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.
"""
import os
from unittest import TestCase
from tests.functional import MagicMock, patch
@ -43,6 +43,7 @@ from openlp.plugins.presentations.lib.pptviewcontroller import PptviewDocument
# start_process(self)
# kill
class TestPptviewDocument(TestCase):
"""
Test the PptviewDocument Class
@ -78,8 +79,8 @@ class TestPptviewDocument(TestCase):
'openlp.plugins.presentations.lib.pptviewcontroller.PresentationDocument.get_temp_folder')
self.presentation_document_setup_patcher = patch(
'openlp.plugins.presentations.lib.pptviewcontroller.PresentationDocument._setup')
self.rect_patcher = patch('openlp.plugins.presentations.lib.pptviewcontroller.RECT')
self.screen_list_patcher = patch('openlp.plugins.presentations.lib.pptviewcontroller.ScreenList')
self.rect_patcher = MagicMock()
self.mock_os = self.os_patcher.start()
self.mock_pptview_document_create_thumbnails = self.pptview_document_create_thumbnails_patcher.start()
@ -118,12 +119,14 @@ class TestPptviewDocument(TestCase):
self.mock_controller.process.OpenPPT.return_value = 0
instance = PptviewDocument(self.mock_controller, self.mock_presentation)
instance.filepath = 'test\path.ppt'
if os.name == 'nt':
result = instance.load_presentation()
# THEN: PptviewDocument.load_presentation should return True
self.assertTrue(result)
def load_presentation_unsuccesfull_test(self):
def load_presentation_un_succesfull_test(self):
"""
Test the PptviewDocument.load_presentation() method when the temporary directory does not exist and the PPT is
not successfully opened
@ -136,6 +139,7 @@ class TestPptviewDocument(TestCase):
self.mock_controller.process.OpenPPT.return_value = -1
instance = PptviewDocument(self.mock_controller, self.mock_presentation)
instance.filepath = 'test\path.ppt'
if os.name == 'nt':
result = instance.load_presentation()
# THEN: The temporary directory should be created and PptviewDocument.load_presentation should return False

View File

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

Binary file not shown.