Exemplo n.º 1
0
 def add_search_to_toolbar(self):
     """
     Creates a search field with button and related signal handling.
     """
     self.search_widget = QtWidgets.QWidget(self)
     self.search_widget.setObjectName('search_widget')
     self.search_layout = QtWidgets.QVBoxLayout(self.search_widget)
     self.search_layout.setObjectName('search_layout')
     self.search_text_layout = QtWidgets.QFormLayout()
     self.search_text_layout.setObjectName('search_text_layout')
     self.search_text_label = QtWidgets.QLabel(self.search_widget)
     self.search_text_label.setObjectName('search_text_label')
     self.search_text_edit = SearchEdit(self.search_widget)
     self.search_text_edit.setObjectName('search_text_edit')
     self.search_text_label.setBuddy(self.search_text_edit)
     self.search_text_layout.addRow(self.search_text_label,
                                    self.search_text_edit)
     self.search_layout.addLayout(self.search_text_layout)
     self.search_button_layout = QtWidgets.QHBoxLayout()
     self.search_button_layout.setObjectName('search_button_layout')
     self.search_button_layout.addStretch()
     self.search_text_button = QtWidgets.QPushButton(self.search_widget)
     self.search_text_button.setObjectName('search_text_button')
     self.search_button_layout.addWidget(self.search_text_button)
     self.search_layout.addLayout(self.search_button_layout)
     self.page_layout.addWidget(self.search_widget)
     # Signals and slots
     self.search_text_edit.returnPressed.connect(
         self.on_search_text_button_clicked)
     self.search_text_button.clicked.connect(
         self.on_search_text_button_clicked)
     self.search_text_edit.textChanged.connect(
         self.on_search_text_edit_changed)
Exemplo n.º 2
0
    def setUp(self):
        """
        Create the UI
        """
        Registry.create()
        self.setup_application()
        self.main_window = QtGui.QMainWindow()
        Registry().register('main_window', self.main_window)

        self.search_edit = SearchEdit(self.main_window)
        # To complete set up we have to set the search types.
        self.search_edit.set_search_types(SEARCH_TYPES)
Exemplo n.º 3
0
 def add_search_to_toolbar(self):
     """
     Creates a search field with button and related signal handling.
     """
     self.search_widget = QtWidgets.QWidget(self)
     self.search_widget.setObjectName('search_widget')
     self.search_layout = QtWidgets.QVBoxLayout(self.search_widget)
     self.search_layout.setObjectName('search_layout')
     self.search_text_layout = QtWidgets.QFormLayout()
     self.search_text_layout.setObjectName('search_text_layout')
     self.search_text_label = QtWidgets.QLabel(self.search_widget)
     self.search_text_label.setObjectName('search_text_label')
     self.search_text_edit = SearchEdit(self.search_widget, self.settings_section)
     self.search_text_edit.setObjectName('search_text_edit')
     self.search_text_label.setBuddy(self.search_text_edit)
     self.search_text_layout.addRow(self.search_text_label, self.search_text_edit)
     self.search_layout.addLayout(self.search_text_layout)
     self.search_button_layout = QtWidgets.QHBoxLayout()
     self.search_button_layout.setObjectName('search_button_layout')
     self.search_button_layout.addStretch()
     self.search_text_button = QtWidgets.QPushButton(self.search_widget)
     self.search_text_button.setObjectName('search_text_button')
     self.search_button_layout.addWidget(self.search_text_button)
     self.search_layout.addLayout(self.search_button_layout)
     self.page_layout.addWidget(self.search_widget)
     # Signals and slots
     self.search_text_edit.returnPressed.connect(self.on_search_text_button_clicked)
     self.search_text_button.clicked.connect(self.on_search_text_button_clicked)
     self.search_text_edit.textChanged.connect(self.on_search_text_edit_changed)
Exemplo n.º 4
0
 def addSearchToToolBar(self):
     """
     Creates a search field with button and related signal handling.
     """
     self.searchWidget = QtGui.QWidget(self)
     self.searchWidget.setObjectName(u'searchWidget')
     self.searchLayout = QtGui.QVBoxLayout(self.searchWidget)
     self.searchLayout.setObjectName(u'searchLayout')
     self.searchTextLayout = QtGui.QFormLayout()
     self.searchTextLayout.setObjectName(u'searchTextLayout')
     self.searchTextLabel = QtGui.QLabel(self.searchWidget)
     self.searchTextLabel.setObjectName(u'searchTextLabel')
     self.searchTextEdit = SearchEdit(self.searchWidget)
     self.searchTextEdit.setObjectName(u'searchTextEdit')
     self.searchTextLabel.setBuddy(self.searchTextEdit)
     self.searchTextLayout.addRow(self.searchTextLabel, self.searchTextEdit)
     self.searchLayout.addLayout(self.searchTextLayout)
     self.searchButtonLayout = QtGui.QHBoxLayout()
     self.searchButtonLayout.setObjectName(u'searchButtonLayout')
     self.searchButtonLayout.addStretch()
     self.searchTextButton = QtGui.QPushButton(self.searchWidget)
     self.searchTextButton.setObjectName(u'searchTextButton')
     self.searchButtonLayout.addWidget(self.searchTextButton)
     self.searchLayout.addLayout(self.searchButtonLayout)
     self.pageLayout.addWidget(self.searchWidget)
     # Signals and slots
     QtCore.QObject.connect(self.searchTextEdit, QtCore.SIGNAL(u'returnPressed()'), self.onSearchTextButtonClicked)
     QtCore.QObject.connect(self.searchTextButton, QtCore.SIGNAL(u'clicked()'), self.onSearchTextButtonClicked)
     QtCore.QObject.connect(self.searchTextEdit, QtCore.SIGNAL(u'textChanged(const QString&)'),
         self.onSearchTextEditChanged)
Exemplo n.º 5
0
    def setUp(self):
        """
        Create the UI
        """
        Registry.create()
        self.setup_application()
        self.main_window = QtWidgets.QMainWindow()
        Registry().register('main_window', self.main_window)

        settings_patcher = patch(
            'openlp.core.lib.searchedit.Settings', return_value=MagicMock(**{'value.return_value': SearchTypes.First}))
        self.addCleanup(settings_patcher.stop)
        self.mocked_settings = settings_patcher.start()

        self.search_edit = SearchEdit(self.main_window, 'settings_section')
        # To complete set up we have to set the search types.
        self.search_edit.set_search_types(SEARCH_TYPES)
Exemplo n.º 6
0
class MediaManagerItem(QtWidgets.QWidget, RegistryProperties):
    """
    MediaManagerItem is a helper widget for plugins.

    None of the following *need* to be used, feel free to override them completely in your plugin's implementation.
    Alternatively, call them from your plugin before or after you've done extra things that you need to.

    **Constructor Parameters**

    ``parent``
        The parent widget. Usually this will be the *Media Manager* itself. This needs to be a class descended from
        ``QWidget``.

    ``plugin``
        The plugin widget. Usually this will be the *Plugin* itself. This needs to be a class descended from ``Plugin``.

    **Member Variables**

    When creating a descendant class from this class for your plugin, the following member variables should be set.

     ``self.on_new_prompt``

        Defaults to *'Select Image(s)'*.

     ``self.on_new_file_masks``
        Defaults to *'Images (*.jpg *jpeg *.gif *.png *.bmp)'*. This assumes that the new action is to load a file. If
        not, you need to override the ``OnNew`` method.

     ``self.PreviewFunction``
        This must be a method which returns a QImage to represent the item (usually a preview). No scaling is required,
        that is performed automatically by OpenLP when necessary. If this method is not defined, a default will be used
        (treat the filename as an image).
    """
    log.info('Media Item loaded')

    def __init__(self, parent=None, plugin=None):
        """
        Constructor to create the media manager item.
        """
        super(MediaManagerItem, self).__init__(parent)
        self.plugin = plugin
        self._setup()
        self.setup_item()

    def _setup(self):
        """
        Run some initial setup. This method is separate from __init__ in order to mock it out in tests.
        """
        self.hide()
        self.whitespace = re.compile(r'[\W_]+', re.UNICODE)
        visible_title = self.plugin.get_string(StringContent.VisibleName)
        self.title = str(visible_title['title'])
        Registry().register(self.plugin.name, self)
        self.settings_section = self.plugin.name
        self.toolbar = None
        self.remote_triggered = None
        self.single_service_item = True
        self.quick_preview_allowed = False
        self.has_search = False
        self.page_layout = QtWidgets.QVBoxLayout(self)
        self.page_layout.setSpacing(0)
        self.page_layout.setContentsMargins(0, 0, 0, 0)
        self.required_icons()
        self.setupUi()
        self.retranslateUi()
        self.auto_select_id = -1

    def setup_item(self):
        """
        Override this for additional Plugin setup
        """
        pass

    def required_icons(self):
        """
        This method is called to define the icons for the plugin. It provides a default set and the plugin is able to
        override the if required.
        """
        self.has_import_icon = False
        self.has_new_icon = True
        self.has_edit_icon = True
        self.has_file_icon = False
        self.has_delete_icon = True
        self.add_to_service_item = False

    def retranslateUi(self):
        """
        This method is called automatically to provide OpenLP with the opportunity to translate the ``MediaManagerItem``
        to another language.
        """
        pass

    def add_toolbar(self):
        """
        A method to help developers easily add a toolbar to the media manager item.
        """
        if self.toolbar is None:
            self.toolbar = OpenLPToolbar(self)
            self.page_layout.addWidget(self.toolbar)

    def setupUi(self):
        """
        This method sets up the interface on the button. Plugin developers use this to add and create toolbars, and the
        rest of the interface of the media manager item.
        """
        # Add a toolbar
        self.add_toolbar()
        # Allow the plugin to define buttons at start of bar
        self.add_start_header_bar()
        # Add the middle of the tool bar (pre defined)
        self.add_middle_header_bar()
        # Allow the plugin to define buttons at end of bar
        self.add_end_header_bar()
        # Add the list view
        self.add_list_view_to_toolbar()

    def add_middle_header_bar(self):
        """
        Create buttons for the media item toolbar
        """
        toolbar_actions = []
        # Import Button
        if self.has_import_icon:
            toolbar_actions.append([
                'Import', StringContent.Import, ':/general/general_import.png',
                self.on_import_click
            ])
        # Load Button
        if self.has_file_icon:
            toolbar_actions.append([
                'Load', StringContent.Load, ':/general/general_open.png',
                self.on_file_click
            ])
        # New Button
        if self.has_new_icon:
            toolbar_actions.append([
                'New', StringContent.New, ':/general/general_new.png',
                self.on_new_click
            ])
        # Edit Button
        if self.has_edit_icon:
            toolbar_actions.append([
                'Edit', StringContent.Edit, ':/general/general_edit.png',
                self.on_edit_click
            ])
        # Delete Button
        if self.has_delete_icon:
            toolbar_actions.append([
                'Delete', StringContent.Delete, ':/general/general_delete.png',
                self.on_delete_click
            ])
        # Preview
        toolbar_actions.append([
            'Preview', StringContent.Preview, ':/general/general_preview.png',
            self.on_preview_click
        ])
        # Live Button
        toolbar_actions.append([
            'Live', StringContent.Live, ':/general/general_live.png',
            self.on_live_click
        ])
        # Add to service Button
        toolbar_actions.append([
            'Service', StringContent.Service, ':/general/general_add.png',
            self.on_add_click
        ])
        for action in toolbar_actions:
            if action[0] == StringContent.Preview:
                self.toolbar.addSeparator()
            self.toolbar.add_toolbar_action(
                '%s%sAction' % (self.plugin.name, action[0]),
                text=self.plugin.get_string(action[1])['title'],
                icon=action[2],
                tooltip=self.plugin.get_string(action[1])['tooltip'],
                triggers=action[3])

    def add_list_view_to_toolbar(self):
        """
        Creates the main widget for listing items the media item is tracking
        """
        # Add the List widget
        self.list_view = ListWidgetWithDnD(self, self.plugin.name)
        self.list_view.setSpacing(1)
        self.list_view.setSelectionMode(
            QtWidgets.QAbstractItemView.ExtendedSelection)
        self.list_view.setAlternatingRowColors(True)
        self.list_view.setObjectName('%sListView' % self.plugin.name)
        # Add to page_layout
        self.page_layout.addWidget(self.list_view)
        # define and add the context menu
        self.list_view.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        if self.has_edit_icon:
            create_widget_action(self.list_view,
                                 text=self.plugin.get_string(
                                     StringContent.Edit)['title'],
                                 icon=':/general/general_edit.png',
                                 triggers=self.on_edit_click)
            create_widget_action(self.list_view, separator=True)
        create_widget_action(
            self.list_view,
            'listView%s%sItem' %
            (self.plugin.name.title(), StringContent.Preview.title()),
            text=self.plugin.get_string(StringContent.Preview)['title'],
            icon=':/general/general_preview.png',
            can_shortcuts=True,
            triggers=self.on_preview_click)
        create_widget_action(
            self.list_view,
            'listView%s%sItem' %
            (self.plugin.name.title(), StringContent.Live.title()),
            text=self.plugin.get_string(StringContent.Live)['title'],
            icon=':/general/general_live.png',
            can_shortcuts=True,
            triggers=self.on_live_click)
        create_widget_action(
            self.list_view,
            'listView%s%sItem' %
            (self.plugin.name.title(), StringContent.Service.title()),
            can_shortcuts=True,
            text=self.plugin.get_string(StringContent.Service)['title'],
            icon=':/general/general_add.png',
            triggers=self.on_add_click)
        if self.has_delete_icon:
            create_widget_action(self.list_view, separator=True)
            create_widget_action(
                self.list_view,
                'listView%s%sItem' %
                (self.plugin.name.title(), StringContent.Delete.title()),
                text=self.plugin.get_string(StringContent.Delete)['title'],
                icon=':/general/general_delete.png',
                can_shortcuts=True,
                triggers=self.on_delete_click)
        if self.add_to_service_item:
            create_widget_action(self.list_view, separator=True)
            create_widget_action(self.list_view,
                                 text=translate(
                                     'OpenLP.MediaManagerItem',
                                     '&Add to selected Service Item'),
                                 icon=':/general/general_add.png',
                                 triggers=self.on_add_edit_click)
        self.add_custom_context_actions()
        # Create the context menu and add all actions from the list_view.
        self.menu = QtWidgets.QMenu()
        self.menu.addActions(self.list_view.actions())
        self.list_view.doubleClicked.connect(self.on_double_clicked)
        self.list_view.itemSelectionChanged.connect(self.on_selection_change)
        self.list_view.customContextMenuRequested.connect(self.context_menu)

    def add_search_to_toolbar(self):
        """
        Creates a search field with button and related signal handling.
        """
        self.search_widget = QtWidgets.QWidget(self)
        self.search_widget.setObjectName('search_widget')
        self.search_layout = QtWidgets.QVBoxLayout(self.search_widget)
        self.search_layout.setObjectName('search_layout')
        self.search_text_layout = QtWidgets.QFormLayout()
        self.search_text_layout.setObjectName('search_text_layout')
        self.search_text_label = QtWidgets.QLabel(self.search_widget)
        self.search_text_label.setObjectName('search_text_label')
        self.search_text_edit = SearchEdit(self.search_widget)
        self.search_text_edit.setObjectName('search_text_edit')
        self.search_text_label.setBuddy(self.search_text_edit)
        self.search_text_layout.addRow(self.search_text_label,
                                       self.search_text_edit)
        self.search_layout.addLayout(self.search_text_layout)
        self.search_button_layout = QtWidgets.QHBoxLayout()
        self.search_button_layout.setObjectName('search_button_layout')
        self.search_button_layout.addStretch()
        self.search_text_button = QtWidgets.QPushButton(self.search_widget)
        self.search_text_button.setObjectName('search_text_button')
        self.search_button_layout.addWidget(self.search_text_button)
        self.search_layout.addLayout(self.search_button_layout)
        self.page_layout.addWidget(self.search_widget)
        # Signals and slots
        self.search_text_edit.returnPressed.connect(
            self.on_search_text_button_clicked)
        self.search_text_button.clicked.connect(
            self.on_search_text_button_clicked)
        self.search_text_edit.textChanged.connect(
            self.on_search_text_edit_changed)

    def add_custom_context_actions(self):
        """
        Implement this method in your descendant media manager item to add any context menu items.
        This method is called automatically.
        """
        pass

    def initialise(self):
        """
        Implement this method in your descendant media manager item to do any UI or other initialisation.
        This method is called automatically.
        """
        pass

    def add_start_header_bar(self):
        """
        Slot at start of toolbar for plugin to add widgets
        """
        pass

    def add_end_header_bar(self):
        """
        Slot at end of toolbar for plugin to add widgets
        """
        pass

    def on_file_click(self):
        """
        Add a file to the list widget to make it available for showing
        """
        files = FileDialog.getOpenFileNames(
            self, self.on_new_prompt,
            Settings().value(self.settings_section + '/last directory'),
            self.on_new_file_masks)
        log.info('New files(s) %s' % files)
        if files:
            self.application.set_busy_cursor()
            self.validate_and_load(files)
        self.application.set_normal_cursor()

    def load_file(self, data):
        """
        Turn file from Drag and Drop into an array so the Validate code can run it.

        :param data: A dictionary containing the list of files to be loaded and the target
        """
        new_files = []
        error_shown = False
        for file_name in data['files']:
            file_type = file_name.split('.')[-1]
            if file_type.lower() not in self.on_new_file_masks:
                if not error_shown:
                    critical_error_message_box(
                        translate('OpenLP.MediaManagerItem',
                                  'Invalid File Type'),
                        translate('OpenLP.MediaManagerItem',
                                  'Invalid File %s.\nSuffix not supported') %
                        file_name)
                    error_shown = True
            else:
                new_files.append(file_name)
        if new_files:
            self.validate_and_load(new_files, data['target'])

    def dnd_move_internal(self, target):
        """
        Handle internal moving of media manager items

        :param target: The target of the DnD action
        """
        pass

    def validate_and_load(self, files, target_group=None):
        """
        Process a list for files either from the File Dialog or from Drag and
        Drop

        :param files: The files to be loaded.
        :param target_group: The QTreeWidgetItem of the group that will be the parent of the added files
        """
        names = []
        full_list = []
        for count in range(self.list_view.count()):
            names.append(self.list_view.item(count).text())
            full_list.append(
                self.list_view.item(count).data(QtCore.Qt.UserRole))
        duplicates_found = False
        files_added = False
        for file_path in files:
            if file_path in full_list:
                duplicates_found = True
            else:
                files_added = True
                full_list.append(file_path)
        if full_list and files_added:
            if target_group is None:
                self.list_view.clear()
            self.load_list(full_list, target_group)
            last_dir = os.path.split(files[0])[0]
            Settings().setValue(self.settings_section + '/last directory',
                                last_dir)
            Settings().setValue(
                '%s/%s files' % (self.settings_section, self.settings_section),
                self.get_file_list())
        if duplicates_found:
            critical_error_message_box(
                UiStrings().Duplicate,
                translate(
                    'OpenLP.MediaManagerItem',
                    'Duplicate files were found on import and were ignored.'))

    def context_menu(self, point):
        """
        Display a context menu

        :param point: The point the cursor was at
        """
        item = self.list_view.itemAt(point)
        # Decide if we have to show the context menu or not.
        if item is None:
            return
        if not item.flags() & QtCore.Qt.ItemIsSelectable:
            return
        self.menu.exec(self.list_view.mapToGlobal(point))

    def get_file_list(self):
        """
        Return the current list of files
        """
        file_list = []
        for index in range(self.list_view.count()):
            list_item = self.list_view.item(index)
            filename = list_item.data(QtCore.Qt.UserRole)
            file_list.append(filename)
        return file_list

    def load_list(self, load_list, target_group):
        """
        Load a list. Needs to be implemented by the plugin.

        :param load_list: List object to load
        :param target_group: Group to load
        """
        raise NotImplementedError(
            'MediaManagerItem.loadList needs to be defined by the plugin')

    def on_new_click(self):
        """
        Hook for plugins to define behaviour for adding new items.
        """
        pass

    def on_edit_click(self):
        """
        Hook for plugins to define behaviour for editing items.
        """
        pass

    def on_delete_click(self):
        """
        Delete an item. Needs to be implemented by the plugin.
        """
        raise NotImplementedError(
            'MediaManagerItem.on_delete_click needs to be defined by the plugin'
        )

    def on_focus(self):
        """
        Run when a tab in the media manager gains focus. This gives the media
        item a chance to focus any elements it wants to.
        """
        pass

    def generate_slide_data(self,
                            service_item,
                            item=None,
                            xml_version=False,
                            remote=False,
                            context=ServiceItemContext.Live):
        """
        Generate the slide data. Needs to be implemented by the plugin.
        :param service_item: The service Item to be processed
        :param item: The database item to be used to build the service item
        :param xml_version:
        :param remote: Was this remote triggered (False)
        :param context: The service context
        """
        raise NotImplementedError(
            'MediaManagerItem.generate_slide_data needs to be defined by the plugin'
        )

    def on_double_clicked(self):
        """
        Allows the list click action to be determined dynamically
        """
        if Settings().value('advanced/double click live'):
            self.on_live_click()
        elif not Settings().value('advanced/single click preview'):
            # NOTE: The above check is necessary to prevent bug #1419300
            self.on_preview_click()

    def on_selection_change(self):
        """
        Allows the change of current item in the list to be actioned
        """
        if Settings().value('advanced/single click preview') and self.quick_preview_allowed \
                and self.list_view.selectedIndexes() and self.auto_select_id == -1:
            self.on_preview_click(True)

    def on_preview_click(self, keep_focus=False):
        """
        Preview an item by building a service item then adding that service item to the preview slide controller.

        :param keep_focus: Do we keep focus (False)
        """
        if not self.list_view.selectedIndexes() and not self.remote_triggered:
            QtWidgets.QMessageBox.information(
                self,
                UiStrings().NISp,
                translate('OpenLP.MediaManagerItem',
                          'You must select one or more items to preview.'))
        else:
            log.debug('%s Preview requested' % self.plugin.name)
            service_item = self.build_service_item()
            if service_item:
                service_item.from_plugin = True
                self.preview_controller.add_service_item(service_item)
                if not keep_focus:
                    self.preview_controller.preview_widget.setFocus()

    def on_live_click(self):
        """
        Send an item live by building a service item then adding that service item to the live slide controller.
        """
        if not self.list_view.selectedIndexes():
            QtWidgets.QMessageBox.information(
                self,
                UiStrings().NISp,
                translate('OpenLP.MediaManagerItem',
                          'You must select one or more items to send live.'))
        else:
            self.go_live()

    def go_live_remote(self, message):
        """
        Remote Call wrapper

        :param message: The passed data item_id:Remote.
        """
        self.go_live(message[0], remote=message[1])

    def go_live(self, item_id=None, remote=False):
        """
        Make the currently selected item go live.

        :param item_id: item to make live
        :param remote: From Remote
        """
        log.debug('%s Live requested', self.plugin.name)
        item = None
        if item_id:
            item = self.create_item_from_id(item_id)
        service_item = self.build_service_item(item, remote=remote)
        if service_item:
            if not item_id:
                service_item.from_plugin = True
            if remote:
                service_item.will_auto_start = True
            self.live_controller.add_service_item(service_item)
            self.live_controller.preview_widget.setFocus()

    def create_item_from_id(self, item_id):
        """
        Create a media item from an item id.

        :param item_id: Id to make live
        """
        item = QtWidgets.QListWidgetItem()
        item.setData(QtCore.Qt.UserRole, item_id)
        return item

    def on_add_click(self):
        """
        Add a selected item to the current service
        """
        if not self.list_view.selectedIndexes():
            QtWidgets.QMessageBox.information(
                self,
                UiStrings().NISp,
                translate('OpenLP.MediaManagerItem',
                          'You must select one or more items to add.'))
        else:
            # Is it possible to process multiple list items to generate
            # multiple service items?
            if self.single_service_item:
                log.debug('%s Add requested', self.plugin.name)
                self.add_to_service(replace=self.remote_triggered)
            else:
                items = self.list_view.selectedIndexes()
                drop_position = self.service_manager.get_drop_position()
                for item in items:
                    self.add_to_service(item, position=drop_position)
                    if drop_position != -1:
                        drop_position += 1

    def add_to_service_remote(self, message):
        """
        Remote Call wrapper

        :param message: The passed data item:Remote.
        """
        self.add_to_service(message[0], remote=message[1])

    def add_to_service(self,
                       item=None,
                       replace=None,
                       remote=False,
                       position=-1):
        """
        Add this item to the current service.

        :param item: Item to be processed
        :param replace: Replace the existing item
        :param remote: Triggered from remote
        :param position: Position to place item
        """
        service_item = self.build_service_item(
            item, True, remote=remote, context=ServiceItemContext.Service)
        if service_item:
            service_item.from_plugin = False
            self.service_manager.add_service_item(service_item,
                                                  replace=replace,
                                                  position=position)

    def on_add_edit_click(self):
        """
        Add a selected item to an existing item in the current service.
        """
        if not self.list_view.selectedIndexes() and not self.remote_triggered:
            QtWidgets.QMessageBox.information(
                self,
                UiStrings().NISp,
                translate('OpenLP.MediaManagerItem',
                          'You must select one or more items.'))
        else:
            log.debug('%s Add requested', self.plugin.name)
            service_item = self.service_manager.get_service_item()
            if not service_item:
                QtWidgets.QMessageBox.information(
                    self,
                    UiStrings().NISs,
                    translate(
                        'OpenLP.MediaManagerItem',
                        'You must select an existing service item to add to.'))
            elif self.plugin.name == service_item.name:
                self.generate_slide_data(service_item)
                self.service_manager.add_service_item(service_item,
                                                      replace=True)
            else:
                # Turn off the remote edit update message indicator
                QtWidgets.QMessageBox.information(
                    self,
                    translate('OpenLP.MediaManagerItem',
                              'Invalid Service Item'),
                    translate('OpenLP.MediaManagerItem',
                              'You must select a %s service item.') %
                    self.title)

    def build_service_item(self,
                           item=None,
                           xml_version=False,
                           remote=False,
                           context=ServiceItemContext.Live):
        """
        Common method for generating a service item
        :param item: Service Item to be built.
        :param xml_version: version of XML (False)
        :param remote: Remote triggered (False)
        :param context: The context on which this is called
        """
        service_item = ServiceItem(self.plugin)
        service_item.add_icon(self.plugin.icon_path)
        if self.generate_slide_data(service_item, item, xml_version, remote,
                                    context):
            return service_item
        else:
            return None

    def service_load(self, item):
        """
        Method to add processing when a service has been loaded and individual service items need to be processed by the
        plugins.

        :param item: The item to be processed and returned.
        """
        return item

    def check_search_result(self):
        """
        Checks if the list_view is empty and adds a "No Search Results" item.
        """
        if self.list_view.count():
            return
        message = translate('OpenLP.MediaManagerItem', 'No Search Results')
        item = QtWidgets.QListWidgetItem(message)
        item.setFlags(QtCore.Qt.NoItemFlags)
        font = QtGui.QFont()
        font.setItalic(True)
        item.setFont(font)
        self.list_view.addItem(item)

    def _get_id_of_item_to_generate(self, item, remote_item):
        """
        Utility method to check items being submitted for slide generation.

        :param item: The item to check.
        :param remote_item: The id to assign if the slide generation was remotely triggered.
        """
        if item is None:
            if self.remote_triggered is None:
                item = self.list_view.currentItem()
                if item is None:
                    return False
                item_id = item.data(QtCore.Qt.UserRole)
            else:
                item_id = remote_item
        else:
            item_id = item.data(QtCore.Qt.UserRole)
        return item_id

    def save_auto_select_id(self):
        """
        Sorts out, what item to select after loading a list.
        """
        # The item to select has not been set.
        if self.auto_select_id == -1:
            item = self.list_view.currentItem()
            if item:
                self.auto_select_id = item.data(QtCore.Qt.UserRole)

    def search(self, string, show_error=True):
        """
        Performs a plugin specific search for items containing ``string``

        :param string: String to be displayed
        :param show_error: Should the error be shown (True)
        """
        raise NotImplementedError(
            'Plugin.search needs to be defined by the plugin')
Exemplo n.º 7
0
class TestSearchEdit(TestCase, TestMixin):
    """
    Test the EditCustomForm.
    """
    def setUp(self):
        """
        Create the UI
        """
        Registry.create()
        self.setup_application()
        self.main_window = QtGui.QMainWindow()
        Registry().register('main_window', self.main_window)

        self.search_edit = SearchEdit(self.main_window)
        # To complete set up we have to set the search types.
        self.search_edit.set_search_types(SEARCH_TYPES)

    def tearDown(self):
        """
        Delete all the C++ objects at the end so that we don't have a segfault
        """
        del self.search_edit
        del self.main_window

    def set_search_types_test(self):
        """
        Test setting the search types of the search edit.
        """
        # GIVEN: The search edit with the search types set. NOTE: The set_search_types(types) is called in the setUp()
        # method!

        # WHEN:

        # THEN: The first search type should be the first one in the list.
        assert self.search_edit.current_search_type() == SearchTypes.First, "The first search type should be selected."

    def set_current_search_type_test(self):
        """
        Test if changing the search type works.
        """
        # GIVEN:
        # WHEN: Change the search type
        result = self.search_edit.set_current_search_type(SearchTypes.Second)

        # THEN:
        assert result, "The call should return success (True)."
        assert self.search_edit.current_search_type() == SearchTypes.Second,\
            "The search type should be SearchTypes.Second"
        assert self.search_edit.placeholderText() == SECOND_PLACEHOLDER_TEXT,\
            "The correct placeholder text should be 'Second Placeholder Text'."

    def clear_button_visibility_test(self):
        """
        Test if the clear button is hidden/shown correctly.
        """
        # GIVEN: Everything is left to its defaults (hidden).
        assert self.search_edit.clear_button.isHidden(), "Pre condition not met. Button should be hidden."

        # WHEN: Type something in the search edit.
        QtTest.QTest.keyPress(self.search_edit, QtCore.Qt.Key_A)
        QtTest.QTest.keyRelease(self.search_edit, QtCore.Qt.Key_A)

        # THEN: The clear button should not be hidden any more.
        assert not self.search_edit.clear_button.isHidden(), "The clear button should be visible."

    def press_clear_button_test(self):
        """
        Check if the search edit behaves correctly when pressing the clear button.
        """
        # GIVEN: A search edit with text.
        QtTest.QTest.keyPress(self.search_edit, QtCore.Qt.Key_A)
        QtTest.QTest.keyRelease(self.search_edit, QtCore.Qt.Key_A)

        # WHEN: Press the clear button.
        QtTest.QTest.mouseClick(self.search_edit.clear_button, QtCore.Qt.LeftButton)

        # THEN: The search edit text should be cleared and the button be hidden.
        assert not self.search_edit.text(), "The search edit should not have any text."
        assert self.search_edit.clear_button.isHidden(), "The clear button should be hidden."
Exemplo n.º 8
0
class BibleMediaItem(MediaManagerItem):
    """
    This is the custom media manager item for Bibles.
    """
    bibles_go_live = QtCore.pyqtSignal(list)
    bibles_add_to_service = QtCore.pyqtSignal(list)
    log.info('Bible Media Item loaded')

    def __init__(self, *args, **kwargs):
        """
        Constructor

        :param args: Positional arguments to pass to the super method. (tuple)
        :param kwargs: Keyword arguments to pass to the super method. (dict)
        """
        self.clear_icon = build_icon(':/bibles/bibles_search_clear.png')
        self.lock_icon = build_icon(':/bibles/bibles_search_lock.png')
        self.unlock_icon = build_icon(':/bibles/bibles_search_unlock.png')
        self.sort_icon = build_icon(':/bibles/bibles_book_sort.png')
        self.bible = None
        self.second_bible = None
        # TODO: Make more central and clean up after!
        self.search_timer = QtCore.QTimer()
        self.search_timer.setInterval(200)
        self.search_timer.setSingleShot(True)
        self.search_timer.timeout.connect(self.on_search_timer_timeout)
        super().__init__(*args, **kwargs)

    def setup_item(self):
        """
        Do some additional setup.

        :return: None
        """
        self.bibles_go_live.connect(self.go_live_remote)
        self.bibles_add_to_service.connect(self.add_to_service_remote)
        # Place to store the search results for both bibles.
        self.settings = self.plugin.settings_tab
        self.quick_preview_allowed = True
        self.has_search = True
        self.search_results = []
        self.second_search_results = []
        Registry().register_function('bibles_load_list', self.reload_bibles)

    def required_icons(self):
        """
        Set which icons the media manager tab should show

        :return: None
        """
        super().required_icons()
        self.has_import_icon = True
        self.has_new_icon = False
        self.has_edit_icon = True
        self.has_delete_icon = True
        self.add_to_service_item = False

    def add_end_header_bar(self):
        self.search_tab_bar = QtWidgets.QTabBar(self)
        self.search_tab_bar.setExpanding(False)
        self.page_layout.addWidget(self.search_tab_bar)
        # Add the Search tab.
        self.search_tab = QtWidgets.QWidget()
        self.search_tab.setSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum)
        self.search_tab_bar.addTab(translate('BiblesPlugin.MediaItem', 'Find'))
        self.search_layout = QtWidgets.QFormLayout(self.search_tab)
        self.search_edit = SearchEdit(self.search_tab, self.settings_section)
        self.search_layout.addRow(translate('BiblesPlugin.MediaItem', 'Find:'), self.search_edit)
        self.search_tab.setVisible(True)
        self.page_layout.addWidget(self.search_tab)
        # Add the Select tab.
        self.select_tab = QtWidgets.QWidget()
        self.select_tab.setSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum)
        self.search_tab_bar.addTab(translate('BiblesPlugin.MediaItem', 'Select'))
        self.select_layout = QtWidgets.QFormLayout(self.select_tab)
        self.book_layout = QtWidgets.QHBoxLayout()
        self.select_book_combo_box = create_horizontal_adjusting_combo_box(self.select_tab, 'select_book_combo_box')
        self.book_layout.addWidget(self.select_book_combo_box)
        self.book_order_button = QtWidgets.QToolButton()
        self.book_order_button.setIcon(self.sort_icon)
        self.book_order_button.setCheckable(True)
        self.book_order_button.setToolTip(translate('BiblesPlugin.MediaItem', 'Sort bible books alphabetically.'))
        self.book_layout.addWidget(self.book_order_button)
        self.select_layout.addRow(translate('BiblesPlugin.MediaItem', 'Book:'), self.book_layout)
        self.verse_title_layout = QtWidgets.QHBoxLayout()
        self.chapter_label = QtWidgets.QLabel(self.select_tab)
        self.verse_title_layout.addWidget(self.chapter_label)
        self.verse_label = QtWidgets.QLabel(self.select_tab)
        self.verse_title_layout.addWidget(self.verse_label)
        self.select_layout.addRow('', self.verse_title_layout)
        self.from_layout = QtWidgets.QHBoxLayout()
        self.from_chapter = QtWidgets.QComboBox(self.select_tab)
        self.from_layout.addWidget(self.from_chapter)
        self.from_verse = QtWidgets.QComboBox(self.select_tab)
        self.from_layout.addWidget(self.from_verse)
        self.select_layout.addRow(translate('BiblesPlugin.MediaItem', 'From:'), self.from_layout)
        self.to_layout = QtWidgets.QHBoxLayout()
        self.to_chapter = QtWidgets.QComboBox(self.select_tab)
        self.to_layout.addWidget(self.to_chapter)
        self.to_verse = QtWidgets.QComboBox(self.select_tab)
        self.to_layout.addWidget(self.to_verse)
        self.select_layout.addRow(translate('BiblesPlugin.MediaItem', 'To:'), self.to_layout)
        self.select_tab.setVisible(False)
        self.page_layout.addWidget(self.select_tab)
        # General Search Opions
        self.options_widget = QtWidgets.QGroupBox(translate('BiblesPlugin.MediaItem', 'Options'), self)
        self.general_bible_layout = QtWidgets.QFormLayout(self.options_widget)
        self.version_combo_box = create_horizontal_adjusting_combo_box(self, 'version_combo_box')
        self.general_bible_layout.addRow('{version}:'.format(version=UiStrings().Version), self.version_combo_box)
        self.second_combo_box = create_horizontal_adjusting_combo_box(self, 'second_combo_box')
        self.general_bible_layout.addRow(translate('BiblesPlugin.MediaItem', 'Second:'), self.second_combo_box)
        self.style_combo_box = create_horizontal_adjusting_combo_box(self, 'style_combo_box')
        self.style_combo_box.addItems(['', '', ''])
        self.general_bible_layout.addRow(UiStrings().LayoutStyle, self.style_combo_box)
        self.search_button_layout = QtWidgets.QHBoxLayout()
        self.search_button_layout.addStretch()
        # Note: If we use QPushButton instead of the QToolButton, the icon will be larger than the Lock icon.
        self.clear_button = QtWidgets.QToolButton(self)
        self.clear_button.setIcon(self.clear_icon)
        self.lock_button = QtWidgets.QToolButton(self)
        self.lock_button.setIcon(self.unlock_icon)
        self.lock_button.setCheckable(True)
        self.search_button_layout.addWidget(self.clear_button)
        self.search_button_layout.addWidget(self.lock_button)
        self.search_button = QtWidgets.QPushButton(self)
        self.search_button_layout.addWidget(self.search_button)
        self.general_bible_layout.addRow(self.search_button_layout)
        self.page_layout.addWidget(self.options_widget)

    def setupUi(self):
        super().setupUi()
        sort_model = QtCore.QSortFilterProxyModel(self.select_book_combo_box)
        model = self.select_book_combo_box.model()
        # Reparent the combo box model to the sort proxy, otherwise it will be deleted when we change the comobox's
        # model
        model.setParent(sort_model)
        sort_model.setSourceModel(model)
        self.select_book_combo_box.setModel(sort_model)

        # Signals & Slots
        # Combo Boxes
        self.select_book_combo_box.activated.connect(self.on_advanced_book_combo_box)
        self.from_chapter.activated.connect(self.on_from_chapter_activated)
        self.from_verse.activated.connect(self.on_from_verse)
        self.to_chapter.activated.connect(self.on_to_chapter)
        self.version_combo_box.currentIndexChanged.connect(self.on_version_combo_box_index_changed)
        self.version_combo_box.currentIndexChanged.connect(self.update_auto_completer)
        self.second_combo_box.currentIndexChanged.connect(self.on_second_combo_box_index_changed)
        self.second_combo_box.currentIndexChanged.connect(self.update_auto_completer)
        self.style_combo_box.currentIndexChanged.connect(self.on_style_combo_box_index_changed)
        self.search_edit.searchTypeChanged.connect(self.update_auto_completer)
        # Buttons
        self.book_order_button.toggled.connect(self.on_book_order_button_toggled)
        self.clear_button.clicked.connect(self.on_clear_button_clicked)
        self.lock_button.toggled.connect(self.on_lock_button_toggled)
        self.search_button.clicked.connect(self.on_search_button_clicked)
        # Other stuff
        self.search_edit.returnPressed.connect(self.on_search_button_clicked)
        self.search_tab_bar.currentChanged.connect(self.on_search_tab_bar_current_changed)
        self.search_edit.textChanged.connect(self.on_search_edit_text_changed)

    def retranslateUi(self):
        log.debug('retranslateUi')
        self.chapter_label.setText(translate('BiblesPlugin.MediaItem', 'Chapter:'))
        self.verse_label.setText(translate('BiblesPlugin.MediaItem', 'Verse:'))
        self.style_combo_box.setItemText(LayoutStyle.VersePerSlide, UiStrings().VersePerSlide)
        self.style_combo_box.setItemText(LayoutStyle.VersePerLine, UiStrings().VersePerLine)
        self.style_combo_box.setItemText(LayoutStyle.Continuous, UiStrings().Continuous)
        self.clear_button.setToolTip(translate('BiblesPlugin.MediaItem', 'Clear the search results.'))
        self.lock_button.setToolTip(
            translate('BiblesPlugin.MediaItem', 'Toggle to keep or clear the previous results.'))
        self.search_button.setText(UiStrings().Search)

    def on_focus(self):
        """
        Set focus on the appropriate widget when BibleMediaItem receives focus

        Reimplements MediaManagerItem.on_focus()

        :return: None
        """
        if self.search_tab.isVisible():
            self.search_edit.setFocus()
            self.search_edit.selectAll()
        else:
            self.select_book_combo_box.setFocus()

    def config_update(self):
        """
        Change the visible widgets when the config changes

        :return: None
        """
        log.debug('config_update')
        visible = Settings().value('{settings_section}/second bibles'.format(settings_section=self.settings_section))
        self.general_bible_layout.labelForField(self.second_combo_box).setVisible(visible)
        self.second_combo_box.setVisible(visible)

    def initialise(self):
        """
        Called to complete initialisation that could not be completed in the constructor.

        :return: None
        """
        log.debug('bible manager initialise')
        self.plugin.manager.media = self
        self.populate_bible_combo_boxes()
        self.search_edit.set_search_types([
            (BibleSearch.Combined, ':/bibles/bibles_search_combined.png',
                translate('BiblesPlugin.MediaItem', 'Text or Reference'),
                translate('BiblesPlugin.MediaItem', 'Text or Reference...')),
            (BibleSearch.Reference, ':/bibles/bibles_search_reference.png',
                translate('BiblesPlugin.MediaItem', 'Scripture Reference'),
                translate('BiblesPlugin.MediaItem', 'Search Scripture Reference...')),
            (BibleSearch.Text, ':/bibles/bibles_search_text.png',
                translate('BiblesPlugin.MediaItem', 'Text Search'),
                translate('BiblesPlugin.MediaItem', 'Search Text...'))
        ])
        if Settings().value(
                '{settings_section}/reset to combined quick search'.format(settings_section=self.settings_section)):
            self.search_edit.set_current_search_type(BibleSearch.Combined)
        self.config_update()
        log.debug('bible manager initialise complete')

    def populate_bible_combo_boxes(self):
        """
        Populate the bible combo boxes with the list of bibles that have been loaded

        :return: None
        """
        log.debug('Loading Bibles')
        self.version_combo_box.clear()
        self.second_combo_box.clear()
        self.second_combo_box.addItem('', None)
        # Get all bibles and sort the list.
        bibles = self.plugin.manager.get_bibles()
        bibles = [(_f, bibles[_f]) for _f in bibles if _f]
        bibles.sort(key=lambda k: get_locale_key(k[0]))
        for bible in bibles:
            self.version_combo_box.addItem(bible[0], bible[1])
            self.second_combo_box.addItem(bible[0], bible[1])
        # set the default value
        bible = Settings().value('{settings_section}/primary bible'.format(settings_section=self.settings_section))
        find_and_set_in_combo_box(self.version_combo_box, bible)

    def reload_bibles(self):
        """
        Reload the bibles and update the combo boxes

        :return: None
        """
        log.debug('Reloading Bibles')
        self.plugin.manager.reload_bibles()
        self.populate_bible_combo_boxes()

    def get_common_books(self, first_bible, second_bible=None):
        """
        Return a list of common books between two bibles.

        :param first_bible: The first bible (BibleDB)
        :param second_bible: The second bible. (Optional, BibleDB
        :return: A list of common books between the two bibles. Or if only one bible is supplied a list of that bibles
                books (list of Book objects)
        """
        if not second_bible:
            return first_bible.get_books()
        book_data = []
        for book in first_bible.get_books():
            for second_book in second_bible.get_books():
                if book.book_reference_id == second_book.book_reference_id:
                    book_data.append(book)
        return book_data

    def initialise_advanced_bible(self, last_book=None):
        """
        This initialises the given bible, which means that its book names and their chapter numbers is added to the
        combo boxes on the 'Select' Tab. This is not of any importance of the 'Search' Tab.

        :param last_book_id: The "book reference id" of the book which is chosen at the moment. (int)
        :return: None
        """
        log.debug('initialise_advanced_bible {bible}, {ref}'.format(bible=self.bible, ref=last_book))
        self.select_book_combo_box.clear()
        if self.bible is None:
            return
        book_data = self.get_common_books(self.bible, self.second_bible)
        language_selection = self.plugin.manager.get_language_selection(self.bible.name)
        self.select_book_combo_box.model().setDynamicSortFilter(False)
        for book in book_data:
            self.select_book_combo_box.addItem(book.get_name(language_selection), book.book_reference_id)
        self.select_book_combo_box.model().setDynamicSortFilter(True)
        if last_book:
            index = self.select_book_combo_box.findData(last_book)
            self.select_book_combo_box.setCurrentIndex(index if index != -1 else 0)
        self.on_advanced_book_combo_box()

    def update_auto_completer(self):
        """
        This updates the bible book completion list for the search field. The completion depends on the bible. It is
        only updated when we are doing reference or combined search, in text search the completion list is removed.

        :return: None
        """
        books = []
        # We have to do a 'Reference Search' (Or as part of Combined Search).
        if self.search_edit.current_search_type() is not BibleSearch.Text:
            if self.bible:
                book_data = self.get_common_books(self.bible, self.second_bible)
                language_selection = self.plugin.manager.get_language_selection(self.bible.name)
                books = [book.get_name(language_selection) for book in book_data]
                books.sort(key=get_locale_key)
        set_case_insensitive_completer(books, self.search_edit)

    def on_import_click(self):
        """
        Create, if not already, the `BibleImportForm` and execute it

        :return: None
        """
        if not hasattr(self, 'import_wizard'):
            self.import_wizard = BibleImportForm(self, self.plugin.manager, self.plugin)
        # If the import was not cancelled then reload.
        if self.import_wizard.exec():
            self.reload_bibles()

    def on_edit_click(self):
        """
        Load the EditBibleForm and reload the bibles if the user accepts it

        :return: None
        """
        if self.bible:
            self.edit_bible_form = EditBibleForm(self, self.main_window, self.plugin.manager)
            self.edit_bible_form.load_bible(self.bible.name)
            if self.edit_bible_form.exec():
                self.reload_bibles()

    def on_delete_click(self):
        """
        Confirm that the user wants to delete the main bible

        :return: None
        """
        if self.bible:
            if QtWidgets.QMessageBox.question(
                self, UiStrings().ConfirmDelete,
                translate('BiblesPlugin.MediaItem',
                          'Are you sure you want to completely delete "{bible}" Bible from OpenLP?\n\n'
                          'You will need to re-import this Bible to use it again.').format(bible=self.bible.name),
                    defaultButton=QtWidgets.QMessageBox.No) == QtWidgets.QMessageBox.No:
                return
            self.plugin.manager.delete_bible(self.bible.name)
            self.reload_bibles()

    def on_search_tab_bar_current_changed(self, index):
        """
        Show the selected tab and set focus to it

        :param index: The tab selected (int)
        :return: None
        """
        search_tab = index == 0
        self.search_tab.setVisible(search_tab)
        self.select_tab.setVisible(not search_tab)
        self.on_focus()

    def on_book_order_button_toggled(self, checked):
        """
        Change the sort order of the book names

        :param checked: Indicates if the button is checked or not (Bool)
        :return: None
        """
        if checked:
            self.select_book_combo_box.model().sort(0)
        else:
            # -1 Removes the sorting, and returns the items to the order they were added in
            self.select_book_combo_box.model().sort(-1)

    def on_clear_button_clicked(self):
        """
        Clear the list_view and the search_edit

        :return: None
        """
        self.list_view.clear()
        self.search_edit.clear()
        self.on_focus()

    def on_lock_button_toggled(self, checked):
        """
        Toggle the lock button, if Search tab is used, set focus to search field.

        :param checked: The state of the toggle button. (bool)
        :return: None
        """
        self.list_view.locked = checked
        if checked:
            self.sender().setIcon(self.lock_icon)
        else:
            self.sender().setIcon(self.unlock_icon)

    def on_style_combo_box_index_changed(self, index):
        """
        Change the layout style and save the setting

        :param index: The index of the current item in the combobox (int)
        :return: None
        """
        # TODO: Change layout_style to a property
        self.settings.layout_style = index
        self.settings.layout_style_combo_box.setCurrentIndex(index)
        Settings().setValue('{section}/verse layout style'.format(section=self.settings_section), index)

    def on_version_combo_box_index_changed(self):
        """
        Update the main bible and save it to settings

        :return: None
        """
        self.bible = self.version_combo_box.currentData()
        if self.bible is not None:
            Settings().setValue('{section}/primary bible'.format(section=self.settings_section), self.bible.name)
        self.initialise_advanced_bible(self.select_book_combo_box.currentData())

    def on_second_combo_box_index_changed(self, selection):
        """
        Update the second bible. If changing from single to dual bible modes as if the user wants to clear the search
        results, if not revert to the previously selected bible

        :return: None
        """
        new_selection = self.second_combo_box.currentData()
        if self.list_view.count():
            # Exclusive or (^) the new and previous selections to detect if the user has switched between single and
            # dual bible mode
            if (new_selection is None) ^ (self.second_bible is None):
                if critical_error_message_box(
                    message=translate('BiblesPlugin.MediaItem',
                                      'OpenLP cannot combine single and dual Bible verse search results. '
                                      'Do you want to clear your search results and start a new search?'),
                        parent=self, question=True) == QtWidgets.QMessageBox.Yes:
                    self.list_view.clear(override_lock=True)
                else:
                    self.second_combo_box.setCurrentIndex(self.second_combo_box.findData(self.second_bible))
                    return
        self.second_bible = new_selection
        if new_selection is None:
            self.style_combo_box.setEnabled(True)
        else:
            self.style_combo_box.setEnabled(False)
            self.initialise_advanced_bible(self.select_book_combo_box.currentData())

    def on_advanced_book_combo_box(self):
        """
        Update the verse selection boxes

        :return: None
        """
        book_ref_id = self.select_book_combo_box.currentData()
        book = self.plugin.manager.get_book_by_id(self.bible.name, book_ref_id)
        self.chapter_count = self.plugin.manager.get_chapter_count(self.bible.name, book)
        verse_count = self.plugin.manager.get_verse_count_by_book_ref_id(self.bible.name, book_ref_id, 1)
        if verse_count == 0:
            self.search_button.setEnabled(False)
            log.warning('Not enough chapters in %s', book_ref_id)
            critical_error_message_box(message=translate('BiblesPlugin.MediaItem', 'Bible not fully loaded.'))
        else:
            self.search_button.setEnabled(True)
            self.adjust_combo_box(1, self.chapter_count, self.from_chapter)
            self.adjust_combo_box(1, self.chapter_count, self.to_chapter)
            self.adjust_combo_box(1, verse_count, self.from_verse)
            self.adjust_combo_box(1, verse_count, self.to_verse)

    def on_from_chapter_activated(self):
        """
        Update the verse selection boxes

        :return: None
        """
        book_ref_id = self.select_book_combo_box.currentData()
        chapter_from = self.from_chapter.currentData()
        chapter_to = self.to_chapter.currentData()
        verse_count = self.plugin.manager.get_verse_count_by_book_ref_id(self.bible.name, book_ref_id, chapter_from)
        self.adjust_combo_box(1, verse_count, self.from_verse)
        if chapter_from >= chapter_to:
            self.adjust_combo_box(1, verse_count, self.to_verse, chapter_from == chapter_to)
        self.adjust_combo_box(chapter_from, self.chapter_count, self.to_chapter, chapter_from < chapter_to)

    def on_from_verse(self):
        """
        Update the verse selection boxes

        :return: None
        """
        chapter_from = self.from_chapter.currentData()
        chapter_to = self.to_chapter.currentData()
        if chapter_from == chapter_to:
            book_ref_id = self.select_book_combo_box.currentData()
            verse_from = self.from_verse.currentData()
            verse_count = self.plugin.manager.get_verse_count_by_book_ref_id(self.bible.name, book_ref_id, chapter_to)
            self.adjust_combo_box(verse_from, verse_count, self.to_verse, True)

    def on_to_chapter(self):
        """
        Update the verse selection boxes

        :return: None
        """
        book_ref_id = self.select_book_combo_box.currentData()
        chapter_from = self.from_chapter.currentData()
        chapter_to = self.to_chapter.currentData()
        verse_from = self.from_verse.currentData()
        verse_to = self.to_verse.currentData()
        verse_count = self.plugin.manager.get_verse_count_by_book_ref_id(self.bible.name, book_ref_id, chapter_to)
        if chapter_from == chapter_to and verse_from > verse_to:
            self.adjust_combo_box(verse_from, verse_count, self.to_verse)
        else:
            self.adjust_combo_box(1, verse_count, self.to_verse)

    def adjust_combo_box(self, range_from, range_to, combo, restore=False):
        """
        Adjusts the given como box to the given values.

        :param range_from: The first number of the range (int).
        :param range_to: The last number of the range (int).
        :param combo: The combo box itself (QComboBox).
        :param restore: If True, then the combo's currentText will be restored after adjusting (if possible).
        """
        log.debug('adjust_combo_box {box}, {start}, {end}'.format(box=combo, start=range_from, end=range_to))
        if restore:
            old_selection = combo.currentData()
        combo.clear()
        for item in range(range_from, range_to + 1):
            combo.addItem(str(item), item)
        if restore:
            index = combo.findData(old_selection)
            combo.setCurrentIndex(index if index != -1 else 0)

    def on_search_button_clicked(self):
        """
        Call the correct search function depending on which tab the user is using

        :return: None
        """
        if not self.bible:
            self.main_window.information_message(UiStrings().BibleNoBiblesTitle, UiStrings().BibleNoBibles)
            return
        self.search_button.setEnabled(False)
        self.application.set_busy_cursor()
        self.application.process_events()
        if self.search_tab.isVisible():
            self.text_search()
        elif self.select_tab.isVisible():
            self.select_search()
        self.search_button.setEnabled(True)
        self.application.set_normal_cursor()

    def select_search(self):
        """
        Preform a search using the passage selected on the `Select` tab

        :return: None
        """
        verse_range = self.plugin.manager.process_verse_range(
            self.select_book_combo_box.currentData(), self.from_chapter.currentData(), self.from_verse.currentData(),
            self.to_chapter.currentData(), self.to_verse.currentData())
        self.search_results = self.plugin.manager.get_verses(self.bible.name, verse_range, False)
        if self.second_bible:
            self.second_search_results = self.plugin.manager.get_verses(self.second_bible.name, verse_range, False)
        self.display_results()

    def text_reference_search(self, search_text):
        """
        We are doing a 'Reference Search'.
        This search is called on def text_search by Reference and Combined Searches.

        :return: None
        """
        verse_refs = self.plugin.manager.parse_ref(self.bible.name, search_text)
        self.search_results = self.plugin.manager.get_verses(self.bible.name, verse_refs, True)
        if self.second_bible and self.search_results:
            self.search_results = self.plugin.manager.get_verses(self.second_bible.name, verse_refs, True)
        self.display_results()

    def on_text_search(self, text, search_while_type=False):
        """
        We are doing a 'Text Search'.
        This search is called on def text_search by 'Search' Text and Combined Searches.
        """
        self.search_results = self.plugin.manager.verse_search(self.bible.name, text)
        if self.second_bible and self.search_results:
            filtered_search_results = []
            not_found_count = 0
            for verse in self.search_results:
                second_verse = self.second_bible.get_verses(
                    [(verse.book.book_reference_id, verse.chapter, verse.verse, verse.verse)], False)
                if second_verse:
                    filtered_search_results.append(verse)
                    self.second_search_results += second_verse
                else:
                    log.debug('Verse "{name} {chapter:d}:{verse:d}" not found in Second Bible "{bible_name}"'.format(
                        name=verse.book.name, chapter=verse.chapter,
                        verse=verse.verse, bible_name=self.second_bible.name))
                    not_found_count += 1
            self.search_results = filtered_search_results
            if not_found_count != 0 and not search_while_type:
                self.main_window.information_message(
                    translate('BiblesPlugin.MediaItem', 'Verses not found'),
                    translate('BiblesPlugin.MediaItem',
                              'The second Bible "{second_name}" does not contain all the verses that are in the main '
                              'Bible "{name}".\nOnly verses found in both Bibles will be shown.\n\n'
                              '{count:d} verses have not been included in the results.'
                              ).format(second_name=self.second_bible.name, name=self.bible.name, count=not_found_count))
        self.display_results()

    def text_search(self, search_while_type=False):
        """
        This triggers the proper 'Search' search based on which search type is used.
        "Eg. "Reference Search", "Text Search" or "Combined search".
        """
        log.debug('text_search called')
        text = self.search_edit.text()
        if text == '':
            self.list_view.clear()
            return
        self.list_view.clear(search_while_typing=search_while_type)
        if self.search_edit.current_search_type() == BibleSearch.Reference:
            if get_reference_match('full').match(text):
                # Valid reference found. Do reference search.
                self.text_reference_search(text)
            elif not search_while_type:
                self.main_window.information_message(
                    translate('BiblesPlugin.BibleManager', 'Scripture Reference Error'),
                    translate('BiblesPlugin.BibleManager',
                              '<strong>The reference you typed is invalid!<br><br>'
                              'Please make sure that your reference follows one of these patterns:</strong><br><br>%s')
                    % UiStrings().BibleScriptureError % get_reference_separators())
        elif self.search_edit.current_search_type() == BibleSearch.Combined and get_reference_match('full').match(text):
                # Valid reference found. Do reference search.
                self.text_reference_search(text)
        else:
            # It can only be a 'Combined' search without a valid reference, or a 'Text' search
            if search_while_type:
                if len(text) > 8 and VALID_TEXT_SEARCH.search(text):
                    self.on_text_search(text, True)
            elif VALID_TEXT_SEARCH.search(text):
                self.on_text_search(text)

    def on_search_edit_text_changed(self):
        """
        If 'search_as_you_type' is enabled, start a timer when the search_edit emits a textChanged signal. This is to
        prevent overloading the system by submitting too many search requests in a short space of time.

        :return: None
        """
        if Settings().value('bibles/is search while typing enabled'):
            if not self.search_timer.isActive():
                self.search_timer.start()

    def on_search_timer_timeout(self):
        """
        Perform a search when the search timer timeouts. The search timer is used for 'search_as_you_type' so that we
        don't overload the system buy submitting too many search requests in a short space of time.

        :return: None
        """
        self.text_search(True)

    def display_results(self):
        """
        Add the search results to the media manager list.

        :return: None
        """
        self.list_view.clear()
        if self.search_results:
            items = self.build_display_results(self.bible, self.second_bible, self.search_results)
            for item in items:
                self.list_view.addItem(item)
            self.list_view.selectAll()
        self.search_results = []
        self.second_search_results = []

    def build_display_results(self, bible, second_bible, search_results):
        """
        Displays the search results in the media manager. All data needed for further action is saved for/in each row.
        """
        verse_separator = get_reference_separators()['verse']
        version = self.plugin.manager.get_meta_data(self.bible.name, 'name').value
        copyright = self.plugin.manager.get_meta_data(self.bible.name, 'copyright').value
        permissions = self.plugin.manager.get_meta_data(self.bible.name, 'permissions').value
        second_name = ''
        second_version = ''
        second_copyright = ''
        second_permissions = ''
        if second_bible:
            second_name = second_bible.name
            second_version = self.plugin.manager.get_meta_data(self.second_bible.name, 'name').value
            second_copyright = self.plugin.manager.get_meta_data(self.second_bible.name, 'copyright').value
            second_permissions = self.plugin.manager.get_meta_data(self.second_bible.name, 'permissions').value
        items = []
        language_selection = self.plugin.manager.get_language_selection(self.bible.name)
        for count, verse in enumerate(search_results):
            data = {
                'book': verse.book.get_name(language_selection),
                'chapter': verse.chapter,
                'verse': verse.verse,
                'bible': self.bible.name,
                'version': version,
                'copyright': copyright,
                'permissions': permissions,
                'text': verse.text,
                'second_bible': second_name,
                'second_version': second_version,
                'second_copyright': second_copyright,
                'second_permissions': second_permissions,
                'second_text': ''
            }

            if second_bible:
                try:
                    data['second_text'] = self.second_search_results[count].text
                except IndexError:
                    log.exception('The second_search_results does not have as many verses as the search_results.')
                    break
                except TypeError:
                    log.exception('The second_search_results does not have this book.')
                    break
                bible_text = '{book} {chapter:d}{sep}{verse:d} ({version}, {second_version})'
            else:
                bible_text = '{book} {chapter:d}{sep}{verse:d} ({version})'
            bible_verse = QtWidgets.QListWidgetItem(bible_text.format(sep=verse_separator, **data))
            bible_verse.setData(QtCore.Qt.UserRole, data)
            items.append(bible_verse)
        return items

    def generate_slide_data(self, service_item, item=None, xml_version=False, remote=False,
                            context=ServiceItemContext.Service):
        """
        Generate the slide data. Needs to be implemented by the plugin.

        :param service_item: The service item to be built on
        :param item: The Song item to be used
        :param xml_version: The xml version (not used)
        :param remote: Triggered from remote
        :param context: Why is it being generated
        """
        log.debug('generating slide data')
        if item:
            items = item
        else:
            items = self.list_view.selectedItems()
        if not items:
            return False
        bible_text = ''
        old_chapter = -1
        raw_slides = []
        verses = VerseReferenceList()
        for bitem in items:
            data = bitem.data(QtCore.Qt.UserRole)
            verses.add(
                data['book'], data['chapter'], data['verse'], data['version'], data['copyright'], data['permissions'])
            verse_text = self.format_verse(old_chapter, data['chapter'], data['verse'])
            # We only support 'Verse Per Slide' when using a scond bible
            if data['second_bible']:
                second_text = self.format_verse(old_chapter, data['chapter'], data['verse'])
                bible_text = '{first_version}{data[text]}\n\n{second_version}{data[second_text]}'\
                    .format(first_version=verse_text, second_version=second_text, data=data)
                raw_slides.append(bible_text.rstrip())
                bible_text = ''
            # If we are 'Verse Per Slide' then create a new slide.
            elif self.settings.layout_style == LayoutStyle.VersePerSlide:
                bible_text = '{first_version}{data[text]}'.format(first_version=verse_text, data=data)
                raw_slides.append(bible_text.rstrip())
                bible_text = ''
            # If we are 'Verse Per Line' then force a new line.
            elif self.settings.layout_style == LayoutStyle.VersePerLine:
                bible_text = '{bible} {verse}{data[text]}\n'.format(bible=bible_text, verse=verse_text, data=data)
            # We have to be 'Continuous'.
            else:
                bible_text = '{bible} {verse}{data[text]}'.format(bible=bible_text, verse=verse_text, data=data)
            bible_text = bible_text.strip(' ')
            old_chapter = data['chapter']
        # Add footer
        service_item.raw_footer.append(verses.format_verses())
        if data['second_bible']:
            verses.add_version(data['second_version'], data['second_copyright'], data['second_permissions'])
        service_item.raw_footer.append(verses.format_versions())
        # If there are no more items we check whether we have to add bible_text.
        if bible_text:
            raw_slides.append(bible_text.lstrip())
        # Service Item: Capabilities
        if self.settings.layout_style == LayoutStyle.Continuous and not data['second_bible']:
            # Split the line but do not replace line breaks in renderer.
            service_item.add_capability(ItemCapabilities.NoLineBreaks)
        service_item.add_capability(ItemCapabilities.CanPreview)
        service_item.add_capability(ItemCapabilities.CanLoop)
        service_item.add_capability(ItemCapabilities.CanWordSplit)
        service_item.add_capability(ItemCapabilities.CanEditTitle)
        # Service Item: Title
        service_item.title = '{verse} {version}'.format(verse=verses.format_verses(), version=verses.format_versions())
        # Service Item: Theme
        if self.settings.bible_theme:
            service_item.theme = self.settings.bible_theme
        for slide in raw_slides:
            service_item.add_from_text(slide)
        return True

    def format_verse(self, old_chapter, chapter, verse):
        """
        Formats and returns the text, each verse starts with, for the given chapter and verse. The text is either
        surrounded by round, square, curly brackets or no brackets at all. For example::

            '{su}1:1{/su}'

        :param old_chapter: The previous verse's chapter number (int).
        :param chapter: The chapter number (int).
        :param verse: The verse number (int).
        :return: An empty or formatted string
        """
        if not self.settings.is_verse_number_visible:
            return ''
        verse_separator = get_reference_separators()['verse']
        if not self.settings.show_new_chapters or old_chapter != chapter:
            verse_text = '{chapter}{sep}{verse}'.format(chapter=chapter, sep=verse_separator, verse=verse)
        else:
            verse_text = verse
        bracket = {
            DisplayStyle.NoBrackets: ('', ''),
            DisplayStyle.Round: ('(', ')'),
            DisplayStyle.Curly: ('{', '}'),
            DisplayStyle.Square: ('[', ']')
        }[self.settings.display_style]
        return '{{su}}{bracket[0]}{verse_text}{bracket[1]}{{/su}}&nbsp;'.format(verse_text=verse_text, bracket=bracket)

    def search(self, string, showError):
        """
        Search for some Bible verses (by reference).
        """
        reference = self.plugin.manager.parse_ref(self.bible.name, string)
        search_results = self.plugin.manager.get_verses(self.bible.name, reference, showError)
        if search_results:
            verse_text = ' '.join([verse.text for verse in search_results])
            return [[string, verse_text]]
        return []

    def create_item_from_id(self, item_id):
        """
        Create a media item from an item id.
        """
        reference = self.plugin.manager.parse_ref(self.bible.name, item_id)
        search_results = self.plugin.manager.get_verses(self.bible.name, reference, False)
        return self.build_display_results(self.bible, None, search_results)
Exemplo n.º 9
0
 def add_end_header_bar(self):
     self.search_tab_bar = QtWidgets.QTabBar(self)
     self.search_tab_bar.setExpanding(False)
     self.page_layout.addWidget(self.search_tab_bar)
     # Add the Search tab.
     self.search_tab = QtWidgets.QWidget()
     self.search_tab.setSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum)
     self.search_tab_bar.addTab(translate('BiblesPlugin.MediaItem', 'Find'))
     self.search_layout = QtWidgets.QFormLayout(self.search_tab)
     self.search_edit = SearchEdit(self.search_tab, self.settings_section)
     self.search_layout.addRow(translate('BiblesPlugin.MediaItem', 'Find:'), self.search_edit)
     self.search_tab.setVisible(True)
     self.page_layout.addWidget(self.search_tab)
     # Add the Select tab.
     self.select_tab = QtWidgets.QWidget()
     self.select_tab.setSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum)
     self.search_tab_bar.addTab(translate('BiblesPlugin.MediaItem', 'Select'))
     self.select_layout = QtWidgets.QFormLayout(self.select_tab)
     self.book_layout = QtWidgets.QHBoxLayout()
     self.select_book_combo_box = create_horizontal_adjusting_combo_box(self.select_tab, 'select_book_combo_box')
     self.book_layout.addWidget(self.select_book_combo_box)
     self.book_order_button = QtWidgets.QToolButton()
     self.book_order_button.setIcon(self.sort_icon)
     self.book_order_button.setCheckable(True)
     self.book_order_button.setToolTip(translate('BiblesPlugin.MediaItem', 'Sort bible books alphabetically.'))
     self.book_layout.addWidget(self.book_order_button)
     self.select_layout.addRow(translate('BiblesPlugin.MediaItem', 'Book:'), self.book_layout)
     self.verse_title_layout = QtWidgets.QHBoxLayout()
     self.chapter_label = QtWidgets.QLabel(self.select_tab)
     self.verse_title_layout.addWidget(self.chapter_label)
     self.verse_label = QtWidgets.QLabel(self.select_tab)
     self.verse_title_layout.addWidget(self.verse_label)
     self.select_layout.addRow('', self.verse_title_layout)
     self.from_layout = QtWidgets.QHBoxLayout()
     self.from_chapter = QtWidgets.QComboBox(self.select_tab)
     self.from_layout.addWidget(self.from_chapter)
     self.from_verse = QtWidgets.QComboBox(self.select_tab)
     self.from_layout.addWidget(self.from_verse)
     self.select_layout.addRow(translate('BiblesPlugin.MediaItem', 'From:'), self.from_layout)
     self.to_layout = QtWidgets.QHBoxLayout()
     self.to_chapter = QtWidgets.QComboBox(self.select_tab)
     self.to_layout.addWidget(self.to_chapter)
     self.to_verse = QtWidgets.QComboBox(self.select_tab)
     self.to_layout.addWidget(self.to_verse)
     self.select_layout.addRow(translate('BiblesPlugin.MediaItem', 'To:'), self.to_layout)
     self.select_tab.setVisible(False)
     self.page_layout.addWidget(self.select_tab)
     # General Search Opions
     self.options_widget = QtWidgets.QGroupBox(translate('BiblesPlugin.MediaItem', 'Options'), self)
     self.general_bible_layout = QtWidgets.QFormLayout(self.options_widget)
     self.version_combo_box = create_horizontal_adjusting_combo_box(self, 'version_combo_box')
     self.general_bible_layout.addRow('{version}:'.format(version=UiStrings().Version), self.version_combo_box)
     self.second_combo_box = create_horizontal_adjusting_combo_box(self, 'second_combo_box')
     self.general_bible_layout.addRow(translate('BiblesPlugin.MediaItem', 'Second:'), self.second_combo_box)
     self.style_combo_box = create_horizontal_adjusting_combo_box(self, 'style_combo_box')
     self.style_combo_box.addItems(['', '', ''])
     self.general_bible_layout.addRow(UiStrings().LayoutStyle, self.style_combo_box)
     self.search_button_layout = QtWidgets.QHBoxLayout()
     self.search_button_layout.addStretch()
     # Note: If we use QPushButton instead of the QToolButton, the icon will be larger than the Lock icon.
     self.clear_button = QtWidgets.QToolButton(self)
     self.clear_button.setIcon(self.clear_icon)
     self.lock_button = QtWidgets.QToolButton(self)
     self.lock_button.setIcon(self.unlock_icon)
     self.lock_button.setCheckable(True)
     self.search_button_layout.addWidget(self.clear_button)
     self.search_button_layout.addWidget(self.lock_button)
     self.search_button = QtWidgets.QPushButton(self)
     self.search_button_layout.addWidget(self.search_button)
     self.general_bible_layout.addRow(self.search_button_layout)
     self.page_layout.addWidget(self.options_widget)
Exemplo n.º 10
0
class BibleMediaItem(MediaManagerItem):
    """
    This is the custom media manager item for Bibles.
    """
    log.info('Bible Media Item loaded')

    def __init__(self, parent, plugin):
        self.icon_path = 'songs/song'
        self.lock_icon = build_icon(':/bibles/bibles_search_lock.png')
        self.unlock_icon = build_icon(':/bibles/bibles_search_unlock.png')
        MediaManagerItem.__init__(self, parent, plugin)
        # Place to store the search results for both bibles.
        self.settings = self.plugin.settings_tab
        self.quick_preview_allowed = True
        self.has_search = True
        self.search_results = {}
        self.second_search_results = {}
        self.check_search_result()
        Registry().register_function('bibles_load_list', self.reload_bibles)

    def __check_second_bible(self, bible, second_bible):
        """
        Check if the first item is a second bible item or not.
        """
        bitem = self.list_view.item(0)
        if not bitem.flags() & QtCore.Qt.ItemIsSelectable:
            # The item is the "No Search Results" item.
            self.list_view.clear()
            self.displayResults(bible, second_bible)
            return
        else:
            item_second_bible = self._decode_qt_object(bitem, 'second_bible')
        if item_second_bible and second_bible or not item_second_bible and not second_bible:
            self.displayResults(bible, second_bible)
        elif critical_error_message_box(
            message=translate('BiblesPlugin.MediaItem',
                'You cannot combine single and dual Bible verse search results. '
                'Do you want to delete your search results and start a new search?'),
            parent=self, question=True) == QtGui.QMessageBox.Yes:
            self.list_view.clear()
            self.displayResults(bible, second_bible)

    def _decode_qt_object(self, bitem, key):
        reference = bitem.data(QtCore.Qt.UserRole)
        obj = reference[str(key)]
        return str(obj).strip()

    def required_icons(self):
        """
        Set which icons the media manager tab should show
        """
        MediaManagerItem.required_icons(self)
        self.has_import_icon = True
        self.has_new_icon = False
        self.has_edit_icon = True
        self.has_delete_icon = True
        self.add_to_service_item = False

    def addSearchTab(self, prefix, name):
        self.searchTabBar.addTab(name)
        tab = QtGui.QWidget()
        tab.setObjectName(prefix + 'Tab')
        tab.setSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Minimum)
        layout = QtGui.QGridLayout(tab)
        layout.setObjectName(prefix + 'Layout')
        setattr(self, prefix + 'Tab', tab)
        setattr(self, prefix + 'Layout', layout)

    def addSearchFields(self, prefix, name):
        """
        Creates and adds generic search tab.

        ``prefix``
            The prefix of the tab, this is either ``quick`` or ``advanced``.

        ``name``
            The translated string to display.
        """
        if prefix == 'quick':
            idx = 2
        else:
            idx = 5
        tab = getattr(self, prefix + 'Tab')
        layout = getattr(self, prefix + 'Layout')
        versionLabel = QtGui.QLabel(tab)
        versionLabel.setObjectName(prefix + 'VersionLabel')
        layout.addWidget(versionLabel, idx, 0, QtCore.Qt.AlignRight)
        versionComboBox = create_horizontal_adjusting_combo_box(tab, prefix + 'VersionComboBox')
        versionLabel.setBuddy(versionComboBox)
        layout.addWidget(versionComboBox, idx, 1, 1, 2)
        secondLabel = QtGui.QLabel(tab)
        secondLabel.setObjectName(prefix + 'SecondLabel')
        layout.addWidget(secondLabel, idx + 1, 0, QtCore.Qt.AlignRight)
        secondComboBox = create_horizontal_adjusting_combo_box(tab, prefix + 'SecondComboBox')
        versionLabel.setBuddy(secondComboBox)
        layout.addWidget(secondComboBox, idx + 1, 1, 1, 2)
        styleLabel = QtGui.QLabel(tab)
        styleLabel.setObjectName(prefix + 'StyleLabel')
        layout.addWidget(styleLabel, idx + 2, 0, QtCore.Qt.AlignRight)
        styleComboBox = create_horizontal_adjusting_combo_box(tab, prefix + 'StyleComboBox')
        styleComboBox.addItems(['', '', ''])
        layout.addWidget(styleComboBox, idx + 2, 1, 1, 2)
        search_button_layout = QtGui.QHBoxLayout()
        search_button_layout.setObjectName(prefix + 'search_button_layout')
        search_button_layout.addStretch()
        lockButton = QtGui.QToolButton(tab)
        lockButton.setIcon(self.unlock_icon)
        lockButton.setCheckable(True)
        lockButton.setObjectName(prefix + 'LockButton')
        search_button_layout.addWidget(lockButton)
        searchButton = QtGui.QPushButton(tab)
        searchButton.setObjectName(prefix + 'SearchButton')
        search_button_layout.addWidget(searchButton)
        layout.addLayout(search_button_layout, idx + 3, 1, 1, 2)
        self.page_layout.addWidget(tab)
        tab.setVisible(False)
        lockButton.toggled.connect(self.onLockButtonToggled)
        setattr(self, prefix + 'VersionLabel', versionLabel)
        setattr(self, prefix + 'VersionComboBox', versionComboBox)
        setattr(self, prefix + 'SecondLabel', secondLabel)
        setattr(self, prefix + 'SecondComboBox', secondComboBox)
        setattr(self, prefix + 'StyleLabel', styleLabel)
        setattr(self, prefix + 'StyleComboBox', styleComboBox)
        setattr(self, prefix + 'LockButton', lockButton)
        setattr(self, prefix + 'SearchButtonLayout', search_button_layout)
        setattr(self, prefix + 'SearchButton', searchButton)

    def add_end_header_bar(self):
        self.searchTabBar = QtGui.QTabBar(self)
        self.searchTabBar.setExpanding(False)
        self.searchTabBar.setObjectName('searchTabBar')
        self.page_layout.addWidget(self.searchTabBar)
        # Add the Quick Search tab.
        self.addSearchTab('quick', translate('BiblesPlugin.MediaItem', 'Quick'))
        self.quickSearchLabel = QtGui.QLabel(self.quickTab)
        self.quickSearchLabel.setObjectName('quickSearchLabel')
        self.quickLayout.addWidget(self.quickSearchLabel, 0, 0, QtCore.Qt.AlignRight)
        self.quickSearchEdit = SearchEdit(self.quickTab)
        self.quickSearchEdit.setSizePolicy(QtGui.QSizePolicy.Ignored, QtGui.QSizePolicy.Fixed)
        self.quickSearchEdit.setObjectName('quickSearchEdit')
        self.quickSearchLabel.setBuddy(self.quickSearchEdit)
        self.quickLayout.addWidget(self.quickSearchEdit, 0, 1, 1, 2)
        self.addSearchFields('quick', translate('BiblesPlugin.MediaItem', 'Quick'))
        self.quickTab.setVisible(True)
        # Add the Advanced Search tab.
        self.addSearchTab('advanced', UiStrings().Advanced)
        self.advancedBookLabel = QtGui.QLabel(self.advancedTab)
        self.advancedBookLabel.setObjectName('advancedBookLabel')
        self.advancedLayout.addWidget(self.advancedBookLabel, 0, 0, QtCore.Qt.AlignRight)
        self.advancedBookComboBox = create_horizontal_adjusting_combo_box(self.advancedTab, 'advancedBookComboBox')
        self.advancedBookLabel.setBuddy(self.advancedBookComboBox)
        self.advancedLayout.addWidget(self.advancedBookComboBox, 0, 1, 1, 2)
        self.advancedChapterLabel = QtGui.QLabel(self.advancedTab)
        self.advancedChapterLabel.setObjectName('advancedChapterLabel')
        self.advancedLayout.addWidget(self.advancedChapterLabel, 1, 1, 1, 2)
        self.advancedVerseLabel = QtGui.QLabel(self.advancedTab)
        self.advancedVerseLabel.setObjectName('advancedVerseLabel')
        self.advancedLayout.addWidget(self.advancedVerseLabel, 1, 2)
        self.advancedFromLabel = QtGui.QLabel(self.advancedTab)
        self.advancedFromLabel.setObjectName('advancedFromLabel')
        self.advancedLayout.addWidget(self.advancedFromLabel, 3, 0, QtCore.Qt.AlignRight)
        self.advancedFromChapter = QtGui.QComboBox(self.advancedTab)
        self.advancedFromChapter.setObjectName('advancedFromChapter')
        self.advancedLayout.addWidget(self.advancedFromChapter, 3, 1)
        self.advancedFromVerse = QtGui.QComboBox(self.advancedTab)
        self.advancedFromVerse.setObjectName('advancedFromVerse')
        self.advancedLayout.addWidget(self.advancedFromVerse, 3, 2)
        self.advancedToLabel = QtGui.QLabel(self.advancedTab)
        self.advancedToLabel.setObjectName('advancedToLabel')
        self.advancedLayout.addWidget(self.advancedToLabel, 4, 0, QtCore.Qt.AlignRight)
        self.advancedToChapter = QtGui.QComboBox(self.advancedTab)
        self.advancedToChapter.setObjectName('advancedToChapter')
        self.advancedLayout.addWidget(self.advancedToChapter, 4, 1)
        self.advancedToVerse = QtGui.QComboBox(self.advancedTab)
        self.advancedToVerse.setObjectName('advancedToVerse')
        self.advancedLayout.addWidget(self.advancedToVerse, 4, 2)
        self.addSearchFields('advanced', UiStrings().Advanced)
        # Combo Boxes
        self.quickVersionComboBox.activated.connect(self.updateAutoCompleter)
        self.quickSecondComboBox.activated.connect(self.updateAutoCompleter)
        self.advancedVersionComboBox.activated.connect(self.onAdvancedVersionComboBox)
        self.advancedSecondComboBox.activated.connect(self.onAdvancedSecondComboBox)
        self.advancedBookComboBox.activated.connect(self.onAdvancedBookComboBox)
        self.advancedFromChapter.activated.connect(self.onAdvancedFromChapter)
        self.advancedFromVerse.activated.connect(self.onAdvancedFromVerse)
        self.advancedToChapter.activated.connect(self.onAdvancedToChapter)
        QtCore.QObject.connect(self.quickSearchEdit, QtCore.SIGNAL('searchTypeChanged(int)'), self.updateAutoCompleter)
        self.quickVersionComboBox.activated.connect(self.updateAutoCompleter)
        self.quickStyleComboBox.activated.connect(self.onQuickStyleComboBoxChanged)
        self.advancedStyleComboBox.activated.connect(self.onAdvancedStyleComboBoxChanged)
        # Buttons
        self.advancedSearchButton.clicked.connect(self.onAdvancedSearchButton)
        self.quickSearchButton.clicked.connect(self.onQuickSearchButton)
        # Other stuff
        self.quickSearchEdit.returnPressed.connect(self.onQuickSearchButton)
        self.searchTabBar.currentChanged.connect(self.onSearchTabBarCurrentChanged)

    def on_focus(self):
        if self.quickTab.isVisible():
            self.quickSearchEdit.setFocus()
        else:
            self.advancedBookComboBox.setFocus()

    def config_update(self):
        log.debug('config_update')
        if Settings().value(self.settings_section + '/second bibles'):
            self.advancedSecondLabel.setVisible(True)
            self.advancedSecondComboBox.setVisible(True)
            self.quickSecondLabel.setVisible(True)
            self.quickSecondComboBox.setVisible(True)
        else:
            self.advancedSecondLabel.setVisible(False)
            self.advancedSecondComboBox.setVisible(False)
            self.quickSecondLabel.setVisible(False)
            self.quickSecondComboBox.setVisible(False)
        self.quickStyleComboBox.setCurrentIndex(self.settings.layout_style)
        self.advancedStyleComboBox.setCurrentIndex(self.settings.layout_style)

    def retranslateUi(self):
        log.debug('retranslateUi')
        self.quickSearchLabel.setText(translate('BiblesPlugin.MediaItem', 'Find:'))
        self.quickVersionLabel.setText('%s:' % UiStrings().Version)
        self.quickSecondLabel.setText(translate('BiblesPlugin.MediaItem', 'Second:'))
        self.quickStyleLabel.setText(UiStrings().LayoutStyle)
        self.quickStyleComboBox.setItemText(LayoutStyle.VersePerSlide, UiStrings().VersePerSlide)
        self.quickStyleComboBox.setItemText(LayoutStyle.VersePerLine, UiStrings().VersePerLine)
        self.quickStyleComboBox.setItemText(LayoutStyle.Continuous, UiStrings().Continuous)
        self.quickLockButton.setToolTip(translate('BiblesPlugin.MediaItem',
            'Toggle to keep or clear the previous results.'))
        self.quickSearchButton.setText(UiStrings().Search)
        self.advancedBookLabel.setText(translate('BiblesPlugin.MediaItem', 'Book:'))
        self.advancedChapterLabel.setText(translate('BiblesPlugin.MediaItem', 'Chapter:'))
        self.advancedVerseLabel.setText(translate('BiblesPlugin.MediaItem', 'Verse:'))
        self.advancedFromLabel.setText(translate('BiblesPlugin.MediaItem', 'From:'))
        self.advancedToLabel.setText(translate('BiblesPlugin.MediaItem', 'To:'))
        self.advancedVersionLabel.setText('%s:' % UiStrings().Version)
        self.advancedSecondLabel.setText(translate('BiblesPlugin.MediaItem', 'Second:'))
        self.advancedStyleLabel.setText(UiStrings().LayoutStyle)
        self.advancedStyleComboBox.setItemText(LayoutStyle.VersePerSlide, UiStrings().VersePerSlide)
        self.advancedStyleComboBox.setItemText(LayoutStyle.VersePerLine, UiStrings().VersePerLine)
        self.advancedStyleComboBox.setItemText(LayoutStyle.Continuous, UiStrings().Continuous)
        self.advancedLockButton.setToolTip(translate('BiblesPlugin.MediaItem',
            'Toggle to keep or clear the previous results.'))
        self.advancedSearchButton.setText(UiStrings().Search)

    def initialise(self):
        log.debug('bible manager initialise')
        self.plugin.manager.media = self
        self.loadBibles()
        self.quickSearchEdit.set_search_types([
            (BibleSearch.Reference, ':/bibles/bibles_search_reference.png',
                translate('BiblesPlugin.MediaItem', 'Scripture Reference'),
                translate('BiblesPlugin.MediaItem', 'Search Scripture Reference...')),
            (BibleSearch.Text, ':/bibles/bibles_search_text.png',
                translate('BiblesPlugin.MediaItem', 'Text Search'),
                translate('BiblesPlugin.MediaItem', 'Search Text...'))
        ])
        self.quickSearchEdit.set_current_search_type(Settings().value('%s/last search type' % self.settings_section))
        self.config_update()
        log.debug('bible manager initialise complete')

    def loadBibles(self):
        log.debug('Loading Bibles')
        self.quickVersionComboBox.clear()
        self.quickSecondComboBox.clear()
        self.advancedVersionComboBox.clear()
        self.advancedSecondComboBox.clear()
        self.quickSecondComboBox.addItem('')
        self.advancedSecondComboBox.addItem('')
        # Get all bibles and sort the list.
        bibles = list(self.plugin.manager.get_bibles().keys())
        bibles = [_f for _f in bibles if _f]
        bibles.sort(key=get_locale_key)
        # Load the bibles into the combo boxes.
        self.quickVersionComboBox.addItems(bibles)
        self.quickSecondComboBox.addItems(bibles)
        self.advancedVersionComboBox.addItems(bibles)
        self.advancedSecondComboBox.addItems(bibles)
        # set the default value
        bible = Settings().value(self.settings_section + '/advanced bible')
        if bible in bibles:
            find_and_set_in_combo_box(self.advancedVersionComboBox, bible)
            self.initialiseAdvancedBible(str(bible))
        elif bibles:
            self.initialiseAdvancedBible(bibles[0])
        bible = Settings().value(self.settings_section + '/quick bible')
        find_and_set_in_combo_box(self.quickVersionComboBox, bible)

    def reload_bibles(self, process=False):
        log.debug('Reloading Bibles')
        self.plugin.manager.reload_bibles()
        self.loadBibles()
        # If called from first time wizard re-run, process any new bibles.
        if process:
            self.plugin.app_startup()
        self.updateAutoCompleter()

    def initialiseAdvancedBible(self, bible, last_book_id=None):
        """
        This initialises the given bible, which means that its book names and
        their chapter numbers is added to the combo boxes on the
        'Advanced Search' Tab. This is not of any importance of the
        'Quick Search' Tab.

        ``bible``
            The bible to initialise (unicode).

        ``last_book_id``
            The "book reference id" of the book which is choosen at the moment.
            (int)
        """
        log.debug('initialiseAdvancedBible %s, %s', bible, last_book_id)
        book_data = self.plugin.manager.get_books(bible)
        secondbible = self.advancedSecondComboBox.currentText()
        if secondbible != '':
            secondbook_data = self.plugin.manager.get_books(secondbible)
            book_data_temp = []
            for book in book_data:
                for secondbook in secondbook_data:
                    if book['book_reference_id'] == \
                        secondbook['book_reference_id']:
                        book_data_temp.append(book)
            book_data = book_data_temp
        self.advancedBookComboBox.clear()
        first = True
        initialise_chapter_verse = False
        language_selection = self.plugin.manager.get_language_selection(bible)
        book_names = BibleStrings().BookNames
        for book in book_data:
            row = self.advancedBookComboBox.count()
            if language_selection == LanguageSelection.Bible:
                self.advancedBookComboBox.addItem(book['name'])
            elif language_selection == LanguageSelection.Application:
                data = BiblesResourcesDB.get_book_by_id(book['book_reference_id'])
                self.advancedBookComboBox.addItem(book_names[data['abbreviation']])
            elif language_selection == LanguageSelection.English:
                data = BiblesResourcesDB.get_book_by_id(book['book_reference_id'])
                self.advancedBookComboBox.addItem(data['name'])
            self.advancedBookComboBox.setItemData(row, book['book_reference_id'])
            if first:
                first = False
                first_book = book
                initialise_chapter_verse = True
            if last_book_id and last_book_id == int(book['book_reference_id']):
                index = self.advancedBookComboBox.findData(book['book_reference_id'])
                if index == -1:
                    # Not Found.
                    index = 0
                self.advancedBookComboBox.setCurrentIndex(index)
                initialise_chapter_verse = False
        if initialise_chapter_verse:
            self.initialiseChapterVerse(bible, first_book['name'],
                first_book['book_reference_id'])

    def initialiseChapterVerse(self, bible, book, book_ref_id):
        log.debug('initialiseChapterVerse %s, %s, %s', bible, book, book_ref_id)
        book = self.plugin.manager.get_book_by_id(bible, book_ref_id)
        self.chapter_count = self.plugin.manager.get_chapter_count(bible, book)
        verse_count = self.plugin.manager.get_verse_count_by_book_ref_id(bible, book_ref_id, 1)
        if verse_count == 0:
            self.advancedSearchButton.setEnabled(False)
            critical_error_message_box(message=translate('BiblesPlugin.MediaItem', 'Bible not fully loaded.'))
        else:
            self.advancedSearchButton.setEnabled(True)
            self.adjustComboBox(1, self.chapter_count, self.advancedFromChapter)
            self.adjustComboBox(1, self.chapter_count, self.advancedToChapter)
            self.adjustComboBox(1, verse_count, self.advancedFromVerse)
            self.adjustComboBox(1, verse_count, self.advancedToVerse)

    def updateAutoCompleter(self):
        """
        This updates the bible book completion list for the search field. The
        completion depends on the bible. It is only updated when we are doing a
        reference search, otherwise the auto completion list is removed.
        """
        log.debug('updateAutoCompleter')
        # Save the current search type to the configuration.
        Settings().setValue('%s/last search type' % self.settings_section, self.quickSearchEdit.current_search_type())
        # Save the current bible to the configuration.
        Settings().setValue(self.settings_section + '/quick bible', self.quickVersionComboBox.currentText())
        books = []
        # We have to do a 'Reference Search'.
        if self.quickSearchEdit.current_search_type() == BibleSearch.Reference:
            bibles = self.plugin.manager.get_bibles()
            bible = self.quickVersionComboBox.currentText()
            if bible:
                book_data = bibles[bible].get_books()
                secondbible = self.quickSecondComboBox.currentText()
                if secondbible != '':
                    secondbook_data = bibles[secondbible].get_books()
                    book_data_temp = []
                    for book in book_data:
                        for secondbook in secondbook_data:
                            if book.book_reference_id == secondbook.book_reference_id:
                                book_data_temp.append(book)
                    book_data = book_data_temp
                language_selection = self.plugin.manager.get_language_selection(bible)
                if language_selection == LanguageSelection.Bible:
                    books = [book.name + ' ' for book in book_data]
                elif language_selection == LanguageSelection.Application:
                    book_names = BibleStrings().BookNames
                    for book in book_data:
                        data = BiblesResourcesDB.get_book_by_id(book.book_reference_id)
                        books.append(str(book_names[data['abbreviation']]) + ' ')
                elif language_selection == LanguageSelection.English:
                    for book in book_data:
                        data = BiblesResourcesDB.get_book_by_id(book.book_reference_id)
                        books.append(data['name'] + ' ')
                books.sort(key=get_locale_key)
        set_case_insensitive_completer(books, self.quickSearchEdit)

    def on_import_click(self):
        if not hasattr(self, 'import_wizard'):
            self.import_wizard = BibleImportForm(self, self.plugin.manager, self.plugin)
        # If the import was not cancelled then reload.
        if self.import_wizard.exec_():
            self.reload_bibles()

    def on_edit_click(self):
        if self.quickTab.isVisible():
            bible = self.quickVersionComboBox.currentText()
        elif self.advancedTab.isVisible():
            bible = self.advancedVersionComboBox.currentText()
        if bible:
            self.editBibleForm = EditBibleForm(self, self.main_window, self.plugin.manager)
            self.editBibleForm.loadBible(bible)
            if self.editBibleForm.exec_():
                self.reload_bibles()

    def on_delete_click(self):
        if self.quickTab.isVisible():
            bible = self.quickVersionComboBox.currentText()
        elif self.advancedTab.isVisible():
            bible = self.advancedVersionComboBox.currentText()
        if bible:
            if QtGui.QMessageBox.question(self, UiStrings().ConfirmDelete,
                translate('BiblesPlugin.MediaItem', 'Are you sure you want to completely delete "%s" Bible from '
                    'OpenLP?\n\nYou will need to re-import this Bible to use it again.') % bible,
                QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Yes | QtGui.QMessageBox.No),
                QtGui.QMessageBox.Yes) == QtGui.QMessageBox.No:
                return
            self.plugin.manager.delete_bible(bible)
            self.reload_bibles()

    def onSearchTabBarCurrentChanged(self, index):
        if index == 0:
            self.advancedTab.setVisible(False)
            self.quickTab.setVisible(True)
            self.quickSearchEdit.setFocus()
        else:
            self.quickTab.setVisible(False)
            self.advancedTab.setVisible(True)
            self.advancedBookComboBox.setFocus()

    def onLockButtonToggled(self, checked):
        if checked:
            self.sender().setIcon(self.lock_icon)
        else:
            self.sender().setIcon(self.unlock_icon)

    def onQuickStyleComboBoxChanged(self):
        self.settings.layout_style = self.quickStyleComboBox.currentIndex()
        self.advancedStyleComboBox.setCurrentIndex(self.settings.layout_style)
        self.settings.layout_style_combo_box.setCurrentIndex(self.settings.layout_style)
        Settings().setValue(self.settings_section + '/verse layout style', self.settings.layout_style)

    def onAdvancedStyleComboBoxChanged(self):
        self.settings.layout_style = self.advancedStyleComboBox.currentIndex()
        self.quickStyleComboBox.setCurrentIndex(self.settings.layout_style)
        self.settings.layout_style_combo_box.setCurrentIndex(self.settings.layout_style)
        Settings().setValue(self.settings_section + '/verse layout style', self.settings.layout_style)

    def onAdvancedVersionComboBox(self):
        Settings().setValue(self.settings_section + '/advanced bible', self.advancedVersionComboBox.currentText())
        self.initialiseAdvancedBible(self.advancedVersionComboBox.currentText(),
            self.advancedBookComboBox.itemData(int(self.advancedBookComboBox.currentIndex())))

    def onAdvancedSecondComboBox(self):
        self.initialiseAdvancedBible(self.advancedVersionComboBox.currentText(),
            self.advancedBookComboBox.itemData(int(self.advancedBookComboBox.currentIndex())))

    def onAdvancedBookComboBox(self):
        item = int(self.advancedBookComboBox.currentIndex())
        self.initialiseChapterVerse(
            self.advancedVersionComboBox.currentText(),
            self.advancedBookComboBox.currentText(),
            self.advancedBookComboBox.itemData(item))

    def onAdvancedFromVerse(self):
        chapter_from = int(self.advancedFromChapter.currentText())
        chapter_to = int(self.advancedToChapter.currentText())
        if chapter_from == chapter_to:
            bible = self.advancedVersionComboBox.currentText()
            book_ref_id = self.advancedBookComboBox.itemData(int(self.advancedBookComboBox.currentIndex()))
            verse_from = int(self.advancedFromVerse.currentText())
            verse_count = self.plugin.manager.get_verse_count_by_book_ref_id(bible, book_ref_id, chapter_to)
            self.adjustComboBox(verse_from, verse_count, self.advancedToVerse, True)

    def onAdvancedToChapter(self):
        bible = self.advancedVersionComboBox.currentText()
        book_ref_id = self.advancedBookComboBox.itemData(int(self.advancedBookComboBox.currentIndex()))
        chapter_from = int(self.advancedFromChapter.currentText())
        chapter_to = int(self.advancedToChapter.currentText())
        verse_from = int(self.advancedFromVerse.currentText())
        verse_to = int(self.advancedToVerse.currentText())
        verse_count = self.plugin.manager.get_verse_count_by_book_ref_id(bible, book_ref_id, chapter_to)
        if chapter_from == chapter_to and verse_from > verse_to:
            self.adjustComboBox(verse_from, verse_count, self.advancedToVerse)
        else:
            self.adjustComboBox(1, verse_count, self.advancedToVerse)

    def onAdvancedFromChapter(self):
        bible = self.advancedVersionComboBox.currentText()
        book_ref_id = self.advancedBookComboBox.itemData(
            int(self.advancedBookComboBox.currentIndex()))
        chapter_from = int(self.advancedFromChapter.currentText())
        chapter_to = int(self.advancedToChapter.currentText())
        verse_count = self.plugin.manager.get_verse_count_by_book_ref_id(bible, book_ref_id, chapter_from)
        self.adjustComboBox(1, verse_count, self.advancedFromVerse)
        if chapter_from > chapter_to:
            self.adjustComboBox(1, verse_count, self.advancedToVerse)
            self.adjustComboBox(chapter_from, self.chapter_count, self.advancedToChapter)
        elif chapter_from == chapter_to:
            self.adjustComboBox(chapter_from, self.chapter_count, self.advancedToChapter)
            self.adjustComboBox(1, verse_count, self.advancedToVerse, True)
        else:
            self.adjustComboBox(chapter_from, self.chapter_count, self.advancedToChapter, True)

    def adjustComboBox(self, range_from, range_to, combo, restore=False):
        """
        Adjusts the given como box to the given values.

        ``range_from``
            The first number of the range (int).

        ``range_to``
            The last number of the range (int).

        ``combo``
            The combo box itself (QComboBox).

        ``restore``
            If True, then the combo's currentText will be restored after
            adjusting (if possible).
        """
        log.debug('adjustComboBox %s, %s, %s', combo, range_from, range_to)
        if restore:
            old_text = combo.currentText()
        combo.clear()
        combo.addItems(list(map(str, list(range(range_from, range_to + 1)))))
        if restore and combo.findText(old_text) != -1:
            combo.setCurrentIndex(combo.findText(old_text))

    def onAdvancedSearchButton(self):
        """
        Does an advanced search and saves the search results.
        """
        log.debug('Advanced Search Button clicked')
        self.advancedSearchButton.setEnabled(False)
        self.application.process_events()
        bible = self.advancedVersionComboBox.currentText()
        second_bible = self.advancedSecondComboBox.currentText()
        book = self.advancedBookComboBox.currentText()
        book_ref_id = self.advancedBookComboBox.itemData(int(self.advancedBookComboBox.currentIndex()))
        chapter_from = self.advancedFromChapter.currentText()
        chapter_to = self.advancedToChapter.currentText()
        verse_from = self.advancedFromVerse.currentText()
        verse_to = self.advancedToVerse.currentText()
        verse_separator = get_reference_separator('sep_v_display')
        range_separator = get_reference_separator('sep_r_display')
        verse_range = chapter_from + verse_separator + verse_from + range_separator + chapter_to + \
            verse_separator + verse_to
        versetext = '%s %s' % (book, verse_range)
        self.application.set_busy_cursor()
        self.search_results = self.plugin.manager.get_verses(bible, versetext, book_ref_id)
        if second_bible:
            self.second_search_results = self.plugin.manager.get_verses(second_bible, versetext, book_ref_id)
        if not self.advancedLockButton.isChecked():
            self.list_view.clear()
        if self.list_view.count() != 0:
            self.__check_second_bible(bible, second_bible)
        elif self.search_results:
            self.displayResults(bible, second_bible)
        self.advancedSearchButton.setEnabled(True)
        self.check_search_result()
        self.application.set_normal_cursor()

    def onQuickSearchButton(self):
        """
        Does a quick search and saves the search results. Quick search can
        either be "Reference Search" or "Text Search".
        """
        log.debug('Quick Search Button clicked')
        self.quickSearchButton.setEnabled(False)
        self.application.process_events()
        bible = self.quickVersionComboBox.currentText()
        second_bible = self.quickSecondComboBox.currentText()
        text = self.quickSearchEdit.text()
        if self.quickSearchEdit.current_search_type() == BibleSearch.Reference:
            # We are doing a 'Reference Search'.
            self.search_results = self.plugin.manager.get_verses(bible, text)
            if second_bible and self.search_results:
                self.second_search_results = self.plugin.manager.get_verses(second_bible, text,
                    self.search_results[0].book.book_reference_id)
        else:
            # We are doing a 'Text Search'.
            self.application.set_busy_cursor()
            bibles = self.plugin.manager.get_bibles()
            self.search_results = self.plugin.manager.verse_search(bible, second_bible, text)
            if second_bible and self.search_results:
                text = []
                new_search_results = []
                count = 0
                passage_not_found = False
                for verse in self.search_results:
                    db_book = bibles[second_bible].get_book_by_book_ref_id(verse.book.book_reference_id)
                    if not db_book:
                        log.debug('Passage "%s %d:%d" not found in Second Bible' %
                            (verse.book.name, verse.chapter, verse.verse))
                        passage_not_found = True
                        count += 1
                        continue
                    new_search_results.append(verse)
                    text.append((verse.book.book_reference_id, verse.chapter,
                        verse.verse, verse.verse))
                if passage_not_found:
                    QtGui.QMessageBox.information(self, translate('BiblesPlugin.MediaItem', 'Information'),
                        translate('BiblesPlugin.MediaItem', 'The second Bible does not contain all the verses '
                            'that are in the main Bible. Only verses found in both Bibles will be shown. %d verses '
                            'have not been included in the results.') % count,
                        QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Ok))
                self.search_results = new_search_results
                self.second_search_results = bibles[second_bible].get_verses(text)
        if not self.quickLockButton.isChecked():
            self.list_view.clear()
        if self.list_view.count() != 0 and self.search_results:
            self.__check_second_bible(bible, second_bible)
        elif self.search_results:
            self.displayResults(bible, second_bible)
        self.quickSearchButton.setEnabled(True)
        self.check_search_result()
        self.application.set_normal_cursor()

    def displayResults(self, bible, second_bible=''):
        """
        Displays the search results in the media manager. All data needed for
        further action is saved for/in each row.
        """
        items = self.buildDisplayResults(bible, second_bible, self.search_results)
        for bible_verse in items:
            self.list_view.addItem(bible_verse)
        self.list_view.selectAll()
        self.search_results = {}
        self.second_search_results = {}

    def buildDisplayResults(self, bible, second_bible, search_results):
        """
        Displays the search results in the media manager. All data needed for
        further action is saved for/in each row.
        """
        verse_separator = get_reference_separator('sep_v_display')
        version = self.plugin.manager.get_meta_data(bible, 'name').value
        copyright = self.plugin.manager.get_meta_data(bible, 'copyright').value
        permissions = self.plugin.manager.get_meta_data(bible, 'permissions').value
        second_version = ''
        second_copyright = ''
        second_permissions = ''
        if second_bible:
            second_version = self.plugin.manager.get_meta_data(second_bible, 'name').value
            second_copyright = self.plugin.manager.get_meta_data(second_bible, 'copyright').value
            second_permissions = self.plugin.manager.get_meta_data(second_bible, 'permissions').value
        items = []
        language_selection = self.plugin.manager.get_language_selection(bible)
        for count, verse in enumerate(search_results):
            book = None
            if language_selection == LanguageSelection.Bible:
                book = verse.book.name
            elif language_selection == LanguageSelection.Application:
                book_names = BibleStrings().BookNames
                data = BiblesResourcesDB.get_book_by_id(verse.book.book_reference_id)
                book = str(book_names[data['abbreviation']])
            elif language_selection == LanguageSelection.English:
                data = BiblesResourcesDB.get_book_by_id(verse.book.book_reference_id)
                book = data['name']
            data = {
                'book': book,
                'chapter': verse.chapter,
                'verse': verse.verse,
                'bible': bible,
                'version': version,
                'copyright': copyright,
                'permissions': permissions,
                'text': verse.text,
                'second_bible': second_bible,
                'second_version': second_version,
                'second_copyright': second_copyright,
                'second_permissions': second_permissions,
                'second_text': ''
            }
            if second_bible:
                try:
                    data['second_text'] = self.second_search_results[count].text
                except IndexError:
                    log.exception('The second_search_results does not have as many verses as the search_results.')
                    break
                bible_text = '%s %d%s%d (%s, %s)' % (book, verse.chapter, verse_separator, verse.verse, version,
                    second_version)
            else:
                bible_text = '%s %d%s%d (%s)' % (book, verse.chapter, verse_separator, verse.verse, version)
            bible_verse = QtGui.QListWidgetItem(bible_text)
            bible_verse.setData(QtCore.Qt.UserRole, data)
            items.append(bible_verse)
        return items

    def generate_slide_data(self, service_item, item=None, xmlVersion=False,
        remote=False, context=ServiceItemContext.Service):
        """
        Generates and formats the slides for the service item as well as the
        service item's title.
        """
        log.debug('generating slide data')
        if item:
            items = item
        else:
            items = self.list_view.selectedItems()
        if not items:
            return False
        bible_text = ''
        old_item = None
        old_chapter = -1
        raw_slides = []
        raw_title = []
        verses = VerseReferenceList()
        for bitem in items:
            book = self._decode_qt_object(bitem, 'book')
            chapter = int(self._decode_qt_object(bitem, 'chapter'))
            verse = int(self._decode_qt_object(bitem, 'verse'))
            bible = self._decode_qt_object(bitem, 'bible')
            version = self._decode_qt_object(bitem, 'version')
            copyright = self._decode_qt_object(bitem, 'copyright')
            permissions = self._decode_qt_object(bitem, 'permissions')
            text = self._decode_qt_object(bitem, 'text')
            second_bible = self._decode_qt_object(bitem, 'second_bible')
            second_version = self._decode_qt_object(bitem, 'second_version')
            second_copyright = self._decode_qt_object(bitem, 'second_copyright')
            second_permissions = self._decode_qt_object(bitem, 'second_permissions')
            second_text = self._decode_qt_object(bitem, 'second_text')
            verses.add(book, chapter, verse, version, copyright, permissions)
            verse_text = self.formatVerse(old_chapter, chapter, verse)
            if second_bible:
                bible_text = '%s&nbsp;%s\n\n%s&nbsp;%s' % (verse_text, text, verse_text, second_text)
                raw_slides.append(bible_text.rstrip())
                bible_text = ''
            # If we are 'Verse Per Slide' then create a new slide.
            elif self.settings.layout_style == LayoutStyle.VersePerSlide:
                bible_text = '%s&nbsp;%s' % (verse_text, text)
                raw_slides.append(bible_text.rstrip())
                bible_text = ''
            # If we are 'Verse Per Line' then force a new line.
            elif self.settings.layout_style == LayoutStyle.VersePerLine:
                bible_text = '%s%s&nbsp;%s\n' % (bible_text, verse_text, text)
            # We have to be 'Continuous'.
            else:
                bible_text = '%s %s&nbsp;%s\n' % (bible_text, verse_text, text)
            bible_text = bible_text.strip(' ')
            if not old_item:
                start_item = bitem
            elif self.checkTitle(bitem, old_item):
                raw_title.append(self.formatTitle(start_item, old_item))
                start_item = bitem
            old_item = bitem
            old_chapter = chapter
        # Add footer
        service_item.raw_footer.append(verses.format_verses())
        if second_bible:
            verses.add_version(second_version, second_copyright, second_permissions)
        service_item.raw_footer.append(verses.format_versions())
        raw_title.append(self.formatTitle(start_item, bitem))
        # If there are no more items we check whether we have to add bible_text.
        if bible_text:
            raw_slides.append(bible_text.lstrip())
            bible_text = ''
        # Service Item: Capabilities
        if self.settings.layout_style == LayoutStyle.Continuous and not second_bible:
            # Split the line but do not replace line breaks in renderer.
            service_item.add_capability(ItemCapabilities.NoLineBreaks)
        service_item.add_capability(ItemCapabilities.CanPreview)
        service_item.add_capability(ItemCapabilities.CanLoop)
        service_item.add_capability(ItemCapabilities.CanWordSplit)
        service_item.add_capability(ItemCapabilities.CanEditTitle)
        # Service Item: Title
        service_item.title = create_separated_list(raw_title)
        # Service Item: Theme
        if not self.settings.bible_theme:
            service_item.theme = None
        else:
            service_item.theme = self.settings.bible_theme
        for slide in raw_slides:
            service_item.add_from_text(slide)
        return True

    def formatTitle(self, start_bitem, old_bitem):
        """
        This method is called, when we have to change the title, because
        we are at the end of a verse range. E. g. if we want to add
        Genesis 1:1-6 as well as Daniel 2:14.

        ``start_item``
            The first item of a range.

        ``old_item``
            The last item of a range.
        """
        verse_separator = get_reference_separator('sep_v_display')
        range_separator = get_reference_separator('sep_r_display')
        old_chapter = self._decode_qt_object(old_bitem, 'chapter')
        old_verse = self._decode_qt_object(old_bitem, 'verse')
        start_book = self._decode_qt_object(start_bitem, 'book')
        start_chapter = self._decode_qt_object(start_bitem, 'chapter')
        start_verse = self._decode_qt_object(start_bitem, 'verse')
        start_bible = self._decode_qt_object(start_bitem, 'bible')
        start_second_bible = self._decode_qt_object(start_bitem, 'second_bible')
        if start_second_bible:
            bibles = '%s, %s' % (start_bible, start_second_bible)
        else:
            bibles = start_bible
        if start_chapter == old_chapter:
            if start_verse == old_verse:
                verse_range = start_chapter + verse_separator + start_verse
            else:
                verse_range = start_chapter + verse_separator + start_verse + range_separator + old_verse
        else:
            verse_range = start_chapter + verse_separator + start_verse + \
                range_separator + old_chapter + verse_separator + old_verse
        return '%s %s (%s)' % (start_book, verse_range, bibles)

    def checkTitle(self, bitem, old_bitem):
        """
        This method checks if we are at the end of an verse range. If that is
        the case, we return True, otherwise False. E. g. if we added
        Genesis 1:1-6, but the next verse is Daniel 2:14, we return True.

        ``item``
            The item we are dealing with at the moment.

        ``old_item``
            The item we were previously dealing with.
        """
        # Get all the necessary meta data.
        book = self._decode_qt_object(bitem, 'book')
        chapter = int(self._decode_qt_object(bitem, 'chapter'))
        verse = int(self._decode_qt_object(bitem, 'verse'))
        bible = self._decode_qt_object(bitem, 'bible')
        second_bible = self._decode_qt_object(bitem, 'second_bible')
        old_book = self._decode_qt_object(old_bitem, 'book')
        old_chapter = int(self._decode_qt_object(old_bitem, 'chapter'))
        old_verse = int(self._decode_qt_object(old_bitem, 'verse'))
        old_bible = self._decode_qt_object(old_bitem, 'bible')
        old_second_bible = self._decode_qt_object(old_bitem, 'second_bible')
        if old_bible != bible or old_second_bible != second_bible or old_book != book:
            # The bible, second bible or book has changed.
            return True
        elif old_verse + 1 != verse and old_chapter == chapter:
            # We are still in the same chapter, but a verse has been skipped.
            return True
        elif old_chapter + 1 == chapter and (verse != 1 or
            old_verse != self.plugin.manager.get_verse_count(old_bible, old_book, old_chapter)):
            # We are in the following chapter, but the last verse was not the
            # last verse of the chapter or the current verse is not the
            # first one of the chapter.
            return True
        return False

    def formatVerse(self, old_chapter, chapter, verse):
        """
        Formats and returns the text, each verse starts with, for the given
        chapter and verse. The text is either surrounded by round, square,
        curly brackets or no brackets at all. For example::

            u'{su}1:1{/su}'

        ``old_chapter``
            The previous verse's chapter number (int).

        ``chapter``
            The chapter number (int).

        ``verse``
            The verse number (int).
        """
        verse_separator = get_reference_separator('sep_v_display')
        if not self.settings.show_new_chapters or old_chapter != chapter:
            verse_text = str(chapter) + verse_separator + str(verse)
        else:
            verse_text = str(verse)
        if self.settings.display_style == DisplayStyle.Round:
            return '{su}(%s){/su}' % verse_text
        if self.settings.display_style == DisplayStyle.Curly:
            return '{su}{%s}{/su}' % verse_text
        if self.settings.display_style == DisplayStyle.Square:
            return '{su}[%s]{/su}' % verse_text
        return '{su}%s{/su}' % verse_text

    def search(self, string, showError):
        """
        Search for some Bible verses (by reference).
        """
        bible = self.quickVersionComboBox.currentText()
        search_results = self.plugin.manager.get_verses(bible, string, False, showError)
        if search_results:
            versetext = ' '.join([verse.text for verse in search_results])
            return [[string, versetext]]
        return []

    def create_item_from_id(self, item_id):
        """
        Create a media item from an item id.
        """
        item = QtGui.QListWidgetItem()
        bible = self.quickVersionComboBox.currentText()
        search_results = self.plugin.manager.get_verses(bible, item_id, False)
        items = self.buildDisplayResults(bible, '', search_results)
        return items
Exemplo n.º 11
0
 def add_end_header_bar(self):
     self.searchTabBar = QtGui.QTabBar(self)
     self.searchTabBar.setExpanding(False)
     self.searchTabBar.setObjectName('searchTabBar')
     self.page_layout.addWidget(self.searchTabBar)
     # Add the Quick Search tab.
     self.addSearchTab('quick', translate('BiblesPlugin.MediaItem', 'Quick'))
     self.quickSearchLabel = QtGui.QLabel(self.quickTab)
     self.quickSearchLabel.setObjectName('quickSearchLabel')
     self.quickLayout.addWidget(self.quickSearchLabel, 0, 0, QtCore.Qt.AlignRight)
     self.quickSearchEdit = SearchEdit(self.quickTab)
     self.quickSearchEdit.setSizePolicy(QtGui.QSizePolicy.Ignored, QtGui.QSizePolicy.Fixed)
     self.quickSearchEdit.setObjectName('quickSearchEdit')
     self.quickSearchLabel.setBuddy(self.quickSearchEdit)
     self.quickLayout.addWidget(self.quickSearchEdit, 0, 1, 1, 2)
     self.addSearchFields('quick', translate('BiblesPlugin.MediaItem', 'Quick'))
     self.quickTab.setVisible(True)
     # Add the Advanced Search tab.
     self.addSearchTab('advanced', UiStrings().Advanced)
     self.advancedBookLabel = QtGui.QLabel(self.advancedTab)
     self.advancedBookLabel.setObjectName('advancedBookLabel')
     self.advancedLayout.addWidget(self.advancedBookLabel, 0, 0, QtCore.Qt.AlignRight)
     self.advancedBookComboBox = create_horizontal_adjusting_combo_box(self.advancedTab, 'advancedBookComboBox')
     self.advancedBookLabel.setBuddy(self.advancedBookComboBox)
     self.advancedLayout.addWidget(self.advancedBookComboBox, 0, 1, 1, 2)
     self.advancedChapterLabel = QtGui.QLabel(self.advancedTab)
     self.advancedChapterLabel.setObjectName('advancedChapterLabel')
     self.advancedLayout.addWidget(self.advancedChapterLabel, 1, 1, 1, 2)
     self.advancedVerseLabel = QtGui.QLabel(self.advancedTab)
     self.advancedVerseLabel.setObjectName('advancedVerseLabel')
     self.advancedLayout.addWidget(self.advancedVerseLabel, 1, 2)
     self.advancedFromLabel = QtGui.QLabel(self.advancedTab)
     self.advancedFromLabel.setObjectName('advancedFromLabel')
     self.advancedLayout.addWidget(self.advancedFromLabel, 3, 0, QtCore.Qt.AlignRight)
     self.advancedFromChapter = QtGui.QComboBox(self.advancedTab)
     self.advancedFromChapter.setObjectName('advancedFromChapter')
     self.advancedLayout.addWidget(self.advancedFromChapter, 3, 1)
     self.advancedFromVerse = QtGui.QComboBox(self.advancedTab)
     self.advancedFromVerse.setObjectName('advancedFromVerse')
     self.advancedLayout.addWidget(self.advancedFromVerse, 3, 2)
     self.advancedToLabel = QtGui.QLabel(self.advancedTab)
     self.advancedToLabel.setObjectName('advancedToLabel')
     self.advancedLayout.addWidget(self.advancedToLabel, 4, 0, QtCore.Qt.AlignRight)
     self.advancedToChapter = QtGui.QComboBox(self.advancedTab)
     self.advancedToChapter.setObjectName('advancedToChapter')
     self.advancedLayout.addWidget(self.advancedToChapter, 4, 1)
     self.advancedToVerse = QtGui.QComboBox(self.advancedTab)
     self.advancedToVerse.setObjectName('advancedToVerse')
     self.advancedLayout.addWidget(self.advancedToVerse, 4, 2)
     self.addSearchFields('advanced', UiStrings().Advanced)
     # Combo Boxes
     self.quickVersionComboBox.activated.connect(self.updateAutoCompleter)
     self.quickSecondComboBox.activated.connect(self.updateAutoCompleter)
     self.advancedVersionComboBox.activated.connect(self.onAdvancedVersionComboBox)
     self.advancedSecondComboBox.activated.connect(self.onAdvancedSecondComboBox)
     self.advancedBookComboBox.activated.connect(self.onAdvancedBookComboBox)
     self.advancedFromChapter.activated.connect(self.onAdvancedFromChapter)
     self.advancedFromVerse.activated.connect(self.onAdvancedFromVerse)
     self.advancedToChapter.activated.connect(self.onAdvancedToChapter)
     QtCore.QObject.connect(self.quickSearchEdit, QtCore.SIGNAL('searchTypeChanged(int)'), self.updateAutoCompleter)
     self.quickVersionComboBox.activated.connect(self.updateAutoCompleter)
     self.quickStyleComboBox.activated.connect(self.onQuickStyleComboBoxChanged)
     self.advancedStyleComboBox.activated.connect(self.onAdvancedStyleComboBoxChanged)
     # Buttons
     self.advancedSearchButton.clicked.connect(self.onAdvancedSearchButton)
     self.quickSearchButton.clicked.connect(self.onQuickSearchButton)
     # Other stuff
     self.quickSearchEdit.returnPressed.connect(self.onQuickSearchButton)
     self.searchTabBar.currentChanged.connect(self.onSearchTabBarCurrentChanged)
Exemplo n.º 12
0
class TestSearchEdit(TestCase, TestMixin):
    """
    Test the EditCustomForm.
    """
    def setUp(self):
        """
        Create the UI
        """
        Registry.create()
        self.setup_application()
        self.main_window = QtWidgets.QMainWindow()
        Registry().register('main_window', self.main_window)

        settings_patcher = patch(
            'openlp.core.lib.searchedit.Settings', return_value=MagicMock(**{'value.return_value': SearchTypes.First}))
        self.addCleanup(settings_patcher.stop)
        self.mocked_settings = settings_patcher.start()

        self.search_edit = SearchEdit(self.main_window, 'settings_section')
        # To complete set up we have to set the search types.
        self.search_edit.set_search_types(SEARCH_TYPES)

    def tearDown(self):
        """
        Delete all the C++ objects at the end so that we don't have a segfault
        """
        del self.search_edit
        del self.main_window

    def test_set_search_types(self):
        """
        Test setting the search types of the search edit.
        """
        # GIVEN: The search edit with the search types set. NOTE: The set_search_types(types) is called in the setUp()
        # method!

        # WHEN:

        # THEN: The first search type should be the first one in the list. The selected type should be saved in the
        #       settings
        self.assertEqual(self.search_edit.current_search_type(), SearchTypes.First,
                         "The first search type should be selected.")
        self.mocked_settings().setValue.assert_called_once_with('settings_section/last search type', 0)

    def test_set_current_search_type(self):
        """
        Test if changing the search type works.
        """
        # GIVEN:
        # WHEN: Change the search type
        result = self.search_edit.set_current_search_type(SearchTypes.Second)

        # THEN:
        self.assertTrue(result, "The call should return success (True).")
        self.assertEqual(self.search_edit.current_search_type(), SearchTypes.Second,
                         "The search type should be SearchTypes.Second")
        self.assertEqual(self.search_edit.placeholderText(), SECOND_PLACEHOLDER_TEXT,
                         "The correct placeholder text should be 'Second Placeholder Text'.")
        self.mocked_settings().setValue.assert_has_calls(
            [call('settings_section/last search type', 0), call('settings_section/last search type', 1)])

    def test_clear_button_visibility(self):
        """
        Test if the clear button is hidden/shown correctly.
        """
        # GIVEN: Everything is left to its defaults (hidden).
        assert self.search_edit.clear_button.isHidden(), "Pre condition not met. Button should be hidden."

        # WHEN: Type something in the search edit.
        QtTest.QTest.keyPress(self.search_edit, QtCore.Qt.Key_A)
        QtTest.QTest.keyRelease(self.search_edit, QtCore.Qt.Key_A)

        # THEN: The clear button should not be hidden any more.
        assert not self.search_edit.clear_button.isHidden(), "The clear button should be visible."

    def test_press_clear_button(self):
        """
        Check if the search edit behaves correctly when pressing the clear button.
        """
        # GIVEN: A search edit with text.
        QtTest.QTest.keyPress(self.search_edit, QtCore.Qt.Key_A)
        QtTest.QTest.keyRelease(self.search_edit, QtCore.Qt.Key_A)

        # WHEN: Press the clear button.
        QtTest.QTest.mouseClick(self.search_edit.clear_button, QtCore.Qt.LeftButton)

        # THEN: The search edit text should be cleared and the button be hidden.
        assert not self.search_edit.text(), "The search edit should not have any text."
        assert self.search_edit.clear_button.isHidden(), "The clear button should be hidden."
Exemplo n.º 13
0
class MediaManagerItem(QtWidgets.QWidget, RegistryProperties):
    """
    MediaManagerItem is a helper widget for plugins.

    None of the following *need* to be used, feel free to override them completely in your plugin's implementation.
    Alternatively, call them from your plugin before or after you've done extra things that you need to.

    **Constructor Parameters**

    ``parent``
        The parent widget. Usually this will be the *Media Manager* itself. This needs to be a class descended from
        ``QWidget``.

    ``plugin``
        The plugin widget. Usually this will be the *Plugin* itself. This needs to be a class descended from ``Plugin``.

    **Member Variables**

    When creating a descendant class from this class for your plugin, the following member variables should be set.

     ``self.on_new_prompt``

        Defaults to *'Select Image(s)'*.

     ``self.on_new_file_masks``
        Defaults to *'Images (*.jpg *jpeg *.gif *.png *.bmp)'*. This assumes that the new action is to load a file. If
        not, you need to override the ``OnNew`` method.

     ``self.PreviewFunction``
        This must be a method which returns a QImage to represent the item (usually a preview). No scaling is required,
        that is performed automatically by OpenLP when necessary. If this method is not defined, a default will be used
        (treat the filename as an image).
    """
    log.info('Media Item loaded')

    def __init__(self, parent=None, plugin=None):
        """
        Constructor to create the media manager item.
        """
        super(MediaManagerItem, self).__init__(parent)
        self.plugin = plugin
        self._setup()
        self.setup_item()

    def _setup(self):
        """
        Run some initial setup. This method is separate from __init__ in order to mock it out in tests.
        """
        self.hide()
        self.whitespace = re.compile(r'[\W_]+', re.UNICODE)
        visible_title = self.plugin.get_string(StringContent.VisibleName)
        self.title = str(visible_title['title'])
        Registry().register(self.plugin.name, self)
        self.settings_section = self.plugin.name
        self.toolbar = None
        self.remote_triggered = None
        self.single_service_item = True
        self.quick_preview_allowed = False
        self.has_search = False
        self.page_layout = QtWidgets.QVBoxLayout(self)
        self.page_layout.setSpacing(0)
        self.page_layout.setContentsMargins(0, 0, 0, 0)
        self.required_icons()
        self.setupUi()
        self.retranslateUi()
        self.auto_select_id = -1

    def setup_item(self):
        """
        Override this for additional Plugin setup
        """
        pass

    def required_icons(self):
        """
        This method is called to define the icons for the plugin. It provides a default set and the plugin is able to
        override the if required.
        """
        self.has_import_icon = False
        self.has_new_icon = True
        self.has_edit_icon = True
        self.has_file_icon = False
        self.has_delete_icon = True
        self.add_to_service_item = False

    def retranslateUi(self):
        """
        This method is called automatically to provide OpenLP with the opportunity to translate the ``MediaManagerItem``
        to another language.
        """
        pass

    def add_toolbar(self):
        """
        A method to help developers easily add a toolbar to the media manager item.
        """
        if self.toolbar is None:
            self.toolbar = OpenLPToolbar(self)
            self.page_layout.addWidget(self.toolbar)

    def setupUi(self):
        """
        This method sets up the interface on the button. Plugin developers use this to add and create toolbars, and the
        rest of the interface of the media manager item.
        """
        # Add a toolbar
        self.add_toolbar()
        # Allow the plugin to define buttons at start of bar
        self.add_start_header_bar()
        # Add the middle of the tool bar (pre defined)
        self.add_middle_header_bar()
        # Allow the plugin to define buttons at end of bar
        self.add_end_header_bar()
        # Add the list view
        self.add_list_view_to_toolbar()

    def add_middle_header_bar(self):
        """
        Create buttons for the media item toolbar
        """
        toolbar_actions = []
        # Import Button
        if self.has_import_icon:
            toolbar_actions.append(['Import', StringContent.Import,
                                    ':/general/general_import.png', self.on_import_click])
        # Load Button
        if self.has_file_icon:
            toolbar_actions.append(['Load', StringContent.Load, ':/general/general_open.png', self.on_file_click])
        # New Button
        if self.has_new_icon:
            toolbar_actions.append(['New', StringContent.New, ':/general/general_new.png', self.on_new_click])
        # Edit Button
        if self.has_edit_icon:
            toolbar_actions.append(['Edit', StringContent.Edit, ':/general/general_edit.png', self.on_edit_click])
        # Delete Button
        if self.has_delete_icon:
            toolbar_actions.append(['Delete', StringContent.Delete,
                                    ':/general/general_delete.png', self.on_delete_click])
        # Preview
        toolbar_actions.append(['Preview', StringContent.Preview,
                                ':/general/general_preview.png', self.on_preview_click])
        # Live Button
        toolbar_actions.append(['Live', StringContent.Live, ':/general/general_live.png', self.on_live_click])
        # Add to service Button
        toolbar_actions.append(['Service', StringContent.Service, ':/general/general_add.png', self.on_add_click])
        for action in toolbar_actions:
            if action[0] == StringContent.Preview:
                self.toolbar.addSeparator()
            self.toolbar.add_toolbar_action('{name}{action}Action'.format(name=self.plugin.name, action=action[0]),
                                            text=self.plugin.get_string(action[1])['title'], icon=action[2],
                                            tooltip=self.plugin.get_string(action[1])['tooltip'],
                                            triggers=action[3])

    def add_list_view_to_toolbar(self):
        """
        Creates the main widget for listing items the media item is tracking
        """
        # Add the List widget
        self.list_view = ListWidgetWithDnD(self, self.plugin.name)
        self.list_view.setObjectName('{name}ListView'.format(name=self.plugin.name))
        # Add to page_layout
        self.page_layout.addWidget(self.list_view)
        if self.has_edit_icon:
            create_widget_action(self.list_view,
                                 text=self.plugin.get_string(StringContent.Edit)['title'],
                                 icon=':/general/general_edit.png',
                                 triggers=self.on_edit_click)
            create_widget_action(self.list_view, separator=True)
        create_widget_action(self.list_view,
                             'listView{plugin}{preview}Item'.format(plugin=self.plugin.name.title(),
                                                                    preview=StringContent.Preview.title()),
                             text=self.plugin.get_string(StringContent.Preview)['title'],
                             icon=':/general/general_preview.png',
                             can_shortcuts=True,
                             triggers=self.on_preview_click)
        create_widget_action(self.list_view,
                             'listView{plugin}{live}Item'.format(plugin=self.plugin.name.title(),
                                                                 live=StringContent.Live.title()),
                             text=self.plugin.get_string(StringContent.Live)['title'],
                             icon=':/general/general_live.png',
                             can_shortcuts=True,
                             triggers=self.on_live_click)
        create_widget_action(self.list_view,
                             'listView{plugin}{service}Item'.format(plugin=self.plugin.name.title(),
                                                                    service=StringContent.Service.title()),
                             can_shortcuts=True,
                             text=self.plugin.get_string(StringContent.Service)['title'],
                             icon=':/general/general_add.png',
                             triggers=self.on_add_click)
        if self.has_delete_icon:
            create_widget_action(self.list_view, separator=True)
            create_widget_action(self.list_view,
                                 'listView{plugin}{delete}Item'.format(plugin=self.plugin.name.title(),
                                                                       delete=StringContent.Delete.title()),
                                 text=self.plugin.get_string(StringContent.Delete)['title'],
                                 icon=':/general/general_delete.png',
                                 can_shortcuts=True, triggers=self.on_delete_click)
        if self.add_to_service_item:
            create_widget_action(self.list_view, separator=True)
            create_widget_action(self.list_view,
                                 text=translate('OpenLP.MediaManagerItem', '&Add to selected Service Item'),
                                 icon=':/general/general_add.png',
                                 triggers=self.on_add_edit_click)
        self.add_custom_context_actions()
        # Create the context menu and add all actions from the list_view.
        self.menu = QtWidgets.QMenu()
        self.menu.addActions(self.list_view.actions())
        self.list_view.doubleClicked.connect(self.on_double_clicked)
        self.list_view.itemSelectionChanged.connect(self.on_selection_change)
        self.list_view.customContextMenuRequested.connect(self.context_menu)

    def add_search_to_toolbar(self):
        """
        Creates a search field with button and related signal handling.
        """
        self.search_widget = QtWidgets.QWidget(self)
        self.search_widget.setObjectName('search_widget')
        self.search_layout = QtWidgets.QVBoxLayout(self.search_widget)
        self.search_layout.setObjectName('search_layout')
        self.search_text_layout = QtWidgets.QFormLayout()
        self.search_text_layout.setObjectName('search_text_layout')
        self.search_text_label = QtWidgets.QLabel(self.search_widget)
        self.search_text_label.setObjectName('search_text_label')
        self.search_text_edit = SearchEdit(self.search_widget, self.settings_section)
        self.search_text_edit.setObjectName('search_text_edit')
        self.search_text_label.setBuddy(self.search_text_edit)
        self.search_text_layout.addRow(self.search_text_label, self.search_text_edit)
        self.search_layout.addLayout(self.search_text_layout)
        self.search_button_layout = QtWidgets.QHBoxLayout()
        self.search_button_layout.setObjectName('search_button_layout')
        self.search_button_layout.addStretch()
        self.search_text_button = QtWidgets.QPushButton(self.search_widget)
        self.search_text_button.setObjectName('search_text_button')
        self.search_button_layout.addWidget(self.search_text_button)
        self.search_layout.addLayout(self.search_button_layout)
        self.page_layout.addWidget(self.search_widget)
        # Signals and slots
        self.search_text_edit.returnPressed.connect(self.on_search_text_button_clicked)
        self.search_text_button.clicked.connect(self.on_search_text_button_clicked)
        self.search_text_edit.textChanged.connect(self.on_search_text_edit_changed)

    def add_custom_context_actions(self):
        """
        Implement this method in your descendant media manager item to add any context menu items.
        This method is called automatically.
        """
        pass

    def initialise(self):
        """
        Implement this method in your descendant media manager item to do any UI or other initialisation.
        This method is called automatically.
        """
        pass

    def add_start_header_bar(self):
        """
        Slot at start of toolbar for plugin to add widgets
        """
        pass

    def add_end_header_bar(self):
        """
        Slot at end of toolbar for plugin to add widgets
        """
        pass

    def on_file_click(self):
        """
        Add a file to the list widget to make it available for showing
        """
        files = FileDialog.getOpenFileNames(self, self.on_new_prompt,
                                            Settings().value(self.settings_section + '/last directory'),
                                            self.on_new_file_masks)
        log.info('New files(s) {files}'.format(files=files))
        if files:
            self.application.set_busy_cursor()
            self.validate_and_load(files)
        self.application.set_normal_cursor()

    def load_file(self, data):
        """
        Turn file from Drag and Drop into an array so the Validate code can run it.

        :param data: A dictionary containing the list of files to be loaded and the target
        """
        new_files = []
        error_shown = False
        for file_name in data['files']:
            file_type = file_name.split('.')[-1]
            if file_type.lower() not in self.on_new_file_masks:
                if not error_shown:
                    critical_error_message_box(translate('OpenLP.MediaManagerItem', 'Invalid File Type'),
                                               translate('OpenLP.MediaManagerItem',
                                                         'Invalid File {name}.\n'
                                                         'Suffix not supported').format(name=file_name))
                    error_shown = True
            else:
                new_files.append(file_name)
        if new_files:
            self.validate_and_load(new_files, data['target'])

    def dnd_move_internal(self, target):
        """
        Handle internal moving of media manager items

        :param target: The target of the DnD action
        """
        pass

    def validate_and_load(self, files, target_group=None):
        """
        Process a list for files either from the File Dialog or from Drag and
        Drop

        :param files: The files to be loaded.
        :param target_group: The QTreeWidgetItem of the group that will be the parent of the added files
        """
        names = []
        full_list = []
        for count in range(self.list_view.count()):
            names.append(self.list_view.item(count).text())
            full_list.append(self.list_view.item(count).data(QtCore.Qt.UserRole))
        duplicates_found = False
        files_added = False
        for file_path in files:
            if file_path in full_list:
                duplicates_found = True
            else:
                files_added = True
                full_list.append(file_path)
        if full_list and files_added:
            if target_group is None:
                self.list_view.clear()
            self.load_list(full_list, target_group)
            last_dir = os.path.split(files[0])[0]
            Settings().setValue(self.settings_section + '/last directory', last_dir)
            Settings().setValue('{section}/{section} files'.format(section=self.settings_section),
                                self.get_file_list())
        if duplicates_found:
            critical_error_message_box(UiStrings().Duplicate,
                                       translate('OpenLP.MediaManagerItem',
                                                 'Duplicate files were found on import and were ignored.'))

    def context_menu(self, point):
        """
        Display a context menu

        :param point: The point the cursor was at
        """
        item = self.list_view.itemAt(point)
        # Decide if we have to show the context menu or not.
        if item is None:
            return
        self.menu.exec(self.list_view.mapToGlobal(point))

    def get_file_list(self):
        """
        Return the current list of files
        """
        file_list = []
        for index in range(self.list_view.count()):
            list_item = self.list_view.item(index)
            filename = list_item.data(QtCore.Qt.UserRole)
            file_list.append(filename)
        return file_list

    def load_list(self, load_list, target_group):
        """
        Load a list. Needs to be implemented by the plugin.

        :param load_list: List object to load
        :param target_group: Group to load
        """
        raise NotImplementedError('MediaManagerItem.loadList needs to be defined by the plugin')

    def on_new_click(self):
        """
        Hook for plugins to define behaviour for adding new items.
        """
        pass

    def on_edit_click(self):
        """
        Hook for plugins to define behaviour for editing items.
        """
        pass

    def on_delete_click(self):
        """
        Delete an item. Needs to be implemented by the plugin.
        """
        raise NotImplementedError('MediaManagerItem.on_delete_click needs to be defined by the plugin')

    def on_focus(self):
        """
        Run when a tab in the media manager gains focus. This gives the media
        item a chance to focus any elements it wants to.
        """
        pass

    def generate_slide_data(self, service_item, item=None, xml_version=False, remote=False,
                            context=ServiceItemContext.Live):
        """
        Generate the slide data. Needs to be implemented by the plugin.
        :param service_item: The service Item to be processed
        :param item: The database item to be used to build the service item
        :param xml_version:
        :param remote: Was this remote triggered (False)
        :param context: The service context
        """
        raise NotImplementedError('MediaManagerItem.generate_slide_data needs to be defined by the plugin')

    def on_double_clicked(self):
        """
        Allows the list click action to be determined dynamically
        """
        if Settings().value('advanced/double click live'):
            self.on_live_click()
        elif not Settings().value('advanced/single click preview'):
            # NOTE: The above check is necessary to prevent bug #1419300
            self.on_preview_click()

    def on_selection_change(self):
        """
        Allows the change of current item in the list to be actioned
        """
        if Settings().value('advanced/single click preview') and self.quick_preview_allowed \
                and self.list_view.selectedIndexes() and self.auto_select_id == -1:
            self.on_preview_click(True)

    def on_preview_click(self, keep_focus=False):
        """
        Preview an item by building a service item then adding that service item to the preview slide controller.

        :param keep_focus: Do we keep focus (False)
        """
        if not self.list_view.selectedIndexes() and not self.remote_triggered:
            QtWidgets.QMessageBox.information(self, UiStrings().NISp,
                                              translate('OpenLP.MediaManagerItem',
                                                        'You must select one or more items to preview.'))
        else:
            log.debug('%s Preview requested' % self.plugin.name)
            Registry().set_flag('has doubleclick added item to service', False)
            service_item = self.build_service_item()
            if service_item:
                service_item.from_plugin = True
                self.preview_controller.add_service_item(service_item)
                if not keep_focus:
                    self.preview_controller.preview_widget.setFocus()

    def on_live_click(self):
        """
        Send an item live by building a service item then adding that service item to the live slide controller.
        """
        if not self.list_view.selectedIndexes():
            QtWidgets.QMessageBox.information(self, UiStrings().NISp,
                                              translate('OpenLP.MediaManagerItem',
                                                        'You must select one or more items to send live.'))
        else:
            self.go_live()

    def go_live_remote(self, message):
        """
        Remote Call wrapper

        :param message: The passed data item_id:Remote.
        """
        self.go_live(message[0], remote=message[1])

    def go_live(self, item_id=None, remote=False):
        """
        Make the currently selected item go live.

        :param item_id: item to make live
        :param remote: From Remote
        """
        log.debug('%s Live requested', self.plugin.name)
        item = None
        if item_id:
            item = self.create_item_from_id(item_id)
        service_item = self.build_service_item(item, remote=remote)
        if service_item:
            if not item_id:
                service_item.from_plugin = True
            if remote:
                service_item.will_auto_start = True
            self.live_controller.add_service_item(service_item)
            self.live_controller.preview_widget.setFocus()

    def create_item_from_id(self, item_id):
        """
        Create a media item from an item id.

        :param item_id: Id to make live
        """
        item = QtWidgets.QListWidgetItem()
        item.setData(QtCore.Qt.UserRole, item_id)
        return item

    def on_add_click(self):
        """
        Add a selected item to the current service
        """
        if not self.list_view.selectedIndexes():
            QtWidgets.QMessageBox.information(self, UiStrings().NISp,
                                              translate('OpenLP.MediaManagerItem',
                                                        'You must select one or more items to add.'))
        else:
            # Is it possible to process multiple list items to generate
            # multiple service items?
            if self.single_service_item:
                log.debug('{plugin} Add requested'.format(plugin=self.plugin.name))
                self.add_to_service(replace=self.remote_triggered)
            else:
                items = self.list_view.selectedIndexes()
                drop_position = self.service_manager.get_drop_position()
                for item in items:
                    self.add_to_service(item, position=drop_position)
                    if drop_position != -1:
                        drop_position += 1

    def add_to_service_remote(self, message):
        """
        Remote Call wrapper

        :param message: The passed data item:Remote.
        """
        self.add_to_service(message[0], remote=message[1])

    def add_to_service(self, item=None, replace=None, remote=False, position=-1):
        """
        Add this item to the current service.

        :param item: Item to be processed
        :param replace: Replace the existing item
        :param remote: Triggered from remote
        :param position: Position to place item
        """
        service_item = self.build_service_item(item, True, remote=remote, context=ServiceItemContext.Service)
        if service_item:
            service_item.from_plugin = False
            self.service_manager.add_service_item(service_item, replace=replace, position=position)

    def on_add_edit_click(self):
        """
        Add a selected item to an existing item in the current service.
        """
        if not self.list_view.selectedIndexes() and not self.remote_triggered:
            QtWidgets.QMessageBox.information(self, UiStrings().NISp,
                                              translate('OpenLP.MediaManagerItem',
                                                        'You must select one or more items.'))
        else:
            log.debug('{plugin} Add requested'.format(plugin=self.plugin.name))
            service_item = self.service_manager.get_service_item()
            if not service_item:
                QtWidgets.QMessageBox.information(self, UiStrings().NISs,
                                                  translate('OpenLP.MediaManagerItem',
                                                            'You must select an existing service item to add to.'))
            elif self.plugin.name == service_item.name:
                self.generate_slide_data(service_item)
                self.service_manager.add_service_item(service_item, replace=True)
            else:
                # Turn off the remote edit update message indicator
                QtWidgets.QMessageBox.information(self, translate('OpenLP.MediaManagerItem', 'Invalid Service Item'),
                                                  translate('OpenLP.MediaManagerItem',
                                                            'You must select a {title} '
                                                            'service item.').format(title=self.title))

    def build_service_item(self, item=None, xml_version=False, remote=False, context=ServiceItemContext.Live):
        """
        Common method for generating a service item
        :param item: Service Item to be built.
        :param xml_version: version of XML (False)
        :param remote: Remote triggered (False)
        :param context: The context on which this is called
        """
        service_item = ServiceItem(self.plugin)
        service_item.add_icon(self.plugin.icon_path)
        if self.generate_slide_data(service_item, item, xml_version, remote, context):
            return service_item
        else:
            return None

    def service_load(self, item):
        """
        Method to add processing when a service has been loaded and individual service items need to be processed by the
        plugins.

        :param item: The item to be processed and returned.
        """
        return item

    def _get_id_of_item_to_generate(self, item, remote_item):
        """
        Utility method to check items being submitted for slide generation.

        :param item: The item to check.
        :param remote_item: The id to assign if the slide generation was remotely triggered.
        """
        if item is None:
            if self.remote_triggered is None:
                item = self.list_view.currentItem()
                if item is None:
                    return False
                item_id = item.data(QtCore.Qt.UserRole)
            else:
                item_id = remote_item
        else:
            item_id = item.data(QtCore.Qt.UserRole)
        return item_id

    def save_auto_select_id(self):
        """
        Sorts out, what item to select after loading a list.
        """
        # The item to select has not been set.
        if self.auto_select_id == -1:
            item = self.list_view.currentItem()
            if item:
                self.auto_select_id = item.data(QtCore.Qt.UserRole)

    def search(self, string, show_error=True):
        """
        Performs a plugin specific search for items containing ``string``

        :param string: String to be displayed
        :param show_error: Should the error be shown (True)
        """
        raise NotImplementedError('Plugin.search needs to be defined by the plugin')
Exemplo n.º 14
0
class MediaManagerItem(QtGui.QWidget):
    """
    MediaManagerItem is a helper widget for plugins.

    None of the following *need* to be used, feel free to override them completely in your plugin's implementation.
    Alternatively, call them from your plugin before or after you've done extra things that you need to.

    **Constructor Parameters**

    ``parent``
        The parent widget. Usually this will be the *Media Manager* itself. This needs to be a class descended from
        ``QWidget``.

    ``plugin``
        The plugin widget. Usually this will be the *Plugin* itself. This needs to be a class descended from ``Plugin``.

    **Member Variables**

    When creating a descendant class from this class for your plugin, the following member variables should be set.

     ``self.on_new_prompt``

        Defaults to *'Select Image(s)'*.

     ``self.on_new_file_masks``
        Defaults to *'Images (*.jpg *jpeg *.gif *.png *.bmp)'*. This assumes that the new action is to load a file. If
        not, you need to override the ``OnNew`` method.

     ``self.PreviewFunction``
        This must be a method which returns a QImage to represent the item (usually a preview). No scaling is required,
        that is performed automatically by OpenLP when necessary. If this method is not defined, a default will be used
        (treat the filename as an image).
    """
    log.info('Media Item loaded')

    def __init__(self, parent=None, plugin=None):
        """
        Constructor to create the media manager item.
        """
        super(MediaManagerItem, self).__init__()
        self.hide()
        self.whitespace = re.compile(r'[\W_]+', re.UNICODE)
        self.plugin = plugin
        visible_title = self.plugin.get_string(StringContent.VisibleName)
        self.title = str(visible_title['title'])
        Registry().register(self.plugin.name, self)
        self.settings_section = self.plugin.name
        self.toolbar = None
        self.remote_triggered = None
        self.single_service_item = True
        self.quick_preview_allowed = False
        self.has_search = False
        self.page_layout = QtGui.QVBoxLayout(self)
        self.page_layout.setSpacing(0)
        self.page_layout.setMargin(0)
        self.required_icons()
        self.setupUi()
        self.retranslateUi()
        self.auto_select_id = -1
        # Need to use event as called across threads and UI is updated
        QtCore.QObject.connect(self, QtCore.SIGNAL('%s_go_live' % self.plugin.name), self.go_live_remote)
        QtCore.QObject.connect(self, QtCore.SIGNAL('%s_add_to_service' % self.plugin.name), self.add_to_service_remote)

    def required_icons(self):
        """
        This method is called to define the icons for the plugin. It provides a default set and the plugin is able to
        override the if required.
        """
        self.has_import_icon = False
        self.has_new_icon = True
        self.has_edit_icon = True
        self.has_file_icon = False
        self.has_delete_icon = True
        self.add_to_service_item = False

    def retranslateUi(self):
        """
        This method is called automatically to provide OpenLP with the opportunity to translate the ``MediaManagerItem``
        to another language.
        """
        pass

    def add_toolbar(self):
        """
        A method to help developers easily add a toolbar to the media manager item.
        """
        if self.toolbar is None:
            self.toolbar = OpenLPToolbar(self)
            self.page_layout.addWidget(self.toolbar)

    def setupUi(self):
        """
        This method sets up the interface on the button. Plugin developers use this to add and create toolbars, and the
        rest of the interface of the media manager item.
        """
        # Add a toolbar
        self.add_toolbar()
        # Allow the plugin to define buttons at start of bar
        self.add_start_header_bar()
        # Add the middle of the tool bar (pre defined)
        self.add_middle_header_bar()
        # Allow the plugin to define buttons at end of bar
        self.add_end_header_bar()
        # Add the list view
        self.add_list_view_to_toolbar()

    def add_middle_header_bar(self):
        """
        Create buttons for the media item toolbar
        """
        toolbar_actions = []
        ## Import Button ##
        if self.has_import_icon:
            toolbar_actions.append(['Import', StringContent.Import,
                ':/general/general_import.png', self.on_import_click])
        ## Load Button ##
        if self.has_file_icon:
            toolbar_actions.append(['Load', StringContent.Load, ':/general/general_open.png', self.on_file_click])
        ## New Button ##
        if self.has_new_icon:
            toolbar_actions.append(['New', StringContent.New, ':/general/general_new.png', self.on_new_click])
        ## Edit Button ##
        if self.has_edit_icon:
            toolbar_actions.append(['Edit', StringContent.Edit, ':/general/general_edit.png', self.on_edit_click])
        ## Delete Button ##
        if self.has_delete_icon:
            toolbar_actions.append(['Delete', StringContent.Delete,
                ':/general/general_delete.png', self.on_delete_click])
        ## Preview ##
        toolbar_actions.append(['Preview', StringContent.Preview,
            ':/general/general_preview.png', self.on_preview_click])
        ## Live Button ##
        toolbar_actions.append(['Live', StringContent.Live, ':/general/general_live.png', self.on_live_click])
        ## Add to service Button ##
        toolbar_actions.append(['Service', StringContent.Service, ':/general/general_add.png', self.on_add_click])
        for action in toolbar_actions:
            if action[0] == StringContent.Preview:
                self.toolbar.addSeparator()
            self.toolbar.add_toolbar_action('%s%sAction' % (self.plugin.name, action[0]),
                text=self.plugin.get_string(action[1])['title'], icon=action[2],
                tooltip=self.plugin.get_string(action[1])['tooltip'],
                triggers=action[3])

    def add_list_view_to_toolbar(self):
        """
        Creates the main widget for listing items the media item is tracking
        """
        # Add the List widget
        self.list_view = ListWidgetWithDnD(self, self.plugin.name)
        self.list_view.setSpacing(1)
        self.list_view.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection)
        self.list_view.setAlternatingRowColors(True)
        self.list_view.setObjectName('%sListView' % self.plugin.name)
        # Add to page_layout
        self.page_layout.addWidget(self.list_view)
        # define and add the context menu
        self.list_view.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        if self.has_edit_icon:
            create_widget_action(self.list_view,
                text=self.plugin.get_string(StringContent.Edit)['title'],
                icon=':/general/general_edit.png',
                triggers=self.on_edit_click)
            create_widget_action(self.list_view, separator=True)
        if self.has_delete_icon:
            create_widget_action(self.list_view,
                'listView%s%sItem' % (self.plugin.name.title(), StringContent.Delete.title()),
                text=self.plugin.get_string(StringContent.Delete)['title'],
                icon=':/general/general_delete.png',
                can_shortcuts=True, triggers=self.on_delete_click)
            create_widget_action(self.list_view, separator=True)
        create_widget_action(self.list_view,
            'listView%s%sItem' % (self.plugin.name.title(), StringContent.Preview.title()),
            text=self.plugin.get_string(StringContent.Preview)['title'],
            icon=':/general/general_preview.png',
            can_shortcuts=True,
            triggers=self.on_preview_click)
        create_widget_action(self.list_view,
            'listView%s%sItem' % (self.plugin.name.title(), StringContent.Live.title()),
            text=self.plugin.get_string(StringContent.Live)['title'],
            icon=':/general/general_live.png',
            can_shortcuts=True,
            triggers=self.on_live_click)
        create_widget_action(self.list_view,
            'listView%s%sItem' % (self.plugin.name.title(), StringContent.Service.title()),
            can_shortcuts=True,
            text=self.plugin.get_string(StringContent.Service)['title'],
            icon=':/general/general_add.png',
            triggers=self.on_add_click)
        if self.add_to_service_item:
            create_widget_action(self.list_view, separator=True)
            create_widget_action(self.list_view,
                text=translate('OpenLP.MediaManagerItem', '&Add to selected Service Item'),
                icon=':/general/general_add.png',
                triggers=self.on_add_edit_click)
        self.add_custom_context_actions()
        # Create the context menu and add all actions from the list_view.
        self.menu = QtGui.QMenu()
        self.menu.addActions(self.list_view.actions())
        self.list_view.doubleClicked.connect(self.on_double_clicked)
        self.list_view.itemSelectionChanged.connect(self.on_selection_change)
        self.list_view.customContextMenuRequested.connect(self.context_menu)

    def add_search_to_toolbar(self):
        """
        Creates a search field with button and related signal handling.
        """
        self.search_widget = QtGui.QWidget(self)
        self.search_widget.setObjectName('search_widget')
        self.search_layout = QtGui.QVBoxLayout(self.search_widget)
        self.search_layout.setObjectName('search_layout')
        self.search_text_layout = QtGui.QFormLayout()
        self.search_text_layout.setObjectName('search_text_layout')
        self.search_text_label = QtGui.QLabel(self.search_widget)
        self.search_text_label.setObjectName('search_text_label')
        self.search_text_edit = SearchEdit(self.search_widget)
        self.search_text_edit.setObjectName('search_text_edit')
        self.search_text_label.setBuddy(self.search_text_edit)
        self.search_text_layout.addRow(self.search_text_label, self.search_text_edit)
        self.search_layout.addLayout(self.search_text_layout)
        self.search_button_layout = QtGui.QHBoxLayout()
        self.search_button_layout.setObjectName('search_button_layout')
        self.search_button_layout.addStretch()
        self.search_text_button = QtGui.QPushButton(self.search_widget)
        self.search_text_button.setObjectName('search_text_button')
        self.search_button_layout.addWidget(self.search_text_button)
        self.search_layout.addLayout(self.search_button_layout)
        self.page_layout.addWidget(self.search_widget)
        # Signals and slots
        self.search_text_edit.returnPressed.connect(self.on_search_text_button_clicked)
        self.search_text_button.clicked.connect(self.on_search_text_button_clicked)
        self.search_text_edit.textChanged.connect(self.on_search_text_edit_changed)

    def add_custom_context_actions(self):
        """
        Implement this method in your descendent media manager item to
        add any context menu items. This method is called automatically.
        """
        pass

    def initialise(self):
        """
        Implement this method in your descendent media manager item to
        do any UI or other initialisation. This method is called automatically.
        """
        pass

    def add_start_header_bar(self):
        """
        Slot at start of toolbar for plugin to addwidgets
        """
        pass

    def add_end_header_bar(self):
        """
        Slot at end of toolbar for plugin to add widgets
        """
        pass

    def on_file_click(self):
        """
        Add a file to the list widget to make it available for showing
        """
        files = QtGui.QFileDialog.getOpenFileNames(self, self.on_new_prompt,
            Settings().value(self.settings_section + '/last directory'), self.on_new_file_masks)
        log.info('New files(s) %s', files)
        if files:
            self.application.set_busy_cursor()
            self.validate_and_load(files)
        self.application.set_normal_cursor()

    def load_file(self, data):
        """
        Turn file from Drag and Drop into an array so the Validate code can run it.

        ``data``
            A dictionary containing the list of files to be loaded and the target
        """
        new_files = []
        error_shown = False
        for file_name in data['files']:
            file_type = file_name.split('.')[-1]
            if file_type.lower() not in self.on_new_file_masks:
                if not error_shown:
                    critical_error_message_box(translate('OpenLP.MediaManagerItem', 'Invalid File Type'),
                        translate('OpenLP.MediaManagerItem', 'Invalid File %s.\nSuffix not supported') % file_name)
                    error_shown = True
            else:
                new_files.append(file_name)
        if new_files:
            self.validate_and_load(new_files, data['target'])

    def dnd_move_internal(self, target):
        """
        Handle internal moving of media manager items

        ``target``
            The target of the DnD action
        """
        pass

    def validate_and_load(self, files, target_group=None):
        """
        Process a list for files either from the File Dialog or from Drag and
        Drop

        ``files``
            The files to be loaded.

        ``target_group``
            The QTreeWidgetItem of the group that will be the parent of the added files
        """
        names = []
        full_list = []
        for count in range(self.list_view.count()):
            names.append(self.list_view.item(count).text())
            full_list.append(self.list_view.item(count).data(QtCore.Qt.UserRole))
        duplicates_found = False
        files_added = False
        for file_path in files:
            filename = os.path.split(str(file_path))[1]
            if filename in names:
                duplicates_found = True
            else:
                files_added = True
                full_list.append(file_path)
        if full_list and files_added:
            if target_group is None:
                self.list_view.clear()
            self.load_list(full_list, target_group)
            last_dir = os.path.split(str(files[0]))[0]
            Settings().setValue(self.settings_section + '/last directory', last_dir)
            Settings().setValue('%s/%s files' % (self.settings_section, self.settings_section), self.get_file_list())
        if duplicates_found:
            critical_error_message_box(UiStrings().Duplicate,
                translate('OpenLP.MediaManagerItem', 'Duplicate files were found on import and were ignored.'))

    def context_menu(self, point):
        """
        Display a context menu
        """
        item = self.list_view.itemAt(point)
        # Decide if we have to show the context menu or not.
        if item is None:
            return
        if not item.flags() & QtCore.Qt.ItemIsSelectable:
            return
        self.menu.exec_(self.list_view.mapToGlobal(point))

    def get_file_list(self):
        """
        Return the current list of files
        """
        file_list = []
        for index in range(self.list_view.count()):
            bitem = self.list_view.item(index)
            filename = bitem.data(QtCore.Qt.UserRole)
            file_list.append(filename)
        return file_list

    def load_list(self, list, target_group):
        """
        Load a list. Needs to be implemented by the plugin.
        """
        raise NotImplementedError('MediaManagerItem.loadList needs to be defined by the plugin')

    def on_new_click(self):
        """
        Hook for plugins to define behaviour for adding new items.
        """
        pass

    def on_edit_click(self):
        """
        Hook for plugins to define behaviour for editing items.
        """
        pass

    def on_delete_click(self):
        """
        Delete an item. Needs to be implemented by the plugin.
        """
        raise NotImplementedError('MediaManagerItem.on_delete_click needs to be defined by the plugin')

    def on_focus(self):
        """
        Run when a tab in the media manager gains focus. This gives the media
        item a chance to focus any elements it wants to.
        """
        pass

    def generate_slide_data(self, service_item, item=None, xml_version=False, remote=False,
            context=ServiceItemContext.Live):
        """
        Generate the slide data. Needs to be implemented by the plugin.
        """
        raise NotImplementedError('MediaManagerItem.generate_slide_data needs to be defined by the plugin')

    def on_double_clicked(self):
        """
        Allows the list click action to be determined dynamically
        """
        if Settings().value('advanced/double click live'):
            self.on_live_click()
        else:
            self.on_preview_click()

    def on_selection_change(self):
        """
        Allows the change of current item in the list to be actioned
        """
        if Settings().value('advanced/single click preview') and self.quick_preview_allowed \
            and self.list_view.selectedIndexes() and self.auto_select_id == -1:
            self.on_preview_click(True)

    def on_preview_click(self, keep_focus=False):
        """
        Preview an item by building a service item then adding that service item to the preview slide controller.
        """
        if not self.list_view.selectedIndexes() and not self.remote_triggered:
            QtGui.QMessageBox.information(self, UiStrings().NISp,
                translate('OpenLP.MediaManagerItem', 'You must select one or more items to preview.'))
        else:
            log.debug('%s Preview requested', self.plugin.name)
            service_item = self.build_service_item()
            if service_item:
                service_item.from_plugin = True
                self.preview_controller.add_service_item(service_item)
                if keep_focus:
                    self.list_view.setFocus()

    def on_live_click(self):
        """
        Send an item live by building a service item then adding that service item to the live slide controller.
        """
        if not self.list_view.selectedIndexes():
            QtGui.QMessageBox.information(self, UiStrings().NISp,
                translate('OpenLP.MediaManagerItem', 'You must select one or more items to send live.'))
        else:
            self.go_live()

    def go_live_remote(self, message):
        """
        Remote Call wrapper

        ``message``
            The passed data item_id:Remote.
        """
        self.go_live(message[0], remote=message[1])

    def go_live(self, item_id=None, remote=False):
        """
        Make the currently selected item go live.
        """
        log.debug('%s Live requested', self.plugin.name)
        item = None
        if item_id:
            item = self.create_item_from_id(item_id)
        service_item = self.build_service_item(item, remote=remote)
        if service_item:
            if not item_id:
                service_item.from_plugin = True
            if remote:
                service_item.will_auto_start = True
            self.live_controller.add_service_item(service_item)

    def create_item_from_id(self, item_id):
        """
        Create a media item from an item id.
        """
        item = QtGui.QListWidgetItem()
        item.setData(QtCore.Qt.UserRole, item_id)
        return item

    def on_add_click(self):
        """
        Add a selected item to the current service
        """
        if not self.list_view.selectedIndexes():
            QtGui.QMessageBox.information(self, UiStrings().NISp,
                translate('OpenLP.MediaManagerItem', 'You must select one or more items to add.'))
        else:
            # Is it possible to process multiple list items to generate
            # multiple service items?
            if self.single_service_item:
                log.debug('%s Add requested', self.plugin.name)
                self.add_to_service(replace=self.remote_triggered)
            else:
                items = self.list_view.selectedIndexes()
                for item in items:
                    self.add_to_service(item)

    def add_to_service_remote(self, message):
        """
        Remote Call wrapper

        ``message``
            The passed data item:Remote.
        """
        self.add_to_service(message[0], remote=message[1])

    def add_to_service(self, item=None, replace=None, remote=False):
        """
        Add this item to the current service.
        """
        service_item = self.build_service_item(item, True, remote=remote, context=ServiceItemContext.Service)
        if service_item:
            service_item.from_plugin = False
            self.service_manager.add_service_item(service_item, replace=replace)

    def on_add_edit_click(self):
        """
        Add a selected item to an existing item in the current service.
        """
        if not self.list_view.selectedIndexes() and not self.remote_triggered:
            QtGui.QMessageBox.information(self, UiStrings().NISp,
                translate('OpenLP.MediaManagerItem', 'You must select one or more items.'))
        else:
            log.debug('%s Add requested', self.plugin.name)
            service_item = self.service_manager.get_service_item()
            if not service_item:
                QtGui.QMessageBox.information(self, UiStrings().NISs,
                    translate('OpenLP.MediaManagerItem', 'You must select an existing service item to add to.'))
            elif self.plugin.name == service_item.name:
                self.generate_slide_data(service_item)
                self.service_manager.add_service_item(service_item, replace=True)
            else:
                # Turn off the remote edit update message indicator
                QtGui.QMessageBox.information(self, translate('OpenLP.MediaManagerItem', 'Invalid Service Item'),
                    translate('OpenLP.MediaManagerItem', 'You must select a %s service item.') % self.title)

    def build_service_item(self, item=None, xml_version=False, remote=False, context=ServiceItemContext.Live):
        """
        Common method for generating a service item
        """
        service_item = ServiceItem(self.plugin)
        service_item.add_icon(self.plugin.icon_path)
        if self.generate_slide_data(service_item, item, xml_version, remote, context):
            return service_item
        else:
            return None

    def service_load(self, item):
        """
        Method to add processing when a service has been loaded and individual service items need to be processed by the
        plugins.

        ``item``
            The item to be processed and returned.
        """
        return item

    def check_search_result(self):
        """
        Checks if the list_view is empty and adds a "No Search Results" item.
        """
        if self.list_view.count():
            return
        message = translate('OpenLP.MediaManagerItem', 'No Search Results')
        item = QtGui.QListWidgetItem(message)
        item.setFlags(QtCore.Qt.NoItemFlags)
        font = QtGui.QFont()
        font.setItalic(True)
        item.setFont(font)
        self.list_view.addItem(item)

    def _get_id_of_item_to_generate(self, item, remote_item):
        """
        Utility method to check items being submitted for slide generation.

        ``item``
            The item to check.

        ``remote_item``
            The id to assign if the slide generation was remotely triggered.
        """
        if item is None:
            if self.remote_triggered is None:
                item = self.list_view.currentItem()
                if item is None:
                    return False
                item_id = item.data(QtCore.Qt.UserRole)
            else:
                item_id = remote_item
        else:
            item_id = item.data(QtCore.Qt.UserRole)
        return item_id

    def save_auto_select_id(self):
        """
        Sorts out, what item to select after loading a list.
        """
        # The item to select has not been set.
        if self.auto_select_id == -1:
            item = self.list_view.currentItem()
            if item:
                self.auto_select_id = item.data(QtCore.Qt.UserRole)

    def search(self, string, show_error=True):
        """
        Performs a plugin specific search for items containing ``string``
        """
        raise NotImplementedError('Plugin.search needs to be defined by the plugin')

    def _get_main_window(self):
        """
        Adds the main window to the class dynamically
        """
        if not hasattr(self, '_main_window'):
            self._main_window = Registry().get('main_window')
        return self._main_window

    main_window = property(_get_main_window)

    def _get_renderer(self):
        """
        Adds the Renderer to the class dynamically
        """
        if not hasattr(self, '_renderer'):
            self._renderer = Registry().get('renderer')
        return self._renderer

    renderer = property(_get_renderer)

    def _get_live_controller(self):
        """
        Adds the live controller to the class dynamically
        """
        if not hasattr(self, '_live_controller'):
            self._live_controller = Registry().get('live_controller')
        return self._live_controller

    live_controller = property(_get_live_controller)

    def _get_preview_controller(self):
        """
        Adds the preview controller to the class dynamically
        """
        if not hasattr(self, '_preview_controller'):
            self._preview_controller = Registry().get('preview_controller')
        return self._preview_controller

    preview_controller = property(_get_preview_controller)

    def _get_plugin_manager(self):
        """
        Adds the plugin manager to the class dynamically
        """
        if not hasattr(self, '_plugin_manager'):
            self._plugin_manager = Registry().get('plugin_manager')
        return self._plugin_manager

    plugin_manager = property(_get_plugin_manager)

    def _get_media_controller(self):
        """
        Adds the media controller to the class dynamically
        """
        if not hasattr(self, '_media_controller'):
            self._media_controller = Registry().get('media_controller')
        return self._media_controller

    media_controller = property(_get_media_controller)

    def _get_service_manager(self):
        """
        Adds the service manager to the class dynamically
        """
        if not hasattr(self, '_service_manager'):
            self._service_manager = Registry().get('service_manager')
        return self._service_manager

    service_manager = property(_get_service_manager)

    def _get_theme_manager(self):
        """
        Adds the theme manager to the class dynamically
        """
        if not hasattr(self, '_theme_manager'):
            self._theme_manager = Registry().get('theme_manager')
        return self._theme_manager

    theme_manager = property(_get_theme_manager)

    def _get_application(self):
        """
        Adds the openlp to the class dynamically.
        Windows needs to access the application in a dynamic manner.
        """
        if os.name == 'nt':
            return Registry().get('application')
        else:
            if not hasattr(self, '_application'):
                self._application = Registry().get('application')
            return self._application

    application = property(_get_application)