forked from openlp/openlp
Reworked SongSelect branch after a major refactor in trunk.
To recap: - Added a standalone CCLI SongSelect importer - Add icons to the import and export menu items - Move Theme import and export up with Settings
This commit is contained in:
parent
80c6c371df
commit
1cf3140073
@ -345,3 +345,4 @@ from .dockwidget import OpenLPDockWidget
|
||||
from .imagemanager import ImageManager
|
||||
from .renderer import Renderer
|
||||
from .mediamanageritem import MediaManagerItem
|
||||
from .historycombobox import HistoryComboBox
|
||||
|
91
openlp/core/lib/historycombobox.py
Normal file
91
openlp/core/lib/historycombobox.py
Normal file
@ -0,0 +1,91 @@
|
||||
# -*- 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 #
|
||||
###############################################################################
|
||||
"""
|
||||
The :mod:`~openlp.core.lib.historycombobox` module contains the HistoryComboBox widget
|
||||
"""
|
||||
|
||||
from PyQt4 import QtCore, QtGui
|
||||
|
||||
|
||||
class HistoryComboBox(QtGui.QComboBox):
|
||||
"""
|
||||
The :class:`~openlp.core.lib.historycombobox.HistoryComboBox` widget emulates the QLineEdit ``returnPressed`` signal
|
||||
for when the :kbd:`Enter` or :kbd:`Return` keys are pressed, and saves anything that is typed into the edit box into
|
||||
its list.
|
||||
"""
|
||||
returnPressed = QtCore.pyqtSignal()
|
||||
|
||||
def __init__(self, parent=None):
|
||||
"""
|
||||
Initialise the combo box, setting duplicates to False and the insert policy to insert items at the top.
|
||||
|
||||
:param parent: The parent widget
|
||||
"""
|
||||
super().__init__(parent)
|
||||
self.setDuplicatesEnabled(False)
|
||||
self.setEditable(True)
|
||||
self.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Preferred)
|
||||
self.setInsertPolicy(QtGui.QComboBox.InsertAtTop)
|
||||
|
||||
def keyPressEvent(self, event):
|
||||
"""
|
||||
Override the inherited keyPressEvent method to emit the ``returnPressed`` signal and to save the current text to
|
||||
the dropdown list.
|
||||
|
||||
:param event: The keyboard event
|
||||
"""
|
||||
# Handle Enter and Return ourselves
|
||||
if event.key() == QtCore.Qt.Key_Enter or event.key() == QtCore.Qt.Key_Return:
|
||||
# Emit the returnPressed signal
|
||||
self.returnPressed.emit()
|
||||
# Save the current text to the dropdown list
|
||||
if self.currentText() and self.findText(self.currentText()) == -1:
|
||||
self.insertItem(0, self.currentText())
|
||||
# Let the parent handle any keypress events
|
||||
super().keyPressEvent(event)
|
||||
|
||||
def focusOutEvent(self, event):
|
||||
"""
|
||||
Override the inherited focusOutEvent to save the current text to the dropdown list.
|
||||
|
||||
:param event: The focus event
|
||||
"""
|
||||
# Save the current text to the dropdown list
|
||||
if self.currentText() and self.findText(self.currentText()) == -1:
|
||||
self.insertItem(0, self.currentText())
|
||||
# Let the parent handle any keypress events
|
||||
super().focusOutEvent(event)
|
||||
|
||||
def getItems(self):
|
||||
"""
|
||||
Get all the items from the history
|
||||
|
||||
:return: A list of strings
|
||||
"""
|
||||
return [self.itemText(i) for i in range(self.count())]
|
@ -29,6 +29,7 @@
|
||||
"""
|
||||
The :mod:`ui` module provides the core user interface for OpenLP
|
||||
"""
|
||||
from PyQt4 import QtGui
|
||||
|
||||
|
||||
class HideMode(object):
|
||||
@ -77,6 +78,29 @@ class DisplayControllerType(object):
|
||||
Plugin = 2
|
||||
|
||||
|
||||
class SingleColumnTableWidget(QtGui.QTableWidget):
|
||||
"""
|
||||
Class to for a single column table widget to use for the verse table widget.
|
||||
"""
|
||||
|
||||
def __init__(self, parent):
|
||||
"""
|
||||
Constructor
|
||||
"""
|
||||
super(SingleColumnTableWidget, self).__init__(parent)
|
||||
self.horizontalHeader().setVisible(False)
|
||||
self.setColumnCount(1)
|
||||
|
||||
def resizeEvent(self, event):
|
||||
"""
|
||||
Resize the first column together with the widget.
|
||||
"""
|
||||
QtGui.QTableWidget.resizeEvent(self, event)
|
||||
if self.columnCount():
|
||||
self.setColumnWidth(0, event.size().width())
|
||||
self.resizeRowsToContents()
|
||||
|
||||
|
||||
from .firsttimeform import FirstTimeForm
|
||||
from .firsttimelanguageform import FirstTimeLanguageForm
|
||||
from .themelayoutform import ThemeLayoutForm
|
||||
@ -102,8 +126,9 @@ from .servicemanager import ServiceManager
|
||||
from .thememanagerhelper import ThemeManagerHelper
|
||||
from .thememanager import ThemeManager
|
||||
|
||||
__all__ = ['SplashScreen', 'AboutForm', 'SettingsForm', 'MainDisplay', 'SlideController', 'ServiceManager',
|
||||
'ThemeManager', 'MediaDockManager', 'ServiceItemEditForm', 'FirstTimeForm', 'FirstTimeLanguageForm', 'ThemeForm',
|
||||
'ThemeLayoutForm', 'FileRenameForm', 'StartTimeForm', 'MainDisplay', 'Display', 'ServiceNoteForm',
|
||||
'SlideController', 'DisplayController', 'GeneralTab', 'ThemesTab', 'AdvancedTab', 'PluginForm',
|
||||
'FormattingTagForm', 'ShortcutListForm', 'FormattingTagController', 'ThemeManagerHelper']
|
||||
__all__ = ['SplashScreen', 'AboutForm', 'SettingsForm', 'MainDisplay', 'SlideController', 'ServiceManager', 'ThemeForm',
|
||||
'ThemeManager', 'MediaDockManager', 'ServiceItemEditForm', 'FirstTimeForm', 'FirstTimeLanguageForm',
|
||||
'Display', 'ServiceNoteForm', 'ThemeLayoutForm', 'FileRenameForm', 'StartTimeForm', 'MainDisplay',
|
||||
'SlideController', 'DisplayController', 'GeneralTab', 'ThemesTab', 'AdvancedTab', 'PluginForm',
|
||||
'FormattingTagForm', 'ShortcutListForm', 'FormattingTagController', 'ThemeManagerHelper',
|
||||
'SingleColumnTableWidget']
|
||||
|
@ -119,8 +119,10 @@ class Ui_MainWindow(object):
|
||||
self.recent_files_menu = QtGui.QMenu(self.file_menu)
|
||||
self.recent_files_menu.setObjectName('recentFilesMenu')
|
||||
self.file_import_menu = QtGui.QMenu(self.file_menu)
|
||||
self.file_import_menu.setIcon(build_icon(u':/general/general_import.png'))
|
||||
self.file_import_menu.setObjectName('file_import_menu')
|
||||
self.file_export_menu = QtGui.QMenu(self.file_menu)
|
||||
self.file_export_menu.setIcon(build_icon(u':/general/general_export.png'))
|
||||
self.file_export_menu.setObjectName('file_export_menu')
|
||||
# View Menu
|
||||
self.view_menu = QtGui.QMenu(self.menu_bar)
|
||||
@ -230,7 +232,7 @@ class Ui_MainWindow(object):
|
||||
main_window, 'modeDefaultItem', checked=False, category=UiStrings().ViewMode, can_shortcuts=True)
|
||||
self.mode_setup_item = create_action(main_window, 'modeSetupItem', checked=False, category=UiStrings().ViewMode,
|
||||
can_shortcuts=True)
|
||||
self.mode_live_item = create_action(main_window, 'modeLiveItem', checked=True, category=UiStrings().ViewMode,
|
||||
self.mode_live_item = create_action(main_window, 'modeLiveItem', checked=True, category=UiStrings().ViewMode,
|
||||
can_shortcuts=True)
|
||||
self.mode_group = QtGui.QActionGroup(main_window)
|
||||
self.mode_group.addAction(self.mode_default_item)
|
||||
@ -239,10 +241,10 @@ class Ui_MainWindow(object):
|
||||
self.mode_default_item.setChecked(True)
|
||||
action_list.add_category(UiStrings().Tools, CategoryOrder.standard_menu)
|
||||
self.tools_add_tool_item = create_action(main_window,
|
||||
'toolsAddToolItem', icon=':/tools/tools_add.png',
|
||||
'toolsAddToolItem', icon=':/tools/tools_add.png',
|
||||
category=UiStrings().Tools, can_shortcuts=True)
|
||||
self.tools_open_data_folder = create_action(main_window,
|
||||
'toolsOpenDataFolder', icon=':/general/general_open.png',
|
||||
'toolsOpenDataFolder', icon=':/general/general_open.png',
|
||||
category=UiStrings().Tools, can_shortcuts=True)
|
||||
self.tools_first_time_wizard = create_action(main_window,
|
||||
'toolsFirstTimeWizard', icon=':/general/general_revert.png',
|
||||
@ -268,24 +270,24 @@ class Ui_MainWindow(object):
|
||||
language_item = create_action(main_window, key, checked=qm_list[key] == saved_language)
|
||||
add_actions(self.language_group, [language_item])
|
||||
self.settings_shortcuts_item = create_action(main_window, 'settingsShortcutsItem',
|
||||
icon=':/system/system_configure_shortcuts.png',
|
||||
icon=':/system/system_configure_shortcuts.png',
|
||||
category=UiStrings().Settings, can_shortcuts=True)
|
||||
# Formatting Tags were also known as display tags.
|
||||
self.formatting_tag_item = create_action(main_window, 'displayTagItem',
|
||||
icon=':/system/tag_editor.png', category=UiStrings().Settings,
|
||||
icon=':/system/tag_editor.png', category=UiStrings().Settings,
|
||||
can_shortcuts=True)
|
||||
self.settings_configure_item = create_action(main_window, 'settingsConfigureItem',
|
||||
icon=':/system/system_settings.png', can_shortcuts=True,
|
||||
icon=':/system/system_settings.png', can_shortcuts=True,
|
||||
category=UiStrings().Settings)
|
||||
# Give QT Extra Hint that this is the Preferences Menu Item
|
||||
self.settings_configure_item.setMenuRole(QtGui.QAction.PreferencesRole)
|
||||
self.settings_import_item = create_action(main_window, 'settingsImportItem',
|
||||
self.settings_import_item = create_action(main_window, 'settingsImportItem',
|
||||
category=UiStrings().Import, can_shortcuts=True)
|
||||
self.settings_export_item = create_action(main_window, 'settingsExportItem',
|
||||
self.settings_export_item = create_action(main_window, 'settingsExportItem',
|
||||
category=UiStrings().Export, can_shortcuts=True)
|
||||
action_list.add_category(UiStrings().Help, CategoryOrder.standard_menu)
|
||||
self.about_item = create_action(main_window, 'aboutItem', icon=':/system/system_about.png',
|
||||
can_shortcuts=True, category=UiStrings().Help,
|
||||
can_shortcuts=True, category=UiStrings().Help,
|
||||
triggers=self.on_about_item_clicked)
|
||||
# Give QT Extra Hint that this is an About Menu Item
|
||||
self.about_item.setMenuRole(QtGui.QAction.AboutRole)
|
||||
@ -302,13 +304,13 @@ class Ui_MainWindow(object):
|
||||
self.web_site_item = create_action(main_window, 'webSiteItem', can_shortcuts=True, category=UiStrings().Help)
|
||||
# Shortcuts not connected to buttons or menu entries.
|
||||
self.search_shortcut_action = create_action(main_window,
|
||||
'searchShortcut', can_shortcuts=True,
|
||||
'searchShortcut', can_shortcuts=True,
|
||||
category=translate('OpenLP.MainWindow', 'General'),
|
||||
triggers=self.on_search_shortcut_triggered)
|
||||
add_actions(self.file_import_menu, (self.settings_import_item, None, self.import_theme_item,
|
||||
self.import_language_item))
|
||||
add_actions(self.file_export_menu, (self.settings_export_item, None, self.export_theme_item,
|
||||
self.export_language_item))
|
||||
add_actions(self.file_import_menu, (self.settings_import_item, self.import_theme_item,
|
||||
self.import_language_item, None))
|
||||
add_actions(self.file_export_menu, (self.settings_export_item, self.export_theme_item,
|
||||
self.export_language_item, None))
|
||||
add_actions(self.file_menu, (self.file_new_item, self.file_open_item,
|
||||
self.file_save_item, self.file_save_as_item, self.recent_files_menu.menuAction(), None,
|
||||
self.file_import_menu.menuAction(), self.file_export_menu.menuAction(), None, self.print_service_order_item,
|
||||
@ -650,7 +652,7 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
|
||||
'Time Wizard?\n\nRe-running this wizard may make changes to your '
|
||||
'current OpenLP configuration and possibly add songs to your '
|
||||
'#existing songs list and change your default theme.'),
|
||||
QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Yes |
|
||||
QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Yes |
|
||||
QtGui.QMessageBox.No),
|
||||
QtGui.QMessageBox.No)
|
||||
if answer == QtGui.QMessageBox.No:
|
||||
|
@ -32,6 +32,7 @@ from PyQt4 import QtCore, QtGui
|
||||
from openlp.core.common import UiStrings, translate
|
||||
from openlp.core.lib import build_icon
|
||||
from openlp.core.lib.ui import create_button_box, create_button
|
||||
from openlp.core.ui import SingleColumnTableWidget
|
||||
from openlp.plugins.songs.lib.ui import SongStrings
|
||||
|
||||
|
||||
@ -346,25 +347,3 @@ def create_combo_box(parent, name):
|
||||
combo_box.setInsertPolicy(QtGui.QComboBox.NoInsert)
|
||||
combo_box.setObjectName(name)
|
||||
return combo_box
|
||||
|
||||
|
||||
class SingleColumnTableWidget(QtGui.QTableWidget):
|
||||
"""
|
||||
Class to for a single column table widget to use for the verse table widget.
|
||||
"""
|
||||
def __init__(self, parent):
|
||||
"""
|
||||
Constructor
|
||||
"""
|
||||
super(SingleColumnTableWidget, self).__init__(parent)
|
||||
self.horizontalHeader().setVisible(False)
|
||||
self.setColumnCount(1)
|
||||
|
||||
def resizeEvent(self, event):
|
||||
"""
|
||||
Resize the first column together with the widget.
|
||||
"""
|
||||
QtGui.QTableWidget.resizeEvent(self, event)
|
||||
if self.columnCount():
|
||||
self.setColumnWidth(0, event.size().width())
|
||||
self.resizeRowsToContents()
|
||||
|
251
openlp/plugins/songs/forms/songselectdialog.py
Normal file
251
openlp/plugins/songs/forms/songselectdialog.py
Normal file
@ -0,0 +1,251 @@
|
||||
# -*- 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 #
|
||||
###############################################################################
|
||||
"""
|
||||
The :mod:`~openlp.plugins.songs.forms.songselectdialog` module contains the user interface code for the dialog
|
||||
"""
|
||||
|
||||
from PyQt4 import QtCore, QtGui
|
||||
|
||||
from openlp.core.lib import HistoryComboBox, translate, build_icon
|
||||
from openlp.core.ui import SingleColumnTableWidget
|
||||
|
||||
|
||||
class Ui_SongSelectDialog(object):
|
||||
"""
|
||||
The actual Qt components that make up the dialog.
|
||||
"""
|
||||
def setup_ui(self, songselect_dialog):
|
||||
songselect_dialog.setObjectName('songselect_dialog')
|
||||
songselect_dialog.resize(616, 378)
|
||||
self.songselect_layout = QtGui.QVBoxLayout(songselect_dialog)
|
||||
self.songselect_layout.setSpacing(0)
|
||||
self.songselect_layout.setMargin(0)
|
||||
self.songselect_layout.setObjectName('songselect_layout')
|
||||
self.stacked_widget = QtGui.QStackedWidget(songselect_dialog)
|
||||
self.stacked_widget.setObjectName('stacked_widget')
|
||||
self.login_page = QtGui.QWidget()
|
||||
self.login_page.setObjectName('login_page')
|
||||
self.login_layout = QtGui.QFormLayout(self.login_page)
|
||||
self.login_layout.setContentsMargins(120, 100, 120, 100)
|
||||
self.login_layout.setSpacing(8)
|
||||
self.login_layout.setObjectName('login_layout')
|
||||
self.notice_layout = QtGui.QHBoxLayout()
|
||||
self.notice_layout.setObjectName('notice_layout')
|
||||
self.notice_label = QtGui.QLabel(self.login_page)
|
||||
self.notice_label.setWordWrap(True)
|
||||
self.notice_label.setObjectName('notice_label')
|
||||
self.notice_layout.addWidget(self.notice_label)
|
||||
self.login_layout.setLayout(0, QtGui.QFormLayout.SpanningRole, self.notice_layout)
|
||||
self.username_label = QtGui.QLabel(self.login_page)
|
||||
self.username_label.setObjectName('usernameLabel')
|
||||
self.login_layout.setWidget(1, QtGui.QFormLayout.LabelRole, self.username_label)
|
||||
self.username_edit = QtGui.QLineEdit(self.login_page)
|
||||
self.username_edit.setObjectName('usernameEdit')
|
||||
self.login_layout.setWidget(1, QtGui.QFormLayout.FieldRole, self.username_edit)
|
||||
self.password_label = QtGui.QLabel(self.login_page)
|
||||
self.password_label.setObjectName('passwordLabel')
|
||||
self.login_layout.setWidget(2, QtGui.QFormLayout.LabelRole, self.password_label)
|
||||
self.password_edit = QtGui.QLineEdit(self.login_page)
|
||||
self.password_edit.setEchoMode(QtGui.QLineEdit.Password)
|
||||
self.password_edit.setObjectName('passwordEdit')
|
||||
self.login_layout.setWidget(2, QtGui.QFormLayout.FieldRole, self.password_edit)
|
||||
self.save_password_checkbox = QtGui.QCheckBox(self.login_page)
|
||||
self.save_password_checkbox.setTristate(False)
|
||||
self.save_password_checkbox.setObjectName('save_password_checkbox')
|
||||
self.login_layout.setWidget(3, QtGui.QFormLayout.FieldRole, self.save_password_checkbox)
|
||||
self.login_button_layout = QtGui.QHBoxLayout()
|
||||
self.login_button_layout.setSpacing(8)
|
||||
self.login_button_layout.setContentsMargins(0, -1, -1, -1)
|
||||
self.login_button_layout.setObjectName('login_button_layout')
|
||||
self.login_spacer = QtGui.QWidget(self.login_page)
|
||||
self.login_spacer.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
|
||||
self.login_spacer.setObjectName('login_spacer')
|
||||
self.login_button_layout.addWidget(self.login_spacer)
|
||||
self.login_progress_bar = QtGui.QProgressBar(self.login_page)
|
||||
self.login_progress_bar.setMinimum(0)
|
||||
self.login_progress_bar.setMaximum(3)
|
||||
self.login_progress_bar.setValue(0)
|
||||
self.login_progress_bar.setMinimumWidth(200)
|
||||
self.login_progress_bar.setVisible(False)
|
||||
self.login_button_layout.addWidget(self.login_progress_bar)
|
||||
self.login_button = QtGui.QPushButton(self.login_page)
|
||||
self.login_button.setIcon(build_icon(':/songs/song_author_edit.png'))
|
||||
self.login_button.setObjectName('login_button')
|
||||
self.login_button_layout.addWidget(self.login_button)
|
||||
self.login_layout.setLayout(4, QtGui.QFormLayout.SpanningRole, self.login_button_layout)
|
||||
self.stacked_widget.addWidget(self.login_page)
|
||||
self.search_page = QtGui.QWidget()
|
||||
self.search_page.setObjectName('search_page')
|
||||
self.search_layout = QtGui.QVBoxLayout(self.search_page)
|
||||
self.search_layout.setSpacing(8)
|
||||
self.search_layout.setMargin(8)
|
||||
self.search_layout.setObjectName('search_layout')
|
||||
self.search_input_layout = QtGui.QHBoxLayout()
|
||||
self.search_input_layout.setSpacing(8)
|
||||
self.search_input_layout.setObjectName('search_input_layout')
|
||||
self.search_label = QtGui.QLabel(self.search_page)
|
||||
self.search_label.setObjectName('search_label')
|
||||
self.search_input_layout.addWidget(self.search_label)
|
||||
self.search_combobox = HistoryComboBox(self.search_page)
|
||||
self.search_combobox.setObjectName('search_combobox')
|
||||
self.search_input_layout.addWidget(self.search_combobox)
|
||||
self.search_button = QtGui.QPushButton(self.search_page)
|
||||
self.search_button.setIcon(build_icon(':/general/general_find.png'))
|
||||
self.search_button.setObjectName('search_button')
|
||||
self.search_input_layout.addWidget(self.search_button)
|
||||
self.search_layout.addLayout(self.search_input_layout)
|
||||
self.search_progress_bar = QtGui.QProgressBar(self.search_page)
|
||||
self.search_progress_bar.setMinimum(0)
|
||||
self.search_progress_bar.setMaximum(3)
|
||||
self.search_progress_bar.setValue(0)
|
||||
self.search_progress_bar.setVisible(False)
|
||||
self.search_layout.addWidget(self.search_progress_bar)
|
||||
self.search_results_widget = QtGui.QListWidget(self.search_page)
|
||||
self.search_results_widget.setProperty("showDropIndicator", False)
|
||||
self.search_results_widget.setAlternatingRowColors(True)
|
||||
self.search_results_widget.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection)
|
||||
self.search_results_widget.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows)
|
||||
self.search_results_widget.setObjectName('search_results_widget')
|
||||
self.search_layout.addWidget(self.search_results_widget)
|
||||
self.result_count_label = QtGui.QLabel(self.search_page)
|
||||
self.result_count_label.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignCenter)
|
||||
self.result_count_label.setObjectName('result_count_label')
|
||||
self.search_layout.addWidget(self.result_count_label)
|
||||
self.view_layout = QtGui.QHBoxLayout()
|
||||
self.view_layout.setSpacing(8)
|
||||
self.view_layout.setObjectName('view_layout')
|
||||
self.logout_button = QtGui.QPushButton(self.search_page)
|
||||
self.logout_button.setIcon(build_icon(':/songs/song_author_edit.png'))
|
||||
self.view_layout.addWidget(self.logout_button)
|
||||
self.view_spacer = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
|
||||
self.view_layout.addItem(self.view_spacer)
|
||||
self.view_button = QtGui.QPushButton(self.search_page)
|
||||
self.view_button.setIcon(build_icon(':/songs/song_search_all.png'))
|
||||
self.view_button.setObjectName('view_button')
|
||||
self.view_layout.addWidget(self.view_button)
|
||||
self.search_layout.addLayout(self.view_layout)
|
||||
self.stacked_widget.addWidget(self.search_page)
|
||||
self.song_page = QtGui.QWidget()
|
||||
self.song_page.setObjectName('song_page')
|
||||
self.song_layout = QtGui.QGridLayout(self.song_page)
|
||||
self.song_layout.setMargin(8)
|
||||
self.song_layout.setSpacing(8)
|
||||
self.song_layout.setObjectName('song_layout')
|
||||
self.title_label = QtGui.QLabel(self.song_page)
|
||||
self.title_label.setObjectName('title_label')
|
||||
self.song_layout.addWidget(self.title_label, 0, 0, 1, 1)
|
||||
self.title_edit = QtGui.QLineEdit(self.song_page)
|
||||
self.title_edit.setReadOnly(True)
|
||||
self.title_edit.setObjectName('title_edit')
|
||||
self.song_layout.addWidget(self.title_edit, 0, 1, 1, 1)
|
||||
self.authors_label = QtGui.QLabel(self.song_page)
|
||||
self.authors_label.setObjectName('authors_label')
|
||||
self.song_layout.addWidget(self.authors_label, 0, 2, 1, 1)
|
||||
self.author_list_widget = QtGui.QListWidget(self.song_page)
|
||||
self.author_list_widget.setObjectName('author_list_widget')
|
||||
self.song_layout.addWidget(self.author_list_widget, 0, 3, 3, 1)
|
||||
self.copyright_label = QtGui.QLabel(self.song_page)
|
||||
self.copyright_label.setObjectName('copyright_label')
|
||||
self.song_layout.addWidget(self.copyright_label, 1, 0, 1, 1)
|
||||
self.copyright_edit = QtGui.QLineEdit(self.song_page)
|
||||
self.copyright_edit.setReadOnly(True)
|
||||
self.copyright_edit.setObjectName('copyright_edit')
|
||||
self.song_layout.addWidget(self.copyright_edit, 1, 1, 1, 1)
|
||||
self.ccli_label = QtGui.QLabel(self.song_page)
|
||||
self.ccli_label.setObjectName('ccli_label')
|
||||
self.song_layout.addWidget(self.ccli_label, 2, 0, 1, 1)
|
||||
self.ccli_edit = QtGui.QLineEdit(self.song_page)
|
||||
self.ccli_edit.setReadOnly(True)
|
||||
self.ccli_edit.setObjectName('ccli_edit')
|
||||
self.song_layout.addWidget(self.ccli_edit, 2, 1, 1, 1)
|
||||
self.lyrics_label = QtGui.QLabel(self.song_page)
|
||||
self.lyrics_label.setAlignment(QtCore.Qt.AlignLeading | QtCore.Qt.AlignLeft | QtCore.Qt.AlignTop)
|
||||
self.lyrics_label.setObjectName('lyrics_label')
|
||||
self.song_layout.addWidget(self.lyrics_label, 3, 0, 1, 1)
|
||||
self.lyrics_table_widget = SingleColumnTableWidget(self.song_page)
|
||||
self.lyrics_table_widget.setObjectName('lyrics_table_widget')
|
||||
self.lyrics_table_widget.setRowCount(0)
|
||||
self.song_layout.addWidget(self.lyrics_table_widget, 3, 1, 1, 3)
|
||||
self.song_progress_bar = QtGui.QProgressBar(self.song_page)
|
||||
self.song_progress_bar.setMinimum(0)
|
||||
self.song_progress_bar.setMaximum(3)
|
||||
self.song_progress_bar.setValue(0)
|
||||
self.song_progress_bar.setVisible(False)
|
||||
self.song_layout.addWidget(self.song_progress_bar, 4, 0, 1, 4)
|
||||
self.import_layout = QtGui.QHBoxLayout()
|
||||
self.import_layout.setObjectName('import_layout')
|
||||
self.back_button = QtGui.QPushButton(self.song_page)
|
||||
self.back_button.setIcon(build_icon(':/general/general_back.png'))
|
||||
self.back_button.setObjectName('back_button')
|
||||
self.import_layout.addWidget(self.back_button)
|
||||
self.import_spacer = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
|
||||
self.import_layout.addItem(self.import_spacer)
|
||||
self.import_button = QtGui.QPushButton(self.song_page)
|
||||
self.import_button.setIcon(build_icon(':/general/general_import.png'))
|
||||
self.import_button.setObjectName('import_button')
|
||||
self.import_layout.addWidget(self.import_button)
|
||||
self.song_layout.addLayout(self.import_layout, 5, 0, 1, 5)
|
||||
self.stacked_widget.addWidget(self.song_page)
|
||||
self.songselect_layout.addWidget(self.stacked_widget)
|
||||
self.username_label.setBuddy(self.username_edit)
|
||||
self.password_label.setBuddy(self.password_edit)
|
||||
self.title_label.setBuddy(self.title_edit)
|
||||
self.authors_label.setBuddy(self.author_list_widget)
|
||||
self.copyright_label.setBuddy(self.copyright_edit)
|
||||
self.ccli_label.setBuddy(self.ccli_edit)
|
||||
self.lyrics_label.setBuddy(self.lyrics_table_widget)
|
||||
|
||||
self.retranslate_ui(songselect_dialog)
|
||||
self.stacked_widget.setCurrentIndex(0)
|
||||
|
||||
def retranslate_ui(self, songselect_dialog):
|
||||
"""
|
||||
Translate the GUI.
|
||||
"""
|
||||
songselect_dialog.setWindowTitle(translate('SongsPlugin.SongSelectForm', 'CCLI SongSelect Importer'))
|
||||
self.notice_label.setText(
|
||||
translate('SongsPlugin.SongSelectForm', '<strong>Note:</strong> '
|
||||
'An Internet connection is required in order to import songs from CCLI SongSelect.')
|
||||
)
|
||||
self.username_label.setText(translate('SongsPlugin.SongSelectForm', 'Username:'))
|
||||
self.password_label.setText(translate('SongsPlugin.SongSelectForm', 'Password:'))
|
||||
self.save_password_checkbox.setText(translate('SongsPlugin.SongSelectForm', 'Save username and password'))
|
||||
self.login_button.setText(translate('SongsPlugin.SongSelectForm', 'Login'))
|
||||
self.search_label.setText(translate('SongsPlugin.SongSelectForm', 'Search Text:'))
|
||||
self.search_button.setText(translate('SongsPlugin.SongSelectForm', 'Search'))
|
||||
self.result_count_label.setText(translate('SongsPlugin.SongSelectForm', 'Found %s song(s)') % 0)
|
||||
self.logout_button.setText(translate('SongsPlugin.SongSelectForm', 'Logout'))
|
||||
self.view_button.setText(translate('SongsPlugin.SongSelectForm', 'View'))
|
||||
self.title_label.setText(translate('SongsPlugin.SongSelectForm', 'Title:'))
|
||||
self.authors_label.setText(translate('SongsPlugin.SongSelectForm', 'Author(s):'))
|
||||
self.copyright_label.setText(translate('SongsPlugin.SongSelectForm', 'Copyright:'))
|
||||
self.ccli_label.setText(translate('SongsPlugin.SongSelectForm', 'CCLI Number:'))
|
||||
self.lyrics_label.setText(translate('SongsPlugin.SongSelectForm', 'Lyrics:'))
|
||||
self.back_button.setText(translate('SongsPlugin.SongSelectForm', 'Back'))
|
||||
self.import_button.setText(translate('SongsPlugin.SongSelectForm', 'Import'))
|
473
openlp/plugins/songs/forms/songselectform.py
Executable file
473
openlp/plugins/songs/forms/songselectform.py
Executable file
@ -0,0 +1,473 @@
|
||||
# -*- 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 #
|
||||
###############################################################################
|
||||
"""
|
||||
The :mod:`~openlp.plugins.songs.forms.songselectform` module contains the GUI for the SongSelect importer
|
||||
"""
|
||||
|
||||
import logging
|
||||
from http.cookiejar import CookieJar
|
||||
from urllib.parse import urlencode
|
||||
from urllib.request import HTTPCookieProcessor, HTTPError, build_opener
|
||||
from html.parser import HTMLParser
|
||||
from time import sleep
|
||||
|
||||
from PyQt4 import QtCore, QtGui
|
||||
from bs4 import BeautifulSoup, NavigableString
|
||||
from openlp.core import Settings
|
||||
|
||||
from openlp.core.common import Registry
|
||||
from openlp.core.lib import translate
|
||||
from openlp.plugins.songs.lib import VerseType, clean_song
|
||||
from openlp.plugins.songs.forms.songselectdialog import Ui_SongSelectDialog
|
||||
from openlp.plugins.songs.lib.db import Author, Song
|
||||
from openlp.plugins.songs.lib.xml import SongXML
|
||||
|
||||
USER_AGENT = 'Mozilla/5.0 (Linux; U; Android 4.0.3; en-us; GT-I9000 ' \
|
||||
'Build/IML74K) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 ' \
|
||||
'Mobile Safari/534.30'
|
||||
BASE_URL = 'https://mobile.songselect.com'
|
||||
LOGIN_URL = BASE_URL + '/account/login'
|
||||
LOGOUT_URL = BASE_URL + '/account/logout'
|
||||
SEARCH_URL = BASE_URL + '/search/results'
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SearchWorker(QtCore.QObject):
|
||||
"""
|
||||
Run the actual SongSelect search, and notify the GUI when we find each song.
|
||||
"""
|
||||
show_info = QtCore.pyqtSignal(str, str)
|
||||
found_song = QtCore.pyqtSignal(dict)
|
||||
finished = QtCore.pyqtSignal(list)
|
||||
quit = QtCore.pyqtSignal()
|
||||
|
||||
def __init__(self, opener, params):
|
||||
super().__init__()
|
||||
self.opener = opener
|
||||
self.params = params
|
||||
self.html_parser = HTMLParser()
|
||||
|
||||
def _search_and_parse_results(self, params):
|
||||
params = urlencode(params)
|
||||
results_page = BeautifulSoup(self.opener.open(SEARCH_URL + '?' + params).read(), 'lxml')
|
||||
search_results = results_page.find_all('li', 'result pane')
|
||||
songs = []
|
||||
for result in search_results:
|
||||
song = {
|
||||
'title': self.html_parser.unescape(result.find('h3').string),
|
||||
'authors': [self.html_parser.unescape(author.string) for author in result.find_all('li')],
|
||||
'link': BASE_URL + result.find('a')['href']
|
||||
}
|
||||
self.found_song.emit(song)
|
||||
songs.append(song)
|
||||
return songs
|
||||
|
||||
def start(self):
|
||||
"""
|
||||
Run a search and then parse the results page of the search.
|
||||
"""
|
||||
songs = self._search_and_parse_results(self.params)
|
||||
search_results = []
|
||||
self.params['page'] = 1
|
||||
total = 0
|
||||
while songs:
|
||||
search_results.extend(songs)
|
||||
self.params['page'] += 1
|
||||
total += len(songs)
|
||||
if total >= 1000:
|
||||
self.show_info.emit(
|
||||
translate('SongsPlugin.SongSelectForm', 'More than 1000 results'),
|
||||
translate('SongsPlugin.SongSelectForm', 'Your search has returned more than 1000 results, it has '
|
||||
'been stopped. Please refine your search to fetch better '
|
||||
'results.'))
|
||||
break
|
||||
songs = self._search_and_parse_results(self.params)
|
||||
self.finished.emit(search_results)
|
||||
self.quit.emit()
|
||||
|
||||
|
||||
class SongSelectForm(QtGui.QDialog, Ui_SongSelectDialog):
|
||||
"""
|
||||
The :class:`SongSelectForm` class is the SongSelect dialog.
|
||||
"""
|
||||
|
||||
def __init__(self, parent=None, plugin=None, db_manager=None):
|
||||
QtGui.QDialog.__init__(self, parent)
|
||||
self.setup_ui(self)
|
||||
self.thread = None
|
||||
self.worker = None
|
||||
self.song_count = 0
|
||||
self.song = None
|
||||
self.plugin = plugin
|
||||
self.db_manager = db_manager
|
||||
self.html_parser = HTMLParser()
|
||||
self.opener = build_opener(HTTPCookieProcessor(CookieJar()))
|
||||
self.opener.addheaders = [('User-Agent', USER_AGENT)]
|
||||
self.save_password_checkbox.toggled.connect(self.on_save_password_checkbox_toggled)
|
||||
self.login_button.clicked.connect(self.on_login_button_clicked)
|
||||
self.search_button.clicked.connect(self.on_search_button_clicked)
|
||||
self.search_combobox.returnPressed.connect(self.on_search_button_clicked)
|
||||
self.logout_button.clicked.connect(self.done)
|
||||
self.search_results_widget.itemDoubleClicked.connect(self.on_search_results_widget_double_clicked)
|
||||
self.search_results_widget.itemSelectionChanged.connect(self.on_search_results_widget_selection_changed)
|
||||
self.view_button.clicked.connect(self.on_view_button_clicked)
|
||||
self.back_button.clicked.connect(self.on_back_button_clicked)
|
||||
self.import_button.clicked.connect(self.on_import_button_clicked)
|
||||
|
||||
def exec_(self):
|
||||
"""
|
||||
Execute the dialog. This method sets everything back to its initial
|
||||
values.
|
||||
"""
|
||||
self.stacked_widget.setCurrentIndex(0)
|
||||
self.username_edit.setEnabled(True)
|
||||
self.password_edit.setEnabled(True)
|
||||
self.save_password_checkbox.setEnabled(True)
|
||||
self.search_combobox.clearEditText()
|
||||
self.search_combobox.clear()
|
||||
self.search_results_widget.clear()
|
||||
self.view_button.setEnabled(False)
|
||||
if Settings().contains(self.plugin.settings_section + '/songselect password'):
|
||||
self.username_edit.setText(Settings().value(self.plugin.settings_section + '/songselect username'))
|
||||
self.password_edit.setText(Settings().value(self.plugin.settings_section + '/songselect password'))
|
||||
self.save_password_checkbox.setChecked(True)
|
||||
if Settings().contains(self.plugin.settings_section + '/songselect searches'):
|
||||
self.search_combobox.addItems(
|
||||
Settings().value(self.plugin.settings_section + '/songselect searches').split('|'))
|
||||
self.username_edit.setFocus()
|
||||
return QtGui.QDialog.exec_(self)
|
||||
|
||||
def done(self, r):
|
||||
"""
|
||||
Log out of SongSelect.
|
||||
|
||||
:param r: The result of the dialog.
|
||||
"""
|
||||
log.debug('Closing SongSelectForm')
|
||||
if self.stacked_widget.currentIndex() > 0:
|
||||
progress_dialog = QtGui.QProgressDialog(
|
||||
translate('SongsPlugin.SongSelectForm', 'Logging out...'), '', 0, 2, self)
|
||||
progress_dialog.setWindowModality(QtCore.Qt.WindowModal)
|
||||
progress_dialog.setCancelButton(None)
|
||||
progress_dialog.setValue(1)
|
||||
progress_dialog.show()
|
||||
progress_dialog.setFocus()
|
||||
self.main_window.application.process_events()
|
||||
sleep(0.5)
|
||||
self.main_window.application.process_events()
|
||||
self.opener.open(LOGOUT_URL)
|
||||
self.main_window.application.process_events()
|
||||
progress_dialog.setValue(2)
|
||||
return QtGui.QDialog.done(self, r)
|
||||
|
||||
def _get_main_window(self):
|
||||
if not hasattr(self, '_main_window'):
|
||||
self._main_window = Registry().get('main_window')
|
||||
return self._main_window
|
||||
|
||||
main_window = property(_get_main_window)
|
||||
|
||||
def _view_song(self, current_item):
|
||||
if not current_item:
|
||||
return
|
||||
else:
|
||||
current_item = current_item.data(QtCore.Qt.UserRole)
|
||||
self.song_progress_bar.setVisible(True)
|
||||
self.import_button.setEnabled(False)
|
||||
self.back_button.setEnabled(False)
|
||||
self.title_edit.setText('')
|
||||
self.title_edit.setEnabled(False)
|
||||
self.copyright_edit.setText('')
|
||||
self.copyright_edit.setEnabled(False)
|
||||
self.ccli_edit.setText('')
|
||||
self.ccli_edit.setEnabled(False)
|
||||
self.author_list_widget.clear()
|
||||
self.author_list_widget.setEnabled(False)
|
||||
self.lyrics_table_widget.clear()
|
||||
self.lyrics_table_widget.setRowCount(0)
|
||||
self.lyrics_table_widget.setEnabled(False)
|
||||
self.stacked_widget.setCurrentIndex(2)
|
||||
song = {}
|
||||
for key, value in current_item.items():
|
||||
song[key] = value
|
||||
self.song_progress_bar.setValue(1)
|
||||
self.main_window.application.process_events()
|
||||
song_page = BeautifulSoup(self.opener.open(song['link']).read(), 'lxml')
|
||||
self.song_progress_bar.setValue(2)
|
||||
self.main_window.application.process_events()
|
||||
try:
|
||||
lyrics_page = BeautifulSoup(self.opener.open(song['link'] + '/lyrics').read(), 'lxml')
|
||||
except HTTPError:
|
||||
lyrics_page = None
|
||||
self.song_progress_bar.setValue(3)
|
||||
self.main_window.application.process_events()
|
||||
song['copyright'] = '/'.join([li.string for li in song_page.find('ul', 'copyright').find_all('li')])
|
||||
song['copyright'] = self.html_parser.unescape(song['copyright'])
|
||||
song['ccli_number'] = song_page.find('ul', 'info').find('li').string.split(':')[1].strip()
|
||||
song['verses'] = []
|
||||
if lyrics_page:
|
||||
verses = lyrics_page.find('section', 'lyrics').find_all('p')
|
||||
verse_labels = lyrics_page.find('section', 'lyrics').find_all('h3')
|
||||
for counter in range(len(verses)):
|
||||
verse = {'label': verse_labels[counter].string, 'lyrics': ''}
|
||||
for v in verses[counter].contents:
|
||||
if isinstance(v, NavigableString):
|
||||
verse['lyrics'] = verse['lyrics'] + v.string
|
||||
else:
|
||||
verse['lyrics'] += '\n'
|
||||
verse['lyrics'] = verse['lyrics'].strip(' \n\r\t')
|
||||
song['verses'].append(self.html_parser.unescape(verse))
|
||||
self.title_edit.setText(song['title'])
|
||||
self.copyright_edit.setText(song['copyright'])
|
||||
self.ccli_edit.setText(song['ccli_number'])
|
||||
for author in song['authors']:
|
||||
QtGui.QListWidgetItem(self.html_parser.unescape(author), self.author_list_widget)
|
||||
for counter, verse in enumerate(song['verses']):
|
||||
log.debug('Verse type: %s', verse['label'])
|
||||
self.lyrics_table_widget.setRowCount(self.lyrics_table_widget.rowCount() + 1)
|
||||
item = QtGui.QTableWidgetItem(verse['lyrics'])
|
||||
item.setData(QtCore.Qt.UserRole, verse['label'])
|
||||
item.setFlags(item.flags() ^ QtCore.Qt.ItemIsEditable)
|
||||
self.lyrics_table_widget.setItem(counter, 0, item)
|
||||
self.lyrics_table_widget.setVerticalHeaderLabels([verse['label'] for verse in song['verses']])
|
||||
self.lyrics_table_widget.resizeRowsToContents()
|
||||
self.title_edit.setEnabled(True)
|
||||
self.copyright_edit.setEnabled(True)
|
||||
self.ccli_edit.setEnabled(True)
|
||||
self.author_list_widget.setEnabled(True)
|
||||
self.lyrics_table_widget.setEnabled(True)
|
||||
self.lyrics_table_widget.repaint()
|
||||
self.import_button.setEnabled(True)
|
||||
self.back_button.setEnabled(True)
|
||||
self.song_progress_bar.setVisible(False)
|
||||
self.song_progress_bar.setValue(0)
|
||||
self.song = song
|
||||
self.main_window.application.process_events()
|
||||
|
||||
def on_save_password_checkbox_toggled(self, checked):
|
||||
"""
|
||||
Show a warning dialog when the user toggles the save checkbox on or off.
|
||||
|
||||
:param checked: If the combobox is checked or not
|
||||
"""
|
||||
if checked and self.login_page.isVisible():
|
||||
answer = QtGui.QMessageBox.question(
|
||||
self, translate('SongsPlugin.SongSelectForm', 'Save Username and Password'),
|
||||
translate('SongsPlugin.SongSelectForm', 'WARNING: Saving your username and password is INSECURE, your '
|
||||
'password is stored in PLAIN TEXT. Click Yes to save your '
|
||||
'password or No to cancel this.'),
|
||||
QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Yes | QtGui.QMessageBox.No), QtGui.QMessageBox.No)
|
||||
if answer == QtGui.QMessageBox.No:
|
||||
self.save_password_checkbox.setChecked(False)
|
||||
|
||||
def on_login_button_clicked(self):
|
||||
"""
|
||||
Log the user in to SongSelect.
|
||||
"""
|
||||
self.username_edit.setEnabled(False)
|
||||
self.password_edit.setEnabled(False)
|
||||
self.save_password_checkbox.setEnabled(False)
|
||||
self.login_button.setEnabled(False)
|
||||
self.login_spacer.setVisible(False)
|
||||
self.login_progress_bar.setVisible(True)
|
||||
self.login_progress_bar.setValue(1)
|
||||
self.main_window.application.process_events()
|
||||
login_page = BeautifulSoup(self.opener.open(LOGIN_URL).read(), 'lxml')
|
||||
self.login_progress_bar.setValue(2)
|
||||
self.main_window.application.process_events()
|
||||
token_input = login_page.find('input', attrs={'name': '__RequestVerificationToken'})
|
||||
data = urlencode({
|
||||
'__RequestVerificationToken': token_input['value'],
|
||||
'UserName': self.username_edit.text(),
|
||||
'Password': self.password_edit.text(),
|
||||
'RememberMe': 'false'
|
||||
})
|
||||
posted_page = BeautifulSoup(self.opener.open(LOGIN_URL, data.encode('utf-8')).read(), 'lxml')
|
||||
self.login_progress_bar.setValue(3)
|
||||
self.main_window.application.process_events()
|
||||
if posted_page.find('input', attrs={'name': '__RequestVerificationToken'}):
|
||||
QtGui.QMessageBox.critical(
|
||||
self,
|
||||
translate('SongsPlugin.SongSelectForm', 'Error Logging In'),
|
||||
translate('SongsPlugin.SongSelectForm',
|
||||
'There was a problem logging in, perhaps your username or password is incorrect?')
|
||||
)
|
||||
else:
|
||||
if self.save_password_checkbox.isChecked():
|
||||
Settings().setValue(self.plugin.settings_section + '/songselect username', self.username_edit.text())
|
||||
Settings().setValue(self.plugin.settings_section + '/songselect password', self.password_edit.text())
|
||||
else:
|
||||
Settings().remove(self.plugin.settings_section + '/songselect username')
|
||||
Settings().remove(self.plugin.settings_section + '/songselect password')
|
||||
self.stacked_widget.setCurrentIndex(1)
|
||||
self.login_progress_bar.setVisible(False)
|
||||
self.login_progress_bar.setValue(0)
|
||||
self.login_spacer.setVisible(True)
|
||||
self.login_button.setEnabled(True)
|
||||
self.search_combobox.setFocus()
|
||||
self.main_window.application.process_events()
|
||||
|
||||
def on_search_button_clicked(self):
|
||||
"""
|
||||
Run a search on SongSelect.
|
||||
"""
|
||||
self.view_button.setEnabled(False)
|
||||
self.search_button.setEnabled(False)
|
||||
self.search_progress_bar.setVisible(True)
|
||||
self.search_progress_bar.setMinimum(0)
|
||||
self.search_progress_bar.setMaximum(0)
|
||||
self.search_progress_bar.setValue(0)
|
||||
self.search_results_widget.clear()
|
||||
self.result_count_label.setText(translate('SongsPlugin.SongSelectForm', 'Found %s song(s)') % self.song_count)
|
||||
self.main_window.application.process_events()
|
||||
self.song_count = 0
|
||||
search_history = self.search_combobox.getItems()
|
||||
Settings().setValue(self.plugin.settings_section + '/songselect searches', '|'.join(search_history))
|
||||
|
||||
# Create thread and run search
|
||||
self.thread = QtCore.QThread()
|
||||
self.worker = SearchWorker(self.opener, {'SearchTerm': self.search_combobox.currentText(),
|
||||
'allowredirect': 'false'})
|
||||
self.worker.moveToThread(self.thread)
|
||||
self.thread.started.connect(self.worker.start)
|
||||
self.worker.show_info.connect(self.on_search_show_info)
|
||||
self.worker.found_song.connect(self.on_search_found_song)
|
||||
self.worker.finished.connect(self.on_search_finished)
|
||||
self.worker.quit.connect(self.thread.quit)
|
||||
self.worker.quit.connect(self.worker.deleteLater)
|
||||
self.thread.finished.connect(self.thread.deleteLater)
|
||||
self.thread.start()
|
||||
|
||||
def on_search_show_info(self, title, message):
|
||||
"""
|
||||
Show an informational message from the search thread
|
||||
:param title:
|
||||
:param message:
|
||||
"""
|
||||
QtGui.QMessageBox.information(self, title, message)
|
||||
|
||||
def on_search_found_song(self, song):
|
||||
"""
|
||||
Add a song to the list when one is found.
|
||||
:param song:
|
||||
"""
|
||||
log.debug('SongSelect (title = "%s"), (link = "%s")', song['title'], song['link'])
|
||||
self.song_count += 1
|
||||
self.result_count_label.setText(translate('SongsPlugin.SongSelectForm', 'Found %s song(s)') % self.song_count)
|
||||
item_title = song['title'] + ' (' + ', '.join(song['authors']) + ')'
|
||||
song_item = QtGui.QListWidgetItem(item_title, self.search_results_widget)
|
||||
song_item.setData(QtCore.Qt.UserRole, song)
|
||||
|
||||
def on_search_finished(self, songs):
|
||||
"""
|
||||
Slot which is called when the search is completed.
|
||||
:param songs:
|
||||
"""
|
||||
self.main_window.application.process_events()
|
||||
self.search_progress_bar.setVisible(False)
|
||||
self.search_button.setEnabled(True)
|
||||
self.main_window.application.process_events()
|
||||
|
||||
def on_search_results_widget_selection_changed(self):
|
||||
"""
|
||||
Enable or disable the view button when the selection changes.
|
||||
"""
|
||||
self.view_button.setEnabled(len(self.search_results_widget.selectedItems()) > 0)
|
||||
|
||||
def on_view_button_clicked(self):
|
||||
"""
|
||||
View a song from SongSelect.
|
||||
"""
|
||||
self._view_song(self.search_results_widget.currentItem())
|
||||
|
||||
def on_search_results_widget_double_clicked(self, current_item):
|
||||
"""
|
||||
View a song from SongSelect
|
||||
:param current_item:
|
||||
"""
|
||||
self._view_song(current_item)
|
||||
|
||||
def on_back_button_clicked(self):
|
||||
"""
|
||||
Go back to the search page.
|
||||
"""
|
||||
self.stacked_widget.setCurrentIndex(1)
|
||||
self.search_combobox.setFocus()
|
||||
|
||||
def on_import_button_clicked(self):
|
||||
"""
|
||||
Import a song from SongSelect.
|
||||
"""
|
||||
song = Song.populate(
|
||||
title=self.song['title'],
|
||||
copyright=self.song['copyright'],
|
||||
ccli_number=self.song['ccli_number']
|
||||
)
|
||||
song_xml = SongXML()
|
||||
verse_order = []
|
||||
for verse in self.song['verses']:
|
||||
verse_type, verse_number = verse['label'].split(' ')[:2]
|
||||
verse_type = VerseType.from_loose_input(verse_type)
|
||||
verse_number = int(verse_number)
|
||||
song_xml.add_verse_to_lyrics(
|
||||
VerseType.tags[verse_type],
|
||||
verse_number,
|
||||
verse['lyrics']
|
||||
)
|
||||
verse_order.append('%s%s' % (VerseType.tags[verse_type], verse_number))
|
||||
song.verse_order = ' '.join(verse_order)
|
||||
song.lyrics = song_xml.extract_xml()
|
||||
clean_song(self.db_manager, song)
|
||||
self.db_manager.save_object(song)
|
||||
song.authors = []
|
||||
for author_name in self.song['authors']:
|
||||
#author_name = unicode(author_name)
|
||||
author = self.db_manager.get_object_filtered(Author, Author.display_name == author_name)
|
||||
if not author:
|
||||
author = Author.populate(
|
||||
first_name=author_name.rsplit(' ', 1)[0],
|
||||
last_name=author_name.rsplit(' ', 1)[1],
|
||||
display_name=author_name
|
||||
)
|
||||
song.authors.append(author)
|
||||
self.db_manager.save_object(song)
|
||||
question_dialog = QtGui.QMessageBox()
|
||||
question_dialog.setWindowTitle(translate('SongsPlugin.SongSelectForm', 'Song Imported'))
|
||||
question_dialog.setText(translate('SongsPlugin.SongSelectForm', 'Your song has been imported, would you like '
|
||||
'to exit now, or import more songs?'))
|
||||
question_dialog.addButton(QtGui.QPushButton(translate('SongsPlugin.SongSelectForm', 'Import More Songs')),
|
||||
QtGui.QMessageBox.YesRole)
|
||||
question_dialog.addButton(QtGui.QPushButton(translate('SongsPlugin.SongSelectForm', 'Exit Now')),
|
||||
QtGui.QMessageBox.NoRole)
|
||||
if question_dialog.exec_() == QtGui.QMessageBox.Yes:
|
||||
self.on_back_button_clicked()
|
||||
else:
|
||||
self.main_window.application.process_events()
|
||||
self.done(QtGui.QDialog.Accepted)
|
@ -38,11 +38,13 @@ import sqlite3
|
||||
|
||||
from PyQt4 import QtCore, QtGui
|
||||
|
||||
from openlp.core.common import UiStrings, translate
|
||||
from openlp.core.common import UiStrings, Registry, translate
|
||||
from openlp.core.lib import Plugin, StringContent, build_icon
|
||||
from openlp.core.lib.db import Manager
|
||||
from openlp.core.lib.ui import create_action
|
||||
from openlp.core.utils.actions import ActionList
|
||||
from openlp.plugins.songs.forms.duplicatesongremovalform import DuplicateSongRemovalForm
|
||||
from openlp.plugins.songs.forms.songselectform import SongSelectForm
|
||||
from openlp.plugins.songs.lib import clean_song, upgrade
|
||||
from openlp.plugins.songs.lib.db import init_schema, Song
|
||||
from openlp.plugins.songs.lib.mediaitem import SongSearch
|
||||
@ -50,30 +52,29 @@ from openlp.plugins.songs.lib.importer import SongFormat
|
||||
from openlp.plugins.songs.lib.olpimport import OpenLPSongImport
|
||||
from openlp.plugins.songs.lib.mediaitem import SongMediaItem
|
||||
from openlp.plugins.songs.lib.songstab import SongsTab
|
||||
from openlp.plugins.songs.forms.duplicatesongremovalform import DuplicateSongRemovalForm
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
__default_settings__ = {
|
||||
'songs/db type': 'sqlite',
|
||||
'songs/last search type': SongSearch.Entire,
|
||||
'songs/last import type': SongFormat.OpenLyrics,
|
||||
'songs/update service on edit': False,
|
||||
'songs/search as type': False,
|
||||
'songs/add song from service': True,
|
||||
'songs/display songbar': True,
|
||||
'songs/last directory import': '',
|
||||
'songs/last directory export': ''
|
||||
}
|
||||
'songs/db type': 'sqlite',
|
||||
'songs/last search type': SongSearch.Entire,
|
||||
'songs/last import type': SongFormat.OpenLyrics,
|
||||
'songs/update service on edit': False,
|
||||
'songs/search as type': False,
|
||||
'songs/add song from service': True,
|
||||
'songs/display songbar': True,
|
||||
'songs/last directory import': '',
|
||||
'songs/last directory export': '',
|
||||
'songs/songselect username': '',
|
||||
'songs/songselect password': '',
|
||||
'songs/songselect searches': ''
|
||||
}
|
||||
|
||||
|
||||
class SongsPlugin(Plugin):
|
||||
"""
|
||||
This is the number 1 plugin, if importance were placed on any
|
||||
plugins. This plugin enables the user to create, edit and display
|
||||
songs. Songs are divided into verses, and the verse order can be
|
||||
specified. Authors, topics and song books can be assigned to songs
|
||||
as well.
|
||||
This plugin enables the user to create, edit and display songs. Songs are divided into verses, and the verse order
|
||||
can be specified. Authors, topics and song books can be assigned to songs as well.
|
||||
"""
|
||||
log.info('Song Plugin loaded')
|
||||
|
||||
@ -86,6 +87,7 @@ class SongsPlugin(Plugin):
|
||||
self.weight = -10
|
||||
self.icon_path = ':/plugins/plugin_songs.png'
|
||||
self.icon = build_icon(self.icon_path)
|
||||
self.songselect_form = None
|
||||
|
||||
def check_pre_conditions(self):
|
||||
"""
|
||||
@ -94,8 +96,12 @@ class SongsPlugin(Plugin):
|
||||
return self.manager.session is not None
|
||||
|
||||
def initialise(self):
|
||||
"""
|
||||
Initialise the plugin
|
||||
"""
|
||||
log.info('Songs Initialising')
|
||||
super(SongsPlugin, self).initialise()
|
||||
self.songselect_form = SongSelectForm(Registry().get('main_window'), self, self.manager)
|
||||
self.song_import_item.setVisible(True)
|
||||
self.song_export_item.setVisible(True)
|
||||
self.tools_reindex_item.setVisible(True)
|
||||
@ -108,28 +114,27 @@ class SongsPlugin(Plugin):
|
||||
|
||||
def add_import_menu_item(self, import_menu):
|
||||
"""
|
||||
Give the Songs plugin the opportunity to add items to the
|
||||
**Import** menu.
|
||||
Give the Songs plugin the opportunity to add items to the **Import** menu.
|
||||
|
||||
``import_menu``
|
||||
The actual **Import** menu item, so that your actions can
|
||||
use it as their parent.
|
||||
:param import_menu: The actual **Import** menu item, so that your actions can use it as their parent.
|
||||
"""
|
||||
# Main song import menu item - will eventually be the only one
|
||||
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'),
|
||||
statustip=translate('SongsPlugin', 'Import songs from CCLI\'s SongSelect service.'),
|
||||
triggers=self.on_import_songselect_item_triggered
|
||||
)
|
||||
import_menu.addAction(self.import_songselect_item)
|
||||
|
||||
def add_export_menu_Item(self, export_menu):
|
||||
"""
|
||||
Give the Songs plugin the opportunity to add items to the
|
||||
**Export** menu.
|
||||
Give the Songs plugin the opportunity to add items to the **Export** menu.
|
||||
|
||||
``export_menu``
|
||||
The actual **Export** menu item, so that your actions can
|
||||
use it as their parent.
|
||||
:param export_menu: The actual **Export** menu item, so that your actions can use it as their parent.
|
||||
"""
|
||||
# Main song import menu item - will eventually be the only one
|
||||
self.song_export_item = create_action(export_menu, 'songExportItem',
|
||||
@ -140,12 +145,9 @@ class SongsPlugin(Plugin):
|
||||
|
||||
def add_tools_menu_item(self, tools_menu):
|
||||
"""
|
||||
Give the Songs plugin the opportunity to add items to the
|
||||
**Tools** menu.
|
||||
Give the Songs plugin the opportunity to add items to the **Tools** menu.
|
||||
|
||||
``tools_menu``
|
||||
The actual **Tools** menu item, so that your actions can
|
||||
use it as their parent.
|
||||
:param tools_menu: The actual **Tools** menu item, so that your actions can use it as their parent.
|
||||
"""
|
||||
log.info('add tools menu')
|
||||
self.tools_reindex_item = create_action(tools_menu, 'toolsReindexItem',
|
||||
@ -165,11 +167,11 @@ class SongsPlugin(Plugin):
|
||||
"""
|
||||
Rebuild each song.
|
||||
"""
|
||||
maxSongs = self.manager.get_object_count(Song)
|
||||
if maxSongs == 0:
|
||||
max_songs = self.manager.get_object_count(Song)
|
||||
if max_songs == 0:
|
||||
return
|
||||
progress_dialog = QtGui.QProgressDialog(translate('SongsPlugin', 'Reindexing songs...'), UiStrings().Cancel,
|
||||
0, maxSongs, self.main_window)
|
||||
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)
|
||||
@ -185,42 +187,61 @@ class SongsPlugin(Plugin):
|
||||
"""
|
||||
DuplicateSongRemovalForm(self).exec_()
|
||||
|
||||
def on_import_songselect_item_triggered(self):
|
||||
"""
|
||||
Run the SongSelect importer.
|
||||
"""
|
||||
self.songselect_form.exec_()
|
||||
self.media_item.on_search_text_button_clicked()
|
||||
|
||||
def on_song_import_item_clicked(self):
|
||||
"""
|
||||
Run the song import wizard.
|
||||
"""
|
||||
if self.media_item:
|
||||
self.media_item.on_import_click()
|
||||
|
||||
def on_song_export_item_clicked(self):
|
||||
"""
|
||||
Run the song export wizard.
|
||||
"""
|
||||
if self.media_item:
|
||||
self.media_item.on_export_click()
|
||||
|
||||
def about(self):
|
||||
"""
|
||||
Provides information for the plugin manager to display.
|
||||
|
||||
:return: A translatable string with some basic information about the Songs plugin
|
||||
"""
|
||||
return translate('SongsPlugin', '<strong>Songs Plugin</strong>'
|
||||
'<br />The songs plugin provides the ability to display and manage songs.')
|
||||
'<br />The songs plugin provides the ability to display and manage songs.')
|
||||
|
||||
def uses_theme(self, theme):
|
||||
"""
|
||||
Called to find out if the song plugin is currently using a theme.
|
||||
|
||||
Returns True if the theme is being used, otherwise returns False.
|
||||
:param theme: The theme to check for usage
|
||||
:return: True if the theme is being used, otherwise returns False
|
||||
"""
|
||||
if self.manager.get_all_objects(Song, Song.theme_name == theme):
|
||||
return True
|
||||
return False
|
||||
|
||||
def rename_theme(self, oldTheme, newTheme):
|
||||
def rename_theme(self, old_theme, new_theme):
|
||||
"""
|
||||
Renames a theme the song plugin is using making the plugin use the new
|
||||
name.
|
||||
|
||||
``oldTheme``
|
||||
``old_theme``
|
||||
The name of the theme the plugin should stop using.
|
||||
|
||||
``newTheme``
|
||||
``new_theme``
|
||||
The new name the plugin should now use.
|
||||
"""
|
||||
songsUsingTheme = self.manager.get_all_objects(Song, Song.theme_name == oldTheme)
|
||||
for song in songsUsingTheme:
|
||||
song.theme_name = newTheme
|
||||
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):
|
||||
|
BIN
resources/images/general_back.png
Normal file
BIN
resources/images/general_back.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 907 B |
BIN
resources/images/general_find.png
Normal file
BIN
resources/images/general_find.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 942 B |
@ -61,6 +61,8 @@
|
||||
<file>general_email.png</file>
|
||||
<file>general_revert.png</file>
|
||||
<file>general_clone.png</file>
|
||||
<file>general_find.png</file>
|
||||
<file>general_back.png</file>
|
||||
</qresource>
|
||||
<qresource prefix="slides">
|
||||
<file>slide_close.png</file>
|
||||
|
Loading…
Reference in New Issue
Block a user