forked from openlp/openlp
HEAD
This commit is contained in:
commit
c607661aee
@ -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)
|
||||
|
@ -444,7 +444,7 @@ class SlideController(DisplayController):
|
||||
# "V1" was the slide we wanted to go.
|
||||
self.preview_widget.change_slide(self.slide_list[self.current_shortcut])
|
||||
self.slide_selected()
|
||||
# Reset the shortcut.
|
||||
# Reset the shortcut.
|
||||
self.current_shortcut = ''
|
||||
|
||||
def set_live_hot_keys(self, parent=None):
|
||||
@ -774,7 +774,7 @@ class SlideController(DisplayController):
|
||||
self._reset_blank()
|
||||
if service_item.is_command():
|
||||
Registry().execute(
|
||||
'%s_start' % service_item.name.lower(), [service_item, self.is_live, self.hide_mode(), slide_no])
|
||||
'%s_start' % service_item.name.lower(), [self.service_item, self.is_live, self.hide_mode(), slide_no])
|
||||
self.slide_list = {}
|
||||
if self.is_live:
|
||||
self.song_menu.menu().clear()
|
||||
@ -1440,4 +1440,4 @@ class LiveController(RegistryMixin, OpenLPMixin, SlideController):
|
||||
"""
|
||||
process the bootstrap post setup request
|
||||
"""
|
||||
self.post_set_up()
|
||||
self.post_set_up()
|
||||
|
@ -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)
|
||||
|
@ -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.
|
||||
"""
|
||||
|
@ -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.
|
||||
"""
|
||||
|
@ -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
|
@ -116,7 +116,7 @@ class PresentationMediaItem(MediaManagerItem):
|
||||
self.display_type_label = QtGui.QLabel(self.presentation_widget)
|
||||
self.display_type_label.setObjectName('display_type_label')
|
||||
self.display_type_combo_box = create_horizontal_adjusting_combo_box(self.presentation_widget,
|
||||
'display_type_combo_box')
|
||||
'display_type_combo_box')
|
||||
self.display_type_label.setBuddy(self.display_type_combo_box)
|
||||
self.display_layout.addRow(self.display_type_label, self.display_type_combo_box)
|
||||
# Add the Presentation widget to the page layout.
|
||||
@ -138,6 +138,9 @@ class PresentationMediaItem(MediaManagerItem):
|
||||
"""
|
||||
self.display_type_combo_box.clear()
|
||||
for item in self.controllers:
|
||||
# For PDF reload backend, since it can have changed
|
||||
if self.controllers[item].name == 'Pdf':
|
||||
self.controllers[item].check_available()
|
||||
# load the drop down selection
|
||||
if self.controllers[item].enabled():
|
||||
self.display_type_combo_box.addItem(item)
|
||||
@ -177,9 +180,8 @@ class PresentationMediaItem(MediaManagerItem):
|
||||
if titles.count(filename) > 0:
|
||||
if not initial_load:
|
||||
critical_error_message_box(translate('PresentationPlugin.MediaItem', 'File Exists'),
|
||||
translate('PresentationPlugin.MediaItem',
|
||||
'A presentation with that filename already exists.')
|
||||
)
|
||||
translate('PresentationPlugin.MediaItem',
|
||||
'A presentation with that filename already exists.'))
|
||||
continue
|
||||
controller_name = self.findControllerByType(filename)
|
||||
if controller_name:
|
||||
@ -203,7 +205,8 @@ class PresentationMediaItem(MediaManagerItem):
|
||||
icon = build_icon(':/general/general_delete.png')
|
||||
else:
|
||||
critical_error_message_box(UiStrings().UnsupportedFile,
|
||||
translate('PresentationPlugin.MediaItem', 'This type of presentation is not supported.'))
|
||||
translate('PresentationPlugin.MediaItem',
|
||||
'This type of presentation is not supported.'))
|
||||
continue
|
||||
item_name = QtGui.QListWidgetItem(filename)
|
||||
item_name.setData(QtCore.Qt.UserRole, file)
|
||||
@ -238,7 +241,7 @@ class PresentationMediaItem(MediaManagerItem):
|
||||
Settings().setValue(self.settings_section + '/presentations files', self.get_file_list())
|
||||
|
||||
def generate_slide_data(self, service_item, item=None, xml_version=False,
|
||||
remote=False, context=ServiceItemContext.Service):
|
||||
remote=False, context=ServiceItemContext.Service, presentation_file=None):
|
||||
"""
|
||||
Load the relevant information for displaying the presentation in the slidecontroller. In the case of
|
||||
powerpoints, an image for each slide.
|
||||
@ -249,45 +252,93 @@ class PresentationMediaItem(MediaManagerItem):
|
||||
items = self.list_view.selectedItems()
|
||||
if len(items) > 1:
|
||||
return False
|
||||
service_item.processor = self.display_type_combo_box.currentText()
|
||||
service_item.add_capability(ItemCapabilities.ProvidesOwnDisplay)
|
||||
filename = presentation_file
|
||||
if filename is None:
|
||||
filename = items[0].data(QtCore.Qt.UserRole)
|
||||
file_type = os.path.splitext(filename)[1][1:]
|
||||
if not self.display_type_combo_box.currentText():
|
||||
return False
|
||||
for bitem in items:
|
||||
filename = bitem.data(QtCore.Qt.UserRole)
|
||||
(path, name) = os.path.split(filename)
|
||||
service_item.title = name
|
||||
if os.path.exists(filename):
|
||||
if service_item.processor == self.automatic:
|
||||
service_item.processor = self.findControllerByType(filename)
|
||||
if not service_item.processor:
|
||||
if (file_type == 'pdf' or file_type == 'xps') and context != ServiceItemContext.Service:
|
||||
service_item.add_capability(ItemCapabilities.CanMaintain)
|
||||
service_item.add_capability(ItemCapabilities.CanPreview)
|
||||
service_item.add_capability(ItemCapabilities.CanLoop)
|
||||
service_item.add_capability(ItemCapabilities.CanAppend)
|
||||
# force a nonexistent theme
|
||||
service_item.theme = -1
|
||||
for bitem in items:
|
||||
filename = presentation_file
|
||||
if filename is None:
|
||||
filename = bitem.data(QtCore.Qt.UserRole)
|
||||
(path, name) = os.path.split(filename)
|
||||
service_item.title = name
|
||||
if os.path.exists(filename):
|
||||
processor = self.findControllerByType(filename)
|
||||
if not processor:
|
||||
return False
|
||||
controller = self.controllers[service_item.processor]
|
||||
doc = controller.add_document(filename)
|
||||
if doc.get_thumbnail_path(1, True) is None:
|
||||
doc.load_presentation()
|
||||
i = 1
|
||||
img = doc.get_thumbnail_path(i, True)
|
||||
if img:
|
||||
while img:
|
||||
service_item.add_from_command(path, name, img)
|
||||
controller = self.controllers[processor]
|
||||
service_item.processor = None
|
||||
doc = controller.add_document(filename)
|
||||
if doc.get_thumbnail_path(1, True) is None or not os.path.isfile(
|
||||
os.path.join(doc.get_temp_folder(), 'mainslide001.png')):
|
||||
doc.load_presentation()
|
||||
i = 1
|
||||
imagefile = 'mainslide%03d.png' % i
|
||||
image = os.path.join(doc.get_temp_folder(), imagefile)
|
||||
while os.path.isfile(image):
|
||||
service_item.add_from_image(image, name)
|
||||
i += 1
|
||||
img = doc.get_thumbnail_path(i, True)
|
||||
imagefile = 'mainslide%03d.png' % i
|
||||
image = os.path.join(doc.get_temp_folder(), imagefile)
|
||||
doc.close_presentation()
|
||||
return True
|
||||
else:
|
||||
# File is no longer present
|
||||
if not remote:
|
||||
critical_error_message_box(translate('PresentationPlugin.MediaItem', 'Missing Presentation'),
|
||||
translate('PresentationPlugin.MediaItem',
|
||||
'The presentation %s is incomplete, please reload.') % filename)
|
||||
translate('PresentationPlugin.MediaItem',
|
||||
'The presentation %s no longer exists.') % filename)
|
||||
return False
|
||||
else:
|
||||
service_item.processor = self.display_type_combo_box.currentText()
|
||||
service_item.add_capability(ItemCapabilities.ProvidesOwnDisplay)
|
||||
for bitem in items:
|
||||
filename = bitem.data(QtCore.Qt.UserRole)
|
||||
(path, name) = os.path.split(filename)
|
||||
service_item.title = name
|
||||
if os.path.exists(filename):
|
||||
if service_item.processor == self.automatic:
|
||||
service_item.processor = self.findControllerByType(filename)
|
||||
if not service_item.processor:
|
||||
return False
|
||||
controller = self.controllers[service_item.processor]
|
||||
doc = controller.add_document(filename)
|
||||
if doc.get_thumbnail_path(1, True) is None:
|
||||
doc.load_presentation()
|
||||
i = 1
|
||||
img = doc.get_thumbnail_path(i, True)
|
||||
if img:
|
||||
while img:
|
||||
service_item.add_from_command(path, name, img)
|
||||
i += 1
|
||||
img = doc.get_thumbnail_path(i, True)
|
||||
doc.close_presentation()
|
||||
return True
|
||||
else:
|
||||
# File is no longer present
|
||||
if not remote:
|
||||
critical_error_message_box(translate('PresentationPlugin.MediaItem',
|
||||
'Missing Presentation'),
|
||||
translate('PresentationPlugin.MediaItem',
|
||||
'The presentation %s is incomplete, please reload.')
|
||||
% filename)
|
||||
return False
|
||||
else:
|
||||
# File is no longer present
|
||||
if not remote:
|
||||
critical_error_message_box(translate('PresentationPlugin.MediaItem', 'Missing Presentation'),
|
||||
translate('PresentationPlugin.MediaItem',
|
||||
'The presentation %s no longer exists.') % filename)
|
||||
return False
|
||||
else:
|
||||
# File is no longer present
|
||||
if not remote:
|
||||
critical_error_message_box(translate('PresentationPlugin.MediaItem', 'Missing Presentation'),
|
||||
translate('PresentationPlugin.MediaItem', 'The presentation %s no longer exists.') % filename)
|
||||
return False
|
||||
|
||||
def findControllerByType(self, filename):
|
||||
"""
|
||||
|
@ -28,11 +28,13 @@
|
||||
###############################################################################
|
||||
|
||||
import logging
|
||||
import copy
|
||||
|
||||
from PyQt4 import QtCore
|
||||
|
||||
from openlp.core.common import Registry
|
||||
from openlp.core.ui import HideMode
|
||||
from openlp.core.lib import ServiceItemContext, ServiceItem
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@ -69,6 +71,7 @@ class Controller(object):
|
||||
return
|
||||
self.doc.slidenumber = slide_no
|
||||
self.hide_mode = hide_mode
|
||||
log.debug('add_handler, slidenumber: %d' % slide_no)
|
||||
if self.is_live:
|
||||
if hide_mode == HideMode.Screen:
|
||||
Registry().execute('live_display_hide', HideMode.Screen)
|
||||
@ -316,6 +319,28 @@ class MessageListener(object):
|
||||
hide_mode = message[2]
|
||||
file = item.get_frame_path()
|
||||
self.handler = item.processor
|
||||
# When starting presentation from the servicemanager we convert
|
||||
# PDF/XPS-serviceitems into image-serviceitems. When started from the mediamanager
|
||||
# the conversion has already been done at this point.
|
||||
if file.endswith('.pdf') or file.endswith('.xps'):
|
||||
log.debug('Converting from pdf/xps to images for serviceitem with file %s', file)
|
||||
# Create a copy of the original item, and then clear the original item so it can be filled with images
|
||||
item_cpy = copy.copy(item)
|
||||
item.__init__(None)
|
||||
if is_live:
|
||||
self.media_item.generate_slide_data(item, item_cpy, False, False, ServiceItemContext.Live, file)
|
||||
else:
|
||||
self.media_item.generate_slide_data(item, item_cpy, False, False, ServiceItemContext.Preview, file)
|
||||
# Some of the original serviceitem attributes is needed in the new serviceitem
|
||||
item.footer = item_cpy.footer
|
||||
item.from_service = item_cpy.from_service
|
||||
item.iconic_representation = item_cpy.iconic_representation
|
||||
item.image_border = item_cpy.image_border
|
||||
item.main = item_cpy.main
|
||||
item.theme_data = item_cpy.theme_data
|
||||
# When presenting PDF or XPS, we are using the image presentation code,
|
||||
# so handler & processor is set to None, and we skip adding the handler.
|
||||
self.handler = None
|
||||
if self.handler == self.media_item.automatic:
|
||||
self.handler = self.media_item.findControllerByType(file)
|
||||
if not self.handler:
|
||||
@ -324,7 +349,12 @@ class MessageListener(object):
|
||||
controller = self.live_handler
|
||||
else:
|
||||
controller = self.preview_handler
|
||||
controller.add_handler(self.controllers[self.handler], file, hide_mode, message[3])
|
||||
# When presenting PDF or XPS, we are using the image presentation code,
|
||||
# so handler & processor is set to None, and we skip adding the handler.
|
||||
if self.handler is None:
|
||||
self.controller = controller
|
||||
else:
|
||||
controller.add_handler(self.controllers[self.handler], file, hide_mode, message[3])
|
||||
|
||||
def slide(self, message):
|
||||
"""
|
||||
|
316
openlp/plugins/presentations/lib/pdfcontroller.py
Normal file
316
openlp/plugins/presentations/lib/pdfcontroller.py
Normal 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
|
@ -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
|
||||
|
@ -43,13 +43,15 @@ from openlp.plugins.presentations.lib import PresentationController, Presentatio
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
__default_settings__ = {
|
||||
'presentations/override app': QtCore.Qt.Unchecked,
|
||||
'presentations/Impress': QtCore.Qt.Checked,
|
||||
'presentations/Powerpoint': QtCore.Qt.Checked,
|
||||
'presentations/Powerpoint Viewer': QtCore.Qt.Checked,
|
||||
'presentations/presentations files': []
|
||||
}
|
||||
__default_settings__ = {'presentations/override app': QtCore.Qt.Unchecked,
|
||||
'presentations/enable_pdf_program': QtCore.Qt.Unchecked,
|
||||
'presentations/pdf_program': '',
|
||||
'presentations/Impress': QtCore.Qt.Checked,
|
||||
'presentations/Powerpoint': QtCore.Qt.Checked,
|
||||
'presentations/Powerpoint Viewer': QtCore.Qt.Checked,
|
||||
'presentations/Pdf': QtCore.Qt.Checked,
|
||||
'presentations/presentations files': []
|
||||
}
|
||||
|
||||
|
||||
class PresentationPlugin(Plugin):
|
||||
@ -144,10 +146,10 @@ class PresentationPlugin(Plugin):
|
||||
Return information about this plugin.
|
||||
"""
|
||||
about_text = translate('PresentationPlugin', '<strong>Presentation '
|
||||
'Plugin</strong><br />The presentation plugin provides the '
|
||||
'ability to show presentations using a number of different '
|
||||
'programs. The choice of available presentation programs is '
|
||||
'available to the user in a drop down box.')
|
||||
'Plugin</strong><br />The presentation plugin provides the '
|
||||
'ability to show presentations using a number of different '
|
||||
'programs. The choice of available presentation programs is '
|
||||
'available to the user in a drop down box.')
|
||||
return about_text
|
||||
|
||||
def set_plugin_text_strings(self):
|
||||
|
@ -111,8 +111,8 @@ class AuthorsForm(QtGui.QDialog, Ui_AuthorsDialog):
|
||||
elif not self.display_edit.text():
|
||||
if critical_error_message_box(
|
||||
message=translate('SongsPlugin.AuthorsForm',
|
||||
'You have not set a display name for the author, combine the first and last names?'),
|
||||
parent=self, question=True) == QtGui.QMessageBox.Yes:
|
||||
'You have not set a display name for the author, combine the first and last names?'),
|
||||
parent=self, question=True) == QtGui.QMessageBox.Yes:
|
||||
self.display_edit.setText(self.first_name_edit.text() + ' ' + self.last_name_edit.text())
|
||||
return QtGui.QDialog.accept(self)
|
||||
else:
|
||||
|
@ -47,8 +47,8 @@ log = logging.getLogger(__name__)
|
||||
|
||||
class DuplicateSongRemovalForm(OpenLPWizard):
|
||||
"""
|
||||
This is the Duplicate Song Removal Wizard. It provides functionality to
|
||||
search for and remove duplicate songs in the database.
|
||||
This is the Duplicate Song Removal Wizard. It provides functionality to search for and remove duplicate songs
|
||||
in the database.
|
||||
"""
|
||||
log.info('DuplicateSongRemovalForm loaded')
|
||||
|
||||
@ -56,19 +56,16 @@ class DuplicateSongRemovalForm(OpenLPWizard):
|
||||
"""
|
||||
Instantiate the wizard, and run any extra setup we need to.
|
||||
|
||||
``parent``
|
||||
The QWidget-derived parent of the wizard.
|
||||
|
||||
``plugin``
|
||||
The songs plugin.
|
||||
:param plugin: The songs plugin.
|
||||
"""
|
||||
self.duplicate_song_list = []
|
||||
self.review_current_count = 0
|
||||
self.review_total_count = 0
|
||||
# Used to interrupt ongoing searches when cancel is clicked.
|
||||
self.break_search = False
|
||||
super(DuplicateSongRemovalForm, self).__init__(Registry().get('main_window'),
|
||||
plugin, 'duplicateSongRemovalWizard', ':/wizards/wizard_duplicateremoval.bmp', False)
|
||||
super(DuplicateSongRemovalForm, self).__init__(
|
||||
Registry().get('main_window'), plugin, 'duplicateSongRemovalWizard', ':/wizards/wizard_duplicateremoval.bmp'
|
||||
, False)
|
||||
self.setMinimumWidth(730)
|
||||
|
||||
def custom_signals(self):
|
||||
@ -127,30 +124,31 @@ class DuplicateSongRemovalForm(OpenLPWizard):
|
||||
"""
|
||||
self.setWindowTitle(translate('Wizard', 'Wizard'))
|
||||
self.title_label.setText(WizardStrings.HeaderStyle % translate('OpenLP.Ui',
|
||||
'Welcome to the Duplicate Song Removal Wizard'))
|
||||
self.information_label.setText(translate("Wizard",
|
||||
'This wizard will help you to remove duplicate songs from the song database. You will have a chance to '
|
||||
'review every potential duplicate song before it is deleted. So no songs will be deleted without your '
|
||||
'explicit approval.'))
|
||||
'Welcome to the Duplicate Song Removal Wizard'))
|
||||
self.information_label.setText(
|
||||
translate("Wizard",
|
||||
'This wizard will help you to remove duplicate songs from the song database. You will have a '
|
||||
'chance to review every potential duplicate song before it is deleted. So no songs will be '
|
||||
'deleted without your explicit approval.'))
|
||||
self.searching_page.setTitle(translate('Wizard', 'Searching for duplicate songs.'))
|
||||
self.searching_page.setSubTitle(translate('Wizard', 'Please wait while your songs database is analyzed.'))
|
||||
self.update_review_counter_text()
|
||||
self.review_page.setSubTitle(translate('Wizard',
|
||||
'Here you can decide which songs to remove and which ones to keep.'))
|
||||
'Here you can decide which songs to remove and which ones to keep.'))
|
||||
|
||||
def update_review_counter_text(self):
|
||||
"""
|
||||
Set the wizard review page header text.
|
||||
"""
|
||||
self.review_page.setTitle(translate('Wizard', 'Review duplicate songs (%s/%s)') % \
|
||||
(self.review_current_count, self.review_total_count))
|
||||
self.review_page.setTitle(
|
||||
translate('Wizard', 'Review duplicate songs (%s/%s)') %
|
||||
(self.review_current_count, self.review_total_count))
|
||||
|
||||
def custom_page_changed(self, page_id):
|
||||
"""
|
||||
Called when changing the wizard page.
|
||||
|
||||
``page_id``
|
||||
ID of the page the wizard changed to.
|
||||
:param page_id: ID of the page the wizard changed to.
|
||||
"""
|
||||
# Hide back button.
|
||||
self.button(QtGui.QWizard.BackButton).hide()
|
||||
@ -172,11 +170,11 @@ class DuplicateSongRemovalForm(OpenLPWizard):
|
||||
for outer_song_counter in range(max_songs - 1):
|
||||
for inner_song_counter in range(outer_song_counter + 1, max_songs):
|
||||
if songs_probably_equal(songs[outer_song_counter], songs[inner_song_counter]):
|
||||
duplicate_added = self.add_duplicates_to_song_list(songs[outer_song_counter],
|
||||
songs[inner_song_counter])
|
||||
duplicate_added = self.add_duplicates_to_song_list(
|
||||
songs[outer_song_counter], songs[inner_song_counter])
|
||||
if duplicate_added:
|
||||
self.found_duplicates_edit.appendPlainText(songs[outer_song_counter].title + " = " +
|
||||
songs[inner_song_counter].title)
|
||||
self.found_duplicates_edit.appendPlainText(
|
||||
songs[outer_song_counter].title + " = " + songs[inner_song_counter].title)
|
||||
self.duplicate_search_progress_bar.setValue(self.duplicate_search_progress_bar.value() + 1)
|
||||
# The call to process_events() will keep the GUI responsive.
|
||||
self.application.process_events()
|
||||
@ -200,23 +198,20 @@ class DuplicateSongRemovalForm(OpenLPWizard):
|
||||
self.button(QtGui.QWizard.FinishButton).setEnabled(True)
|
||||
self.button(QtGui.QWizard.NextButton).hide()
|
||||
self.button(QtGui.QWizard.CancelButton).hide()
|
||||
QtGui.QMessageBox.information(self, translate('Wizard', 'Information'),
|
||||
QtGui.QMessageBox.information(
|
||||
self, translate('Wizard', 'Information'),
|
||||
translate('Wizard', 'No duplicate songs have been found in the database.'),
|
||||
QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Ok))
|
||||
|
||||
def add_duplicates_to_song_list(self, search_song, duplicate_song):
|
||||
"""
|
||||
Inserts a song duplicate (two similar songs) to the duplicate song list.
|
||||
If one of the two songs is already part of the duplicate song list,
|
||||
don't add another duplicate group but add the other song to that group.
|
||||
Returns True if at least one of the songs was added, False if both were already
|
||||
member of a group.
|
||||
If one of the two songs is already part of the duplicate song list, don't add another duplicate group but
|
||||
add the other song to that group.
|
||||
Returns True if at least one of the songs was added, False if both were already member of a group.
|
||||
|
||||
``search_song``
|
||||
The song we searched the duplicate for.
|
||||
|
||||
``duplicate_song``
|
||||
The duplicate song.
|
||||
:param search_song: The song we searched the duplicate for.
|
||||
:param duplicate_song: The duplicate song.
|
||||
"""
|
||||
duplicate_group_found = False
|
||||
duplicate_added = False
|
||||
@ -259,8 +254,8 @@ class DuplicateSongRemovalForm(OpenLPWizard):
|
||||
|
||||
def validateCurrentPage(self):
|
||||
"""
|
||||
Controls whether we should switch to the next wizard page. This method loops
|
||||
on the review page as long as there are more song duplicates to review.
|
||||
Controls whether we should switch to the next wizard page. This method loops on the review page as long as
|
||||
there are more song duplicates to review.
|
||||
"""
|
||||
if self.currentId() == self.review_page_id:
|
||||
# As long as it's not the last duplicate list entry we revisit the review page.
|
||||
@ -273,12 +268,10 @@ class DuplicateSongRemovalForm(OpenLPWizard):
|
||||
|
||||
def remove_button_clicked(self, song_review_widget):
|
||||
"""
|
||||
Removes a song from the database, removes the GUI element representing the
|
||||
song on the review page, and disable the remove button if only one duplicate
|
||||
is left.
|
||||
Removes a song from the database, removes the GUI element representing the song on the review page, and
|
||||
disable the remove button if only one duplicate is left.
|
||||
|
||||
``song_review_widget``
|
||||
The SongReviewWidget whose song we should delete.
|
||||
:param song_review_widget: The SongReviewWidget whose song we should delete.
|
||||
"""
|
||||
# Remove song from duplicate song list.
|
||||
self.duplicate_song_list[-1].remove(song_review_widget.song)
|
||||
@ -315,9 +308,8 @@ class DuplicateSongRemovalForm(OpenLPWizard):
|
||||
|
||||
def process_current_duplicate_entry(self):
|
||||
"""
|
||||
Update the review counter in the wizard header, add song widgets for
|
||||
the current duplicate group to review, if it's the last
|
||||
duplicate song group, hide the "next" button and show the "finish" button.
|
||||
Update the review counter in the wizard header, add song widgets for the current duplicate group to review,
|
||||
if it's the last duplicate song group, hide the "next" button and show the "finish" button.
|
||||
"""
|
||||
# Update the counter.
|
||||
self.review_current_count = self.review_total_count - (len(self.duplicate_song_list) - 1)
|
||||
|
@ -154,8 +154,8 @@ class Ui_EditSongDialog(object):
|
||||
self.topics_layout.setObjectName('topics_layout')
|
||||
self.topic_add_layout = QtGui.QHBoxLayout()
|
||||
self.topic_add_layout.setObjectName('topic_add_layout')
|
||||
self.topicsComboBox = create_combo_box(self.topics_group_box, 'topicsComboBox')
|
||||
self.topic_add_layout.addWidget(self.topicsComboBox)
|
||||
self.topics_combo_box = create_combo_box(self.topics_group_box, 'topics_combo_box')
|
||||
self.topic_add_layout.addWidget(self.topics_combo_box)
|
||||
self.topic_add_button = QtGui.QPushButton(self.topics_group_box)
|
||||
self.topic_add_button.setObjectName('topic_add_button')
|
||||
self.topic_add_layout.addWidget(self.topic_add_button)
|
||||
@ -297,7 +297,7 @@ class Ui_EditSongDialog(object):
|
||||
self.verse_edit_all_button.setText(translate('SongsPlugin.EditSongForm', 'Ed&it All'))
|
||||
self.verse_delete_button.setText(UiStrings().Delete)
|
||||
self.song_tab_widget.setTabText(self.song_tab_widget.indexOf(self.lyrics_tab),
|
||||
translate('SongsPlugin.EditSongForm', 'Title && Lyrics'))
|
||||
translate('SongsPlugin.EditSongForm', 'Title && Lyrics'))
|
||||
self.authors_group_box.setTitle(SongStrings.Authors)
|
||||
self.author_add_button.setText(translate('SongsPlugin.EditSongForm', '&Add to Song'))
|
||||
self.author_remove_button.setText(translate('SongsPlugin.EditSongForm', '&Remove'))
|
||||
@ -309,7 +309,7 @@ class Ui_EditSongDialog(object):
|
||||
self.song_book_name_label.setText(translate('SongsPlugin.EditSongForm', 'Book:'))
|
||||
self.song_book_number_label.setText(translate('SongsPlugin.EditSongForm', 'Number:'))
|
||||
self.song_tab_widget.setTabText(self.song_tab_widget.indexOf(self.authors_tab),
|
||||
translate('SongsPlugin.EditSongForm', 'Authors, Topics && Song Book'))
|
||||
translate('SongsPlugin.EditSongForm', 'Authors, Topics && Song Book'))
|
||||
self.theme_group_box.setTitle(UiStrings().Theme)
|
||||
self.theme_add_button.setText(translate('SongsPlugin.EditSongForm', 'New &Theme'))
|
||||
self.rights_group_box.setTitle(translate('SongsPlugin.EditSongForm', 'Copyright Information'))
|
||||
@ -317,9 +317,9 @@ class Ui_EditSongDialog(object):
|
||||
self.ccli_label.setText(UiStrings().CCLINumberLabel)
|
||||
self.comments_group_box.setTitle(translate('SongsPlugin.EditSongForm', 'Comments'))
|
||||
self.song_tab_widget.setTabText(self.song_tab_widget.indexOf(self.theme_tab),
|
||||
translate('SongsPlugin.EditSongForm', 'Theme, Copyright Info && Comments'))
|
||||
translate('SongsPlugin.EditSongForm', 'Theme, Copyright Info && Comments'))
|
||||
self.song_tab_widget.setTabText(self.song_tab_widget.indexOf(self.audio_tab),
|
||||
translate('SongsPlugin.EditSongForm', 'Linked Audio'))
|
||||
translate('SongsPlugin.EditSongForm', 'Linked Audio'))
|
||||
self.from_file_button.setText(translate('SongsPlugin.EditSongForm', 'Add &File(s)'))
|
||||
self.from_media_button.setText(translate('SongsPlugin.EditSongForm', 'Add &Media'))
|
||||
self.audio_remove_button.setText(translate('SongsPlugin.EditSongForm', '&Remove'))
|
||||
|
@ -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,
|
||||
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 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,18 +521,18 @@ 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.'))
|
||||
'from the list, or type in a new author and click the "Add Author to Song" button to add '
|
||||
'the new author.'))
|
||||
|
||||
def on_authors_list_view_clicked(self):
|
||||
"""
|
||||
@ -540,10 +551,11 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
|
||||
self.authors_list_view.takeItem(row)
|
||||
|
||||
def on_topic_add_button_clicked(self):
|
||||
item = int(self.topicsComboBox.currentIndex())
|
||||
text = self.topicsComboBox.currentText()
|
||||
item = int(self.topics_combo_box.currentIndex())
|
||||
text = self.topics_combo_box.currentText()
|
||||
if item == 0 and text:
|
||||
if QtGui.QMessageBox.question(self, translate('SongsPlugin.EditSongForm', 'Add Topic'),
|
||||
if QtGui.QMessageBox.question(
|
||||
self, translate('SongsPlugin.EditSongForm', 'Add Topic'),
|
||||
translate('SongsPlugin.EditSongForm', 'This topic does not exist, do you want to add it?'),
|
||||
QtGui.QMessageBox.Yes | QtGui.QMessageBox.No, QtGui.QMessageBox.Yes) == QtGui.QMessageBox.Yes:
|
||||
topic = Topic.populate(name=text)
|
||||
@ -552,25 +564,26 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
|
||||
topic_item.setData(QtCore.Qt.UserRole, topic.id)
|
||||
self.topics_list_view.addItem(topic_item)
|
||||
self.load_topics()
|
||||
self.topicsComboBox.setCurrentIndex(0)
|
||||
self.topics_combo_box.setCurrentIndex(0)
|
||||
else:
|
||||
return
|
||||
elif item > 0:
|
||||
item_id = (self.topicsComboBox.itemData(item))
|
||||
item_id = (self.topics_combo_box.itemData(item))
|
||||
topic = self.manager.get_object(Topic, item_id)
|
||||
if self.topics_list_view.findItems(str(topic.name),
|
||||
QtCore.Qt.MatchExactly):
|
||||
if self.topics_list_view.findItems(str(topic.name), QtCore.Qt.MatchExactly):
|
||||
critical_error_message_box(
|
||||
message=translate('SongsPlugin.EditSongForm', 'This topic is already in the list.'))
|
||||
else:
|
||||
topic_item = QtGui.QListWidgetItem(str(topic.name))
|
||||
topic_item.setData(QtCore.Qt.UserRole, topic.id)
|
||||
self.topics_list_view.addItem(topic_item)
|
||||
self.topicsComboBox.setCurrentIndex(0)
|
||||
self.topics_combo_box.setCurrentIndex(0)
|
||||
else:
|
||||
QtGui.QMessageBox.warning(self, UiStrings().NISs,
|
||||
QtGui.QMessageBox.warning(
|
||||
self, UiStrings().NISs,
|
||||
translate('SongsPlugin.EditSongForm', 'You have not selected a valid topic. Either select a topic '
|
||||
'from the list, or type in a new topic and click the "Add Topic to Song" button to add the new topic.'))
|
||||
'from the list, or type in a new topic and click the "Add Topic to Song" button to add the '
|
||||
'new topic.'))
|
||||
|
||||
def on_topic_list_view_clicked(self):
|
||||
self.topic_remove_button.setEnabled(True)
|
||||
@ -588,7 +601,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
|
||||
def on_verse_add_button_clicked(self):
|
||||
self.verse_form.set_verse('', True)
|
||||
if self.verse_form.exec_():
|
||||
after_text, verse_tag, verse_num = self.verse_form.get_verse()
|
||||
after_text, verse_tag, verse_num = self.verse_form.get_verse
|
||||
verse_def = '%s%s' % (verse_tag, verse_num)
|
||||
item = QtGui.QTableWidgetItem(after_text)
|
||||
item.setData(QtCore.Qt.UserRole, verse_def)
|
||||
@ -606,7 +619,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
|
||||
verse_id = item.data(QtCore.Qt.UserRole)
|
||||
self.verse_form.set_verse(temp_text, True, verse_id)
|
||||
if self.verse_form.exec_():
|
||||
after_text, verse_tag, verse_num = self.verse_form.get_verse()
|
||||
after_text, verse_tag, verse_num = self.verse_form.get_verse
|
||||
verse_def = '%s%s' % (verse_tag, verse_num)
|
||||
item.setData(QtCore.Qt.UserRole, verse_def)
|
||||
item.setText(after_text)
|
||||
@ -628,6 +641,11 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
|
||||
self.on_verse_order_text_changed(self.verse_order_edit.text())
|
||||
|
||||
def on_verse_edit_all_button_clicked(self):
|
||||
"""
|
||||
Verse edit all button (save) pressed
|
||||
|
||||
:return:
|
||||
"""
|
||||
verse_list = ''
|
||||
if self.verse_list_widget.rowCount() > 0:
|
||||
for row in range(self.verse_list_widget.rowCount()):
|
||||
@ -643,7 +661,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
|
||||
self.verse_form.set_verse('')
|
||||
if not self.verse_form.exec_():
|
||||
return
|
||||
verse_list = self.verse_form.get_all_verses()
|
||||
verse_list = self.verse_form.get_all_verses
|
||||
verse_list = str(verse_list.replace('\r\n', '\n'))
|
||||
self.verse_list_widget.clear()
|
||||
self.verse_list_widget.setRowCount(0)
|
||||
@ -686,6 +704,10 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
|
||||
self.on_verse_order_text_changed(self.verse_order_edit.text())
|
||||
|
||||
def on_verse_delete_button_clicked(self):
|
||||
"""
|
||||
Verse Delete button pressed
|
||||
|
||||
"""
|
||||
self.verse_list_widget.removeRow(self.verse_list_widget.currentRow())
|
||||
if not self.verse_list_widget.selectedItems():
|
||||
self.verse_edit_button.setEnabled(False)
|
||||
@ -696,8 +718,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
|
||||
Checks if the verse order is complete or missing. Shows a error message according to the state of the verse
|
||||
order.
|
||||
|
||||
``text``
|
||||
The text of the verse order edit (ignored).
|
||||
:param text: The text of the verse order edit (ignored).
|
||||
"""
|
||||
# Extract all verses which were used in the order.
|
||||
verses_in_order = self._extract_verse_order(self.verse_order_edit.text())
|
||||
@ -719,6 +740,9 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
|
||||
self.warning_label.setText(label_text)
|
||||
|
||||
def on_copyright_insert_button_triggered(self):
|
||||
"""
|
||||
Copyright insert button pressed
|
||||
"""
|
||||
text = self.copyright_edit.text()
|
||||
pos = self.copyright_edit.cursorPosition()
|
||||
sign = SongStrings.CopyrightSymbol
|
||||
@ -728,6 +752,9 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
|
||||
self.copyright_edit.setCursorPosition(pos + len(sign))
|
||||
|
||||
def on_maintenance_button_clicked(self):
|
||||
"""
|
||||
Maintenance button pressed
|
||||
"""
|
||||
temp_song_book = None
|
||||
item = int(self.song_book_combo_box.currentIndex())
|
||||
text = self.song_book_combo_box.currentText()
|
||||
@ -745,8 +772,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
|
||||
Save and Preview button clicked.
|
||||
The Song is valid so as the plugin to add it to preview to see.
|
||||
|
||||
``button``
|
||||
A button (QPushButton).
|
||||
:param button: A button (QPushButton).
|
||||
"""
|
||||
log.debug('onPreview')
|
||||
if button.objectName() == 'preview_button':
|
||||
@ -758,9 +784,9 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
|
||||
Loads file(s) from the filesystem.
|
||||
"""
|
||||
filters = '%s (*)' % UiStrings().AllFiles
|
||||
filenames = FileDialog.getOpenFileNames(self,
|
||||
translate('SongsPlugin.EditSongForm', 'Open File(s)'), '', filters)
|
||||
for filename in filenames:
|
||||
file_names = FileDialog.getOpenFileNames(self, translate('SongsPlugin.EditSongForm', 'Open File(s)'), '',
|
||||
filters)
|
||||
for filename in file_names:
|
||||
item = QtGui.QListWidgetItem(os.path.split(str(filename))[1])
|
||||
item.setData(QtCore.Qt.UserRole, filename)
|
||||
self.audio_list_widget.addItem(item)
|
||||
@ -770,7 +796,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
|
||||
Loads file(s) from the media plugin.
|
||||
"""
|
||||
if self.media_form.exec_():
|
||||
for filename in self.media_form.getSelectedFiles():
|
||||
for filename in self.media_form.get_selected_files():
|
||||
item = QtGui.QListWidgetItem(os.path.split(str(filename))[1])
|
||||
item.setData(QtCore.Qt.UserRole, filename)
|
||||
self.audio_list_widget.addItem(item)
|
||||
@ -814,7 +840,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
|
||||
|
||||
def clear_caches(self):
|
||||
"""
|
||||
Free up autocompletion memory on dialog exit
|
||||
Free up auto-completion memory on dialog exit
|
||||
"""
|
||||
log.debug('SongEditForm.clearCaches')
|
||||
self.authors = []
|
||||
@ -826,7 +852,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
|
||||
"""
|
||||
Exit Dialog and do not save
|
||||
"""
|
||||
log.debug ('SongEditForm.reject')
|
||||
log.debug('SongEditForm.reject')
|
||||
self.clear_caches()
|
||||
QtGui.QDialog.reject(self)
|
||||
|
||||
@ -843,13 +869,10 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
|
||||
|
||||
def save_song(self, preview=False):
|
||||
"""
|
||||
Get all the data from the widgets on the form, and then save it to the
|
||||
database. The form has been validated and all reference items
|
||||
(Authors, Books and Topics) have been saved before this function is
|
||||
called.
|
||||
Get all the data from the widgets on the form, and then save it to the database. The form has been validated
|
||||
and all reference items (Authors, Books and Topics) have been saved before this function is called.
|
||||
|
||||
``preview``
|
||||
Should be ``True`` if the song is also previewed (boolean).
|
||||
:param preview: Should be ``True`` if the song is also previewed (boolean).
|
||||
"""
|
||||
# The Song() assignment. No database calls should be made while a
|
||||
# Song() is in a partially complete state.
|
||||
@ -863,9 +886,9 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
|
||||
self.song.search_lyrics = ''
|
||||
self.song.verse_order = ''
|
||||
self.song.comments = self.comments_edit.toPlainText()
|
||||
ordertext = self.verse_order_edit.text()
|
||||
order_text = self.verse_order_edit.text()
|
||||
order = []
|
||||
for item in ordertext.split():
|
||||
for item in order_text.split():
|
||||
verse_tag = VerseType.tags[VerseType.from_translated_tag(item[0])]
|
||||
verse_num = item[1:].lower()
|
||||
order.append('%s%s' % (verse_tag, verse_num))
|
||||
@ -874,8 +897,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
|
||||
self.song.song_number = self.song_book_number_edit.text()
|
||||
book_name = self.song_book_combo_box.currentText()
|
||||
if book_name:
|
||||
self.song.book = self.manager.get_object_filtered(Book,
|
||||
Book.name == book_name)
|
||||
self.song.book = self.manager.get_object_filtered(Book, Book.name == book_name)
|
||||
else:
|
||||
self.song.book = None
|
||||
theme_name = self.theme_combo_box.currentText()
|
||||
@ -887,15 +909,15 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
|
||||
self.song.authors = []
|
||||
for row in range(self.authors_list_view.count()):
|
||||
item = self.authors_list_view.item(row)
|
||||
authorId = (item.data(QtCore.Qt.UserRole))
|
||||
author = self.manager.get_object(Author, authorId)
|
||||
author_id = (item.data(QtCore.Qt.UserRole))
|
||||
author = self.manager.get_object(Author, author_id)
|
||||
if author is not None:
|
||||
self.song.authors.append(author)
|
||||
self.song.topics = []
|
||||
for row in range(self.topics_list_view.count()):
|
||||
item = self.topics_list_view.item(row)
|
||||
topicId = (item.data(QtCore.Qt.UserRole))
|
||||
topic = self.manager.get_object(Topic, topicId)
|
||||
topic_id = (item.data(QtCore.Qt.UserRole))
|
||||
topic = self.manager.get_object(Topic, topic_id)
|
||||
if topic is not None:
|
||||
self.song.topics.append(topic)
|
||||
# Save the song here because we need a valid id for the audio files.
|
||||
@ -904,7 +926,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
|
||||
audio_files = [a.file_name for a in self.song.media_files]
|
||||
log.debug(audio_files)
|
||||
save_path = os.path.join(AppLocation.get_section_data_path(self.media_item.plugin.name), 'audio',
|
||||
str(self.song.id))
|
||||
str(self.song.id))
|
||||
check_directory_exists(save_path)
|
||||
self.song.media_files = []
|
||||
files = []
|
||||
@ -912,8 +934,8 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
|
||||
item = self.audio_list_widget.item(row)
|
||||
filename = item.data(QtCore.Qt.UserRole)
|
||||
if not filename.startswith(save_path):
|
||||
oldfile, filename = filename, os.path.join(save_path, os.path.split(filename)[1])
|
||||
shutil.copyfile(oldfile, filename)
|
||||
old_file, filename = filename, os.path.join(save_path, os.path.split(filename)[1])
|
||||
shutil.copyfile(old_file, filename)
|
||||
files.append(filename)
|
||||
media_file = MediaFile()
|
||||
media_file.file_name = filename
|
||||
|
@ -86,4 +86,4 @@ class Ui_EditVerseDialog(object):
|
||||
self.split_button.setToolTip(UiStrings().SplitToolTip)
|
||||
self.insert_button.setText(translate('SongsPlugin.EditVerseForm', '&Insert'))
|
||||
self.insert_button.setToolTip(translate('SongsPlugin.EditVerseForm',
|
||||
'Split a slide into two by inserting a verse splitter.'))
|
||||
'Split a slide into two by inserting a verse splitter.'))
|
||||
|
@ -50,16 +50,18 @@ class EditVerseForm(QtGui.QDialog, Ui_EditVerseDialog):
|
||||
"""
|
||||
super(EditVerseForm, self).__init__(parent)
|
||||
self.setupUi(self)
|
||||
self.verse_text_edit.customContextMenuRequested.connect(self.context_menu)
|
||||
self.insert_button.clicked.connect(self.on_insert_button_clicked)
|
||||
self.split_button.clicked.connect(self.on_split_button_clicked)
|
||||
self.verse_text_edit.cursorPositionChanged.connect(self.on_cursor_position_changed)
|
||||
self.verse_type_combo_box.currentIndexChanged.connect(self.on_verse_type_combo_box_changed)
|
||||
|
||||
def context_menu(self, point):
|
||||
item = self.serviceManagerList.itemAt(point)
|
||||
|
||||
def insert_verse(self, verse_tag, verse_num=1):
|
||||
"""
|
||||
Insert a verse
|
||||
|
||||
:param verse_tag: The verse tag
|
||||
:param verse_num: The verse number
|
||||
"""
|
||||
if self.verse_text_edit.textCursor().columnNumber() != 0:
|
||||
self.verse_text_edit.insertPlainText('\n')
|
||||
verse_tag = VerseType.translated_name(verse_tag)
|
||||
@ -67,24 +69,36 @@ class EditVerseForm(QtGui.QDialog, Ui_EditVerseDialog):
|
||||
self.verse_text_edit.setFocus()
|
||||
|
||||
def on_split_button_clicked(self):
|
||||
"""
|
||||
The split button has been pressed
|
||||
"""
|
||||
text = self.verse_text_edit.toPlainText()
|
||||
position = self.verse_text_edit.textCursor().position()
|
||||
insert_string = '[---]'
|
||||
if position and text[position-1] != '\n':
|
||||
insert_string = '\n' + insert_string
|
||||
if position == len(text) or text[position] != '\n':
|
||||
if position == len(text) or text[position] != '\n':
|
||||
insert_string += '\n'
|
||||
self.verse_text_edit.insertPlainText(insert_string)
|
||||
self.verse_text_edit.setFocus()
|
||||
|
||||
def on_insert_button_clicked(self):
|
||||
"""
|
||||
The insert button has been pressed
|
||||
"""
|
||||
verse_type_index = self.verse_type_combo_box.currentIndex()
|
||||
self.insert_verse(VerseType.tags[verse_type_index], self.verse_number_box.value())
|
||||
|
||||
def on_verse_type_combo_box_changed(self):
|
||||
"""
|
||||
The verse type combo has been changed
|
||||
"""
|
||||
self.update_suggested_verse_number()
|
||||
|
||||
def on_cursor_position_changed(self):
|
||||
"""
|
||||
The cursor position has been changed
|
||||
"""
|
||||
self.update_suggested_verse_number()
|
||||
|
||||
def update_suggested_verse_number(self):
|
||||
@ -117,6 +131,13 @@ class EditVerseForm(QtGui.QDialog, Ui_EditVerseDialog):
|
||||
self.verse_number_box.setValue(verse_num)
|
||||
|
||||
def set_verse(self, text, single=False, tag='%s1' % VerseType.tags[VerseType.Verse]):
|
||||
"""
|
||||
Save the verse
|
||||
|
||||
:param text: The text
|
||||
:param single: is this a single verse
|
||||
:param tag: The tag
|
||||
"""
|
||||
self.has_single_verse = single
|
||||
if single:
|
||||
verse_type_index = VerseType.from_tag(tag[0], None)
|
||||
@ -136,10 +157,20 @@ class EditVerseForm(QtGui.QDialog, Ui_EditVerseDialog):
|
||||
self.verse_text_edit.moveCursor(QtGui.QTextCursor.End)
|
||||
|
||||
def get_verse(self):
|
||||
"""
|
||||
Extract the verse text
|
||||
|
||||
:return: The text
|
||||
"""
|
||||
return self.verse_text_edit.toPlainText(), VerseType.tags[self.verse_type_combo_box.currentIndex()], \
|
||||
str(self.verse_number_box.value())
|
||||
|
||||
def get_all_verses(self):
|
||||
"""
|
||||
Extract all the verses
|
||||
|
||||
:return: The text
|
||||
"""
|
||||
text = self.verse_text_edit.toPlainText()
|
||||
if not text.startswith('---['):
|
||||
text = '---[%s:1]---\n%s' % (VerseType.translated_names[VerseType.Verse], text)
|
||||
|
@ -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.'))
|
||||
|
||||
|
@ -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()]
|
||||
|
||||
|
@ -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()
|
||||
|
@ -55,11 +55,8 @@ class SongExportForm(OpenLPWizard):
|
||||
"""
|
||||
Instantiate the wizard, and run any extra setup we need to.
|
||||
|
||||
``parent``
|
||||
The QWidget-derived parent of the wizard.
|
||||
|
||||
``plugin``
|
||||
The songs plugin.
|
||||
:param parent: The QWidget-derived parent of the wizard.
|
||||
:param plugin: The songs plugin.
|
||||
"""
|
||||
super(SongExportForm, self).__init__(parent, plugin, 'song_export_wizard', ':/wizards/wizard_exportsong.bmp')
|
||||
self.stop_export_flag = False
|
||||
@ -82,71 +79,71 @@ class SongExportForm(OpenLPWizard):
|
||||
"""
|
||||
Song wizard specific signals.
|
||||
"""
|
||||
self.availableListWidget.itemActivated.connect(self.onItemActivated)
|
||||
self.searchLineEdit.textEdited.connect(self.onSearchLineEditChanged)
|
||||
self.uncheckButton.clicked.connect(self.onUncheckButtonClicked)
|
||||
self.checkButton.clicked.connect(self.onCheckButtonClicked)
|
||||
self.directoryButton.clicked.connect(self.onDirectoryButtonClicked)
|
||||
self.available_list_widget.itemActivated.connect(self.on_item_activated)
|
||||
self.search_line_edit.textEdited.connect(self.on_search_line_edit_changed)
|
||||
self.uncheck_button.clicked.connect(self.on_uncheck_button_clicked)
|
||||
self.check_button.clicked.connect(self.on_check_button_clicked)
|
||||
self.directory_button.clicked.connect(self.on_directory_button_clicked)
|
||||
|
||||
def add_custom_pages(self):
|
||||
"""
|
||||
Add song wizard specific pages.
|
||||
"""
|
||||
# The page with all available songs.
|
||||
self.availableSongsPage = QtGui.QWizardPage()
|
||||
self.availableSongsPage.setObjectName('availableSongsPage')
|
||||
self.availableSongsLayout = QtGui.QHBoxLayout(self.availableSongsPage)
|
||||
self.availableSongsLayout.setObjectName('availableSongsLayout')
|
||||
self.verticalLayout = QtGui.QVBoxLayout()
|
||||
self.verticalLayout.setObjectName('verticalLayout')
|
||||
self.availableListWidget = QtGui.QListWidget(self.availableSongsPage)
|
||||
self.availableListWidget.setObjectName('availableListWidget')
|
||||
self.verticalLayout.addWidget(self.availableListWidget)
|
||||
self.horizontalLayout = QtGui.QHBoxLayout()
|
||||
self.horizontalLayout.setObjectName('horizontalLayout')
|
||||
self.searchLabel = QtGui.QLabel(self.availableSongsPage)
|
||||
self.searchLabel.setObjectName('searchLabel')
|
||||
self.horizontalLayout.addWidget(self.searchLabel)
|
||||
self.searchLineEdit = QtGui.QLineEdit(self.availableSongsPage)
|
||||
self.searchLineEdit.setObjectName('searchLineEdit')
|
||||
self.horizontalLayout.addWidget(self.searchLineEdit)
|
||||
self.available_songs_page = QtGui.QWizardPage()
|
||||
self.available_songs_page.setObjectName('available_songs_page')
|
||||
self.available_songs_layout = QtGui.QHBoxLayout(self.available_songs_page)
|
||||
self.available_songs_layout.setObjectName('available_songs_layout')
|
||||
self.vertical_layout = QtGui.QVBoxLayout()
|
||||
self.vertical_layout.setObjectName('vertical_layout')
|
||||
self.available_list_widget = QtGui.QListWidget(self.available_songs_page)
|
||||
self.available_list_widget.setObjectName('available_list_widget')
|
||||
self.vertical_layout.addWidget(self.available_list_widget)
|
||||
self.horizontal_layout = QtGui.QHBoxLayout()
|
||||
self.horizontal_layout.setObjectName('horizontal_layout')
|
||||
self.search_label = QtGui.QLabel(self.available_songs_page)
|
||||
self.search_label.setObjectName('search_label')
|
||||
self.horizontal_layout.addWidget(self.search_label)
|
||||
self.search_line_edit = QtGui.QLineEdit(self.available_songs_page)
|
||||
self.search_line_edit.setObjectName('search_line_edit')
|
||||
self.horizontal_layout.addWidget(self.search_line_edit)
|
||||
spacer_item = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
|
||||
self.horizontalLayout.addItem(spacer_item)
|
||||
self.uncheckButton = QtGui.QPushButton(self.availableSongsPage)
|
||||
self.uncheckButton.setObjectName('uncheckButton')
|
||||
self.horizontalLayout.addWidget(self.uncheckButton)
|
||||
self.checkButton = QtGui.QPushButton(self.availableSongsPage)
|
||||
self.checkButton.setObjectName('selectButton')
|
||||
self.horizontalLayout.addWidget(self.checkButton)
|
||||
self.verticalLayout.addLayout(self.horizontalLayout)
|
||||
self.availableSongsLayout.addLayout(self.verticalLayout)
|
||||
self.addPage(self.availableSongsPage)
|
||||
self.horizontal_layout.addItem(spacer_item)
|
||||
self.uncheck_button = QtGui.QPushButton(self.available_songs_page)
|
||||
self.uncheck_button.setObjectName('uncheck_button')
|
||||
self.horizontal_layout.addWidget(self.uncheck_button)
|
||||
self.check_button = QtGui.QPushButton(self.available_songs_page)
|
||||
self.check_button.setObjectName('selectButton')
|
||||
self.horizontal_layout.addWidget(self.check_button)
|
||||
self.vertical_layout.addLayout(self.horizontal_layout)
|
||||
self.available_songs_layout.addLayout(self.vertical_layout)
|
||||
self.addPage(self.available_songs_page)
|
||||
# The page with the selected songs.
|
||||
self.exportSongPage = QtGui.QWizardPage()
|
||||
self.exportSongPage.setObjectName('availableSongsPage')
|
||||
self.exportSongLayout = QtGui.QHBoxLayout(self.exportSongPage)
|
||||
self.exportSongLayout.setObjectName('exportSongLayout')
|
||||
self.gridLayout = QtGui.QGridLayout()
|
||||
self.gridLayout.setObjectName('gridLayout')
|
||||
self.selectedListWidget = QtGui.QListWidget(self.exportSongPage)
|
||||
self.selectedListWidget.setObjectName('selectedListWidget')
|
||||
self.gridLayout.addWidget(self.selectedListWidget, 1, 0, 1, 1)
|
||||
# FIXME: self.horizontalLayout is already defined above?!?!?
|
||||
self.horizontalLayout = QtGui.QHBoxLayout()
|
||||
self.horizontalLayout.setObjectName('horizontalLayout')
|
||||
self.directoryLabel = QtGui.QLabel(self.exportSongPage)
|
||||
self.directoryLabel.setObjectName('directoryLabel')
|
||||
self.horizontalLayout.addWidget(self.directoryLabel)
|
||||
self.directoryLineEdit = QtGui.QLineEdit(self.exportSongPage)
|
||||
self.directoryLineEdit.setObjectName('directoryLineEdit')
|
||||
self.horizontalLayout.addWidget(self.directoryLineEdit)
|
||||
self.directoryButton = QtGui.QToolButton(self.exportSongPage)
|
||||
self.directoryButton.setIcon(build_icon(':/exports/export_load.png'))
|
||||
self.directoryButton.setObjectName('directoryButton')
|
||||
self.horizontalLayout.addWidget(self.directoryButton)
|
||||
self.gridLayout.addLayout(self.horizontalLayout, 0, 0, 1, 1)
|
||||
self.exportSongLayout.addLayout(self.gridLayout)
|
||||
self.addPage(self.exportSongPage)
|
||||
self.export_song_page = QtGui.QWizardPage()
|
||||
self.export_song_page.setObjectName('available_songs_page')
|
||||
self.export_song_layout = QtGui.QHBoxLayout(self.export_song_page)
|
||||
self.export_song_layout.setObjectName('export_song_layout')
|
||||
self.grid_layout = QtGui.QGridLayout()
|
||||
self.grid_layout.setObjectName('grid_layout')
|
||||
self.selected_list_widget = QtGui.QListWidget(self.export_song_page)
|
||||
self.selected_list_widget.setObjectName('selected_list_widget')
|
||||
self.grid_layout.addWidget(self.selected_list_widget, 1, 0, 1, 1)
|
||||
# FIXME: self.horizontal_layout is already defined above?!?!?
|
||||
self.horizontal_layout = QtGui.QHBoxLayout()
|
||||
self.horizontal_layout.setObjectName('horizontal_layout')
|
||||
self.directory_label = QtGui.QLabel(self.export_song_page)
|
||||
self.directory_label.setObjectName('directory_label')
|
||||
self.horizontal_layout.addWidget(self.directory_label)
|
||||
self.directory_line_edit = QtGui.QLineEdit(self.export_song_page)
|
||||
self.directory_line_edit.setObjectName('directory_line_edit')
|
||||
self.horizontal_layout.addWidget(self.directory_line_edit)
|
||||
self.directory_button = QtGui.QToolButton(self.export_song_page)
|
||||
self.directory_button.setIcon(build_icon(':/exports/export_load.png'))
|
||||
self.directory_button.setObjectName('directory_button')
|
||||
self.horizontal_layout.addWidget(self.directory_button)
|
||||
self.grid_layout.addLayout(self.horizontal_layout, 0, 0, 1, 1)
|
||||
self.export_song_layout.addLayout(self.grid_layout)
|
||||
self.addPage(self.export_song_page)
|
||||
|
||||
def retranslateUi(self):
|
||||
"""
|
||||
@ -154,22 +151,23 @@ class SongExportForm(OpenLPWizard):
|
||||
"""
|
||||
self.setWindowTitle(translate('SongsPlugin.ExportWizardForm', 'Song Export Wizard'))
|
||||
self.title_label.setText(WizardStrings.HeaderStyle %
|
||||
translate('OpenLP.Ui', 'Welcome to the Song Export Wizard'))
|
||||
translate('OpenLP.Ui', 'Welcome to the Song Export Wizard'))
|
||||
self.information_label.setText(translate('SongsPlugin.ExportWizardForm', 'This wizard will help to'
|
||||
' export your songs to the open and free <strong>OpenLyrics </strong> worship song format.'))
|
||||
self.availableSongsPage.setTitle(translate('SongsPlugin.ExportWizardForm', 'Select Songs'))
|
||||
self.availableSongsPage.setSubTitle(translate('SongsPlugin.ExportWizardForm',
|
||||
'Check the songs you want to export.'))
|
||||
self.searchLabel.setText('%s:' % UiStrings().Search)
|
||||
self.uncheckButton.setText(translate('SongsPlugin.ExportWizardForm', 'Uncheck All'))
|
||||
self.checkButton.setText(translate('SongsPlugin.ExportWizardForm', 'Check All'))
|
||||
self.exportSongPage.setTitle(translate('SongsPlugin.ExportWizardForm', 'Select Directory'))
|
||||
self.exportSongPage.setSubTitle(translate('SongsPlugin.ExportWizardForm',
|
||||
'Select the directory where you want the songs to be saved.'))
|
||||
self.directoryLabel.setText(translate('SongsPlugin.ExportWizardForm', 'Directory:'))
|
||||
' export your songs to the open and free <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.search_label.setText('%s:' % UiStrings().Search)
|
||||
self.uncheck_button.setText(translate('SongsPlugin.ExportWizardForm', 'Uncheck All'))
|
||||
self.check_button.setText(translate('SongsPlugin.ExportWizardForm', 'Check All'))
|
||||
self.export_song_page.setTitle(translate('SongsPlugin.ExportWizardForm', 'Select Directory'))
|
||||
self.export_song_page.setSubTitle(translate('SongsPlugin.ExportWizardForm',
|
||||
'Select the directory where you want the songs to be saved.'))
|
||||
self.directory_label.setText(translate('SongsPlugin.ExportWizardForm', 'Directory:'))
|
||||
self.progress_page.setTitle(translate('SongsPlugin.ExportWizardForm', 'Exporting'))
|
||||
self.progress_page.setSubTitle(translate('SongsPlugin.ExportWizardForm',
|
||||
'Please wait while your songs are exported.'))
|
||||
'Please wait while your songs are exported.'))
|
||||
self.progress_label.setText(WizardStrings.Ready)
|
||||
self.progress_bar.setFormat(WizardStrings.PercentSymbolFormat)
|
||||
|
||||
@ -179,46 +177,46 @@ class SongExportForm(OpenLPWizard):
|
||||
"""
|
||||
if self.currentPage() == self.welcome_page:
|
||||
return True
|
||||
elif self.currentPage() == self.availableSongsPage:
|
||||
elif self.currentPage() == self.available_songs_page:
|
||||
items = [
|
||||
item for item in self._findListWidgetItems(
|
||||
self.availableListWidget) if item.checkState()
|
||||
item for item in self._find_list_widget_items(self.available_list_widget) if item.checkState()
|
||||
]
|
||||
if not items:
|
||||
critical_error_message_box(UiStrings().NISp,
|
||||
critical_error_message_box(
|
||||
UiStrings().NISp,
|
||||
translate('SongsPlugin.ExportWizardForm', 'You need to add at least one Song to export.'))
|
||||
return False
|
||||
self.selectedListWidget.clear()
|
||||
self.selected_list_widget.clear()
|
||||
# Add the songs to the list of selected songs.
|
||||
for item in items:
|
||||
song = QtGui.QListWidgetItem(item.text())
|
||||
song.setData(QtCore.Qt.UserRole, item.data(QtCore.Qt.UserRole))
|
||||
song.setFlags(QtCore.Qt.ItemIsEnabled)
|
||||
self.selectedListWidget.addItem(song)
|
||||
self.selected_list_widget.addItem(song)
|
||||
return True
|
||||
elif self.currentPage() == self.exportSongPage:
|
||||
if not self.directoryLineEdit.text():
|
||||
elif self.currentPage() == self.export_song_page:
|
||||
if not self.directory_line_edit.text():
|
||||
critical_error_message_box(
|
||||
translate('SongsPlugin.ExportWizardForm', 'No Save Location specified'),
|
||||
translate('SongsPlugin.ExportWizardForm', 'You need to specify a directory.'))
|
||||
return False
|
||||
return True
|
||||
elif self.currentPage() == self.progress_page:
|
||||
self.availableListWidget.clear()
|
||||
self.selectedListWidget.clear()
|
||||
self.available_list_widget.clear()
|
||||
self.selected_list_widget.clear()
|
||||
return True
|
||||
|
||||
def setDefaults(self):
|
||||
def set_defaults(self):
|
||||
"""
|
||||
Set default form values for the song export wizard.
|
||||
"""
|
||||
self.restart()
|
||||
self.finish_button.setVisible(False)
|
||||
self.cancel_button.setVisible(True)
|
||||
self.availableListWidget.clear()
|
||||
self.selectedListWidget.clear()
|
||||
self.directoryLineEdit.clear()
|
||||
self.searchLineEdit.clear()
|
||||
self.available_list_widget.clear()
|
||||
self.selected_list_widget.clear()
|
||||
self.directory_line_edit.clear()
|
||||
self.search_line_edit.clear()
|
||||
# Load the list of songs.
|
||||
self.application.set_busy_cursor()
|
||||
songs = self.plugin.manager.get_all_objects(Song)
|
||||
@ -233,7 +231,7 @@ class SongExportForm(OpenLPWizard):
|
||||
item.setData(QtCore.Qt.UserRole, song)
|
||||
item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsUserCheckable | QtCore.Qt.ItemIsEnabled)
|
||||
item.setCheckState(QtCore.Qt.Unchecked)
|
||||
self.availableListWidget.addItem(item)
|
||||
self.available_list_widget.addItem(item)
|
||||
self.application.set_normal_cursor()
|
||||
|
||||
def pre_wizard(self):
|
||||
@ -244,86 +242,81 @@ class SongExportForm(OpenLPWizard):
|
||||
self.progress_label.setText(translate('SongsPlugin.ExportWizardForm', 'Starting export...'))
|
||||
self.application.process_events()
|
||||
|
||||
def performWizard(self):
|
||||
def perform_wizard(self):
|
||||
"""
|
||||
Perform the actual export. This creates an *openlyricsexport* instance
|
||||
and calls the *do_export* method.
|
||||
Perform the actual export. This creates an *openlyricsexport* instance and calls the *do_export* method.
|
||||
"""
|
||||
songs = [
|
||||
song.data(QtCore.Qt.UserRole)
|
||||
for song in self._findListWidgetItems(self.selectedListWidget)
|
||||
for song in self._find_list_widget_items(self.selected_list_widget)
|
||||
]
|
||||
exporter = OpenLyricsExport(self, songs, self.directoryLineEdit.text())
|
||||
exporter = OpenLyricsExport(self, songs, self.directory_line_edit.text())
|
||||
if exporter.do_export():
|
||||
self.progress_label.setText(translate('SongsPlugin.SongExportForm',
|
||||
'Finished export. To import these files use the <strong>OpenLyrics</strong> importer.'))
|
||||
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')
|
||||
|
@ -55,11 +55,8 @@ class SongImportForm(OpenLPWizard):
|
||||
"""
|
||||
Instantiate the wizard, and run any extra setup we need to.
|
||||
|
||||
``parent``
|
||||
The QWidget-derived parent of the wizard.
|
||||
|
||||
``plugin``
|
||||
The songs plugin.
|
||||
:param parent: The QWidget-derived parent of the wizard.
|
||||
:param plugin: The songs plugin.
|
||||
"""
|
||||
super(SongImportForm, self).__init__(parent, plugin, 'songImportWizard', ':/wizards/wizard_importsong.bmp')
|
||||
self.clipboard = self.main_window.clipboard
|
||||
@ -72,9 +69,9 @@ class SongImportForm(OpenLPWizard):
|
||||
super(SongImportForm, self).setupUi(image)
|
||||
self.current_format = SongFormat.OpenLyrics
|
||||
self.format_stack.setCurrentIndex(self.current_format)
|
||||
self.format_combo_box.currentIndexChanged.connect(self.onCurrentIndexChanged)
|
||||
self.format_combo_box.currentIndexChanged.connect(self.on_current_index_changed)
|
||||
|
||||
def onCurrentIndexChanged(self, index):
|
||||
def on_current_index_changed(self, index):
|
||||
"""
|
||||
Called when the format combo box's index changed.
|
||||
"""
|
||||
@ -99,10 +96,10 @@ class SongImportForm(OpenLPWizard):
|
||||
select_mode = SongFormat.get(song_format, 'selectMode')
|
||||
if select_mode == SongFormatSelect.MultipleFiles:
|
||||
self.format_widgets[song_format]['addButton'].clicked.connect(self.on_add_button_clicked)
|
||||
self.format_widgets[song_format]['removeButton'].clicked.connect(self.onRemoveButtonClicked)
|
||||
self.format_widgets[song_format]['removeButton'].clicked.connect(self.on_remove_button_clicked)
|
||||
else:
|
||||
self.format_widgets[song_format]['browseButton'].clicked.connect(self.on_browse_button_clicked)
|
||||
self.format_widgets[song_format]['file_path_edit'].textChanged.connect(self.onFilepathEditTextChanged)
|
||||
self.format_widgets[song_format]['file_path_edit'].textChanged.connect(self.on_filepath_edit_text_changed)
|
||||
|
||||
def add_custom_pages(self):
|
||||
"""
|
||||
@ -131,7 +128,7 @@ class SongImportForm(OpenLPWizard):
|
||||
self.format_stack.setObjectName('format_stack')
|
||||
self.disablable_formats = []
|
||||
for self.current_format in SongFormat.get_format_list():
|
||||
self.addFileSelectItem()
|
||||
self.add_file_select_item()
|
||||
self.source_layout.addLayout(self.format_stack)
|
||||
self.addPage(self.source_page)
|
||||
|
||||
@ -140,33 +137,35 @@ class SongImportForm(OpenLPWizard):
|
||||
Song wizard localisation.
|
||||
"""
|
||||
self.setWindowTitle(translate('SongsPlugin.ImportWizardForm', 'Song Import Wizard'))
|
||||
self.title_label.setText(WizardStrings.HeaderStyle % translate('OpenLP.Ui', 'Welcome to the Song Import Wizard'))
|
||||
self.information_label.setText(translate('SongsPlugin.ImportWizardForm',
|
||||
'This wizard will help you to import songs from a variety of '
|
||||
'formats. Click the next button below to start the process by selecting a format to import from.'))
|
||||
self.title_label.setText(WizardStrings.HeaderStyle % translate('OpenLP.Ui',
|
||||
'Welcome to the Song Import Wizard'))
|
||||
self.information_label.setText(
|
||||
translate('SongsPlugin.ImportWizardForm',
|
||||
'This wizard will help you to import songs from a variety of formats. Click the next button '
|
||||
'below to start the process by selecting a format to import from.'))
|
||||
self.source_page.setTitle(WizardStrings.ImportSelect)
|
||||
self.source_page.setSubTitle(WizardStrings.ImportSelectLong)
|
||||
self.format_label.setText(WizardStrings.FormatLabel)
|
||||
for format in SongFormat.get_format_list():
|
||||
for format_list in SongFormat.get_format_list():
|
||||
format_name, custom_combo_text, description_text, select_mode = \
|
||||
SongFormat.get(format, 'name', 'comboBoxText', 'descriptionText', 'selectMode')
|
||||
SongFormat.get(format_list, 'name', 'comboBoxText', 'descriptionText', 'selectMode')
|
||||
combo_box_text = (custom_combo_text if custom_combo_text else format_name)
|
||||
self.format_combo_box.setItemText(format, combo_box_text)
|
||||
self.format_combo_box.setItemText(format_list, combo_box_text)
|
||||
if description_text is not None:
|
||||
self.format_widgets[format]['description_label'].setText(description_text)
|
||||
self.format_widgets[format_list]['description_label'].setText(description_text)
|
||||
if select_mode == SongFormatSelect.MultipleFiles:
|
||||
self.format_widgets[format]['addButton'].setText(
|
||||
self.format_widgets[format_list]['addButton'].setText(
|
||||
translate('SongsPlugin.ImportWizardForm', 'Add Files...'))
|
||||
self.format_widgets[format]['removeButton'].setText(
|
||||
self.format_widgets[format_list]['removeButton'].setText(
|
||||
translate('SongsPlugin.ImportWizardForm', 'Remove File(s)'))
|
||||
else:
|
||||
self.format_widgets[format]['browseButton'].setText(UiStrings().Browse)
|
||||
self.format_widgets[format_list]['browseButton'].setText(UiStrings().Browse)
|
||||
f_label = 'Filename:'
|
||||
if select_mode == SongFormatSelect.SingleFolder:
|
||||
f_label = 'Folder:'
|
||||
self.format_widgets[format]['filepathLabel'].setText(translate('SongsPlugin.ImportWizardForm', f_label))
|
||||
for format in self.disablable_formats:
|
||||
self.format_widgets[format]['disabled_label'].setText(SongFormat.get(format, 'disabledLabelText'))
|
||||
self.format_widgets[format_list]['filepathLabel'].setText(translate('SongsPlugin.ImportWizardForm', f_label))
|
||||
for format_list in self.disablable_formats:
|
||||
self.format_widgets[format_list]['disabled_label'].setText(SongFormat.get(format_list, 'disabledLabelText'))
|
||||
self.progress_page.setTitle(WizardStrings.Importing)
|
||||
self.progress_page.setSubTitle(
|
||||
translate('SongsPlugin.ImportWizardForm', 'Please wait while your songs are imported.'))
|
||||
@ -179,7 +178,7 @@ class SongImportForm(OpenLPWizard):
|
||||
labels = [self.format_widgets[f]['filepathLabel'] for f in formats]
|
||||
# Get max width of all labels
|
||||
max_label_width = max(self.format_label.minimumSizeHint().width(),
|
||||
max([label.minimumSizeHint().width() for label in labels]))
|
||||
max([label.minimumSizeHint().width() for label in labels]))
|
||||
self.format_spacer.changeSize(max_label_width, 0, QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed)
|
||||
spacers = [self.format_widgets[f]['filepathSpacer'] for f in formats]
|
||||
for index, spacer in enumerate(spacers):
|
||||
@ -187,9 +186,9 @@ class SongImportForm(OpenLPWizard):
|
||||
max_label_width - labels[index].minimumSizeHint().width(), 0,
|
||||
QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed)
|
||||
# Align descriptionLabels with rest of layout
|
||||
for format in SongFormat.get_format_list():
|
||||
if SongFormat.get(format, 'descriptionText') is not None:
|
||||
self.format_widgets[format]['descriptionSpacer'].changeSize(
|
||||
for format_list in SongFormat.get_format_list():
|
||||
if SongFormat.get(format_list, 'descriptionText') is not None:
|
||||
self.format_widgets[format_list]['descriptionSpacer'].changeSize(
|
||||
max_label_width + self.format_h_spacing, 0, QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed)
|
||||
|
||||
def custom_page_changed(self, page_id):
|
||||
@ -197,11 +196,11 @@ class SongImportForm(OpenLPWizard):
|
||||
Called when changing to a page other than the progress page.
|
||||
"""
|
||||
if self.page(page_id) == self.source_page:
|
||||
self.onCurrentIndexChanged(self.format_stack.currentIndex())
|
||||
self.on_current_index_changed(self.format_stack.currentIndex())
|
||||
|
||||
def validateCurrentPage(self):
|
||||
"""
|
||||
Re-implement te validateCurrentPage() method. Validate the current page before moving on to the next page.
|
||||
Re-implement the validateCurrentPage() method. Validate the current page before moving on to the next page.
|
||||
Provide each song format class with a chance to validate its input by overriding isValidSource().
|
||||
"""
|
||||
if self.currentPage() == self.welcome_page:
|
||||
@ -230,37 +229,36 @@ class SongImportForm(OpenLPWizard):
|
||||
"""
|
||||
Opens a QFileDialog and writes the filenames to the given listbox.
|
||||
|
||||
``title``
|
||||
The title of the dialog (unicode).
|
||||
|
||||
``listbox``
|
||||
A listbox (QListWidget).
|
||||
|
||||
``filters``
|
||||
The file extension filters. It should contain the file descriptions
|
||||
:param title: The title of the dialog (unicode).
|
||||
:param listbox: A listbox (QListWidget).
|
||||
:param filters: The file extension filters. It should contain the file descriptions
|
||||
as well as the file extensions. For example::
|
||||
|
||||
u'SongBeamer Files (*.sng)'
|
||||
"""
|
||||
if filters:
|
||||
filters += ';;'
|
||||
filters += '%s (*)' % UiStrings().AllFiles
|
||||
filenames = FileDialog.getOpenFileNames(self, title,
|
||||
file_names = FileDialog.getOpenFileNames(
|
||||
self, title,
|
||||
Settings().value(self.plugin.settings_section + '/last directory import'), filters)
|
||||
if filenames:
|
||||
listbox.addItems(filenames)
|
||||
if file_names:
|
||||
listbox.addItems(file_names)
|
||||
Settings().setValue(self.plugin.settings_section + '/last directory import',
|
||||
os.path.split(str(filenames[0]))[0])
|
||||
os.path.split(str(file_names[0]))[0])
|
||||
|
||||
def get_list_of_files(self, listbox):
|
||||
def get_list_of_files(self, list_box):
|
||||
"""
|
||||
Return a list of file from the listbox
|
||||
Return a list of file from the list_box
|
||||
|
||||
:param list_box: The source list box
|
||||
"""
|
||||
return [listbox.item(i).text() for i in range(listbox.count())]
|
||||
return [list_box.item(i).text() for i in range(list_box.count())]
|
||||
|
||||
def remove_selected_items(self, list_box):
|
||||
"""
|
||||
Remove selected list_box items
|
||||
|
||||
:param list_box: the source list box
|
||||
"""
|
||||
for item in list_box.selectedItems():
|
||||
item = list_box.takeItem(list_box.row(item))
|
||||
@ -291,14 +289,14 @@ class SongImportForm(OpenLPWizard):
|
||||
self.get_files(title, self.format_widgets[this_format]['file_list_widget'], ext_filter)
|
||||
self.source_page.emit(QtCore.SIGNAL('completeChanged()'))
|
||||
|
||||
def onRemoveButtonClicked(self):
|
||||
def on_remove_button_clicked(self):
|
||||
"""
|
||||
Remove a file from the list.
|
||||
"""
|
||||
self.remove_selected_items(self.format_widgets[self.current_format]['file_list_widget'])
|
||||
self.source_page.emit(QtCore.SIGNAL('completeChanged()'))
|
||||
|
||||
def onFilepathEditTextChanged(self):
|
||||
def on_filepath_edit_text_changed(self):
|
||||
"""
|
||||
Called when the content of the Filename/Folder edit box changes.
|
||||
"""
|
||||
@ -315,12 +313,12 @@ class SongImportForm(OpenLPWizard):
|
||||
if last_import_type < 0 or last_import_type >= self.format_combo_box.count():
|
||||
last_import_type = 0
|
||||
self.format_combo_box.setCurrentIndex(last_import_type)
|
||||
for format in SongFormat.get_format_list():
|
||||
select_mode = SongFormat.get(format, 'selectMode')
|
||||
for format_list in SongFormat.get_format_list():
|
||||
select_mode = SongFormat.get(format_list, 'selectMode')
|
||||
if select_mode == SongFormatSelect.MultipleFiles:
|
||||
self.format_widgets[format]['file_list_widget'].clear()
|
||||
self.format_widgets[format_list]['file_list_widget'].clear()
|
||||
else:
|
||||
self.format_widgets[format]['file_path_edit'].setText('')
|
||||
self.format_widgets[format_list]['file_path_edit'].setText('')
|
||||
self.error_report_text_edit.clear()
|
||||
self.error_report_text_edit.setHidden(True)
|
||||
self.error_copy_to_button.setHidden(True)
|
||||
@ -334,22 +332,22 @@ class SongImportForm(OpenLPWizard):
|
||||
self.progress_label.setText(WizardStrings.StartingImport)
|
||||
self.application.process_events()
|
||||
|
||||
def performWizard(self):
|
||||
def perform_wizard(self):
|
||||
"""
|
||||
Perform the actual import. This method pulls in the correct importer
|
||||
class, and then runs the ``doImport`` method of the importer to do
|
||||
the actual importing.
|
||||
Perform the actual import. This method pulls in the correct importer class, and then runs the ``doImport``
|
||||
method of the importer to do the actual importing.
|
||||
"""
|
||||
source_format = self.current_format
|
||||
select_mode = SongFormat.get(source_format, 'selectMode')
|
||||
if select_mode == SongFormatSelect.SingleFile:
|
||||
importer = self.plugin.importSongs(source_format,
|
||||
filename=self.format_widgets[source_format]['file_path_edit'].text())
|
||||
importer = self.plugin.import_songs(source_format,
|
||||
filename=self.format_widgets[source_format]['file_path_edit'].text())
|
||||
elif select_mode == SongFormatSelect.SingleFolder:
|
||||
importer = self.plugin.importSongs(source_format,
|
||||
folder=self.format_widgets[source_format]['file_path_edit'].text())
|
||||
importer = self.plugin.import_songs(source_format,
|
||||
folder=self.format_widgets[source_format]['file_path_edit'].text())
|
||||
else:
|
||||
importer = self.plugin.importSongs(source_format,
|
||||
importer = self.plugin.import_songs(
|
||||
source_format,
|
||||
filenames=self.get_list_of_files(self.format_widgets[source_format]['file_list_widget']))
|
||||
importer.doImport()
|
||||
self.progress_label.setText(WizardStrings.FinishedImport)
|
||||
@ -364,15 +362,15 @@ class SongImportForm(OpenLPWizard):
|
||||
"""
|
||||
Save the error report to a file.
|
||||
"""
|
||||
filename = QtGui.QFileDialog.getSaveFileName(self,
|
||||
Settings().value(self.plugin.settings_section + '/last directory import'))
|
||||
filename = QtGui.QFileDialog.getSaveFileName(
|
||||
self, Settings().value(self.plugin.settings_section + '/last directory import'))
|
||||
if not filename:
|
||||
return
|
||||
report_file = codecs.open(filename, 'w', 'utf-8')
|
||||
report_file.write(self.error_report_text_edit.toPlainText())
|
||||
report_file.close()
|
||||
|
||||
def addFileSelectItem(self):
|
||||
def add_file_select_item(self):
|
||||
"""
|
||||
Add a file selection page.
|
||||
"""
|
||||
@ -382,75 +380,75 @@ class SongImportForm(OpenLPWizard):
|
||||
page = QtGui.QWidget()
|
||||
page.setObjectName(prefix + 'Page')
|
||||
if can_disable:
|
||||
importWidget = self.disablableWidget(page, prefix)
|
||||
import_widget = self.disablable_widget(page, prefix)
|
||||
else:
|
||||
importWidget = page
|
||||
importLayout = QtGui.QVBoxLayout(importWidget)
|
||||
importLayout.setMargin(0)
|
||||
importLayout.setObjectName(prefix + 'ImportLayout')
|
||||
import_widget = page
|
||||
import_layout = QtGui.QVBoxLayout(import_widget)
|
||||
import_layout.setMargin(0)
|
||||
import_layout.setObjectName(prefix + 'ImportLayout')
|
||||
if description_text is not None:
|
||||
descriptionLayout = QtGui.QHBoxLayout()
|
||||
descriptionLayout.setObjectName(prefix + 'DescriptionLayout')
|
||||
descriptionSpacer = QtGui.QSpacerItem(0, 0, QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed)
|
||||
descriptionLayout.addSpacerItem(descriptionSpacer)
|
||||
description_label = QtGui.QLabel(importWidget)
|
||||
description_layout = QtGui.QHBoxLayout()
|
||||
description_layout.setObjectName(prefix + 'DescriptionLayout')
|
||||
description_spacer = QtGui.QSpacerItem(0, 0, QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed)
|
||||
description_layout.addSpacerItem(description_spacer)
|
||||
description_label = QtGui.QLabel(import_widget)
|
||||
description_label.setWordWrap(True)
|
||||
description_label.setOpenExternalLinks(True)
|
||||
description_label.setObjectName(prefix + '_description_label')
|
||||
descriptionLayout.addWidget(description_label)
|
||||
importLayout.addLayout(descriptionLayout)
|
||||
description_layout.addWidget(description_label)
|
||||
import_layout.addLayout(description_layout)
|
||||
self.format_widgets[this_format]['description_label'] = description_label
|
||||
self.format_widgets[this_format]['descriptionSpacer'] = descriptionSpacer
|
||||
self.format_widgets[this_format]['descriptionSpacer'] = description_spacer
|
||||
if select_mode == SongFormatSelect.SingleFile or select_mode == SongFormatSelect.SingleFolder:
|
||||
file_path_layout = QtGui.QHBoxLayout()
|
||||
file_path_layout.setObjectName(prefix + '_file_path_layout')
|
||||
file_path_layout.setContentsMargins(0, self.format_v_spacing, 0, 0)
|
||||
filepathLabel = QtGui.QLabel(importWidget)
|
||||
filepathLabel.setObjectName(prefix + 'FilepathLabel')
|
||||
file_path_layout.addWidget(filepathLabel)
|
||||
filepathSpacer = QtGui.QSpacerItem(0, 0, QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed)
|
||||
file_path_layout.addSpacerItem(filepathSpacer)
|
||||
file_path_edit = QtGui.QLineEdit(importWidget)
|
||||
file_path_label = QtGui.QLabel(import_widget)
|
||||
file_path_label.setObjectName(prefix + 'FilepathLabel')
|
||||
file_path_layout.addWidget(file_path_label)
|
||||
file_path_spacer = QtGui.QSpacerItem(0, 0, QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed)
|
||||
file_path_layout.addSpacerItem(file_path_spacer)
|
||||
file_path_edit = QtGui.QLineEdit(import_widget)
|
||||
file_path_edit.setObjectName(prefix + '_file_path_edit')
|
||||
file_path_layout.addWidget(file_path_edit)
|
||||
browseButton = QtGui.QToolButton(importWidget)
|
||||
browseButton.setIcon(self.open_icon)
|
||||
browseButton.setObjectName(prefix + 'BrowseButton')
|
||||
file_path_layout.addWidget(browseButton)
|
||||
importLayout.addLayout(file_path_layout)
|
||||
importLayout.addSpacerItem(self.stack_spacer)
|
||||
self.format_widgets[this_format]['filepathLabel'] = filepathLabel
|
||||
self.format_widgets[this_format]['filepathSpacer'] = filepathSpacer
|
||||
browse_button = QtGui.QToolButton(import_widget)
|
||||
browse_button.setIcon(self.open_icon)
|
||||
browse_button.setObjectName(prefix + 'BrowseButton')
|
||||
file_path_layout.addWidget(browse_button)
|
||||
import_layout.addLayout(file_path_layout)
|
||||
import_layout.addSpacerItem(self.stack_spacer)
|
||||
self.format_widgets[this_format]['filepathLabel'] = file_path_label
|
||||
self.format_widgets[this_format]['filepathSpacer'] = file_path_spacer
|
||||
self.format_widgets[this_format]['file_path_layout'] = file_path_layout
|
||||
self.format_widgets[this_format]['file_path_edit'] = file_path_edit
|
||||
self.format_widgets[this_format]['browseButton'] = browseButton
|
||||
self.format_widgets[this_format]['browseButton'] = browse_button
|
||||
elif select_mode == SongFormatSelect.MultipleFiles:
|
||||
fileListWidget = QtGui.QListWidget(importWidget)
|
||||
fileListWidget.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection)
|
||||
fileListWidget.setObjectName(prefix + 'FileListWidget')
|
||||
importLayout.addWidget(fileListWidget)
|
||||
file_list_widget = QtGui.QListWidget(import_widget)
|
||||
file_list_widget.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection)
|
||||
file_list_widget.setObjectName(prefix + 'FileListWidget')
|
||||
import_layout.addWidget(file_list_widget)
|
||||
button_layout = QtGui.QHBoxLayout()
|
||||
button_layout.setObjectName(prefix + '_button_layout')
|
||||
addButton = QtGui.QPushButton(importWidget)
|
||||
addButton.setIcon(self.open_icon)
|
||||
addButton.setObjectName(prefix + 'AddButton')
|
||||
button_layout.addWidget(addButton)
|
||||
add_button = QtGui.QPushButton(import_widget)
|
||||
add_button.setIcon(self.open_icon)
|
||||
add_button.setObjectName(prefix + 'AddButton')
|
||||
button_layout.addWidget(add_button)
|
||||
button_layout.addStretch()
|
||||
removeButton = QtGui.QPushButton(importWidget)
|
||||
removeButton.setIcon(self.delete_icon)
|
||||
removeButton.setObjectName(prefix + 'RemoveButton')
|
||||
button_layout.addWidget(removeButton)
|
||||
importLayout.addLayout(button_layout)
|
||||
self.format_widgets[this_format]['file_list_widget'] = fileListWidget
|
||||
remove_button = QtGui.QPushButton(import_widget)
|
||||
remove_button.setIcon(self.delete_icon)
|
||||
remove_button.setObjectName(prefix + 'RemoveButton')
|
||||
button_layout.addWidget(remove_button)
|
||||
import_layout.addLayout(button_layout)
|
||||
self.format_widgets[this_format]['file_list_widget'] = file_list_widget
|
||||
self.format_widgets[this_format]['button_layout'] = button_layout
|
||||
self.format_widgets[this_format]['addButton'] = addButton
|
||||
self.format_widgets[this_format]['removeButton'] = removeButton
|
||||
self.format_widgets[this_format]['addButton'] = add_button
|
||||
self.format_widgets[this_format]['removeButton'] = remove_button
|
||||
self.format_stack.addWidget(page)
|
||||
self.format_widgets[this_format]['page'] = page
|
||||
self.format_widgets[this_format]['importLayout'] = importLayout
|
||||
self.format_widgets[this_format]['importLayout'] = import_layout
|
||||
self.format_combo_box.addItem('')
|
||||
|
||||
def disablableWidget(self, page, prefix):
|
||||
def disablable_widget(self, page, prefix):
|
||||
"""
|
||||
Disable a widget.
|
||||
"""
|
||||
@ -526,10 +524,10 @@ class SongImportSourcePage(QtGui.QWizardPage):
|
||||
if wizard.format_widgets[this_format]['file_list_widget'].count() > 0:
|
||||
return True
|
||||
else:
|
||||
filepath = str(wizard.format_widgets[this_format]['file_path_edit'].text())
|
||||
if filepath:
|
||||
if select_mode == SongFormatSelect.SingleFile and os.path.isfile(filepath):
|
||||
file_path = str(wizard.format_widgets[this_format]['file_path_edit'].text())
|
||||
if file_path:
|
||||
if select_mode == SongFormatSelect.SingleFile and os.path.isfile(file_path):
|
||||
return True
|
||||
elif select_mode == SongFormatSelect.SingleFolder and os.path.isdir(filepath):
|
||||
elif select_mode == SongFormatSelect.SingleFolder and os.path.isdir(file_path):
|
||||
return True
|
||||
return False
|
||||
|
@ -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)
|
||||
|
@ -98,8 +98,7 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog):
|
||||
"""
|
||||
Get the ID of the currently selected item.
|
||||
|
||||
``list_widget``
|
||||
The list widget to examine.
|
||||
:param list_widget: The list widget to examine.
|
||||
"""
|
||||
item = list_widget.currentItem()
|
||||
if item:
|
||||
@ -163,6 +162,9 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog):
|
||||
def check_author_exists(self, new_author, edit=False):
|
||||
"""
|
||||
Returns *False* if the given Author already exists, otherwise *True*.
|
||||
|
||||
:param new_author: The new Author.
|
||||
:param edit: Are we editing the song?
|
||||
"""
|
||||
authors = self.manager.get_all_objects(
|
||||
Author,
|
||||
@ -177,6 +179,9 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog):
|
||||
def check_topic_exists(self, new_topic, edit=False):
|
||||
"""
|
||||
Returns *False* if the given Topic already exists, otherwise *True*.
|
||||
|
||||
:param new_topic: The new Topic.
|
||||
:param edit: Are we editing the song?
|
||||
"""
|
||||
topics = self.manager.get_all_objects(Topic, Topic.name == new_topic.name)
|
||||
return self.__check_object_exists(topics, new_topic, edit)
|
||||
@ -184,17 +189,21 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog):
|
||||
def check_song_book_exists(self, new_book, edit=False):
|
||||
"""
|
||||
Returns *False* if the given Topic already exists, otherwise *True*.
|
||||
|
||||
:param new_book: The new Book.
|
||||
:param edit: Are we editing the song?
|
||||
"""
|
||||
books = self.manager.get_all_objects(Book,
|
||||
and_(Book.name == new_book.name, Book.publisher == new_book.publisher))
|
||||
books = self.manager.get_all_objects(
|
||||
Book, and_(Book.name == new_book.name, Book.publisher == new_book.publisher))
|
||||
return self.__check_object_exists(books, new_book, edit)
|
||||
|
||||
def __check_object_exists(self, existing_objects, new_object, edit):
|
||||
"""
|
||||
Utility method to check for an existing object.
|
||||
|
||||
``edit``
|
||||
If we edit an item, this should be *True*.
|
||||
:param existing_objects: The objects reference
|
||||
:param new_object: An individual object
|
||||
:param edit: If we edit an item, this should be *True*.
|
||||
"""
|
||||
if existing_objects:
|
||||
# If we edit an existing object, we need to make sure that we do
|
||||
@ -297,8 +306,7 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog):
|
||||
'author %s use the existing author %s?') %
|
||||
(author.display_name, temp_display_name, author.display_name), parent=self, question=True) == \
|
||||
QtGui.QMessageBox.Yes:
|
||||
self._merge_objects(author, self.merge_authors,
|
||||
self.reset_authors)
|
||||
self._merge_objects(author, self.merge_authors, self.reset_authors)
|
||||
else:
|
||||
# We restore the author's old first and last name as well as
|
||||
# his display name.
|
||||
@ -330,15 +338,16 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog):
|
||||
message=translate('SongsPlugin.SongMaintenanceForm', 'Could not save your changes.'))
|
||||
elif critical_error_message_box(
|
||||
message=translate('SongsPlugin.SongMaintenanceForm',
|
||||
'The topic %s already exists. Would you like to make songs with topic %s use the existing topic %s?') %
|
||||
(topic.name, temp_name, topic.name), parent=self, question=True) == QtGui.QMessageBox.Yes:
|
||||
'The topic %s already exists. Would you like to make songs with topic %s use the '
|
||||
'existing topic %s?') % (topic.name, temp_name, topic.name),
|
||||
parent=self, question=True) == QtGui.QMessageBox.Yes:
|
||||
self._merge_objects(topic, self.merge_topics, self.reset_topics)
|
||||
else:
|
||||
# We restore the topics's old name.
|
||||
topic.name = temp_name
|
||||
critical_error_message_box(
|
||||
message=translate('SongsPlugin.SongMaintenanceForm',
|
||||
'Could not save your modified topic, because it already exists.'))
|
||||
'Could not save your modified topic, because it already exists.'))
|
||||
|
||||
def on_edit_book_button_clicked(self):
|
||||
"""
|
||||
@ -367,8 +376,9 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog):
|
||||
message=translate('SongsPlugin.SongMaintenanceForm', 'Could not save your changes.'))
|
||||
elif critical_error_message_box(
|
||||
message=translate('SongsPlugin.SongMaintenanceForm',
|
||||
'The book %s already exists. Would you like to make songs with book %s use the existing book %s?') %
|
||||
(book.name, temp_name, book.name), parent=self, question=True) == QtGui.QMessageBox.Yes:
|
||||
'The book %s already exists. Would you like to make '
|
||||
'songs with book %s use the existing book %s?') % (book.name, temp_name, book.name),
|
||||
parent=self, question=True) == QtGui.QMessageBox.Yes:
|
||||
self._merge_objects(book, self.merge_song_books, self.reset_song_books)
|
||||
else:
|
||||
# We restore the book's old name and publisher.
|
||||
@ -390,8 +400,7 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog):
|
||||
"""
|
||||
Merges two authors into one author.
|
||||
|
||||
``old_author``
|
||||
The object, which was edited, that will be deleted
|
||||
:param old_author: The object, which was edited, that will be deleted
|
||||
"""
|
||||
# Find the duplicate.
|
||||
existing_author = self.manager.get_object_filtered(
|
||||
@ -418,15 +427,11 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog):
|
||||
"""
|
||||
Merges two topics into one topic.
|
||||
|
||||
``old_topic``
|
||||
The object, which was edited, that will be deleted
|
||||
:param old_topic: The object, which was edited, that will be deleted
|
||||
"""
|
||||
# Find the duplicate.
|
||||
existing_topic = self.manager.get_object_filtered(
|
||||
Topic,
|
||||
and_(
|
||||
Topic.name == old_topic.name, Topic.id != old_topic.id
|
||||
)
|
||||
Topic, and_(Topic.name == old_topic.name, Topic.id != old_topic.id)
|
||||
)
|
||||
# Find the songs, which have the old_topic as topic.
|
||||
songs = self.manager.get_all_objects(Song, Song.topics.contains(old_topic))
|
||||
@ -467,30 +472,34 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog):
|
||||
Delete the author if the author is not attached to any songs.
|
||||
"""
|
||||
self._delete_item(Author, self.authors_list_widget, self.reset_authors,
|
||||
translate('SongsPlugin.SongMaintenanceForm', 'Delete Author'),
|
||||
translate('SongsPlugin.SongMaintenanceForm', 'Are you sure you want to delete the selected author?'),
|
||||
translate('SongsPlugin.SongMaintenanceForm',
|
||||
'This author cannot be deleted, they are currently assigned to at least one song.'))
|
||||
translate('SongsPlugin.SongMaintenanceForm', 'Delete Author'),
|
||||
translate('SongsPlugin.SongMaintenanceForm',
|
||||
'Are you sure you want to delete the selected author?'),
|
||||
translate('SongsPlugin.SongMaintenanceForm',
|
||||
'This author cannot be deleted, they are currently assigned to at least one song'
|
||||
'.'))
|
||||
|
||||
def on_delete_topic_button_clicked(self):
|
||||
"""
|
||||
Delete the Book if the Book is not attached to any songs.
|
||||
"""
|
||||
self._delete_item(Topic, self.topics_list_widget, self.reset_topics,
|
||||
translate('SongsPlugin.SongMaintenanceForm', 'Delete Topic'),
|
||||
translate('SongsPlugin.SongMaintenanceForm', 'Are you sure you want to delete the selected topic?'),
|
||||
translate('SongsPlugin.SongMaintenanceForm',
|
||||
'This topic cannot be deleted, it is currently assigned to at least one song.'))
|
||||
translate('SongsPlugin.SongMaintenanceForm', 'Delete Topic'),
|
||||
translate('SongsPlugin.SongMaintenanceForm',
|
||||
'Are you sure you want to delete the selected topic?'),
|
||||
translate('SongsPlugin.SongMaintenanceForm',
|
||||
'This topic cannot be deleted, it is currently assigned to at least one song.'))
|
||||
|
||||
def on_delete_book_button_clicked(self):
|
||||
"""
|
||||
Delete the Book if the Book is not attached to any songs.
|
||||
"""
|
||||
self._delete_item(Book, self.song_books_list_widget, self.reset_song_books,
|
||||
translate('SongsPlugin.SongMaintenanceForm', 'Delete Book'),
|
||||
translate('SongsPlugin.SongMaintenanceForm', 'Are you sure you want to delete the selected book?'),
|
||||
translate('SongsPlugin.SongMaintenanceForm',
|
||||
'This book cannot be deleted, it is currently assigned to at least one song.'))
|
||||
translate('SongsPlugin.SongMaintenanceForm', 'Delete Book'),
|
||||
translate('SongsPlugin.SongMaintenanceForm',
|
||||
'Are you sure you want to delete the selected book?'),
|
||||
translate('SongsPlugin.SongMaintenanceForm',
|
||||
'This book cannot be deleted, it is currently assigned to at least one song.'))
|
||||
|
||||
def on_authors_list_row_changed(self, row):
|
||||
"""
|
||||
|
@ -186,7 +186,7 @@ class SongReviewWidget(QtGui.QWidget):
|
||||
# Some pixels are missing at the bottom of the table, but all themes I tried still allowed
|
||||
# to read the last verse line, so I'll just leave it at that.
|
||||
self.song_info_verse_list_widget.setFixedHeight(self.song_info_verse_list_widget.verticalHeader().length() +
|
||||
self.song_info_verse_list_widget.verticalHeader().offset() + 6)
|
||||
self.song_info_verse_list_widget.verticalHeader().offset() + 6)
|
||||
self.song_group_box_layout.addWidget(self.song_info_verse_list_widget)
|
||||
self.song_group_box_layout.addStretch()
|
||||
self.song_vertical_layout.addWidget(self.song_group_box)
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
||||
|
@ -119,9 +119,11 @@ class SongsPlugin(Plugin):
|
||||
:param import_menu: The actual **Import** menu item, so that your actions can use it as their parent.
|
||||
"""
|
||||
# Main song import menu item - will eventually be the only one
|
||||
self.song_import_item = create_action(import_menu, 'songImportItem', text=translate('SongsPlugin', '&Song'),
|
||||
tooltip=translate('SongsPlugin', 'Import songs using the import wizard.'),
|
||||
triggers=self.on_song_import_item_clicked)
|
||||
self.song_import_item = create_action(
|
||||
import_menu, 'songImportItem',
|
||||
text=translate('SongsPlugin', '&Song'),
|
||||
tooltip=translate('SongsPlugin', 'Import songs using the import wizard.'),
|
||||
triggers=self.on_song_import_item_clicked)
|
||||
import_menu.addAction(self.song_import_item)
|
||||
self.import_songselect_item = create_action(
|
||||
import_menu, 'import_songselect_item', text=translate('SongsPlugin', 'CCLI SongSelect'),
|
||||
@ -137,7 +139,8 @@ class SongsPlugin(Plugin):
|
||||
:param export_menu: The actual **Export** menu item, so that your actions can use it as their parent.
|
||||
"""
|
||||
# Main song import menu item - will eventually be the only one
|
||||
self.song_export_item = create_action(export_menu, 'songExportItem',
|
||||
self.song_export_item = create_action(
|
||||
export_menu, 'songExportItem',
|
||||
text=translate('SongsPlugin', '&Song'),
|
||||
tooltip=translate('SongsPlugin', 'Exports songs using the export wizard.'),
|
||||
triggers=self.on_song_export_item_clicked)
|
||||
@ -150,16 +153,17 @@ class SongsPlugin(Plugin):
|
||||
:param tools_menu: The actual **Tools** menu item, so that your actions can use it as their parent.
|
||||
"""
|
||||
log.info('add tools menu')
|
||||
self.tools_reindex_item = create_action(tools_menu, 'toolsReindexItem',
|
||||
self.tools_reindex_item = create_action(
|
||||
tools_menu, 'toolsReindexItem',
|
||||
text=translate('SongsPlugin', '&Re-index Songs'),
|
||||
icon=':/plugins/plugin_songs.png',
|
||||
statustip=translate('SongsPlugin', 'Re-index the songs database to improve searching and ordering.'),
|
||||
visible=False, triggers=self.on_tools_reindex_item_triggered)
|
||||
tools_menu.addAction(self.tools_reindex_item)
|
||||
self.tools_find_duplicates = create_action(tools_menu, 'toolsFindDuplicates',
|
||||
self.tools_find_duplicates = create_action(
|
||||
tools_menu, 'toolsFindDuplicates',
|
||||
text=translate('SongsPlugin', 'Find &Duplicate Songs'),
|
||||
statustip=translate('SongsPlugin',
|
||||
'Find and remove duplicate songs in the song database.'),
|
||||
statustip=translate('SongsPlugin', 'Find and remove duplicate songs in the song database.'),
|
||||
visible=False, triggers=self.on_tools_find_duplicates_triggered, can_shortcuts=True)
|
||||
tools_menu.addAction(self.tools_find_duplicates)
|
||||
|
||||
@ -170,8 +174,8 @@ class SongsPlugin(Plugin):
|
||||
max_songs = self.manager.get_object_count(Song)
|
||||
if max_songs == 0:
|
||||
return
|
||||
progress_dialog = QtGui.QProgressDialog(translate('SongsPlugin', 'Reindexing songs...'), UiStrings().Cancel,
|
||||
0, max_songs, self.main_window)
|
||||
progress_dialog = QtGui.QProgressDialog(
|
||||
translate('SongsPlugin', 'Reindexing songs...'), UiStrings().Cancel, 0, max_songs, self.main_window)
|
||||
progress_dialog.setWindowTitle(translate('SongsPlugin', 'Reindexing songs'))
|
||||
progress_dialog.setWindowModality(QtCore.Qt.WindowModal)
|
||||
songs = self.manager.get_all_objects(Song)
|
||||
@ -230,22 +234,25 @@ class SongsPlugin(Plugin):
|
||||
|
||||
def rename_theme(self, old_theme, new_theme):
|
||||
"""
|
||||
Renames a theme the song plugin is using making the plugin use the new
|
||||
name.
|
||||
Renames a theme the song plugin is using making the plugin use the new name.
|
||||
|
||||
``old_theme``
|
||||
The name of the theme the plugin should stop using.
|
||||
|
||||
``new_theme``
|
||||
The new name the plugin should now use.
|
||||
:param old_theme: The name of the theme the plugin should stop using.
|
||||
:param new_theme: The new name the plugin should now use.
|
||||
"""
|
||||
songs_using_theme = self.manager.get_all_objects(Song, Song.theme_name == old_theme)
|
||||
for song in songs_using_theme:
|
||||
song.theme_name = new_theme
|
||||
self.manager.save_object(song)
|
||||
|
||||
def importSongs(self, format, **kwargs):
|
||||
class_ = SongFormat.get(format, 'class')
|
||||
def import_songs(self, import_format, **kwargs):
|
||||
"""
|
||||
Add the correct importer class
|
||||
|
||||
:param import_format: The import_format to be used
|
||||
:param kwargs: The arguments
|
||||
:return: the correct importer
|
||||
"""
|
||||
class_ = SongFormat.get(import_format, 'class')
|
||||
importer = class_(self.manager, **kwargs)
|
||||
importer.register(self.media_item.import_wizard)
|
||||
return importer
|
||||
@ -278,8 +285,7 @@ class SongsPlugin(Plugin):
|
||||
|
||||
def first_time(self):
|
||||
"""
|
||||
If the first time wizard has run, this function is run to import all the
|
||||
new songs into the database.
|
||||
If the first time wizard has run, this function is run to import all the new songs into the database.
|
||||
"""
|
||||
self.application.process_events()
|
||||
self.on_tools_reindex_item_triggered()
|
||||
@ -293,7 +299,7 @@ class SongsPlugin(Plugin):
|
||||
if sfile.startswith('songs_') and sfile.endswith('.sqlite'):
|
||||
self.application.process_events()
|
||||
song_dbs.append(os.path.join(db_dir, sfile))
|
||||
song_count += self._countSongs(os.path.join(db_dir, sfile))
|
||||
song_count += self._count_songs(os.path.join(db_dir, sfile))
|
||||
if not song_dbs:
|
||||
return
|
||||
self.application.process_events()
|
||||
@ -340,9 +346,11 @@ class SongsPlugin(Plugin):
|
||||
for song in songs:
|
||||
self.manager.delete_object(Song, song.id)
|
||||
|
||||
def _countSongs(self, db_file):
|
||||
def _count_songs(self, db_file):
|
||||
"""
|
||||
Provide a count of the songs in the database
|
||||
|
||||
:param db_file: the database name to count
|
||||
"""
|
||||
connection = sqlite3.connect(db_file)
|
||||
cursor = connection.cursor()
|
||||
|
@ -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']
|
||||
|
@ -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')
|
||||
|
@ -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.')
|
@ -29,7 +29,7 @@
|
||||
"""
|
||||
This module contains tests for the pptviewcontroller module of the Presentations plugin.
|
||||
"""
|
||||
|
||||
import os
|
||||
from unittest import TestCase
|
||||
|
||||
from tests.functional import MagicMock, patch
|
||||
@ -43,6 +43,7 @@ from openlp.plugins.presentations.lib.pptviewcontroller import PptviewDocument
|
||||
# start_process(self)
|
||||
# kill
|
||||
|
||||
|
||||
class TestPptviewDocument(TestCase):
|
||||
"""
|
||||
Test the PptviewDocument Class
|
||||
@ -78,8 +79,8 @@ class TestPptviewDocument(TestCase):
|
||||
'openlp.plugins.presentations.lib.pptviewcontroller.PresentationDocument.get_temp_folder')
|
||||
self.presentation_document_setup_patcher = patch(
|
||||
'openlp.plugins.presentations.lib.pptviewcontroller.PresentationDocument._setup')
|
||||
self.rect_patcher = patch('openlp.plugins.presentations.lib.pptviewcontroller.RECT')
|
||||
self.screen_list_patcher = patch('openlp.plugins.presentations.lib.pptviewcontroller.ScreenList')
|
||||
self.rect_patcher = MagicMock()
|
||||
|
||||
self.mock_os = self.os_patcher.start()
|
||||
self.mock_pptview_document_create_thumbnails = self.pptview_document_create_thumbnails_patcher.start()
|
||||
@ -118,12 +119,14 @@ class TestPptviewDocument(TestCase):
|
||||
self.mock_controller.process.OpenPPT.return_value = 0
|
||||
instance = PptviewDocument(self.mock_controller, self.mock_presentation)
|
||||
instance.filepath = 'test\path.ppt'
|
||||
result = instance.load_presentation()
|
||||
|
||||
# THEN: PptviewDocument.load_presentation should return True
|
||||
self.assertTrue(result)
|
||||
if os.name == 'nt':
|
||||
result = instance.load_presentation()
|
||||
|
||||
def load_presentation_unsuccesfull_test(self):
|
||||
# THEN: PptviewDocument.load_presentation should return True
|
||||
self.assertTrue(result)
|
||||
|
||||
def load_presentation_un_succesfull_test(self):
|
||||
"""
|
||||
Test the PptviewDocument.load_presentation() method when the temporary directory does not exist and the PPT is
|
||||
not successfully opened
|
||||
@ -136,8 +139,9 @@ class TestPptviewDocument(TestCase):
|
||||
self.mock_controller.process.OpenPPT.return_value = -1
|
||||
instance = PptviewDocument(self.mock_controller, self.mock_presentation)
|
||||
instance.filepath = 'test\path.ppt'
|
||||
result = instance.load_presentation()
|
||||
if os.name == 'nt':
|
||||
result = instance.load_presentation()
|
||||
|
||||
# THEN: The temporary directory should be created and PptviewDocument.load_presentation should return False
|
||||
self.mock_os.makedirs.assert_called_once_with('temp folder')
|
||||
self.assertFalse(result)
|
||||
# THEN: The temporary directory should be created and PptviewDocument.load_presentation should return False
|
||||
self.mock_os.makedirs.assert_called_once_with('temp folder')
|
||||
self.assertFalse(result)
|
||||
|
@ -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/')
|
||||
|
BIN
tests/resources/presentations/pdf_test1.pdf
Normal file
BIN
tests/resources/presentations/pdf_test1.pdf
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user