# -*- coding: utf-8 -*- # vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4 ############################################################################### # OpenLP - Open Source Lyrics Projection # # --------------------------------------------------------------------------- # # Copyright (c) 2008-2012 Raoul Snyman # # Portions copyright (c) 2008-2012 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 # # --------------------------------------------------------------------------- # # 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 # ############################################################################### """ The song import functions for OpenLP. """ import codecs import logging import os from PyQt4 import QtCore, QtGui from openlp.core.lib import Receiver, SettingsManager, translate from openlp.core.lib.ui import UiStrings, critical_error_message_box from openlp.core.lib.settings import Settings from openlp.core.ui.wizard import OpenLPWizard, WizardStrings from openlp.plugins.songs.lib.importer import SongFormat, SongFormatSelect log = logging.getLogger(__name__) class SongImportForm(OpenLPWizard): """ This is the Song Import Wizard, which allows easy importing of Songs into OpenLP from other formats like OpenLyrics, OpenSong and CCLI. """ log.info(u'SongImportForm loaded') def __init__(self, parent, plugin): """ Instantiate the wizard, and run any extra setup we need to. ``parent`` The QWidget-derived parent of the wizard. ``plugin`` The songs plugin. """ self.clipboard = plugin.formParent.clipboard OpenLPWizard.__init__(self, parent, plugin, u'songImportWizard', u':/wizards/wizard_importsong.bmp') def setupUi(self, image): """ Set up the song wizard UI. """ self.formatWidgets = dict([(format, {}) for format in SongFormat.get_format_list()]) OpenLPWizard.setupUi(self, image) self.currentFormat = SongFormat.OpenLyrics self.formatStack.setCurrentIndex(self.currentFormat) QtCore.QObject.connect(self.formatComboBox, QtCore.SIGNAL(u'currentIndexChanged(int)'), self.onCurrentIndexChanged) def onCurrentIndexChanged(self, index): """ Called when the format combo box's index changed. """ self.currentFormat = index self.formatStack.setCurrentIndex(index) self.sourcePage.emit(QtCore.SIGNAL(u'completeChanged()')) def customInit(self): """ Song wizard specific initialisation. """ for format in SongFormat.get_format_list(): if not SongFormat.get(format, u'availability'): self.formatWidgets[format][u'disabledWidget'].setVisible(True) self.formatWidgets[format][u'importWidget'].setVisible(False) def customSignals(self): """ Song wizard specific signals. """ for format in SongFormat.get_format_list(): select_mode = SongFormat.get(format, u'selectMode') if select_mode == SongFormatSelect.MultipleFiles: QtCore.QObject.connect(self.formatWidgets[format][u'addButton'], QtCore.SIGNAL(u'clicked()'), self.onAddButtonClicked) QtCore.QObject.connect( self.formatWidgets[format][u'removeButton'], QtCore.SIGNAL(u'clicked()'), self.onRemoveButtonClicked) else: QtCore.QObject.connect( self.formatWidgets[format][u'browseButton'], QtCore.SIGNAL(u'clicked()'), self.onBrowseButtonClicked) QtCore.QObject.connect( self.formatWidgets[format][u'filepathEdit'], QtCore.SIGNAL(u'textChanged (const QString&)'), self.onFilepathEditTextChanged) def addCustomPages(self): """ Add song wizard specific pages. """ # Source Page self.sourcePage = SongImportSourcePage() self.sourcePage.setObjectName(u'SourcePage') self.sourceLayout = QtGui.QVBoxLayout(self.sourcePage) self.sourceLayout.setObjectName(u'SourceLayout') self.formatLayout = QtGui.QFormLayout() self.formatLayout.setObjectName(u'FormatLayout') self.formatLabel = QtGui.QLabel(self.sourcePage) self.formatLabel.setObjectName(u'FormatLabel') self.formatComboBox = QtGui.QComboBox(self.sourcePage) self.formatComboBox.setObjectName(u'FormatComboBox') self.formatLayout.addRow(self.formatLabel, self.formatComboBox) self.formatSpacer = QtGui.QSpacerItem(10, 0, QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Minimum) self.formatLayout.setItem(1, QtGui.QFormLayout.LabelRole, self.formatSpacer) self.sourceLayout.addLayout(self.formatLayout) self.formatHSpacing = self.formatLayout.horizontalSpacing() self.formatVSpacing = self.formatLayout.verticalSpacing() self.formatLayout.setVerticalSpacing(0) self.stackSpacer = QtGui.QSpacerItem(10, 0, QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Expanding) self.formatStack = QtGui.QStackedLayout() self.formatStack.setObjectName(u'FormatStack') self.disablableFormats = [] for self.currentFormat in SongFormat.get_format_list(): self.addFileSelectItem() self.sourceLayout.addLayout(self.formatStack) self.addPage(self.sourcePage) def retranslateUi(self): """ Song wizard localisation. """ self.setWindowTitle( translate('SongsPlugin.ImportWizardForm', 'Song Import Wizard')) self.titleLabel.setText(WizardStrings.HeaderStyle % translate('OpenLP.Ui', 'Welcome to the Song Import Wizard')) self.informationLabel.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.sourcePage.setTitle(WizardStrings.ImportSelect) self.sourcePage.setSubTitle(WizardStrings.ImportSelectLong) self.formatLabel.setText(WizardStrings.FormatLabel) for format in SongFormat.get_format_list(): format_name, custom_combo_text, description_text, select_mode = \ SongFormat.get(format, u'name', u'comboBoxText', u'descriptionText', u'selectMode') combo_box_text = (custom_combo_text if custom_combo_text else format_name) self.formatComboBox.setItemText(format, combo_box_text) if description_text is not None: self.formatWidgets[format][u'descriptionLabel'].setText( description_text) if select_mode == SongFormatSelect.MultipleFiles: self.formatWidgets[format][u'addButton'].setText( translate('SongsPlugin.ImportWizardForm', 'Add Files...')) self.formatWidgets[format][u'removeButton'].setText( translate('SongsPlugin.ImportWizardForm', 'Remove File(s)')) else: self.formatWidgets[format][u'browseButton'].setText( UiStrings().Browse) f_label = 'Filename:' if select_mode == SongFormatSelect.SingleFolder: f_label = 'Folder:' self.formatWidgets[format][u'filepathLabel'].setText( translate('SongsPlugin.ImportWizardForm', f_label)) for format in self.disablableFormats: self.formatWidgets[format][u'disabledLabel'].setText( SongFormat.get(format, u'disabledLabelText')) self.progressPage.setTitle(WizardStrings.Importing) self.progressPage.setSubTitle( translate('SongsPlugin.ImportWizardForm', 'Please wait while your songs are imported.')) self.progressLabel.setText(WizardStrings.Ready) self.progressBar.setFormat(WizardStrings.PercentSymbolFormat) self.errorCopyToButton.setText(translate('SongsPlugin.ImportWizardForm', 'Copy')) self.errorSaveToButton.setText(translate('SongsPlugin.ImportWizardForm', 'Save to File')) # Align all QFormLayouts towards each other. formats = filter(lambda f: u'filepathLabel' in self.formatWidgets[f], SongFormat.get_format_list()) labels = [self.formatWidgets[f][u'filepathLabel'] for f in formats] # Get max width of all labels max_label_width = max(self.formatLabel.minimumSizeHint().width(), max([label.minimumSizeHint().width() for label in labels])) self.formatSpacer.changeSize(max_label_width, 0, QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) spacers = [self.formatWidgets[f][u'filepathSpacer'] for f in formats] for index, spacer in enumerate(spacers): spacer.changeSize( 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, u'descriptionText') is not None: self.formatWidgets[format][u'descriptionSpacer'].changeSize( max_label_width + self.formatHSpacing, 0, QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) def customPageChanged(self, pageId): """ Called when changing to a page other than the progress page. """ if self.page(pageId) == self.sourcePage: self.onCurrentIndexChanged(self.formatStack.currentIndex()) def validateCurrentPage(self): """ 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.welcomePage: return True elif self.currentPage() == self.sourcePage: format = self.currentFormat Settings().setValue(u'songs/last import type', format) select_mode, class_, error_msg = SongFormat.get(format, u'selectMode', u'class', u'invalidSourceMsg') if select_mode == SongFormatSelect.MultipleFiles: import_source = self.getListOfFiles( self.formatWidgets[format][u'fileListWidget']) error_title = UiStrings().IFSp focus_button = self.formatWidgets[format][u'addButton'] else: import_source = \ self.formatWidgets[format][u'filepathEdit'].text() error_title = (UiStrings().IFSs if select_mode == SongFormatSelect.SingleFile else UiStrings().IFdSs) focus_button = self.formatWidgets[format][u'browseButton'] if not class_.isValidSource(import_source): critical_error_message_box(error_title, error_msg) focus_button.setFocus() return False return True elif self.currentPage() == self.progressPage: return True def getFiles(self, title, listbox, filters=u''): """ 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 as well as the file extensions. For example:: u'SongBeamer Files (*.sng)' """ if filters: filters += u';;' filters += u'%s (*)' % UiStrings().AllFiles filenames = QtGui.QFileDialog.getOpenFileNames(self, title, SettingsManager.get_last_dir(self.plugin.settingsSection, 1), filters) if filenames: listbox.addItems(filenames) SettingsManager.set_last_dir(self.plugin.settingsSection, os.path.split(unicode(filenames[0]))[0], 1) def getListOfFiles(self, listbox): """ Return a list of file from the listbox """ return [unicode(listbox.item(i).text()) for i in range(listbox.count())] def removeSelectedItems(self, listbox): """ Remove selected listbox items """ for item in listbox.selectedItems(): item = listbox.takeItem(listbox.row(item)) del item def onBrowseButtonClicked(self): format = self.currentFormat select_mode, format_name, filter = SongFormat.get(format, u'selectMode', u'name', u'filter') filepathEdit = self.formatWidgets[format][u'filepathEdit'] if select_mode == SongFormatSelect.SingleFile: self.getFileName(WizardStrings.OpenTypeFile % format_name, filepathEdit, filter) elif select_mode == SongFormatSelect.SingleFolder: self.getFolder(WizardStrings.OpenTypeFolder % format_name, filepathEdit) def onAddButtonClicked(self): format = self.currentFormat select_mode, format_name, filter, custom_title = SongFormat.get(format, u'selectMode', u'name', u'filter', u'getFilesTitle') title = custom_title if custom_title \ else WizardStrings.OpenTypeFile % format_name if select_mode == SongFormatSelect.MultipleFiles: self.getFiles(title, self.formatWidgets[format][u'fileListWidget'], filter) self.sourcePage.emit(QtCore.SIGNAL(u'completeChanged()')) def onRemoveButtonClicked(self): self.removeSelectedItems( self.formatWidgets[self.currentFormat][u'fileListWidget']) self.sourcePage.emit(QtCore.SIGNAL(u'completeChanged()')) def onFilepathEditTextChanged(self): """ Called when the content of the Filename/Folder edit box changes. """ self.sourcePage.emit(QtCore.SIGNAL(u'completeChanged()')) def setDefaults(self): """ Set default form values for the song import wizard. """ self.restart() self.finishButton.setVisible(False) self.cancelButton.setVisible(True) last_import_type = Settings().value( u'songs/last import type').toInt()[0] if last_import_type < 0 or \ last_import_type >= self.formatComboBox.count(): last_import_type = 0 self.formatComboBox.setCurrentIndex(last_import_type) for format in SongFormat.get_format_list(): select_mode = SongFormat.get(format, u'selectMode') if select_mode == SongFormatSelect.MultipleFiles: self.formatWidgets[format][u'fileListWidget'].clear() else: self.formatWidgets[format][u'filepathEdit'].setText(u'') self.errorReportTextEdit.clear() self.errorReportTextEdit.setHidden(True) self.errorCopyToButton.setHidden(True) self.errorSaveToButton.setHidden(True) def preWizard(self): """ Perform pre import tasks """ OpenLPWizard.preWizard(self) self.progressLabel.setText(WizardStrings.StartingImport) Receiver.send_message(u'openlp_process_events') def performWizard(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. """ source_format = self.currentFormat select_mode = SongFormat.get(source_format, u'selectMode') if select_mode == SongFormatSelect.SingleFile: importer = self.plugin.importSongs(source_format, filename=unicode( self.formatWidgets[source_format][u'filepathEdit'].text())) elif select_mode == SongFormatSelect.SingleFolder: importer = self.plugin.importSongs(source_format, folder=unicode( self.formatWidgets[source_format][u'filepathEdit'].text())) else: importer = self.plugin.importSongs(source_format, filenames=self.getListOfFiles( self.formatWidgets[source_format][u'fileListWidget'])) importer.doImport() self.progressLabel.setText(WizardStrings.FinishedImport) def onErrorCopyToButtonClicked(self): """ Copy the error report to the clipboard. """ self.clipboard.setText(self.errorReportTextEdit.toPlainText()) def onErrorSaveToButtonClicked(self): """ Save the error report to a file. """ filename = QtGui.QFileDialog.getSaveFileName(self, SettingsManager.get_last_dir(self.plugin.settingsSection, 1)) if not filename: return report_file = codecs.open(filename, u'w', u'utf-8') report_file.write(self.errorReportTextEdit.toPlainText()) report_file.close() def addFileSelectItem(self): format = self.currentFormat prefix, can_disable, description_text, select_mode = SongFormat.get( format, u'prefix', u'canDisable', u'descriptionText', u'selectMode') page = QtGui.QWidget() page.setObjectName(prefix + u'Page') if can_disable: importWidget = self.disablableWidget(page, prefix) else: importWidget = page importLayout = QtGui.QVBoxLayout(importWidget) importLayout.setMargin(0) importLayout.setObjectName(prefix + u'ImportLayout') if description_text is not None: descriptionLayout = QtGui.QHBoxLayout() descriptionLayout.setObjectName(prefix + u'DescriptionLayout') descriptionSpacer = QtGui.QSpacerItem(0, 0, QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) descriptionLayout.addSpacerItem(descriptionSpacer) descriptionLabel = QtGui.QLabel(importWidget) descriptionLabel.setWordWrap(True) descriptionLabel.setOpenExternalLinks(True) descriptionLabel.setObjectName(prefix + u'DescriptionLabel') descriptionLayout.addWidget(descriptionLabel) importLayout.addLayout(descriptionLayout) self.formatWidgets[format][u'descriptionLabel'] = descriptionLabel self.formatWidgets[format][u'descriptionSpacer'] = descriptionSpacer if select_mode == SongFormatSelect.SingleFile or \ select_mode == SongFormatSelect.SingleFolder: filepathLayout = QtGui.QHBoxLayout() filepathLayout.setObjectName(prefix + u'FilepathLayout') filepathLayout.setContentsMargins(0, self.formatVSpacing, 0, 0) filepathLabel = QtGui.QLabel(importWidget) filepathLabel.setObjectName(prefix + u'FilepathLabel') filepathLayout.addWidget(filepathLabel) filepathSpacer = QtGui.QSpacerItem(0, 0, QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) filepathLayout.addSpacerItem(filepathSpacer) filepathEdit = QtGui.QLineEdit(importWidget) filepathEdit.setObjectName(prefix + u'FilepathEdit') filepathLayout.addWidget(filepathEdit) browseButton = QtGui.QToolButton(importWidget) browseButton.setIcon(self.openIcon) browseButton.setObjectName(prefix + u'BrowseButton') filepathLayout.addWidget(browseButton) importLayout.addLayout(filepathLayout) importLayout.addSpacerItem(self.stackSpacer) self.formatWidgets[format][u'filepathLabel'] = filepathLabel self.formatWidgets[format][u'filepathSpacer'] = filepathSpacer self.formatWidgets[format][u'filepathLayout'] = filepathLayout self.formatWidgets[format][u'filepathEdit'] = filepathEdit self.formatWidgets[format][u'browseButton'] = browseButton elif select_mode == SongFormatSelect.MultipleFiles: fileListWidget = QtGui.QListWidget(importWidget) fileListWidget.setSelectionMode( QtGui.QAbstractItemView.ExtendedSelection) fileListWidget.setObjectName(prefix + u'FileListWidget') importLayout.addWidget(fileListWidget) buttonLayout = QtGui.QHBoxLayout() buttonLayout.setObjectName(prefix + u'ButtonLayout') addButton = QtGui.QPushButton(importWidget) addButton.setIcon(self.openIcon) addButton.setObjectName(prefix + u'AddButton') buttonLayout.addWidget(addButton) buttonLayout.addStretch() removeButton = QtGui.QPushButton(importWidget) removeButton.setIcon(self.deleteIcon) removeButton.setObjectName(prefix + u'RemoveButton') buttonLayout.addWidget(removeButton) importLayout.addLayout(buttonLayout) self.formatWidgets[format][u'fileListWidget'] = fileListWidget self.formatWidgets[format][u'buttonLayout'] = buttonLayout self.formatWidgets[format][u'addButton'] = addButton self.formatWidgets[format][u'removeButton'] = removeButton self.formatStack.addWidget(page) self.formatWidgets[format][u'page'] = page self.formatWidgets[format][u'importLayout'] = importLayout self.formatComboBox.addItem(u'') def disablableWidget(self, page, prefix): format = self.currentFormat self.disablableFormats.append(format) layout = QtGui.QVBoxLayout(page) layout.setMargin(0) layout.setSpacing(0) layout.setObjectName(prefix + u'Layout') disabledWidget = QtGui.QWidget(page) disabledWidget.setVisible(False) disabledWidget.setObjectName(prefix + u'DisabledWidget') disabledLayout = QtGui.QVBoxLayout(disabledWidget) disabledLayout.setMargin(0) disabledLayout.setObjectName(prefix + u'DisabledLayout') disabledLabel = QtGui.QLabel(disabledWidget) disabledLabel.setWordWrap(True) disabledLabel.setObjectName(prefix + u'DisabledLabel') disabledLayout.addWidget(disabledLabel) disabledLayout.addSpacerItem(self.stackSpacer) layout.addWidget(disabledWidget) importWidget = QtGui.QWidget(page) importWidget.setObjectName(prefix + u'ImportWidget') layout.addWidget(importWidget) self.formatWidgets[format][u'layout'] = layout self.formatWidgets[format][u'disabledWidget'] = disabledWidget self.formatWidgets[format][u'disabledLayout'] = disabledLayout self.formatWidgets[format][u'disabledLabel'] = disabledLabel self.formatWidgets[format][u'importWidget'] = importWidget return importWidget class SongImportSourcePage(QtGui.QWizardPage): """ Subclass of QtGui.QWizardPage to override isComplete() for Source Page. """ def isComplete(self): """ Return True if: * an available format is selected, and * if MultipleFiles mode, at least one file is selected * or if SingleFile mode, the specified file exists * or if SingleFolder mode, the specified folder exists When this method returns True, the wizard's Next button is enabled. """ wizard = self.wizard() format = wizard.currentFormat select_mode, format_available = SongFormat.get(format, u'selectMode', u'availability') if format_available: if select_mode == SongFormatSelect.MultipleFiles: if wizard.formatWidgets[format][u'fileListWidget'].count() > 0: return True else: filepath = unicode( wizard.formatWidgets[format][u'filepathEdit'].text()) if filepath: if select_mode == SongFormatSelect.SingleFile and \ os.path.isfile(filepath): return True elif select_mode == SongFormatSelect.SingleFolder and \ os.path.isdir(filepath): return True return False