Source code for openlp.plugins.songs.forms.songselectform

# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4

###############################################################################
# OpenLP - Open Source Lyrics Projection                                      #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2017 OpenLP Developers                                   #
# --------------------------------------------------------------------------- #
# 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
import os
from time import sleep

from PyQt5 import QtCore, QtWidgets

from openlp.core import Settings
from openlp.core.common import Registry, is_win
from openlp.core.lib import translate
from openlp.plugins.songs.forms.songselectdialog import Ui_SongSelectDialog
from openlp.plugins.songs.lib.songselect import SongSelectImport

log = logging.getLogger(__name__)


[docs]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() quit = QtCore.pyqtSignal() def __init__(self, importer, search_text): super().__init__() self.importer = importer self.search_text = search_text
[docs] def start(self): """ Run a search and then parse the results page of the search. """ songs = self.importer.search(self.search_text, 1000, self._found_song_callback) if len(songs) >= 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.')) self.finished.emit() self.quit.emit()
def _found_song_callback(self, song): """ A callback used by the paginate function to notify watching processes when it finds a song. :param song: The song that was found """ self.found_song.emit(song)
[docs]class SongSelectForm(QtWidgets.QDialog, Ui_SongSelectDialog): """ The :class:`SongSelectForm` class is the SongSelect dialog. """ def __init__(self, parent=None, plugin=None, db_manager=None): QtWidgets.QDialog.__init__(self, parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint) self.plugin = plugin self.db_manager = db_manager self.setup_ui(self)
[docs] def initialise(self): """ Initialise the SongSelectForm """ self.thread = None self.worker = None self.song_count = 0 self.song = None self.set_progress_visible(False) self.song_select_importer = SongSelectImport(self.db_manager) 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.stop_button.clicked.connect(self.on_stop_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)
[docs] 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 QtWidgets.QDialog.exec(self)
[docs] 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 = QtWidgets.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.application.process_events() sleep(0.5) self.application.process_events() self.song_select_importer.logout() self.application.process_events() progress_dialog.setValue(2) return QtWidgets.QDialog.done(self, r)
def _update_login_progress(self): """ Update the progress bar as the user logs in. """ self.login_progress_bar.setValue(self.login_progress_bar.value() + 1) self.application.process_events() def _update_song_progress(self): """ Update the progress bar as the song is being downloaded. """ self.song_progress_bar.setValue(self.song_progress_bar.value() + 1) self.application.process_events() def _view_song(self, current_item): """ Load a song into the song view. """ if not current_item: return else: current_item = current_item.data(QtCore.Qt.UserRole) # Stop the current search, if it's running self.song_select_importer.stop() # Clear up the UI 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(0) self.application.process_events() # Get the full song song = self.song_select_importer.get_song(song, self._update_song_progress) if not song: QtWidgets.QMessageBox.critical( self, translate('SongsPlugin.SongSelectForm', 'Incomplete song'), translate('SongsPlugin.SongSelectForm', 'This song is missing some information, like the lyrics, ' 'and cannot be imported.'), QtWidgets.QMessageBox.StandardButtons(QtWidgets.QMessageBox.Ok), QtWidgets.QMessageBox.Ok) self.stacked_widget.setCurrentIndex(1) return # Update the UI self.title_edit.setText(song['title']) self.copyright_edit.setText(song['copyright']) self.ccli_edit.setText(song['ccli_number']) for author in song['authors']: QtWidgets.QListWidgetItem(author, self.author_list_widget) for counter, verse in enumerate(song['verses']): self.lyrics_table_widget.setRowCount(self.lyrics_table_widget.rowCount() + 1) item = QtWidgets.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.application.process_events()
[docs] 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 = QtWidgets.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.'), QtWidgets.QMessageBox.StandardButtons(QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No), QtWidgets.QMessageBox.No) if answer == QtWidgets.QMessageBox.No: self.save_password_checkbox.setChecked(False)
[docs] 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.setValue(0) self.login_progress_bar.setVisible(True) self.application.process_events() # Log the user in if not self.song_select_importer.login( self.username_edit.text(), self.password_edit.text(), self._update_login_progress): QtWidgets.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.username_edit.setEnabled(True) self.password_edit.setEnabled(True) self.save_password_checkbox.setEnabled(True) self.search_combobox.setFocus() self.application.process_events()
[docs] def on_search_button_clicked(self): """ Run a search on SongSelect. """ # Set up UI components self.view_button.setEnabled(False) self.search_button.setEnabled(False) self.search_combobox.setEnabled(False) self.search_progress_bar.setMinimum(0) self.search_progress_bar.setMaximum(0) self.search_progress_bar.setValue(0) self.set_progress_visible(True) self.search_results_widget.clear() self.result_count_label.setText(translate('SongsPlugin.SongSelectForm', 'Found {count:d} song(s)').format(count=self.song_count)) self.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.song_select_importer, self.search_combobox.currentText()) 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()
[docs] def on_stop_button_clicked(self): """ Stop the search when the stop button is clicked. """ self.song_select_importer.stop()
[docs] def on_search_show_info(self, title, message): """ Show an informational message from the search thread :param title: :param message: """ QtWidgets.QMessageBox.information(self, title, message)
[docs] def on_search_found_song(self, song): """ Add a song to the list when one is found. :param song: """ self.song_count += 1 self.result_count_label.setText(translate('SongsPlugin.SongSelectForm', 'Found {count:d} song(s)').format(count=self.song_count)) item_title = song['title'] + ' (' + ', '.join(song['authors']) + ')' song_item = QtWidgets.QListWidgetItem(item_title, self.search_results_widget) song_item.setData(QtCore.Qt.UserRole, song)
[docs] def on_search_finished(self): """ Slot which is called when the search is completed. """ self.application.process_events() self.set_progress_visible(False) self.search_button.setEnabled(True) self.search_combobox.setEnabled(True) self.application.process_events()
[docs] 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)
[docs] def on_view_button_clicked(self): """ View a song from SongSelect. """ self._view_song(self.search_results_widget.currentItem())
[docs] def on_search_results_widget_double_clicked(self, current_item): """ View a song from SongSelect :param current_item: """ self._view_song(current_item)
[docs] def on_back_button_clicked(self): """ Go back to the search page. """ self.stacked_widget.setCurrentIndex(1) self.search_combobox.setFocus()
[docs] def on_import_button_clicked(self): """ Import a song from SongSelect. """ self.song_select_importer.save_song(self.song) self.song = None if QtWidgets.QMessageBox.question(self, translate('SongsPlugin.SongSelectForm', 'Song Imported'), translate('SongsPlugin.SongSelectForm', 'Your song has been imported, would you ' 'like to import more songs?'), QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No, QtWidgets.QMessageBox.Yes) == QtWidgets.QMessageBox.Yes: self.on_back_button_clicked() else: self.application.process_events() self.done(QtWidgets.QDialog.Accepted)
[docs] def set_progress_visible(self, is_visible): """ Show or hide the search progress, including the stop button. """ self.search_progress_bar.setVisible(is_visible) self.stop_button.setVisible(is_visible)
@property def application(self): """ Adds the openlp to the class dynamically. Windows needs to access the application in a dynamic manner. """ if is_win(): return Registry().get('application') else: if not hasattr(self, '_application'): self._application = Registry().get('application') return self._application