diff --git a/openlp/core/lib/toolbar.py b/openlp/core/lib/toolbar.py index 763e6bf67..716445889 100644 --- a/openlp/core/lib/toolbar.py +++ b/openlp/core/lib/toolbar.py @@ -28,8 +28,6 @@ Provide common toolbar handling for OpenLP """ import logging -from PyQt4 import QtCore, QtGui - from openlp.core.lib import build_icon log = logging.getLogger(__name__) @@ -51,7 +49,8 @@ class OpenLPToolbar(QtGui.QToolBar): log.debug(u'Init done') def addToolbarButton(self, title, icon, tooltip=None, slot=None, - checkable=False): + checkable=False, shortcut=0, alternate=0, + context=QtCore.Qt.WidgetShortcut): """ A method to help developers easily add a button to the toolbar. @@ -72,6 +71,15 @@ class OpenLPToolbar(QtGui.QToolBar): ``checkable`` If *True* the button has two, *off* and *on*, states. Default is *False*, which means the buttons has only one state. + + ``shortcut`` + The primary shortcut for this action + + ``alternate`` + The alternate shortcut for this action + + ``context`` + Specify the context in which this shortcut is valid """ newAction = None if icon: @@ -93,6 +101,8 @@ class OpenLPToolbar(QtGui.QToolBar): QtCore.QObject.connect(newAction, QtCore.SIGNAL(u'toggled(bool)'), slot) self.actions[title] = newAction + newAction.setShortcuts([shortcut, alternate]) + newAction.setShortcutContext(context) return newAction def addToolbarSeparator(self, handle): diff --git a/openlp/core/ui/maindisplay.py b/openlp/core/ui/maindisplay.py index ec5ab7b3f..911aeda10 100644 --- a/openlp/core/ui/maindisplay.py +++ b/openlp/core/ui/maindisplay.py @@ -54,45 +54,6 @@ class DisplayWidget(QtGui.QGraphicsView): QtGui.QGraphicsView.__init__(self) self.parent = parent self.live = live - self.hotkey_map = { - QtCore.Qt.Key_Return: 'servicemanager_next_item', - QtCore.Qt.Key_Space: 'slidecontroller_live_next_noloop', - QtCore.Qt.Key_Enter: 'slidecontroller_live_next_noloop', - QtCore.Qt.Key_0: 'servicemanager_next_item', - QtCore.Qt.Key_Backspace: 'slidecontroller_live_previous_noloop'} - self.setStyleSheet(u'border: none;') - - def keyPressEvent(self, event): - """ - Handle key events from display screen - """ - # Key events only needed for live - if not self.live: - return - if isinstance(event, QtGui.QKeyEvent): - # Here accept the event and do something - if event.key() == QtCore.Qt.Key_Up: - Receiver.send_message(u'slidecontroller_live_previous') - event.accept() - elif event.key() == QtCore.Qt.Key_Down: - Receiver.send_message(u'slidecontroller_live_next') - event.accept() - elif event.key() == QtCore.Qt.Key_PageUp: - Receiver.send_message(u'slidecontroller_live_first') - event.accept() - elif event.key() == QtCore.Qt.Key_PageDown: - Receiver.send_message(u'slidecontroller_live_last') - event.accept() - elif event.key() in self.hotkey_map: - Receiver.send_message(self.hotkey_map[event.key()]) - event.accept() - elif event.key() == QtCore.Qt.Key_Escape: - self.setVisible(False) - self.videoStop() - event.accept() - event.ignore() - else: - event.ignore() class MainDisplay(DisplayWidget): diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py index 36368d236..81e0bf07a 100644 --- a/openlp/core/ui/servicemanager.py +++ b/openlp/core/ui/servicemanager.py @@ -49,34 +49,6 @@ class ServiceManagerList(QtGui.QTreeWidget): QtGui.QTreeWidget.__init__(self, parent) self.mainwindow = mainwindow - def keyPressEvent(self, event): - if isinstance(event, QtGui.QKeyEvent): - #here accept the event and do something - if event.key() == QtCore.Qt.Key_Enter: - self.mainwindow.makeLive() - event.accept() - elif event.key() == QtCore.Qt.Key_Home: - self.mainwindow.onServiceTop() - event.accept() - elif event.key() == QtCore.Qt.Key_End: - self.mainwindow.onServiceEnd() - event.accept() - elif event.key() == QtCore.Qt.Key_PageUp: - self.mainwindow.onServiceUp() - event.accept() - elif event.key() == QtCore.Qt.Key_PageDown: - self.mainwindow.onServiceDown() - event.accept() - elif event.key() == QtCore.Qt.Key_Up: - self.mainwindow.onMoveSelectionUp() - event.accept() - elif event.key() == QtCore.Qt.Key_Down: - self.mainwindow.onMoveSelectionDown() - event.accept() - event.ignore() - else: - event.ignore() - def mouseMoveEvent(self, event): """ Drag and drop event does not care what data is selected @@ -178,50 +150,72 @@ class ServiceManager(QtGui.QWidget): self.layout.addWidget(self.serviceManagerList) # Add the bottom toolbar self.orderToolbar = OpenLPToolbar(self) - self.orderToolbar.addToolbarButton( + self.serviceManagerList.moveTop = self.orderToolbar.addToolbarButton( translate('OpenLP.ServiceManager', 'Move to &top'), u':/services/service_top.png', translate('OpenLP.ServiceManager', 'Move item to the top of the service.'), - self.onServiceTop) - self.orderToolbar.addToolbarButton( + self.onServiceTop, shortcut=QtCore.Qt.Key_Home) + self.serviceManagerList.moveUp = self.orderToolbar.addToolbarButton( translate('OpenLP.ServiceManager', 'Move &up'), u':/services/service_up.png', translate('OpenLP.ServiceManager', 'Move item up one position in the service.'), - self.onServiceUp) - self.orderToolbar.addToolbarButton( + self.onServiceUp, shortcut=QtCore.Qt.Key_PageUp) + self.serviceManagerList.moveDown = self.orderToolbar.addToolbarButton( translate('OpenLP.ServiceManager', 'Move &down'), u':/services/service_down.png', translate('OpenLP.ServiceManager', 'Move item down one position in the service.'), - self.onServiceDown) - self.orderToolbar.addToolbarButton( + self.onServiceDown, shortcut=QtCore.Qt.Key_PageDown) + self.serviceManagerList.moveBottom = self.orderToolbar.addToolbarButton( translate('OpenLP.ServiceManager', 'Move to &bottom'), u':/services/service_bottom.png', translate('OpenLP.ServiceManager', 'Move item to the end of the service.'), - self.onServiceEnd) + self.onServiceEnd, shortcut=QtCore.Qt.Key_End) + self.serviceManagerList.down = self.orderToolbar.addToolbarButton( + translate('OpenLP.ServiceManager', 'Move &down'), + None, + translate('OpenLP.ServiceManager', + 'Moves the selection up the window.'), + self.onMoveSelectionDown, shortcut=QtCore.Qt.Key_Up) + self.serviceManagerList.down.setVisible(False) + self.serviceManagerList.up = self.orderToolbar.addToolbarButton( + translate('OpenLP.ServiceManager', 'Move up'), + None, + translate('OpenLP.ServiceManager', + 'Moves the selection up the window.'), + self.onMoveSelectionUp, shortcut=QtCore.Qt.Key_Up) + self.serviceManagerList.up.setVisible(False) self.orderToolbar.addSeparator() - self.orderToolbar.addToolbarButton( + self.serviceManagerList.delete = self.orderToolbar.addToolbarButton( translate('OpenLP.ServiceManager', '&Delete From Service'), u':/general/general_delete.png', translate('OpenLP.ServiceManager', 'Delete the selected item from the service.'), self.onDeleteFromService) self.orderToolbar.addSeparator() - self.orderToolbar.addToolbarButton( + self.serviceManagerList.expand = self.orderToolbar.addToolbarButton( translate('OpenLP.ServiceManager', '&Expand all'), u':/services/service_expand_all.png', translate('OpenLP.ServiceManager', 'Expand all the service items.'), self.onExpandAll) - self.orderToolbar.addToolbarButton( + self.serviceManagerList.collapse = self.orderToolbar.addToolbarButton( translate('OpenLP.ServiceManager', '&Collapse all'), u':/services/service_collapse_all.png', translate('OpenLP.ServiceManager', 'Collapse all the service items.'), self.onCollapseAll) + self.orderToolbar.addSeparator() + self.serviceManagerList.makeLive = self.orderToolbar.addToolbarButton( + translate('OpenLP.ServiceManager', 'Go Live'), + u':/general/general_live.png', + translate('OpenLP.ServiceManager', + 'Send the selected item to Live.'), + self.makeLive, shortcut=QtCore.Qt.Key_Enter, + alternate=QtCore.Qt.Key_Return) self.orderToolbar.setObjectName(u'orderToolbar') self.layout.addWidget(self.orderToolbar) # Connect up our signals and slots @@ -292,8 +286,28 @@ class ServiceManager(QtGui.QWidget): self.themeMenu = QtGui.QMenu( translate('OpenLP.ServiceManager', '&Change Item Theme')) self.menu.addMenu(self.themeMenu) + self.setServiceHotkeys() + self.serviceManagerList.addActions( + [self.serviceManagerList.moveDown, + self.serviceManagerList.moveUp, + self.serviceManagerList.makeLive, + self.serviceManagerList.moveTop, + self.serviceManagerList.moveBottom, + self.serviceManagerList.up, + self.serviceManagerList.down + ]) self.configUpdated() + def setServiceHotkeys(self): + actionList = self.mainwindow.actionList + actionList.add_action(self.serviceManagerList.moveDown, u'Service') + actionList.add_action(self.serviceManagerList.moveUp, u'Service') + actionList.add_action(self.serviceManagerList.moveTop, u'Service') + actionList.add_action(self.serviceManagerList.moveBottom, u'Service') + actionList.add_action(self.serviceManagerList.makeLive, u'Service') + actionList.add_action(self.serviceManagerList.up, u'Service') + actionList.add_action(self.serviceManagerList.down, u'Service') + def setModified(self, modified=True): """ Setter for property "modified". Sets whether or not the current service diff --git a/openlp/core/ui/slidecontroller.py b/openlp/core/ui/slidecontroller.py index 28b419616..3ff97e7f4 100644 --- a/openlp/core/ui/slidecontroller.py +++ b/openlp/core/ui/slidecontroller.py @@ -33,6 +33,7 @@ from PyQt4.phonon import Phonon from openlp.core.ui import HideMode, MainDisplay from openlp.core.lib import OpenLPToolbar, Receiver, resize_image, \ ItemCapabilities, translate +from openlp.core.utils import ActionList log = logging.getLogger(__name__) @@ -44,34 +45,6 @@ class SlideList(QtGui.QTableWidget): def __init__(self, parent=None, name=None): QtGui.QTableWidget.__init__(self, parent.Controller) self.parent = parent - self.hotkeyMap = { - QtCore.Qt.Key_Return: 'servicemanager_next_item', - QtCore.Qt.Key_Space: 'slidecontroller_live_next_noloop', - QtCore.Qt.Key_Enter: 'slidecontroller_live_next_noloop', - QtCore.Qt.Key_0: 'servicemanager_next_item', - QtCore.Qt.Key_Backspace: 'slidecontroller_live_previous_noloop'} - - def keyPressEvent(self, event): - if isinstance(event, QtGui.QKeyEvent): - #here accept the event and do something - if event.key() == QtCore.Qt.Key_Up: - self.parent.onSlideSelectedPrevious() - event.accept() - elif event.key() == QtCore.Qt.Key_Down: - self.parent.onSlideSelectedNext() - event.accept() - elif event.key() == QtCore.Qt.Key_PageUp: - self.parent.onSlideSelectedFirst() - event.accept() - elif event.key() == QtCore.Qt.Key_PageDown: - self.parent.onSlideSelectedLast() - event.accept() - elif event.key() in self.hotkeyMap and self.parent.isLive: - Receiver.send_message(self.hotkeyMap[event.key()]) - event.accept() - event.ignore() - else: - event.ignore() class SlideController(QtGui.QWidget): @@ -162,12 +135,14 @@ class SlideController(QtGui.QWidget): sizeToolbarPolicy.setHeightForWidth( self.Toolbar.sizePolicy().hasHeightForWidth()) self.Toolbar.setSizePolicy(sizeToolbarPolicy) - self.Toolbar.addToolbarButton( - u'Previous Slide', u':/slides/slide_previous.png', + self.previousItem = self.Toolbar.addToolbarButton( + translate('OpenLP.SlideController', 'Previous Slide'), + u':/slides/slide_previous.png', translate('OpenLP.SlideController', 'Move to previous'), self.onSlideSelectedPrevious) - self.Toolbar.addToolbarButton( - u'Next Slide', u':/slides/slide_next.png', + self.nextItem = self.Toolbar.addToolbarButton( + translate('OpenLP.SlideController', 'Next Slide'), + u':/slides/slide_next.png', translate('OpenLP.SlideController', 'Move to next'), self.onSlideSelectedNext) if self.isLive: @@ -339,6 +314,25 @@ class SlideController(QtGui.QWidget): self.onGoLiveClick) self.Toolbar.makeWidgetsInvisible(self.songEditList) self.Mediabar.setVisible(False) + if self.isLive: + self.setLiveHotkeys(self) + self.PreviewListWidget.addActions( + [self.previousItem, + self.nextItem, + self.previousService, + self.nextService, + self.escapeItem]) + self.display.addActions( + [self.previousItem, + self.nextItem, + self.previousService, + self.nextService, + self.escapeItem]) + else: + self.setPreviewHotkeys() + self.PreviewListWidget.addActions( + [self.nextItem, + self.previousItem]) QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'slidecontroller_%s_stop_loop' % self.typePrefix), self.onStopLoop) @@ -379,6 +373,55 @@ class SlideController(QtGui.QWidget): QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'config_screen_changed'), self.screenSizeChanged) + def setPreviewHotkeys(self, parent=None): + actionList = self.parent.actionList + self.previousItem.setShortcuts([QtCore.Qt.Key_Up, 0]) + actionList.add_action(self.previousItem, u'Preview') + self.nextItem.setShortcuts([QtCore.Qt.Key_Down, 0]) + actionList.add_action(self.nextItem, u'Preview') + + def setLiveHotkeys(self, parent=None): + actionList = self.parent.actionList + self.previousItem.setShortcuts([QtCore.Qt.Key_Up, QtCore.Qt.Key_PageUp]) + self.previousItem.setShortcutContext( + QtCore.Qt.WidgetWithChildrenShortcut) + actionList.add_action(self.nextItem, u'Live') + self.nextItem.setShortcuts([QtCore.Qt.Key_Down, QtCore.Qt.Key_PageDown]) + self.nextItem.setShortcutContext(QtCore.Qt.WidgetWithChildrenShortcut) + actionList.add_action(self.nextItem, u'Live') + self.previousService = QtGui.QAction(translate( + 'OpenLP.SlideController', 'Previous Service'), parent) + self.previousService.setShortcuts([QtCore.Qt.Key_Left, 0]) + self.previousService.setShortcutContext(QtCore.Qt.WidgetWithChildrenShortcut) + QtCore.QObject.connect(self.previousService, + QtCore.SIGNAL(u'triggered()'), self.servicePrevious) + actionList.add_action(self.previousService, u'Live') + self.nextService = QtGui.QAction(translate( + 'OpenLP.SlideController', 'Next Service'), parent) + self.nextService.setShortcuts([QtCore.Qt.Key_Right, 0]) + self.nextService.setShortcutContext(QtCore.Qt.WidgetWithChildrenShortcut) + QtCore.QObject.connect(self.nextService, + QtCore.SIGNAL(u'triggered()'), self.serviceNext) + actionList.add_action(self.nextService, u'Live') + self.escapeItem = QtGui.QAction(translate( + 'OpenLP.SlideController', 'Escape Item'), parent) + self.escapeItem.setShortcuts([QtCore.Qt.Key_Escape, 0]) + self.escapeItem.setShortcutContext(QtCore.Qt.WidgetWithChildrenShortcut) + QtCore.QObject.connect(self.escapeItem, + QtCore.SIGNAL(u'triggered()'), self.liveEscape) + actionList.add_action(self.escapeItem, u'Live') + + def liveEscape(self): + self.display.setVisible(False) + self.display.videoStop() + + def servicePrevious(self): + Receiver.send_message('servicemanager_previous_item') + + def serviceNext(self): + Receiver.send_message('servicemanager_next_item') + + def screenSizeChanged(self): """ Settings dialog has changed the screen size of adjust output and @@ -389,6 +432,13 @@ class SlideController(QtGui.QWidget): self.display.imageManager = self.parent.renderManager.image_manager self.display.alertTab = self.alertTab self.display.setup() + if self.isLive: + self.display.addActions( + [self.previousItem, + self.nextItem, + self.previousService, + self.nextService, + self.escapeItem]) # The SlidePreview's ratio. self.ratio = float(self.screens.current[u'size'].width()) / \ float(self.screens.current[u'size'].height()) @@ -1071,4 +1121,3 @@ class SlideController(QtGui.QWidget): if self.DesktopScreen.isChecked: self.DesktopScreen.setChecked(False) self.HideMenu.setDefaultAction(self.DesktopScreen) - diff --git a/openlp/core/utils/actions.py b/openlp/core/utils/actions.py index 4434c416e..00c428480 100644 --- a/openlp/core/utils/actions.py +++ b/openlp/core/utils/actions.py @@ -27,6 +27,7 @@ The :mod:`~openlp.core.utils.actions` module provides action list classes used by the shortcuts system. """ +from PyQt4 import QtCore class ActionCategory(object): """ @@ -181,4 +182,4 @@ class ActionList(object): if weight is None: self.categories[category].actions.append(action) else: - self.categories[category].actions.add(action, weight) \ No newline at end of file + self.categories[category].actions.add(action, weight) diff --git a/openlp/plugins/bibles/lib/__init__.py b/openlp/plugins/bibles/lib/__init__.py index f2797e16d..34e1bf5a5 100644 --- a/openlp/plugins/bibles/lib/__init__.py +++ b/openlp/plugins/bibles/lib/__init__.py @@ -109,7 +109,7 @@ def parse_reference(reference): regular expression looks like this: 1. ``^\s*(?!\s)(?P[\d]*[^\d]+)(?(?:`` + range_string + ``(?:%(sep_l)s|(?=\s*$)))+)\s*$`` @@ -177,7 +177,7 @@ def parse_reference(reference): to_verse = -1 if to_chapter > from_chapter: ref_list.append((book, from_chapter, from_verse, -1)) - for i in range(from_chapter + 1, to_chapter - 1): + for i in range(from_chapter + 1, to_chapter): ref_list.append((book, i, 1, -1)) ref_list.append((book, to_chapter, 1, to_verse)) elif to_verse >= from_verse or to_verse == -1: diff --git a/openlp/plugins/songs/forms/songimportform.py b/openlp/plugins/songs/forms/songimportform.py index d14ab9c36..3de251462 100644 --- a/openlp/plugins/songs/forms/songimportform.py +++ b/openlp/plugins/songs/forms/songimportform.py @@ -654,7 +654,7 @@ class SongImportForm(OpenLPWizard): 'Select EasiSlides songfile'), self.easiSlidesFilenameEdit ) - + def onEWBrowseButtonClicked(self): """ Get EasyWorship song database files @@ -784,7 +784,6 @@ class SongImportForm(OpenLPWizard): filenames=self.getListOfFiles(self.songBeamerFileListWidget) ) if importer.do_import(): - # reload songs self.progressLabel.setText( translate('SongsPlugin.SongImportForm', 'Finished import.')) else: diff --git a/openlp/plugins/songs/forms/songmaintenanceform.py b/openlp/plugins/songs/forms/songmaintenanceform.py index bf7fb40b7..7da8ddd81 100644 --- a/openlp/plugins/songs/forms/songmaintenanceform.py +++ b/openlp/plugins/songs/forms/songmaintenanceform.py @@ -50,6 +50,14 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog): self.authorform = AuthorsForm(self) self.topicform = TopicsForm(self) self.bookform = SongBookForm(self) + # Disable all edit and delete buttons, as there is no row selected. + self.authorsDeleteButton.setEnabled(False) + self.authorsEditButton.setEnabled(False) + self.topicsDeleteButton.setEnabled(False) + self.topicsEditButton.setEnabled(False) + self.booksDeleteButton.setEnabled(False) + self.booksEditButton.setEnabled(False) + # Signals QtCore.QObject.connect(self.authorsAddButton, QtCore.SIGNAL(u'pressed()'), self.onAuthorAddButtonClick) QtCore.QObject.connect(self.topicsAddButton, @@ -68,6 +76,15 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog): QtCore.SIGNAL(u'pressed()'), self.onTopicDeleteButtonClick) QtCore.QObject.connect(self.booksDeleteButton, QtCore.SIGNAL(u'pressed()'), self.onBookDeleteButtonClick) + QtCore.QObject.connect(self.authorsListWidget, + QtCore.SIGNAL(u'currentRowChanged(int)'), + self.onAuthorsListRowChanged) + QtCore.QObject.connect(self.topicsListWidget, + QtCore.SIGNAL(u'currentRowChanged(int)'), + self.onTopicsListRowChanged) + QtCore.QObject.connect(self.booksListWidget, + QtCore.SIGNAL(u'currentRowChanged(int)'), + self.onBooksListRowChanged) def exec_(self): self.typeListWidget.setCurrentRow(0) @@ -115,12 +132,6 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog): u' '.join([author.first_name, author.last_name])) author_name.setData(QtCore.Qt.UserRole, QtCore.QVariant(author.id)) self.authorsListWidget.addItem(author_name) - if self.authorsListWidget.count() == 0: - self.authorsDeleteButton.setEnabled(False) - self.authorsEditButton.setEnabled(False) - else: - self.authorsDeleteButton.setEnabled(True) - self.authorsEditButton.setEnabled(True) def resetTopics(self): """ @@ -132,12 +143,6 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog): topic_name = QtGui.QListWidgetItem(topic.name) topic_name.setData(QtCore.Qt.UserRole, QtCore.QVariant(topic.id)) self.topicsListWidget.addItem(topic_name) - if self.topicsListWidget.count() == 0: - self.topicsDeleteButton.setEnabled(False) - self.topicsEditButton.setEnabled(False) - else: - self.topicsDeleteButton.setEnabled(True) - self.topicsEditButton.setEnabled(True) def resetBooks(self): """ @@ -150,26 +155,22 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog): book.publisher)) book_name.setData(QtCore.Qt.UserRole, QtCore.QVariant(book.id)) self.booksListWidget.addItem(book_name) - if self.booksListWidget.count() == 0: - self.booksDeleteButton.setEnabled(False) - self.booksEditButton.setEnabled(False) - else: - self.booksDeleteButton.setEnabled(True) - self.booksEditButton.setEnabled(True) def checkAuthor(self, new_author, edit=False): """ - Returns False if the given Author is already in the list otherwise - True. + Returns *False* if the given Author already exists, otherwise *True*. + + ``edit`` + If we edit an item, this should be *True*. """ authors = self.manager.get_all_objects(Author, and_(Author.first_name == new_author.first_name, Author.last_name == new_author.last_name, Author.display_name == new_author.display_name)) + # Check if this author already exists. if len(authors) > 0: # If we edit an existing Author, we need to make sure that we do - # not return False when nothing has changed (because this would - # cause an error message later on). + # not return False when nothing has changed. if edit: for author in authors: if author.id != new_author.id: @@ -182,14 +183,16 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog): def checkTopic(self, new_topic, edit=False): """ - Returns False if the given Topic is already in the list otherwise True. + Returns *False* if the given Topic already exists, otherwise *True*. + + ``edit`` + If we edit an item, this should be *True*. """ topics = self.manager.get_all_objects(Topic, Topic.name == new_topic.name) if len(topics) > 0: # If we edit an existing Topic, we need to make sure that we do - # not return False when nothing has changed (because this would - # cause an error message later on). + # not return False when nothing has changed. if edit: for topic in topics: if topic.id != new_topic.id: @@ -202,15 +205,17 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog): def checkBook(self, new_book, edit=False): """ - Returns False if the given Book is already in the list otherwise True. + Returns *False* if the given Topic already exists, otherwise *True*. + + ``edit`` + If we edit an item, this should be *True*. """ books = self.manager.get_all_objects(Book, and_(Book.name == new_book.name, Book.publisher == new_book.publisher)) if len(books) > 0: # If we edit an existing Book, we need to make sure that we do - # not return False when nothing has changed (because this would - # cause an error message later on). + # not return False when nothing has changed. if edit: for book in books: if book.id != new_book.id: @@ -390,12 +395,14 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog): Merges two authors into one author. ``old_author`` - The author which will be deleted afterwards. + The object, which was edited, that will be deleted """ + # Find the duplicate. existing_author = self.manager.get_object_filtered(Author, and_(Author.first_name == old_author.first_name, Author.last_name == old_author.last_name, Author.display_name == old_author.display_name)) + # Find the songs, which have the old_author as author. songs = self.manager.get_all_objects(Song, Song.authors.contains(old_author)) for song in songs: @@ -412,10 +419,12 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog): Merges two topics into one topic. ``old_topic`` - The topic which will be deleted afterwards. + The object, which was edited, that will be deleted """ + # Find the duplicate. existing_topic = self.manager.get_object_filtered(Topic, Topic.name == old_topic.name) + # Find the songs, which have the old_topic as topic. songs = self.manager.get_all_objects(Song, Song.topics.contains(old_topic)) for song in songs: @@ -432,11 +441,13 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog): Merges two books into one book. ``old_book`` - The book which will be deleted afterwards. + The object, which was edited, that will be deleted """ + # Find the duplicate. existing_book = self.manager.get_object_filtered(Book, and_(Book.name == old_book.name, Book.publisher == old_book.publisher)) + # Find the songs, which have the old_book as book. songs = self.manager.get_all_objects(Song, Song.song_book_id == old_book.id) for song in songs: @@ -482,3 +493,46 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog): 'This book cannot be deleted, it is currently ' 'assigned to at least one song.'), translate('SongsPlugin.SongMaintenanceForm', 'No book selected!')) + + def onAuthorsListRowChanged(self, row): + """ + Called when the *authorsListWidget* current's row has changed. + + ``row`` + The current row. If there is no current row, the value is -1 + """ + if row == -1: + self.authorsDeleteButton.setEnabled(False) + self.authorsEditButton.setEnabled(False) + else: + self.authorsDeleteButton.setEnabled(True) + self.authorsEditButton.setEnabled(True) + + def onTopicsListRowChanged(self, row): + """ + Called when the *booksListWidget* current's row has changed. + + ``row`` + The current row. If there is no current row, the value is -1. + """ + if row == -1: + self.topicsDeleteButton.setEnabled(False) + self.topicsEditButton.setEnabled(False) + else: + self.topicsDeleteButton.setEnabled(True) + self.topicsEditButton.setEnabled(True) + + def onBooksListRowChanged(self, row): + """ + Called when the *booksListWidget* current's row has changed. + + ``row`` + The current row. If there is no current row, the value is -1. + """ + if row == -1: + self.booksDeleteButton.setEnabled(False) + self.booksEditButton.setEnabled(False) + else: + self.booksDeleteButton.setEnabled(True) + self.booksEditButton.setEnabled(True) + diff --git a/openlp/plugins/songs/lib/easislidesimport.py b/openlp/plugins/songs/lib/easislidesimport.py index 8685b6934..316b78d8c 100644 --- a/openlp/plugins/songs/lib/easislidesimport.py +++ b/openlp/plugins/songs/lib/easislidesimport.py @@ -313,7 +313,6 @@ class EasiSlidesImport(SongImport): tag = SeqTypes[tag.lower()] else: continue - if tag in versetags: self.verse_order_list.append(tag) else: diff --git a/openlp/plugins/songs/lib/mediaitem.py b/openlp/plugins/songs/lib/mediaitem.py index 67021616b..625f99f18 100644 --- a/openlp/plugins/songs/lib/mediaitem.py +++ b/openlp/plugins/songs/lib/mediaitem.py @@ -157,7 +157,6 @@ class SongMediaItem(MediaManagerItem): (5, u':/slides/slide_theme.png', translate('SongsPlugin.MediaItem', 'Themes')) ]) - self.configUpdated() def onSearchTextButtonClick(self): @@ -269,8 +268,8 @@ class SongMediaItem(MediaManagerItem): def onImportClick(self): if not hasattr(self, u'import_wizard'): self.import_wizard = SongImportForm(self, self.parent) - self.import_wizard.exec_() - Receiver.send_message(u'songs_load_list') + if self.import_wizard.exec_() == QtGui.QDialog.Accepted: + Receiver.send_message(u'songs_load_list') def onNewClick(self): log.debug(u'onNewClick') diff --git a/openlp/plugins/songs/lib/openlyricsimport.py b/openlp/plugins/songs/lib/openlyricsimport.py index 220160b1d..37407c75c 100644 --- a/openlp/plugins/songs/lib/openlyricsimport.py +++ b/openlp/plugins/songs/lib/openlyricsimport.py @@ -61,17 +61,18 @@ class OpenLyricsImport(SongImport): Imports the songs. """ self.import_wizard.progressBar.setMaximum(len(self.import_source)) + parser = etree.XMLParser(remove_blank_text=True) for file_path in self.import_source: if self.stop_import_flag: return False self.import_wizard.incrementProgressBar(unicode(translate( 'SongsPlugin.OpenLyricsImport', 'Importing %s...')) % os.path.basename(file_path)) - parser = etree.XMLParser(remove_blank_text=True) - parsed_file = etree.parse(file_path, parser) - xml = unicode(etree.tostring(parsed_file)) - if self.openLyrics.xml_to_song(xml) is None: - log.debug(u'File could not be imported: %s' % file_path) - # Importing this song failed! For now we stop import. - return False + try: + parsed_file = etree.parse(file_path, parser) + xml = unicode(etree.tostring(parsed_file)) + if self.openLyrics.xml_to_song(xml) is None: + log.debug(u'File could not be imported: %s' % file_path) + except etree.XMLSyntaxError: + log.exception(u'XML syntax error in file %s' % file_path) return True diff --git a/openlp/plugins/songs/lib/songimport.py b/openlp/plugins/songs/lib/songimport.py index 3ce271466..cab0aacf6 100644 --- a/openlp/plugins/songs/lib/songimport.py +++ b/openlp/plugins/songs/lib/songimport.py @@ -197,16 +197,24 @@ class SongImport(QtCore.QObject): return self.media_files.append(filename) - def add_verse(self, verse, versetag=u'V'): + def add_verse(self, versetext, versetag=u'V', lang=None): """ - Add a verse. This is the whole verse, lines split by \n - Verse tag can be V1/C1/B etc, or 'V' and 'C' (will count the verses/ - choruses itself) or None, where it will assume verse - It will also attempt to detect duplicates. In this case it will just - add to the verse order + Add a verse. This is the whole verse, lines split by \n. It will also + attempt to detect duplicates. In this case it will just add to the verse + order. + + ``versetext`` + The text of the verse. + + ``versetag`` + The verse tag can be V1/C1/B etc, or 'V' and 'C' (will count the + verses/choruses itself) or None, where it will assume verse. + + ``lang`` + The language code (ISO-639) of the verse, for example *en* or *de*. """ - for (oldversetag, oldverse) in self.verses: - if oldverse.strip() == verse.strip(): + for (oldversetag, oldverse, oldlang) in self.verses: + if oldverse.strip() == versetext.strip(): self.verse_order_list.append(oldversetag) return if versetag[0] in self.versecounts: @@ -217,7 +225,7 @@ class SongImport(QtCore.QObject): versetag += unicode(self.versecounts[versetag[0]]) elif int(versetag[1:]) > self.versecounts[versetag[0]]: self.versecounts[versetag[0]] = int(versetag[1:]) - self.verses.append([versetag, verse.rstrip()]) + self.verses.append([versetag, versetext.rstrip(), lang]) self.verse_order_list.append(versetag) if versetag.startswith(u'V') and self.contains_verse(u'C1'): self.verse_order_list.append(u'C1') @@ -266,7 +274,7 @@ class SongImport(QtCore.QObject): verses_changed_to_other = {} sxml = SongXML() other_count = 1 - for (versetag, versetext) in self.verses: + for (versetag, versetext, lang) in self.verses: if versetag[0] == u'C': versetype = VerseType.to_string(VerseType.Chorus) elif versetag[0] == u'V': @@ -286,7 +294,7 @@ class SongImport(QtCore.QObject): versetype = VerseType.to_string(VerseType.Other) log.info(u'Versetype %s changing to %s' , versetag, newversetag) versetag = newversetag - sxml.add_verse_to_lyrics(versetype, versetag[1:], versetext) + sxml.add_verse_to_lyrics(versetype, versetag[1:], versetext, lang) song.search_lyrics += u' ' + self.remove_punctuation(versetext) song.search_lyrics = song.search_lyrics.lower() song.lyrics = unicode(sxml.extract_xml(), u'utf-8') @@ -338,7 +346,7 @@ class SongImport(QtCore.QObject): + u'========================================' print u'TITLE: ' + self.title print u'ALT TITLE: ' + self.alternate_title - for (versetag, versetext) in self.verses: + for (versetag, versetext, lang) in self.verses: print u'VERSE ' + versetag + u': ' + versetext print u'ORDER: ' + u' '.join(self.verse_order_list) for author in self.authors: diff --git a/openlp/plugins/songs/lib/xml.py b/openlp/plugins/songs/lib/xml.py index 88459b797..91bfdb025 100644 --- a/openlp/plugins/songs/lib/xml.py +++ b/openlp/plugins/songs/lib/xml.py @@ -31,7 +31,7 @@ The basic XML for storing the lyrics in the song database is of the format:: - + @@ -84,7 +84,7 @@ class SongXML(object): self.song_xml = objectify.fromstring(u'') self.lyrics = etree.SubElement(self.song_xml, u'lyrics') - def add_verse_to_lyrics(self, type, number, content): + def add_verse_to_lyrics(self, type, number, content, lang=None): """ Add a verse to the ** tag. @@ -97,9 +97,15 @@ class SongXML(object): ``content`` The actual text of the verse to be stored. + + ``lang`` + The verse's language code (ISO-639). This is not required, but + should be added if available. """ verse = etree.Element(u'verse', type=unicode(type), label=unicode(number)) + if lang: + verse.set(u'lang', lang) verse.text = etree.CDATA(content) self.lyrics.append(verse) @@ -117,6 +123,11 @@ class SongXML(object): ``xml`` The XML of the song to be parsed. + + The returned list has the following format:: + + [[{'lang': 'en', 'type': 'V', 'label': '1'}, u"The English verse."], + [{'lang': 'en', 'type': 'C', 'label': '1'}, u"The English chorus."]] """ self.song_xml = None if xml[:5] == u'* - The attribute *translit* and *lang* are not supported. + The attribute *translit* is not supported. ** OpenLP supports this property. @@ -268,13 +279,16 @@ class OpenLyrics(object): # No xml get out of here. if not xml: return None - song = Song() if xml[:5] == u'').sub(u'', xml) song_xml = objectify.fromstring(xml) - properties = song_xml.properties + try: + properties = song_xml.properties + except AttributeError: + return None + song = Song() self._process_copyright(properties, song) self._process_cclinumber(properties, song) self._process_titles(properties, song) @@ -442,7 +456,10 @@ class OpenLyrics(object): if not verse_number: verse_number = u'1' temp_verse_order.append((verse_type, verse_number, verse_part)) - sxml.add_verse_to_lyrics(verse_type, verse_number, text) + lang = None + if self._get(verse, u'lang'): + lang = self._get(verse, u'lang') + sxml.add_verse_to_lyrics(verse_type, verse_number, text, lang) search_text = search_text + text song.search_lyrics = search_text.lower() song.lyrics = unicode(sxml.extract_xml(), u'utf-8') diff --git a/openlp/plugins/songs/songsplugin.py b/openlp/plugins/songs/songsplugin.py index 17e609fd4..7efe73db2 100644 --- a/openlp/plugins/songs/songsplugin.py +++ b/openlp/plugins/songs/songsplugin.py @@ -134,7 +134,7 @@ class SongsPlugin(Plugin): def onToolsReindexItemTriggered(self): """ - Rebuild the search title of each song. + Rebuild each song. """ maxSongs = self.manager.get_object_count(Song) progressDialog = QtGui.QProgressDialog( @@ -150,8 +150,13 @@ class SongsPlugin(Plugin): song.title = u'' if song.alternate_title is None: song.alternate_title = u'' - song.search_title = self.whitespace.sub(u' ', song.title.lower() + \ + song.search_title = self.whitespace.sub(u' ', song.title.lower() + u' ' + song.alternate_title.lower()) + # Remove the "language" attribute from lyrics tag. This is not very + # important, but this keeps the database clean. This can be removed + # when everybody has run the reindex tool once. + song.lyrics = song.lyrics.replace( + u'', u'') lyrics = u'' verses = SongXML().get_verses(song.lyrics) for verse in verses: @@ -159,8 +164,7 @@ class SongsPlugin(Plugin): song.search_lyrics = lyrics.lower() progressDialog.setValue(counter) self.manager.save_objects(songs) - counter += 1 - progressDialog.setValue(counter) + progressDialog.setValue(counter + 1) self.mediaItem.displayResultsSong( self.manager.get_all_objects(Song, order_by_ref=Song.search_title))