Example #1
0
    def test_on_search_finished(self):
        """
        Test that search fields are enabled when search is finished.
        """
        # GIVEN: A mocked SongSelect form
        ssform = SongSelectForm(None, MagicMock(), MagicMock())
        ssform.initialise()

        # WHEN: The search is finished
        ssform.on_search_finished()

        # THEN: The search box and search button should be enabled
        self.assertTrue(ssform.search_button.isEnabled())
        self.assertTrue(ssform.search_combobox.isEnabled())
Example #2
0
    def test_update_song_progress(self):
        """
        Test the _update_song_progress() method
        """
        # GIVEN: A SongSelect form
        ssform = SongSelectForm(None, MagicMock(), MagicMock())

        # WHEN: _update_song_progress() is called
        with patch.object(ssform, 'song_progress_bar') as mocked_song_progress_bar:
            mocked_song_progress_bar.value.return_value = 2
            ssform._update_song_progress()

        # THEN: The song progress bar should be updated
        mocked_song_progress_bar.setValue.assert_called_with(3)
Example #3
0
    def test_update_song_progress(self):
        """
        Test the _update_song_progress() method
        """
        # GIVEN: A SongSelect form
        ssform = SongSelectForm(None, MagicMock(), MagicMock())

        # WHEN: _update_song_progress() is called
        with patch.object(ssform, 'song_progress_bar') as mocked_song_progress_bar:
            mocked_song_progress_bar.value.return_value = 2
            ssform._update_song_progress()

        # THEN: The song progress bar should be updated
        mocked_song_progress_bar.setValue.assert_called_with(3)
Example #4
0
    def test_on_search_results_widget_double_clicked(self):
        """
        Test that a song is retrieved when a song in the results list is double-clicked
        """
        # GIVEN: A SongSelect form
        ssform = SongSelectForm(None, MagicMock(), MagicMock())
        expected_song = {'title': 'Amazing Grace'}

        # WHEN: A song result is double-clicked
        with patch.object(ssform, '_view_song') as mocked_view_song:
            ssform.on_search_results_widget_double_clicked(expected_song)

        # THEN: The song is fetched and shown to the user
        mocked_view_song.assert_called_with(expected_song)
Example #5
0
    def test_login_fails(self, mocked_translate, mocked_critical, MockedSongSelectImport):
        """
        Test that when the login fails, the form returns to the correct state
        """
        # GIVEN: A valid SongSelectForm with a mocked out SongSelectImport, and a bunch of mocked out controls
        mocked_song_select_import = MagicMock()
        mocked_song_select_import.login.return_value = False
        MockedSongSelectImport.return_value = mocked_song_select_import
        mocked_translate.side_effect = lambda *args: args[1]
        ssform = SongSelectForm(None, MagicMock(), MagicMock())
        ssform.initialise()
        with patch.object(ssform, 'username_edit') as mocked_username_edit, \
                patch.object(ssform, 'password_edit') as mocked_password_edit, \
                patch.object(ssform, 'save_password_checkbox') as mocked_save_password_checkbox, \
                patch.object(ssform, 'login_button') as mocked_login_button, \
                patch.object(ssform, 'login_spacer') as mocked_login_spacer, \
                patch.object(ssform, 'login_progress_bar') as mocked_login_progress_bar, \
                patch.object(ssform.application, 'process_events') as mocked_process_events:

            # WHEN: The login button is clicked, and the login is rigged to fail
            ssform.on_login_button_clicked()

            # THEN: The right things should have happened in the right order
            expected_username_calls = [call(False), call(True)]
            expected_password_calls = [call(False), call(True)]
            expected_save_password_calls = [call(False), call(True)]
            expected_login_btn_calls = [call(False), call(True)]
            expected_login_spacer_calls = [call(False), call(True)]
            expected_login_progress_visible_calls = [call(True), call(False)]
            expected_login_progress_value_calls = [call(0), call(0)]
            self.assertEqual(expected_username_calls, mocked_username_edit.setEnabled.call_args_list,
                             'The username edit should be disabled then enabled')
            self.assertEqual(expected_password_calls, mocked_password_edit.setEnabled.call_args_list,
                             'The password edit should be disabled then enabled')
            self.assertEqual(expected_save_password_calls, mocked_save_password_checkbox.setEnabled.call_args_list,
                             'The save password checkbox should be disabled then enabled')
            self.assertEqual(expected_login_btn_calls, mocked_login_button.setEnabled.call_args_list,
                             'The login button should be disabled then enabled')
            self.assertEqual(expected_login_spacer_calls, mocked_login_spacer.setVisible.call_args_list,
                             'Thee login spacer should be make invisible, then visible')
            self.assertEqual(expected_login_progress_visible_calls,
                             mocked_login_progress_bar.setVisible.call_args_list,
                             'Thee login progress bar should be make visible, then invisible')
            self.assertEqual(expected_login_progress_value_calls, mocked_login_progress_bar.setValue.call_args_list,
                             'Thee login progress bar should have the right values set')
            self.assertEqual(2, mocked_process_events.call_count,
                             'The process_events() method should be called twice')
            mocked_critical.assert_called_with(ssform, 'Error Logging In', 'There was a problem logging in, '
                                                                           'perhaps your username or password is '
                                                                           'incorrect?')
Example #6
0
    def test_on_search_show_info(self, mocked_information):
        """
        Test that when the search_show_info signal is emitted, the on_search_show_info() method shows a dialog
        """
        # GIVEN: A SongSelect form
        ssform = SongSelectForm(None, MagicMock(), MagicMock())
        expected_title = 'Test Title'
        expected_text = 'This is a test'

        # WHEN: on_search_show_info is called
        ssform.on_search_show_info(expected_title, expected_text)

        # THEN: An information dialog should be shown
        mocked_information.assert_called_with(ssform, expected_title, expected_text)
Example #7
0
    def test_on_search_results_widget_double_clicked(self):
        """
        Test that a song is retrieved when a song in the results list is double-clicked
        """
        # GIVEN: A SongSelect form
        ssform = SongSelectForm(None, MagicMock(), MagicMock())
        expected_song = {'title': 'Amazing Grace'}

        # WHEN: A song result is double-clicked
        with patch.object(ssform, '_view_song') as mocked_view_song:
            ssform.on_search_results_widget_double_clicked(expected_song)

        # THEN: The song is fetched and shown to the user
        mocked_view_song.assert_called_with(expected_song)
Example #8
0
    def test_on_search_show_info(self, mocked_information):
        """
        Test that when the search_show_info signal is emitted, the on_search_show_info() method shows a dialog
        """
        # GIVEN: A SongSelect form
        ssform = SongSelectForm(None, MagicMock(), MagicMock())
        expected_title = 'Test Title'
        expected_text = 'This is a test'

        # WHEN: on_search_show_info is called
        ssform.on_search_show_info(expected_title, expected_text)

        # THEN: An information dialog should be shown
        mocked_information.assert_called_with(ssform, expected_title, expected_text)
Example #9
0
    def test_on_search_button_clicked(self, MockedSearchWorker, MockedQtThread, MockedSettings):
        """
        Test that search fields are disabled when search button is clicked.
        """
        # GIVEN: A mocked SongSelect form
        ssform = SongSelectForm(None, MagicMock(), MagicMock())
        ssform.initialise()

        # WHEN: The search button is clicked
        ssform.on_search_button_clicked()

        # THEN: The search box and search button should be disabled
        self.assertFalse(ssform.search_button.isEnabled())
        self.assertFalse(ssform.search_combobox.isEnabled())
Example #10
0
    def test_on_search_results_widget_selection_changed(self):
        """
        Test that the view button is updated when the search results list is changed
        """
        # GIVEN: A SongSelect form
        ssform = SongSelectForm(None, MagicMock(), MagicMock())

        # WHEN: There is at least 1 item selected
        with patch.object(ssform, 'search_results_widget') as mocked_search_results_widget, \
                patch.object(ssform, 'view_button') as mocked_view_button:
            mocked_search_results_widget.selectedItems.return_value = [1]
            ssform.on_search_results_widget_selection_changed()

        # THEN: The view button should be enabled
        mocked_view_button.setEnabled.assert_called_with(True)
Example #11
0
    def test_on_search_results_widget_selection_changed(self):
        """
        Test that the view button is updated when the search results list is changed
        """
        # GIVEN: A SongSelect form
        ssform = SongSelectForm(None, MagicMock(), MagicMock())

        # WHEN: There is at least 1 item selected
        with patch.object(ssform, 'search_results_widget') as mocked_search_results_widget, \
                patch.object(ssform, 'view_button') as mocked_view_button:
            mocked_search_results_widget.selectedItems.return_value = [1]
            ssform.on_search_results_widget_selection_changed()

        # THEN: The view button should be enabled
        mocked_view_button.setEnabled.assert_called_with(True)
Example #12
0
    def test_on_back_button_clicked(self):
        """
        Test that when the back button is clicked, the stacked widget is set back one page
        """
        # GIVEN: A SongSelect form
        ssform = SongSelectForm(None, MagicMock(), MagicMock())

        # WHEN: The back button is clicked
        with patch.object(ssform, 'stacked_widget') as mocked_stacked_widget, \
                patch.object(ssform, 'search_combobox') as mocked_search_combobox:
            ssform.on_back_button_clicked()

        # THEN: The stacked widget should be set back one page
        mocked_stacked_widget.setCurrentIndex.assert_called_with(1)
        mocked_search_combobox.setFocus.assert_called_with()
Example #13
0
    def test_on_back_button_clicked(self):
        """
        Test that when the back button is clicked, the stacked widget is set back one page
        """
        # GIVEN: A SongSelect form
        ssform = SongSelectForm(None, MagicMock(), MagicMock())

        # WHEN: The back button is clicked
        with patch.object(ssform, 'stacked_widget') as mocked_stacked_widget, \
                patch.object(ssform, 'search_combobox') as mocked_search_combobox:
            ssform.on_back_button_clicked()

        # THEN: The stacked widget should be set back one page
        mocked_stacked_widget.setCurrentIndex.assert_called_with(1)
        mocked_search_combobox.setFocus.assert_called_with()
Example #14
0
    def test_on_view_button_clicked(self):
        """
        Test that a song is retrieved when the view button is clicked
        """
        # GIVEN: A SongSelect form
        ssform = SongSelectForm(None, MagicMock(), MagicMock())
        expected_song = {'title': 'Amazing Grace'}

        # WHEN: A song result is double-clicked
        with patch.object(ssform, '_view_song') as mocked_view_song, \
                patch.object(ssform, 'search_results_widget') as mocked_search_results_widget:
            mocked_search_results_widget.currentItem.return_value = expected_song
            ssform.on_view_button_clicked()

        # THEN: The song is fetched and shown to the user
        mocked_view_song.assert_called_with(expected_song)
Example #15
0
    def test_on_view_button_clicked(self):
        """
        Test that a song is retrieved when the view button is clicked
        """
        # GIVEN: A SongSelect form
        ssform = SongSelectForm(None, MagicMock(), MagicMock())
        expected_song = {'title': 'Amazing Grace'}

        # WHEN: A song result is double-clicked
        with patch.object(ssform, '_view_song') as mocked_view_song, \
                patch.object(ssform, 'search_results_widget') as mocked_search_results_widget:
            mocked_search_results_widget.currentItem.return_value = expected_song
            ssform.on_view_button_clicked()

        # THEN: The song is fetched and shown to the user
        mocked_view_song.assert_called_with(expected_song)
Example #16
0
    def test_on_stop_button_clicked(self, MockedSongSelectImport):
        """
        Test that the search is stopped when the stop button is clicked
        """
        # GIVEN: A mocked SongSelectImporter and a SongSelect form
        mocked_song_select_importer = MagicMock()
        MockedSongSelectImport.return_value = mocked_song_select_importer
        ssform = SongSelectForm(None, MagicMock(), MagicMock())
        ssform.initialise()

        # WHEN: The stop button is clicked
        ssform.on_stop_button_clicked()

        # THEN: The view button, search box and search button should be enabled
        mocked_song_select_importer.stop.assert_called_with()
        self.assertTrue(ssform.search_button.isEnabled())
        self.assertTrue(ssform.search_combobox.isEnabled())
Example #17
0
 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.songselect_form.initialise()
     self.song_import_item.setVisible(True)
     self.song_export_item.setVisible(True)
     self.tools_reindex_item.setVisible(True)
     self.tools_find_duplicates.setVisible(True)
     action_list = ActionList.get_instance()
     action_list.add_action(self.song_import_item, UiStrings().Import)
     action_list.add_action(self.song_export_item, UiStrings().Export)
     action_list.add_action(self.tools_reindex_item, UiStrings().Tools)
     action_list.add_action(self.tools_find_duplicates, UiStrings().Tools)
Example #18
0
    def test_on_import_no_clicked(self, mocked_translate, mocked_question):
        """
        Test that when a song is imported and the user clicks the "no" button, the UI exits
        """
        # GIVEN: A valid SongSelectForm with a mocked out QMessageBox.question() method
        mocked_translate.side_effect = lambda *args: args[1]
        mocked_question.return_value = QtWidgets.QMessageBox.No
        ssform = SongSelectForm(None, MagicMock(), MagicMock())
        mocked_song_select_importer = MagicMock()
        ssform.song_select_importer = mocked_song_select_importer
        ssform.song = None

        # WHEN: The import button is clicked, and the user clicks Yes
        with patch.object(ssform, 'done') as mocked_done:
            ssform.on_import_button_clicked()

        # THEN: The on_back_button_clicked() method should have been called
        mocked_song_select_importer.save_song.assert_called_with(None)
        mocked_question.assert_called_with(
            ssform, 'Song Imported',
            'Your song has been imported, would you like to import more songs?',
            QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No,
            QtWidgets.QMessageBox.Yes)
        mocked_done.assert_called_with(QtWidgets.QDialog.Accepted)
        self.assertIsNone(ssform.song)
Example #19
0
    def on_import_yes_clicked_test(self, mocked_translate, mocked_question):
        """
        Test that when a song is imported and the user clicks the "yes" button, the UI goes back to the previous page
        """
        # GIVEN: A valid SongSelectForm with a mocked out QMessageBox.question() method
        mocked_translate.side_effect = lambda *args: args[1]
        mocked_question.return_value = QtGui.QMessageBox.Yes
        ssform = SongSelectForm(None, MagicMock(), MagicMock())
        mocked_song_select_importer = MagicMock()
        ssform.song_select_importer = mocked_song_select_importer
        ssform.song = None

        # WHEN: The import button is clicked, and the user clicks Yes
        with patch.object(
                ssform,
                'on_back_button_clicked') as mocked_on_back_button_clicked:
            ssform.on_import_button_clicked()

        # THEN: The on_back_button_clicked() method should have been called
        mocked_song_select_importer.save_song.assert_called_with(None)
        mocked_question.assert_called_with(
            ssform, 'Song Imported',
            'Your song has been imported, would you like to import more songs?',
            QtGui.QMessageBox.Yes | QtGui.QMessageBox.No,
            QtGui.QMessageBox.Yes)
        mocked_on_back_button_clicked.assert_called_with()
        self.assertIsNone(ssform.song)
Example #20
0
    def on_import_no_clicked_test(self, mocked_translate, mocked_question):
        """
        Test that when a song is imported and the user clicks the "no" button, the UI exits
        """
        # GIVEN: A valid SongSelectForm with a mocked out QMessageBox.question() method
        mocked_translate.side_effect = lambda *args: args[1]
        mocked_question.return_value = QtGui.QMessageBox.No
        ssform = SongSelectForm(None, MagicMock(), MagicMock())
        mocked_song_select_importer = MagicMock()
        ssform.song_select_importer = mocked_song_select_importer
        ssform.song = None

        # WHEN: The import button is clicked, and the user clicks Yes
        with patch.object(ssform, 'done') as mocked_done:
            ssform.on_import_button_clicked()

        # THEN: The on_back_button_clicked() method should have been called
        mocked_song_select_importer.save_song.assert_called_with(None)
        mocked_question.assert_called_with(ssform, 'Song Imported',
                                           'Your song has been imported, would you like to import more songs?',
                                           QtGui.QMessageBox.Yes | QtGui.QMessageBox.No, QtGui.QMessageBox.Yes)
        mocked_done.assert_called_with(QtGui.QDialog.Accepted)
        self.assertIsNone(ssform.song)
Example #21
0
    def test_create_form(self):
        """
        Test that we can create the SongSelect form
        """
        # GIVEN: The SongSelectForm class and a mocked db manager
        mocked_plugin = MagicMock()
        mocked_db_manager = MagicMock()

        # WHEN: We create an instance
        ssform = SongSelectForm(None, mocked_plugin, mocked_db_manager)

        # THEN: The correct properties should have been assigned
        assert mocked_plugin == ssform.plugin, 'The correct plugin should have been assigned'
        assert mocked_db_manager == ssform.db_manager, 'The correct db_manager should have been assigned'
Example #22
0
    def test_on_import_yes_clicked(self, mocked_translate, mocked_question):
        """
        Test that when a song is imported and the user clicks the "yes" button, the UI goes back to the previous page
        """
        # GIVEN: A valid SongSelectForm with a mocked out QMessageBox.question() method
        mocked_translate.side_effect = lambda *args: args[1]
        mocked_question.return_value = QtWidgets.QMessageBox.Yes
        ssform = SongSelectForm(None, MagicMock(), MagicMock())
        mocked_song_select_importer = MagicMock()
        ssform.song_select_importer = mocked_song_select_importer
        ssform.song = None

        # WHEN: The import button is clicked, and the user clicks Yes
        with patch.object(ssform, 'on_back_button_clicked') as mocked_on_back_button_clicked:
            ssform.on_import_button_clicked()

        # THEN: The on_back_button_clicked() method should have been called
        mocked_song_select_importer.save_song.assert_called_with(None)
        mocked_question.assert_called_with(ssform, 'Song Imported',
                                           'Your song has been imported, would you like to import more songs?',
                                           QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No,
                                           QtWidgets.QMessageBox.Yes)
        mocked_on_back_button_clicked.assert_called_with()
        self.assertIsNone(ssform.song)
Example #23
0
 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.songselect_form.initialise()
     self.song_import_item.setVisible(True)
     self.song_export_item.setVisible(True)
     self.song_tools_menu.menuAction().setVisible(True)
     action_list = ActionList.get_instance()
     action_list.add_action(self.song_import_item, UiStrings().Import)
     action_list.add_action(self.song_export_item, UiStrings().Export)
     action_list.add_action(self.tools_reindex_item, UiStrings().Tools)
     action_list.add_action(self.tools_find_duplicates, UiStrings().Tools)
     action_list.add_action(self.tools_report_song_list, UiStrings().Tools)
Example #24
0
    def test_on_search_button_clicked(self, MockedSearchWorker, mocked_run_thread):
        """
        Test that search fields are disabled when search button is clicked.
        """
        # GIVEN: A mocked SongSelect form
        ssform = SongSelectForm(None, MagicMock(), MagicMock())
        ssform.initialise()

        # WHEN: The search button is clicked
        ssform.on_search_button_clicked()

        # THEN: The search box and search button should be disabled
        assert ssform.search_button.isEnabled() is False
        assert ssform.search_combobox.isEnabled() is False
Example #25
0
    def test_login_fails(self, mocked_translate, mocked_critical,
                         MockedSongSelectImport):
        """
        Test that when the login fails, the form returns to the correct state
        """
        # GIVEN: A valid SongSelectForm with a mocked out SongSelectImport, and a bunch of mocked out controls
        mocked_song_select_import = MagicMock()
        mocked_song_select_import.login.return_value = False
        MockedSongSelectImport.return_value = mocked_song_select_import
        mocked_translate.side_effect = lambda *args: args[1]
        ssform = SongSelectForm(None, MagicMock(), MagicMock())
        ssform.initialise()
        with patch.object(ssform, 'username_edit') as mocked_username_edit, \
                patch.object(ssform, 'password_edit') as mocked_password_edit, \
                patch.object(ssform, 'save_password_checkbox') as mocked_save_password_checkbox, \
                patch.object(ssform, 'login_button') as mocked_login_button, \
                patch.object(ssform, 'login_spacer') as mocked_login_spacer, \
                patch.object(ssform, 'login_progress_bar') as mocked_login_progress_bar, \
                patch.object(ssform.application, 'process_events') as mocked_process_events:

            # WHEN: The login button is clicked, and the login is rigged to fail
            ssform.on_login_button_clicked()

            # THEN: The right things should have happened in the right order
            expected_username_calls = [call(False), call(True)]
            expected_password_calls = [call(False), call(True)]
            expected_save_password_calls = [call(False), call(True)]
            expected_login_btn_calls = [call(False), call(True)]
            expected_login_spacer_calls = [call(False), call(True)]
            expected_login_progress_visible_calls = [call(True), call(False)]
            expected_login_progress_value_calls = [call(0), call(0)]
            assert expected_username_calls == mocked_username_edit.setEnabled.call_args_list, \
                'The username edit should be disabled then enabled'
            assert expected_password_calls == mocked_password_edit.setEnabled.call_args_list, \
                'The password edit should be disabled then enabled'
            assert expected_save_password_calls == mocked_save_password_checkbox.setEnabled.call_args_list, \
                'The save password checkbox should be disabled then enabled'
            assert expected_login_btn_calls == mocked_login_button.setEnabled.call_args_list, \
                'The login button should be disabled then enabled'
            assert expected_login_spacer_calls == mocked_login_spacer.setVisible.call_args_list, \
                'Thee login spacer should be make invisible, then visible'
            assert expected_login_progress_visible_calls == mocked_login_progress_bar.setVisible.call_args_list, \
                'Thee login progress bar should be make visible, then invisible'
            assert expected_login_progress_value_calls == mocked_login_progress_bar.setValue.call_args_list, \
                'Thee login progress bar should have the right values set'
            assert 2 == mocked_process_events.call_count, 'The process_events() method should be called twice'
            mocked_critical.assert_called_with(
                ssform, 'Error Logging In', 'There was a problem logging in, '
                'perhaps your username or password is '
                'incorrect?')
Example #26
0
    def test_on_search_finished(self):
        """
        Test that search fields are enabled when search is finished.
        """
        # GIVEN: A mocked SongSelect form
        ssform = SongSelectForm(None, MagicMock(), MagicMock())
        ssform.initialise()

        # WHEN: The search is finished
        ssform.on_search_finished()

        # THEN: The search box and search button should be enabled
        self.assertTrue(ssform.search_button.isEnabled())
        self.assertTrue(ssform.search_combobox.isEnabled())
Example #27
0
    def test_on_stop_button_clicked(self, MockedSongSelectImport):
        """
        Test that the search is stopped when the stop button is clicked
        """
        # GIVEN: A mocked SongSelectImporter and a SongSelect form
        mocked_song_select_importer = MagicMock()
        MockedSongSelectImport.return_value = mocked_song_select_importer
        ssform = SongSelectForm(None, MagicMock(), MagicMock())
        ssform.initialise()

        # WHEN: The stop button is clicked
        ssform.on_stop_button_clicked()

        # THEN: The view button, search box and search button should be enabled
        mocked_song_select_importer.stop.assert_called_with()
        self.assertTrue(ssform.search_button.isEnabled())
        self.assertTrue(ssform.search_combobox.isEnabled())
Example #28
0
class SongsPlugin(Plugin):
    """
    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')

    def __init__(self):
        """
        Create and set up the Songs plugin.
        """
        super(SongsPlugin, self).__init__('songs', __default_settings__, SongMediaItem, SongsTab)
        self.manager = Manager('songs', init_schema, upgrade_mod=upgrade)
        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):
        """
        Check the plugin can run.
        """
        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.songselect_form.initialise()
        self.song_import_item.setVisible(True)
        self.song_export_item.setVisible(True)
        self.song_tools_menu.menuAction().setVisible(True)
        action_list = ActionList.get_instance()
        action_list.add_action(self.song_import_item, UiStrings().Import)
        action_list.add_action(self.song_export_item, UiStrings().Export)
        action_list.add_action(self.tools_reindex_item, UiStrings().Tools)
        action_list.add_action(self.tools_find_duplicates, UiStrings().Tools)
        action_list.add_action(self.tools_report_song_list, UiStrings().Tools)

    def add_import_menu_item(self, import_menu):
        """
        Give the Songs plugin the opportunity to add items to the **Import** menu.

        :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)
        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.

        :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',
            text=translate('SongsPlugin', '&Song'),
            tooltip=translate('SongsPlugin', 'Exports songs using the export wizard.'),
            triggers=self.on_song_export_item_clicked)
        export_menu.addAction(self.song_export_item)

    def add_tools_menu_item(self, tools_menu):
        """
        Give the Songs plugin the opportunity to add items to the **Tools** menu.

        :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_menu = tools_menu
        self.song_tools_menu = QtWidgets.QMenu(tools_menu)
        self.song_tools_menu.setObjectName('song_tools_menu')
        self.song_tools_menu.setTitle(translate('SongsPlugin', 'Songs'))
        self.tools_reindex_item = create_action(
            tools_menu, 'toolsReindexItem',
            text=translate('SongsPlugin', '&Re-index Songs'),
            icon=':/plugins/plugin_songs.png',
            statustip=translate('SongsPlugin', 'Re-index the songs database to improve searching and ordering.'),
            triggers=self.on_tools_reindex_item_triggered)
        self.tools_find_duplicates = create_action(
            tools_menu, 'toolsFindDuplicates',
            text=translate('SongsPlugin', 'Find &Duplicate Songs'),
            statustip=translate('SongsPlugin', 'Find and remove duplicate songs in the song database.'),
            triggers=self.on_tools_find_duplicates_triggered, can_shortcuts=True)
        self.tools_report_song_list = create_action(
            tools_menu, 'toolsSongListReport',
            text=translate('SongsPlugin', 'Song List Report'),
            statustip=translate('SongsPlugin', 'Produce a CSV file of all the songs in the database.'),
            triggers=self.on_tools_report_song_list_triggered)

        self.tools_menu.addAction(self.song_tools_menu.menuAction())
        self.song_tools_menu.addAction(self.tools_reindex_item)
        self.song_tools_menu.addAction(self.tools_find_duplicates)
        self.song_tools_menu.addAction(self.tools_report_song_list)

        self.song_tools_menu.menuAction().setVisible(False)

    @staticmethod
    def on_tools_report_song_list_triggered():
        reporting.report_song_list()

    def on_tools_reindex_item_triggered(self):
        """
        Rebuild each song.
        """
        max_songs = self.manager.get_object_count(Song)
        if max_songs == 0:
            return
        progress_dialog = QtWidgets.QProgressDialog(
            translate('SongsPlugin', 'Reindexing songs...'), UiStrings().Cancel, 0, max_songs, self.main_window)
        progress_dialog.setWindowTitle(translate('SongsPlugin', 'Reindexing songs'))
        progress_dialog.setWindowModality(QtCore.Qt.WindowModal)
        songs = self.manager.get_all_objects(Song)
        for number, song in enumerate(songs):
            clean_song(self.manager, song)
            progress_dialog.setValue(number + 1)
        self.manager.save_objects(songs)
        self.media_item.on_search_text_button_clicked()

    def on_tools_find_duplicates_triggered(self):
        """
        Search for duplicates in the song database.
        """
        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()

    @staticmethod
    def about():
        """
        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.')

    def uses_theme(self, theme):
        """
        Called to find out if the song plugin is currently using a theme.

        :param theme: The theme to check for usage
        :return: count of the number of times the theme is used.
        """
        return len(self.manager.get_all_objects(Song, Song.theme_name == theme))

    def rename_theme(self, old_theme, new_theme):
        """
        Renames a theme the song plugin is using making the plugin use the new name.

        :param old_theme: The name of the theme the plugin should stop using.
        :param new_theme: The new name the plugin should now use.
        """
        songs_using_theme = self.manager.get_all_objects(Song, Song.theme_name == old_theme)
        for song in songs_using_theme:
            song.theme_name = new_theme
            self.manager.save_object(song)

    def import_songs(self, import_format, **kwargs):
        """
        Add the correct importer class

        :param import_format: The import_format to be used
        :param kwargs: The arguments
        :return: the correct importer
        """
        class_ = SongFormat.get(import_format, 'class')
        importer = class_(self.manager, **kwargs)
        importer.register(self.media_item.import_wizard)
        return importer

    def set_plugin_text_strings(self):
        """
        Called to define all translatable texts of the plugin
        """
        # Name PluginList
        self.text_strings[StringContent.Name] = {
            'singular': translate('SongsPlugin', 'Song', 'name singular'),
            'plural': translate('SongsPlugin', 'Songs', 'name plural')
        }
        # Name for MediaDockManager, SettingsManager
        self.text_strings[StringContent.VisibleName] = {
            'title': translate('SongsPlugin', 'Songs', 'container title')
        }
        # Middle Header Bar
        tooltips = {
            'load': '',
            'import': '',
            'new': translate('SongsPlugin', 'Add a new song.'),
            'edit': translate('SongsPlugin', 'Edit the selected song.'),
            'delete': translate('SongsPlugin', 'Delete the selected song.'),
            'preview': translate('SongsPlugin', 'Preview the selected song.'),
            'live': translate('SongsPlugin', 'Send the selected song live.'),
            'service': translate('SongsPlugin', 'Add the selected song to the service.')
        }
        self.set_plugin_ui_text_strings(tooltips)

    def first_time(self):
        """
        If the first time wizard has run, this function is run to import all the new songs into the database.
        """
        self.application.process_events()
        self.on_tools_reindex_item_triggered()
        self.application.process_events()
        db_dir = os.path.join(gettempdir(), 'openlp')
        if not os.path.exists(db_dir):
            return
        song_dbs = []
        song_count = 0
        for sfile in os.listdir(db_dir):
            if sfile.startswith('songs_') and sfile.endswith('.sqlite'):
                self.application.process_events()
                song_dbs.append(os.path.join(db_dir, sfile))
                song_count += SongsPlugin._count_songs(os.path.join(db_dir, sfile))
        if not song_dbs:
            return
        self.application.process_events()
        progress = QtWidgets.QProgressDialog(self.main_window)
        progress.setWindowModality(QtCore.Qt.WindowModal)
        progress.setWindowTitle(translate('OpenLP.Ui', 'Importing Songs'))
        progress.setLabelText(translate('OpenLP.Ui', 'Starting import...'))
        progress.setCancelButton(None)
        progress.setRange(0, song_count)
        progress.setMinimumDuration(0)
        progress.forceShow()
        self.application.process_events()
        for db in song_dbs:
            importer = OpenLPSongImport(self.manager, filename=db)
            importer.do_import(progress)
            self.application.process_events()
        progress.setValue(song_count)
        self.media_item.on_search_text_button_clicked()

    def finalise(self):
        """
        Time to tidy up on exit
        """
        log.info('Songs Finalising')
        self.new_service_created()
        # Clean up files and connections
        self.manager.finalise()
        self.song_import_item.setVisible(False)
        self.song_export_item.setVisible(False)
        action_list = ActionList.get_instance()
        action_list.remove_action(self.song_import_item, UiStrings().Import)
        action_list.remove_action(self.song_export_item, UiStrings().Export)
        action_list.remove_action(self.tools_reindex_item, UiStrings().Tools)
        action_list.remove_action(self.tools_find_duplicates, UiStrings().Tools)
        action_list.add_action(self.tools_report_song_list, UiStrings().Tools)
        self.song_tools_menu.menuAction().setVisible(False)
        super(SongsPlugin, self).finalise()

    def new_service_created(self):
        """
        Remove temporary songs from the database
        """
        songs = self.manager.get_all_objects(Song, Song.temporary is True)
        for song in songs:
            self.manager.delete_object(Song, song.id)

    @staticmethod
    def _count_songs(db_file):
        """
        Provide a count of the songs in the database

        :param db_file: the database name to count
        """
        connection = sqlite3.connect(db_file)
        cursor = connection.cursor()
        cursor.execute('SELECT COUNT(id) AS song_count FROM songs')
        song_count = cursor.fetchone()[0]
        connection.close()
        try:
            song_count = int(song_count)
        except (TypeError, ValueError):
            song_count = 0
        return song_count
Example #29
0
class SongsPlugin(Plugin):
    """
    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')

    def __init__(self):
        """
        Create and set up the Songs plugin.
        """
        super(SongsPlugin, self).__init__('songs', __default_settings__,
                                          SongMediaItem, SongsTab)
        self.manager = Manager('songs', init_schema, upgrade_mod=upgrade)
        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):
        """
        Check the plugin can run.
        """
        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.songselect_form.initialise()
        self.song_import_item.setVisible(True)
        self.song_export_item.setVisible(True)
        self.tools_reindex_item.setVisible(True)
        self.tools_find_duplicates.setVisible(True)
        action_list = ActionList.get_instance()
        action_list.add_action(self.song_import_item, UiStrings().Import)
        action_list.add_action(self.song_export_item, UiStrings().Export)
        action_list.add_action(self.tools_reindex_item, UiStrings().Tools)
        action_list.add_action(self.tools_find_duplicates, UiStrings().Tools)

    def add_import_menu_item(self, import_menu):
        """
        Give the Songs plugin the opportunity to add items to the **Import** menu.

        :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)
        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.

        :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',
            text=translate('SongsPlugin', '&Song'),
            tooltip=translate('SongsPlugin',
                              'Exports songs using the export wizard.'),
            triggers=self.on_song_export_item_clicked)
        export_menu.addAction(self.song_export_item)

    def add_tools_menu_item(self, tools_menu):
        """
        Give the Songs plugin the opportunity to add items to the **Tools** menu.

        :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',
            text=translate('SongsPlugin', '&Re-index Songs'),
            icon=':/plugins/plugin_songs.png',
            statustip=translate(
                'SongsPlugin',
                'Re-index the songs database to improve searching and ordering.'
            ),
            visible=False,
            triggers=self.on_tools_reindex_item_triggered)
        tools_menu.addAction(self.tools_reindex_item)
        self.tools_find_duplicates = create_action(
            tools_menu,
            'toolsFindDuplicates',
            text=translate('SongsPlugin', 'Find &Duplicate Songs'),
            statustip=translate(
                'SongsPlugin',
                'Find and remove duplicate songs in the song database.'),
            visible=False,
            triggers=self.on_tools_find_duplicates_triggered,
            can_shortcuts=True)
        tools_menu.addAction(self.tools_find_duplicates)

    def on_tools_reindex_item_triggered(self):
        """
        Rebuild each song.
        """
        max_songs = self.manager.get_object_count(Song)
        if max_songs == 0:
            return
        progress_dialog = QtGui.QProgressDialog(
            translate('SongsPlugin', 'Reindexing songs...'),
            UiStrings().Cancel, 0, max_songs, self.main_window)
        progress_dialog.setWindowTitle(
            translate('SongsPlugin', 'Reindexing songs'))
        progress_dialog.setWindowModality(QtCore.Qt.WindowModal)
        songs = self.manager.get_all_objects(Song)
        for number, song in enumerate(songs):
            clean_song(self.manager, song)
            progress_dialog.setValue(number + 1)
        self.manager.save_objects(songs)
        self.media_item.on_search_text_button_clicked()

    def on_tools_find_duplicates_triggered(self):
        """
        Search for duplicates in the song database.
        """
        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.'
        )

    def uses_theme(self, theme):
        """
        Called to find out if the song plugin is currently using a theme.

        :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, old_theme, new_theme):
        """
        Renames a theme the song plugin is using making the plugin use the new name.

        :param old_theme: The name of the theme the plugin should stop using.
        :param new_theme: The new name the plugin should now use.
        """
        songs_using_theme = self.manager.get_all_objects(
            Song, Song.theme_name == old_theme)
        for song in songs_using_theme:
            song.theme_name = new_theme
            self.manager.save_object(song)

    def import_songs(self, import_format, **kwargs):
        """
        Add the correct importer class

        :param import_format: The import_format to be used
        :param kwargs: The arguments
        :return: the correct importer
        """
        class_ = SongFormat.get(import_format, 'class')
        importer = class_(self.manager, **kwargs)
        importer.register(self.media_item.import_wizard)
        return importer

    def set_plugin_text_strings(self):
        """
        Called to define all translatable texts of the plugin
        """
        # Name PluginList
        self.text_strings[StringContent.Name] = {
            'singular': translate('SongsPlugin', 'Song', 'name singular'),
            'plural': translate('SongsPlugin', 'Songs', 'name plural')
        }
        # Name for MediaDockManager, SettingsManager
        self.text_strings[StringContent.VisibleName] = {
            'title': translate('SongsPlugin', 'Songs', 'container title')
        }
        # Middle Header Bar
        tooltips = {
            'load':
            '',
            'import':
            '',
            'new':
            translate('SongsPlugin', 'Add a new song.'),
            'edit':
            translate('SongsPlugin', 'Edit the selected song.'),
            'delete':
            translate('SongsPlugin', 'Delete the selected song.'),
            'preview':
            translate('SongsPlugin', 'Preview the selected song.'),
            'live':
            translate('SongsPlugin', 'Send the selected song live.'),
            'service':
            translate('SongsPlugin', 'Add the selected song to the service.')
        }
        self.set_plugin_ui_text_strings(tooltips)

    def first_time(self):
        """
        If the first time wizard has run, this function is run to import all the new songs into the database.
        """
        self.application.process_events()
        self.on_tools_reindex_item_triggered()
        self.application.process_events()
        db_dir = os.path.join(gettempdir(), 'openlp')
        if not os.path.exists(db_dir):
            return
        song_dbs = []
        song_count = 0
        for sfile in os.listdir(db_dir):
            if sfile.startswith('songs_') and sfile.endswith('.sqlite'):
                self.application.process_events()
                song_dbs.append(os.path.join(db_dir, sfile))
                song_count += self._count_songs(os.path.join(db_dir, sfile))
        if not song_dbs:
            return
        self.application.process_events()
        progress = QtGui.QProgressDialog(self.main_window)
        progress.setWindowModality(QtCore.Qt.WindowModal)
        progress.setWindowTitle(translate('OpenLP.Ui', 'Importing Songs'))
        progress.setLabelText(translate('OpenLP.Ui', 'Starting import...'))
        progress.setCancelButton(None)
        progress.setRange(0, song_count)
        progress.setMinimumDuration(0)
        progress.forceShow()
        self.application.process_events()
        for db in song_dbs:
            importer = OpenLPSongImport(self.manager, filename=db)
            importer.do_import(progress)
            self.application.process_events()
        progress.setValue(song_count)
        self.media_item.on_search_text_button_clicked()

    def finalise(self):
        """
        Time to tidy up on exit
        """
        log.info('Songs Finalising')
        self.new_service_created()
        # Clean up files and connections
        self.manager.finalise()
        self.song_import_item.setVisible(False)
        self.song_export_item.setVisible(False)
        self.tools_reindex_item.setVisible(False)
        self.tools_find_duplicates.setVisible(False)
        action_list = ActionList.get_instance()
        action_list.remove_action(self.song_import_item, UiStrings().Import)
        action_list.remove_action(self.song_export_item, UiStrings().Export)
        action_list.remove_action(self.tools_reindex_item, UiStrings().Tools)
        action_list.remove_action(self.tools_find_duplicates,
                                  UiStrings().Tools)
        super(SongsPlugin, self).finalise()

    def new_service_created(self):
        """
        Remove temporary songs from the database
        """
        songs = self.manager.get_all_objects(Song, Song.temporary is True)
        for song in songs:
            self.manager.delete_object(Song, song.id)

    def _count_songs(self, db_file):
        """
        Provide a count of the songs in the database

        :param db_file: the database name to count
        """
        connection = sqlite3.connect(db_file)
        cursor = connection.cursor()
        cursor.execute('SELECT COUNT(id) AS song_count FROM songs')
        song_count = cursor.fetchone()[0]
        connection.close()
        try:
            song_count = int(song_count)
        except (TypeError, ValueError):
            song_count = 0
        return song_count