This commit is contained in:
Phill Ridout 2017-11-06 22:41:36 +00:00
commit 66534a58ab
140 changed files with 2413 additions and 2450 deletions

View File

@ -46,6 +46,7 @@ if __name__ == '__main__':
"""
Instantiate and run the application.
"""
faulthandler.enable()
set_up_fault_handling()
# Add support for using multiprocessing from frozen Windows executable (built using PyInstaller),
# see https://docs.python.org/3/library/multiprocessing.html#multiprocessing.freeze_support

View File

@ -39,9 +39,9 @@ from openlp.core.api.http import application
from openlp.core.api.poll import Poller
from openlp.core.common.applocation import AppLocation
from openlp.core.common.i18n import UiStrings
from openlp.core.common.mixins import OpenLPMixin, RegistryMixin
from openlp.core.common.mixins import LogMixin, RegistryProperties
from openlp.core.common.path import create_paths
from openlp.core.common.registry import RegistryProperties, Registry
from openlp.core.common.registry import Registry, RegistryBase
from openlp.core.common.settings import Settings
from openlp.core.common.i18n import translate
@ -73,7 +73,7 @@ class HttpWorker(QtCore.QObject):
pass
class HttpServer(RegistryMixin, RegistryProperties, OpenLPMixin):
class HttpServer(RegistryBase, RegistryProperties, LogMixin):
"""
Wrapper round a server instance
"""

View File

@ -23,7 +23,7 @@
import json
from openlp.core.common.httputils import get_web_page
from openlp.core.common.registry import RegistryProperties
from openlp.core.common.mixins import RegistryProperties
from openlp.core.common.settings import Settings

View File

@ -31,8 +31,8 @@ import time
from PyQt5 import QtCore
from openlp.core.common.mixins import OpenLPMixin
from openlp.core.common.registry import Registry, RegistryProperties
from openlp.core.common.mixins import LogMixin, RegistryProperties
from openlp.core.common.registry import Registry
from openlp.core.common.settings import Settings
log = logging.getLogger(__name__)
@ -61,7 +61,7 @@ class WebSocketWorker(QtCore.QObject):
self.ws_server.stop = True
class WebSocketServer(RegistryProperties, OpenLPMixin):
class WebSocketServer(RegistryProperties, LogMixin):
"""
Wrapper round a server instance
"""

View File

@ -38,7 +38,7 @@ from PyQt5 import QtCore, QtWidgets
from openlp.core.common import is_macosx, is_win
from openlp.core.common.applocation import AppLocation
from openlp.core.common.i18n import LanguageManager, UiStrings, translate
from openlp.core.common.mixins import OpenLPMixin
from openlp.core.common.mixins import LogMixin
from openlp.core.common.path import create_paths, copytree
from openlp.core.common.registry import Registry
from openlp.core.common.settings import Settings
@ -59,7 +59,7 @@ __all__ = ['OpenLP', 'main']
log = logging.getLogger()
class OpenLP(OpenLPMixin, QtWidgets.QApplication):
class OpenLP(QtWidgets.QApplication, LogMixin):
"""
The core application class. This class inherits from Qt's QApplication
class in order to provide the core of the application.

View File

@ -25,25 +25,29 @@ Provide Error Handling and login Services
import logging
import inspect
from openlp.core.common import trace_error_handler, de_hump
from openlp.core.common import is_win, trace_error_handler
from openlp.core.common.registry import Registry
DO_NOT_TRACE_EVENTS = ['timerEvent', 'paintEvent', 'drag_enter_event', 'drop_event', 'on_controller_size_changed',
'preview_size_changed', 'resizeEvent']
class OpenLPMixin(object):
class LogMixin(object):
"""
Base Calling object for OpenLP classes.
"""
def __init__(self, *args, **kwargs):
super(OpenLPMixin, self).__init__(*args, **kwargs)
self.logger = logging.getLogger("%s.%s" % (self.__module__, self.__class__.__name__))
if self.logger.getEffectiveLevel() == logging.DEBUG:
@property
def logger(self):
if hasattr(self, '_logger') and self._logger:
return self._logger
else:
self._logger = logging.getLogger("%s.%s" % (self.__module__, self.__class__.__name__))
if self._logger.getEffectiveLevel() == logging.DEBUG:
for name, m in inspect.getmembers(self, inspect.ismethod):
if name not in DO_NOT_TRACE_EVENTS:
if not name.startswith("_") and not name.startswith("log"):
setattr(self, name, self.logging_wrapper(m, self))
return self._logger
def logging_wrapper(self, func, parent):
"""
@ -93,30 +97,127 @@ class OpenLPMixin(object):
self.logger.exception(message)
class RegistryMixin(object):
class RegistryProperties(object):
"""
This adds registry components to classes to use at run time.
"""
def __init__(self, parent):
@property
def application(self):
"""
Register the class and bootstrap hooks.
Adds the openlp to the class dynamically.
Windows needs to access the application in a dynamic manner.
"""
try:
super(RegistryMixin, self).__init__(parent)
except TypeError:
super(RegistryMixin, self).__init__()
Registry().register(de_hump(self.__class__.__name__), self)
Registry().register_function('bootstrap_initialise', self.bootstrap_initialise)
Registry().register_function('bootstrap_post_set_up', self.bootstrap_post_set_up)
if is_win():
return Registry().get('application')
else:
if not hasattr(self, '_application') or not self._application:
self._application = Registry().get('application')
return self._application
def bootstrap_initialise(self):
@property
def plugin_manager(self):
"""
Dummy method to be overridden
Adds the plugin manager to the class dynamically
"""
pass
if not hasattr(self, '_plugin_manager') or not self._plugin_manager:
self._plugin_manager = Registry().get('plugin_manager')
return self._plugin_manager
def bootstrap_post_set_up(self):
@property
def image_manager(self):
"""
Dummy method to be overridden
Adds the image manager to the class dynamically
"""
pass
if not hasattr(self, '_image_manager') or not self._image_manager:
self._image_manager = Registry().get('image_manager')
return self._image_manager
@property
def media_controller(self):
"""
Adds the media controller to the class dynamically
"""
if not hasattr(self, '_media_controller') or not self._media_controller:
self._media_controller = Registry().get('media_controller')
return self._media_controller
@property
def service_manager(self):
"""
Adds the service manager to the class dynamically
"""
if not hasattr(self, '_service_manager') or not self._service_manager:
self._service_manager = Registry().get('service_manager')
return self._service_manager
@property
def preview_controller(self):
"""
Adds the preview controller to the class dynamically
"""
if not hasattr(self, '_preview_controller') or not self._preview_controller:
self._preview_controller = Registry().get('preview_controller')
return self._preview_controller
@property
def live_controller(self):
"""
Adds the live controller to the class dynamically
"""
if not hasattr(self, '_live_controller') or not self._live_controller:
self._live_controller = Registry().get('live_controller')
return self._live_controller
@property
def main_window(self):
"""
Adds the main window to the class dynamically
"""
if not hasattr(self, '_main_window') or not self._main_window:
self._main_window = Registry().get('main_window')
return self._main_window
@property
def renderer(self):
"""
Adds the Renderer to the class dynamically
"""
if not hasattr(self, '_renderer') or not self._renderer:
self._renderer = Registry().get('renderer')
return self._renderer
@property
def theme_manager(self):
"""
Adds the theme manager to the class dynamically
"""
if not hasattr(self, '_theme_manager') or not self._theme_manager:
self._theme_manager = Registry().get('theme_manager')
return self._theme_manager
@property
def settings_form(self):
"""
Adds the settings form to the class dynamically
"""
if not hasattr(self, '_settings_form') or not self._settings_form:
self._settings_form = Registry().get('settings_form')
return self._settings_form
@property
def alerts_manager(self):
"""
Adds the alerts manager to the class dynamically
"""
if not hasattr(self, '_alerts_manager') or not self._alerts_manager:
self._alerts_manager = Registry().get('alerts_manager')
return self._alerts_manager
@property
def projector_manager(self):
"""
Adds the projector manager to the class dynamically
"""
if not hasattr(self, '_projector_manager') or not self._projector_manager:
self._projector_manager = Registry().get('projector_manager')
return self._projector_manager

View File

@ -25,7 +25,7 @@ Provide Registry Services
import logging
import sys
from openlp.core.common import is_win, trace_error_handler
from openlp.core.common import de_hump, trace_error_handler
log = logging.getLogger(__name__)
@ -61,6 +61,15 @@ class Registry(object):
registry.initialising = True
return registry
@classmethod
def destroy(cls):
"""
Destroy the Registry.
"""
if cls.__instance__.running_under_test:
del cls.__instance__
cls.__instance__ = None
def get(self, key):
"""
Extracts the registry value from the list based on the key passed in
@ -119,7 +128,7 @@ class Registry(object):
:param event: The function description..
:param function: The function to be called when the event happens.
"""
if event in self.functions_list:
if event in self.functions_list and function in self.functions_list[event]:
self.functions_list[event].remove(function)
def execute(self, event, *args, **kwargs):
@ -178,128 +187,30 @@ class Registry(object):
del self.working_flags[key]
class RegistryProperties(object):
class RegistryBase(object):
"""
This adds registry components to classes to use at run time.
"""
def __init__(self, *args, **kwargs):
"""
Register the class and bootstrap hooks.
"""
try:
super().__init__(*args, **kwargs)
except TypeError:
super().__init__()
Registry().register(de_hump(self.__class__.__name__), self)
Registry().register_function('bootstrap_initialise', self.bootstrap_initialise)
Registry().register_function('bootstrap_post_set_up', self.bootstrap_post_set_up)
@property
def application(self):
def bootstrap_initialise(self):
"""
Adds the openlp to the class dynamically.
Windows needs to access the application in a dynamic manner.
Dummy method to be overridden
"""
if is_win():
return Registry().get('application')
else:
if not hasattr(self, '_application') or not self._application:
self._application = Registry().get('application')
return self._application
pass
@property
def plugin_manager(self):
def bootstrap_post_set_up(self):
"""
Adds the plugin manager to the class dynamically
Dummy method to be overridden
"""
if not hasattr(self, '_plugin_manager') or not self._plugin_manager:
self._plugin_manager = Registry().get('plugin_manager')
return self._plugin_manager
@property
def image_manager(self):
"""
Adds the image manager to the class dynamically
"""
if not hasattr(self, '_image_manager') or not self._image_manager:
self._image_manager = Registry().get('image_manager')
return self._image_manager
@property
def media_controller(self):
"""
Adds the media controller to the class dynamically
"""
if not hasattr(self, '_media_controller') or not self._media_controller:
self._media_controller = Registry().get('media_controller')
return self._media_controller
@property
def service_manager(self):
"""
Adds the service manager to the class dynamically
"""
if not hasattr(self, '_service_manager') or not self._service_manager:
self._service_manager = Registry().get('service_manager')
return self._service_manager
@property
def preview_controller(self):
"""
Adds the preview controller to the class dynamically
"""
if not hasattr(self, '_preview_controller') or not self._preview_controller:
self._preview_controller = Registry().get('preview_controller')
return self._preview_controller
@property
def live_controller(self):
"""
Adds the live controller to the class dynamically
"""
if not hasattr(self, '_live_controller') or not self._live_controller:
self._live_controller = Registry().get('live_controller')
return self._live_controller
@property
def main_window(self):
"""
Adds the main window to the class dynamically
"""
if not hasattr(self, '_main_window') or not self._main_window:
self._main_window = Registry().get('main_window')
return self._main_window
@property
def renderer(self):
"""
Adds the Renderer to the class dynamically
"""
if not hasattr(self, '_renderer') or not self._renderer:
self._renderer = Registry().get('renderer')
return self._renderer
@property
def theme_manager(self):
"""
Adds the theme manager to the class dynamically
"""
if not hasattr(self, '_theme_manager') or not self._theme_manager:
self._theme_manager = Registry().get('theme_manager')
return self._theme_manager
@property
def settings_form(self):
"""
Adds the settings form to the class dynamically
"""
if not hasattr(self, '_settings_form') or not self._settings_form:
self._settings_form = Registry().get('settings_form')
return self._settings_form
@property
def alerts_manager(self):
"""
Adds the alerts manager to the class dynamically
"""
if not hasattr(self, '_alerts_manager') or not self._alerts_manager:
self._alerts_manager = Registry().get('alerts_manager')
return self._alerts_manager
@property
def projector_manager(self):
"""
Adds the projector manager to the class dynamically
"""
if not hasattr(self, '_projector_manager') or not self._projector_manager:
self._projector_manager = Registry().get('projector_manager')
return self._projector_manager
pass

View File

@ -25,9 +25,9 @@ import re
from string import Template
from PyQt5 import QtGui, QtCore, QtWebKitWidgets
from openlp.core.common.mixins import OpenLPMixin, RegistryMixin
from openlp.core.common.mixins import LogMixin, RegistryProperties
from openlp.core.common.path import path_to_str
from openlp.core.common.registry import Registry, RegistryProperties
from openlp.core.common.registry import Registry, RegistryBase
from openlp.core.common.settings import Settings
from openlp.core.display.screens import ScreenList
from openlp.core.lib import FormattingTags, ImageSource, ItemCapabilities, ServiceItem, expand_tags, build_chords_css, \
@ -46,7 +46,7 @@ VERSE_FOR_LINE_COUNT = '\n'.join(map(str, range(100)))
FOOTER = ['Arky Arky (Unknown)', 'Public Domain', 'CCLI 123456']
class Renderer(OpenLPMixin, RegistryMixin, RegistryProperties):
class Renderer(RegistryBase, LogMixin, RegistryProperties):
"""
Class to pull all Renderer interactions into one place. The plugins will call helper methods to do the rendering but
this class will provide display defense code.

View File

@ -350,6 +350,7 @@ class Manager(object):
resulting in the plugin_name being used.
:param upgrade_mod: The upgrade_schema function for this database
"""
super().__init__()
self.is_dirty = False
self.session = None
self.db_url = None

View File

@ -30,14 +30,15 @@ from PyQt5 import QtCore, QtWidgets
from openlp.core.common.i18n import UiStrings, translate
from openlp.core.common.path import Path, path_to_str, str_to_path
from openlp.core.common.registry import Registry, RegistryProperties
from openlp.core.common.mixins import RegistryProperties
from openlp.core.common.registry import Registry
from openlp.core.common.settings import Settings
from openlp.core.lib import ServiceItem, StringContent, ServiceItemContext
from openlp.core.lib.searchedit import SearchEdit
from openlp.core.lib.ui import create_widget_action, critical_error_message_box
from openlp.core.ui.lib.filedialog import FileDialog
from openlp.core.ui.lib.listwidgetwithdnd import ListWidgetWithDnD
from openlp.core.ui.lib.toolbar import OpenLPToolbar
from openlp.core.widgets.dialogs import FileDialog
from openlp.core.widgets.edits import SearchEdit
from openlp.core.widgets.toolbar import OpenLPToolbar
from openlp.core.widgets.views import ListWidgetWithDnD
log = logging.getLogger(__name__)

View File

@ -26,9 +26,10 @@ import logging
from PyQt5 import QtCore
from openlp.core.common.registry import Registry, RegistryProperties
from openlp.core.common.settings import Settings
from openlp.core.common.i18n import UiStrings
from openlp.core.common.mixins import RegistryProperties
from openlp.core.common.registry import Registry
from openlp.core.common.settings import Settings
from openlp.core.version import get_version
log = logging.getLogger(__name__)

View File

@ -26,12 +26,12 @@ import os
from openlp.core.common import extension_loader
from openlp.core.common.applocation import AppLocation
from openlp.core.common.registry import RegistryProperties
from openlp.core.common.mixins import OpenLPMixin, RegistryMixin
from openlp.core.common.mixins import LogMixin, RegistryProperties
from openlp.core.common.registry import RegistryBase
from openlp.core.lib import Plugin, PluginStatus
class PluginManager(RegistryMixin, OpenLPMixin, RegistryProperties):
class PluginManager(RegistryBase, LogMixin, RegistryProperties):
"""
This is the Plugin manager, which loads all the plugins,
and executes all the hooks, as and when necessary.

View File

@ -49,7 +49,7 @@ from openlp.core.lib.projector import upgrade
Base = declarative_base(MetaData())
class CommonBase(object):
class CommonMixin(object):
"""
Base class to automate table name and ID column.
"""
@ -60,7 +60,7 @@ class CommonBase(object):
id = Column(Integer, primary_key=True)
class Manufacturer(CommonBase, Base):
class Manufacturer(Base, CommonMixin):
"""
Projector manufacturer table.
@ -85,7 +85,7 @@ class Manufacturer(CommonBase, Base):
lazy='joined')
class Model(CommonBase, Base):
class Model(Base, CommonMixin):
"""
Projector model table.
@ -113,7 +113,7 @@ class Model(CommonBase, Base):
lazy='joined')
class Source(CommonBase, Base):
class Source(Base, CommonMixin):
"""
Projector video source table.
@ -140,7 +140,7 @@ class Source(CommonBase, Base):
text = Column(String(30))
class Projector(CommonBase, Base):
class Projector(Base, CommonMixin):
"""
Projector table.
@ -213,7 +213,7 @@ class Projector(CommonBase, Base):
lazy='joined')
class ProjectorSource(CommonBase, Base):
class ProjectorSource(Base, CommonMixin):
"""
Projector local source table
This table allows mapping specific projector source input to a local

View File

@ -514,7 +514,7 @@ class PJLinkCommands(object):
self.sw_version_received = data
class PJLink(PJLinkCommands, QtNetwork.QTcpSocket):
class PJLink(QtNetwork.QTcpSocket, PJLinkCommands):
"""
Socket service for PJLink TCP socket.
"""

View File

@ -1,175 +0,0 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2017 OpenLP Developers #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
import logging
from PyQt5 import QtCore, QtWidgets
from openlp.core.common.settings import Settings
from openlp.core.lib import build_icon
from openlp.core.lib.ui import create_widget_action
log = logging.getLogger(__name__)
class SearchEdit(QtWidgets.QLineEdit):
"""
This is a specialised QLineEdit with a "clear" button inside for searches.
"""
searchTypeChanged = QtCore.pyqtSignal(QtCore.QVariant)
cleared = QtCore.pyqtSignal()
def __init__(self, parent, settings_section):
"""
Constructor.
"""
super().__init__(parent)
self.settings_section = settings_section
self._current_search_type = -1
self.clear_button = QtWidgets.QToolButton(self)
self.clear_button.setIcon(build_icon(':/system/clear_shortcut.png'))
self.clear_button.setCursor(QtCore.Qt.ArrowCursor)
self.clear_button.setStyleSheet('QToolButton { border: none; padding: 0px; }')
self.clear_button.resize(18, 18)
self.clear_button.hide()
self.clear_button.clicked.connect(self._on_clear_button_clicked)
self.textChanged.connect(self._on_search_edit_text_changed)
self._update_style_sheet()
self.setAcceptDrops(False)
def _update_style_sheet(self):
"""
Internal method to update the stylesheet depending on which widgets are available and visible.
"""
frame_width = self.style().pixelMetric(QtWidgets.QStyle.PM_DefaultFrameWidth)
right_padding = self.clear_button.width() + frame_width
if hasattr(self, 'menu_button'):
left_padding = self.menu_button.width()
stylesheet = 'QLineEdit {{ padding-left:{left}px; padding-right: {right}px; }} '.format(left=left_padding,
right=right_padding)
else:
stylesheet = 'QLineEdit {{ padding-right: {right}px; }} '.format(right=right_padding)
self.setStyleSheet(stylesheet)
msz = self.minimumSizeHint()
self.setMinimumSize(max(msz.width(), self.clear_button.width() + (frame_width * 2) + 2),
max(msz.height(), self.clear_button.height() + (frame_width * 2) + 2))
def resizeEvent(self, event):
"""
Reimplemented method to react to resizing of the widget.
:param event: The event that happened.
"""
size = self.clear_button.size()
frame_width = self.style().pixelMetric(QtWidgets.QStyle.PM_DefaultFrameWidth)
self.clear_button.move(self.rect().right() - frame_width - size.width(),
(self.rect().bottom() + 1 - size.height()) // 2)
if hasattr(self, 'menu_button'):
size = self.menu_button.size()
self.menu_button.move(self.rect().left() + frame_width + 2, (self.rect().bottom() + 1 - size.height()) // 2)
def current_search_type(self):
"""
Readonly property to return the current search type.
"""
return self._current_search_type
def set_current_search_type(self, identifier):
"""
Set a new current search type.
:param identifier: The search type identifier (int).
"""
menu = self.menu_button.menu()
for action in menu.actions():
if identifier == action.data():
self.setPlaceholderText(action.placeholder_text)
self.menu_button.setDefaultAction(action)
self._current_search_type = identifier
Settings().setValue('{section}/last used search type'.format(section=self.settings_section), identifier)
self.searchTypeChanged.emit(identifier)
return True
def set_search_types(self, items):
"""
A list of tuples to be used in the search type menu. The first item in the list will be preselected as the
default.
:param items: The list of tuples to use. The tuples should contain an integer identifier, an icon (QIcon
instance or string) and a title for the item in the menu. In short, they should look like this::
(<identifier>, <icon>, <title>, <place holder text>)
For instance::
(1, <QIcon instance>, "Titles", "Search Song Titles...")
Or::
(2, ":/songs/authors.png", "Authors", "Search Authors...")
"""
menu = QtWidgets.QMenu(self)
for identifier, icon, title, placeholder in items:
action = create_widget_action(
menu, text=title, icon=icon, data=identifier, triggers=self._on_menu_action_triggered)
action.placeholder_text = placeholder
if not hasattr(self, 'menu_button'):
self.menu_button = QtWidgets.QToolButton(self)
self.menu_button.setIcon(build_icon(':/system/clear_shortcut.png'))
self.menu_button.setCursor(QtCore.Qt.ArrowCursor)
self.menu_button.setPopupMode(QtWidgets.QToolButton.InstantPopup)
self.menu_button.setStyleSheet('QToolButton { border: none; padding: 0px 10px 0px 0px; }')
self.menu_button.resize(QtCore.QSize(28, 18))
self.menu_button.setMenu(menu)
self.set_current_search_type(
Settings().value('{section}/last used search type'.format(section=self.settings_section)))
self.menu_button.show()
self._update_style_sheet()
def _on_search_edit_text_changed(self, text):
"""
Internally implemented slot to react to when the text in the line edit has changed so that we can show or hide
the clear button.
:param text: A :class:`~PyQt5.QtCore.QString` instance which represents the text in the line edit.
"""
self.clear_button.setVisible(bool(text))
def _on_clear_button_clicked(self):
"""
Internally implemented slot to react to the clear button being clicked to clear the line edit. Once it has
cleared the line edit, it emits the ``cleared()`` signal so that an application can react to the clearing of the
line edit.
"""
self.clear()
self.cleared.emit()
def _on_menu_action_triggered(self):
"""
Internally implemented slot to react to the select of one of the search types in the menu. Once it has set the
correct action on the button, and set the current search type (using the list of identifiers provided by the
developer), the ``searchTypeChanged(int)`` signal is emitted with the identifier.
"""
for action in self.menu_button.menu().actions():
# Why is this needed?
action.setChecked(False)
self.set_current_search_type(self.sender().data())

View File

@ -35,7 +35,7 @@ from PyQt5 import QtGui
from openlp.core.common import md5_hash
from openlp.core.common.applocation import AppLocation
from openlp.core.common.i18n import translate
from openlp.core.common.registry import RegistryProperties
from openlp.core.common.mixins import RegistryProperties
from openlp.core.common.settings import Settings
from openlp.core.lib import ImageSource, build_icon, clean_tags, expand_tags, expand_chords

View File

@ -25,7 +25,7 @@ own tab to the settings dialog.
"""
from PyQt5 import QtWidgets
from openlp.core.common.registry import RegistryProperties
from openlp.core.common.mixins import RegistryProperties
class SettingsTab(QtWidgets.QWidget, RegistryProperties):

View File

@ -32,8 +32,9 @@ from openlp.core.common.applocation import AppLocation
from openlp.core.common.i18n import UiStrings, format_time, translate
from openlp.core.common.settings import Settings
from openlp.core.lib import SettingsTab, build_icon
from openlp.core.ui.lib import PathEdit, PathType
from openlp.core.ui.style import HAS_DARK_STYLE
from openlp.core.widgets.edits import PathEdit
from openlp.core.widgets.enums import PathEditType
log = logging.getLogger(__name__)
@ -122,7 +123,7 @@ class AdvancedTab(SettingsTab):
self.data_directory_layout.setObjectName('data_directory_layout')
self.data_directory_new_label = QtWidgets.QLabel(self.data_directory_group_box)
self.data_directory_new_label.setObjectName('data_directory_current_label')
self.data_directory_path_edit = PathEdit(self.data_directory_group_box, path_type=PathType.Directories,
self.data_directory_path_edit = PathEdit(self.data_directory_group_box, path_type=PathEditType.Directories,
default_path=AppLocation.get_directory(AppLocation.DataDir))
self.data_directory_layout.addRow(self.data_directory_new_label, self.data_directory_path_edit)
self.new_data_directory_has_files_label = QtWidgets.QLabel(self.data_directory_group_box)

View File

@ -72,10 +72,10 @@ except ImportError:
from openlp.core.common import is_linux
from openlp.core.common.i18n import UiStrings, translate
from openlp.core.common.registry import RegistryProperties
from openlp.core.common.mixins import RegistryProperties
from openlp.core.common.settings import Settings
from openlp.core.ui.exceptiondialog import Ui_ExceptionDialog
from openlp.core.ui.lib.filedialog import FileDialog
from openlp.core.widgets.dialogs import FileDialog
from openlp.core.version import get_version

View File

@ -25,7 +25,8 @@ The file rename dialog.
from PyQt5 import QtCore, QtWidgets
from openlp.core.common.i18n import translate
from openlp.core.common.registry import Registry, RegistryProperties
from openlp.core.common.mixins import RegistryProperties
from openlp.core.common.registry import Registry
from openlp.core.ui.filerenamedialog import Ui_FileRenameDialog

View File

@ -38,7 +38,8 @@ from openlp.core.common import clean_button_text, trace_error_handler
from openlp.core.common.applocation import AppLocation
from openlp.core.common.i18n import translate
from openlp.core.common.path import Path, create_paths
from openlp.core.common.registry import Registry, RegistryProperties
from openlp.core.common.mixins import RegistryProperties
from openlp.core.common.registry import Registry
from openlp.core.common.settings import Settings
from openlp.core.lib import PluginStatus, build_icon
from openlp.core.lib.ui import critical_error_message_box

View File

@ -33,7 +33,8 @@ from openlp.core.common.registry import Registry
from openlp.core.common.settings import Settings
from openlp.core.display.screens import ScreenList
from openlp.core.lib import SettingsTab
from openlp.core.ui.lib import ColorButton, PathEdit
from openlp.core.widgets.buttons import ColorButton
from openlp.core.widgets.edits import PathEdit
log = logging.getLogger(__name__)

View File

@ -1,84 +0,0 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2017 OpenLP Developers #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
The :mod:`~openlp.core.ui.lib.historycombobox` module contains the HistoryComboBox widget
"""
from PyQt5 import QtCore, QtWidgets
class HistoryComboBox(QtWidgets.QComboBox):
"""
The :class:`~openlp.core.common.historycombobox.HistoryComboBox` widget emulates the QLineEdit ``returnPressed``
signal for when the :kbd:`Enter` or :kbd:`Return` keys are pressed, and saves anything that is typed into the edit
box into its list.
"""
returnPressed = QtCore.pyqtSignal()
def __init__(self, parent=None):
"""
Initialise the combo box, setting duplicates to False and the insert policy to insert items at the top.
:param parent: The parent widget
"""
super().__init__(parent)
self.setDuplicatesEnabled(False)
self.setEditable(True)
self.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Preferred)
self.setInsertPolicy(QtWidgets.QComboBox.InsertAtTop)
def keyPressEvent(self, event):
"""
Override the inherited keyPressEvent method to emit the ``returnPressed`` signal and to save the current text to
the dropdown list.
:param event: The keyboard event
"""
# Handle Enter and Return ourselves
if event.key() == QtCore.Qt.Key_Enter or event.key() == QtCore.Qt.Key_Return:
# Emit the returnPressed signal
self.returnPressed.emit()
# Save the current text to the dropdown list
if self.currentText() and self.findText(self.currentText()) == -1:
self.insertItem(0, self.currentText())
# Let the parent handle any keypress events
super().keyPressEvent(event)
def focusOutEvent(self, event):
"""
Override the inherited focusOutEvent to save the current text to the dropdown list.
:param event: The focus event
"""
# Save the current text to the dropdown list
if self.currentText() and self.findText(self.currentText()) == -1:
self.insertItem(0, self.currentText())
# Let the parent handle any keypress events
super().focusOutEvent(event)
def getItems(self):
"""
Get all the items from the history
:return: A list of strings
"""
return [self.itemText(i) for i in range(self.count())]

View File

@ -1,153 +0,0 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2017 OpenLP Developers #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
Extend QListWidget to handle drag and drop functionality
"""
import os
from PyQt5 import QtCore, QtGui, QtWidgets
from openlp.core.common.i18n import UiStrings
from openlp.core.common.registry import Registry
class ListWidgetWithDnD(QtWidgets.QListWidget):
"""
Provide a list widget to store objects and handle drag and drop events
"""
def __init__(self, parent=None, name=''):
"""
Initialise the list widget
"""
super().__init__(parent)
self.mime_data_text = name
self.no_results_text = UiStrings().NoResults
self.setSpacing(1)
self.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
self.setAlternatingRowColors(True)
self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
def activateDnD(self):
"""
Activate DnD of widget
"""
self.setAcceptDrops(True)
self.setDragDropMode(QtWidgets.QAbstractItemView.DragDrop)
Registry().register_function(('%s_dnd' % self.mime_data_text), self.parent().load_file)
def clear(self, search_while_typing=False):
"""
Re-implement clear, so that we can customise feedback when using 'Search as you type'
:param search_while_typing: True if we want to display the customised message
:return: None
"""
if search_while_typing:
self.no_results_text = UiStrings().ShortResults
else:
self.no_results_text = UiStrings().NoResults
super().clear()
def mouseMoveEvent(self, event):
"""
Drag and drop event does not care what data is selected as the recipient will use events to request the data
move just tell it what plugin to call
"""
if event.buttons() != QtCore.Qt.LeftButton:
event.ignore()
return
if not self.selectedItems():
event.ignore()
return
drag = QtGui.QDrag(self)
mime_data = QtCore.QMimeData()
drag.setMimeData(mime_data)
mime_data.setText(self.mime_data_text)
drag.exec(QtCore.Qt.CopyAction)
def dragEnterEvent(self, event):
"""
When something is dragged into this object, check if you should be able to drop it in here.
"""
if event.mimeData().hasUrls():
event.accept()
else:
event.ignore()
def dragMoveEvent(self, event):
"""
Make an object droppable, and set it to copy the contents of the object, not move it.
"""
if event.mimeData().hasUrls():
event.setDropAction(QtCore.Qt.CopyAction)
event.accept()
else:
event.ignore()
def dropEvent(self, event):
"""
Receive drop event check if it is a file and process it if it is.
:param event: Handle of the event pint passed
"""
if event.mimeData().hasUrls():
event.setDropAction(QtCore.Qt.CopyAction)
event.accept()
files = []
for url in event.mimeData().urls():
local_file = os.path.normpath(url.toLocalFile())
if os.path.isfile(local_file):
files.append(local_file)
elif os.path.isdir(local_file):
listing = os.listdir(local_file)
for file in listing:
files.append(os.path.join(local_file, file))
Registry().execute('{mime_data}_dnd'.format(mime_data=self.mime_data_text),
{'files': files, 'target': self.itemAt(event.pos())})
else:
event.ignore()
def allItems(self):
"""
An generator to list all the items in the widget
:return: a generator
"""
for row in range(self.count()):
yield self.item(row)
def paintEvent(self, event):
"""
Re-implement paintEvent so that we can add 'No Results' text when the listWidget is empty.
:param event: A QPaintEvent
:return: None
"""
super().paintEvent(event)
if not self.count():
viewport = self.viewport()
painter = QtGui.QPainter(viewport)
font = QtGui.QFont()
font.setItalic(True)
painter.setFont(font)
painter.drawText(QtCore.QRect(0, 0, viewport.width(), viewport.height()),
(QtCore.Qt.AlignHCenter | QtCore.Qt.TextWordWrap), self.no_results_text)

View File

@ -1,196 +0,0 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2017 OpenLP Developers #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
from enum import Enum
from PyQt5 import QtCore, QtWidgets
from openlp.core.common.i18n import UiStrings, translate
from openlp.core.common.path import Path, path_to_str, str_to_path
from openlp.core.lib import build_icon
from openlp.core.ui.lib.filedialog import FileDialog
class PathType(Enum):
Files = 1
Directories = 2
class PathEdit(QtWidgets.QWidget):
"""
The :class:`~openlp.core.ui.lib.pathedit.PathEdit` class subclasses QWidget to create a custom widget for use when
a file or directory needs to be selected.
"""
pathChanged = QtCore.pyqtSignal(Path)
def __init__(self, parent=None, path_type=PathType.Files, default_path=None, dialog_caption=None, show_revert=True):
"""
Initialise the PathEdit widget
:param QtWidget.QWidget | None: The parent of the widget. This is just passed to the super method.
:param str dialog_caption: Used to customise the caption in the QFileDialog.
:param openlp.core.common.path.Path default_path: The default path. This is set as the path when the revert
button is clicked
:param bool show_revert: Used to determine if the 'revert button' should be visible.
:rtype: None
"""
super().__init__(parent)
self.default_path = default_path
self.dialog_caption = dialog_caption
self._path_type = path_type
self._path = None
self.filters = '{all_files} (*)'.format(all_files=UiStrings().AllFiles)
self._setup(show_revert)
def _setup(self, show_revert):
"""
Set up the widget
:param bool show_revert: Show or hide the revert button
:rtype: None
"""
widget_layout = QtWidgets.QHBoxLayout()
widget_layout.setContentsMargins(0, 0, 0, 0)
self.line_edit = QtWidgets.QLineEdit(self)
widget_layout.addWidget(self.line_edit)
self.browse_button = QtWidgets.QToolButton(self)
self.browse_button.setIcon(build_icon(':/general/general_open.png'))
widget_layout.addWidget(self.browse_button)
self.revert_button = QtWidgets.QToolButton(self)
self.revert_button.setIcon(build_icon(':/general/general_revert.png'))
self.revert_button.setVisible(show_revert)
widget_layout.addWidget(self.revert_button)
self.setLayout(widget_layout)
# Signals and Slots
self.browse_button.clicked.connect(self.on_browse_button_clicked)
self.revert_button.clicked.connect(self.on_revert_button_clicked)
self.line_edit.editingFinished.connect(self.on_line_edit_editing_finished)
self.update_button_tool_tips()
@QtCore.pyqtProperty('QVariant')
def path(self):
"""
A property getter method to return the selected path.
:return: The selected path
:rtype: openlp.core.common.path.Path
"""
return self._path
@path.setter
def path(self, path):
"""
A Property setter method to set the selected path
:param openlp.core.common.path.Path path: The path to set the widget to
:rtype: None
"""
self._path = path
text = path_to_str(path)
self.line_edit.setText(text)
self.line_edit.setToolTip(text)
@property
def path_type(self):
"""
A property getter method to return the path_type. Path type allows you to sepecify if the user is restricted to
selecting a file or directory.
:return: The type selected
:rtype: PathType
"""
return self._path_type
@path_type.setter
def path_type(self, path_type):
"""
A Property setter method to set the path type
:param PathType path_type: The type of path to select
:rtype: None
"""
self._path_type = path_type
self.update_button_tool_tips()
def update_button_tool_tips(self):
"""
Called to update the tooltips on the buttons. This is changing path types, and when the widget is initalised
:rtype: None
"""
if self._path_type == PathType.Directories:
self.browse_button.setToolTip(translate('OpenLP.PathEdit', 'Browse for directory.'))
self.revert_button.setToolTip(translate('OpenLP.PathEdit', 'Revert to default directory.'))
else:
self.browse_button.setToolTip(translate('OpenLP.PathEdit', 'Browse for file.'))
self.revert_button.setToolTip(translate('OpenLP.PathEdit', 'Revert to default file.'))
def on_browse_button_clicked(self):
"""
A handler to handle a click on the browse button.
Show the QFileDialog and process the input from the user
:rtype: None
"""
caption = self.dialog_caption
path = None
if self._path_type == PathType.Directories:
if not caption:
caption = translate('OpenLP.PathEdit', 'Select Directory')
path = FileDialog.getExistingDirectory(self, caption, self._path, FileDialog.ShowDirsOnly)
elif self._path_type == PathType.Files:
if not caption:
caption = self.dialog_caption = translate('OpenLP.PathEdit', 'Select File')
path, filter_used = FileDialog.getOpenFileName(self, caption, self._path, self.filters)
if path:
self.on_new_path(path)
def on_revert_button_clicked(self):
"""
A handler to handle a click on the revert button.
Set the new path to the value of the default_path instance variable.
:rtype: None
"""
self.on_new_path(self.default_path)
def on_line_edit_editing_finished(self):
"""
A handler to handle when the line edit has finished being edited.
:rtype: None
"""
path = str_to_path(self.line_edit.text())
self.on_new_path(path)
def on_new_path(self, path):
"""
A method called to validate and set a new path.
Emits the pathChanged Signal
:param openlp.core.common.path.Path path: The new path
:rtype: None
"""
if self._path != path:
self._path = path
self.pathChanged.emit(path)

View File

@ -1,205 +0,0 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2017 OpenLP Developers #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
The :mod:`~openlp.core.lib.spelltextedit` module contains a classes to add spell checking to an edit widget.
"""
import logging
import re
try:
import enchant
from enchant import DictNotFoundError
from enchant.errors import Error
ENCHANT_AVAILABLE = True
except ImportError:
ENCHANT_AVAILABLE = False
# based on code from http://john.nachtimwald.com/2009/08/22/qplaintextedit-with-in-line-spell-check
from PyQt5 import QtCore, QtGui, QtWidgets
from openlp.core.common.i18n import translate
from openlp.core.lib import FormattingTags
from openlp.core.lib.ui import create_action
log = logging.getLogger(__name__)
class SpellTextEdit(QtWidgets.QPlainTextEdit):
"""
Spell checking widget based on QPlanTextEdit.
"""
def __init__(self, parent=None, formatting_tags_allowed=True):
"""
Constructor.
"""
global ENCHANT_AVAILABLE
super(SpellTextEdit, self).__init__(parent)
self.formatting_tags_allowed = formatting_tags_allowed
# Default dictionary based on the current locale.
if ENCHANT_AVAILABLE:
try:
self.dictionary = enchant.Dict()
self.highlighter = Highlighter(self.document())
self.highlighter.spelling_dictionary = self.dictionary
except (Error, DictNotFoundError):
ENCHANT_AVAILABLE = False
log.debug('Could not load default dictionary')
def mousePressEvent(self, event):
"""
Handle mouse clicks within the text edit region.
"""
if event.button() == QtCore.Qt.RightButton:
# Rewrite the mouse event to a left button event so the cursor is moved to the location of the pointer.
event = QtGui.QMouseEvent(QtCore.QEvent.MouseButtonPress,
event.pos(), QtCore.Qt.LeftButton, QtCore.Qt.LeftButton, QtCore.Qt.NoModifier)
QtWidgets.QPlainTextEdit.mousePressEvent(self, event)
def contextMenuEvent(self, event):
"""
Provide the context menu for the text edit region.
"""
popup_menu = self.createStandardContextMenu()
# Select the word under the cursor.
cursor = self.textCursor()
# only select text if not already selected
if not cursor.hasSelection():
cursor.select(QtGui.QTextCursor.WordUnderCursor)
self.setTextCursor(cursor)
# Add menu with available languages.
if ENCHANT_AVAILABLE:
lang_menu = QtWidgets.QMenu(translate('OpenLP.SpellTextEdit', 'Language:'))
for lang in enchant.list_languages():
action = create_action(lang_menu, lang, text=lang, checked=lang == self.dictionary.tag)
lang_menu.addAction(action)
popup_menu.insertSeparator(popup_menu.actions()[0])
popup_menu.insertMenu(popup_menu.actions()[0], lang_menu)
lang_menu.triggered.connect(self.set_language)
# Check if the selected word is misspelled and offer spelling suggestions if it is.
if ENCHANT_AVAILABLE and self.textCursor().hasSelection():
text = self.textCursor().selectedText()
if not self.dictionary.check(text):
spell_menu = QtWidgets.QMenu(translate('OpenLP.SpellTextEdit', 'Spelling Suggestions'))
for word in self.dictionary.suggest(text):
action = SpellAction(word, spell_menu)
action.correct.connect(self.correct_word)
spell_menu.addAction(action)
# Only add the spelling suggests to the menu if there are suggestions.
if spell_menu.actions():
popup_menu.insertMenu(popup_menu.actions()[0], spell_menu)
tag_menu = QtWidgets.QMenu(translate('OpenLP.SpellTextEdit', 'Formatting Tags'))
if self.formatting_tags_allowed:
for html in FormattingTags.get_html_tags():
action = SpellAction(html['desc'], tag_menu)
action.correct.connect(self.html_tag)
tag_menu.addAction(action)
popup_menu.insertSeparator(popup_menu.actions()[0])
popup_menu.insertMenu(popup_menu.actions()[0], tag_menu)
popup_menu.exec(event.globalPos())
def set_language(self, action):
"""
Changes the language for this spelltextedit.
:param action: The action.
"""
self.dictionary = enchant.Dict(action.text())
self.highlighter.spelling_dictionary = self.dictionary
self.highlighter.highlightBlock(self.toPlainText())
self.highlighter.rehighlight()
def correct_word(self, word):
"""
Replaces the selected text with word.
"""
cursor = self.textCursor()
cursor.beginEditBlock()
cursor.removeSelectedText()
cursor.insertText(word)
cursor.endEditBlock()
def html_tag(self, tag):
"""
Replaces the selected text with word.
"""
tag = tag.replace('&', '')
for html in FormattingTags.get_html_tags():
if tag == html['desc']:
cursor = self.textCursor()
if self.textCursor().hasSelection():
text = cursor.selectedText()
cursor.beginEditBlock()
cursor.removeSelectedText()
cursor.insertText(html['start tag'])
cursor.insertText(text)
cursor.insertText(html['end tag'])
cursor.endEditBlock()
else:
cursor = self.textCursor()
cursor.insertText(html['start tag'])
cursor.insertText(html['end tag'])
class Highlighter(QtGui.QSyntaxHighlighter):
"""
Provides a text highlighter for pointing out spelling errors in text.
"""
WORDS = r'(?iu)[\w\']+'
def __init__(self, *args):
"""
Constructor
"""
super(Highlighter, self).__init__(*args)
self.spelling_dictionary = None
def highlightBlock(self, text):
"""
Highlight mis spelt words in a block of text.
Note, this is a Qt hook.
"""
if not self.spelling_dictionary:
return
text = str(text)
char_format = QtGui.QTextCharFormat()
char_format.setUnderlineColor(QtCore.Qt.red)
char_format.setUnderlineStyle(QtGui.QTextCharFormat.SpellCheckUnderline)
for word_object in re.finditer(self.WORDS, text):
if not self.spelling_dictionary.check(word_object.group()):
self.setFormat(word_object.start(), word_object.end() - word_object.start(), char_format)
class SpellAction(QtWidgets.QAction):
"""
A special QAction that returns the text in a signal.
"""
correct = QtCore.pyqtSignal(str)
def __init__(self, *args):
"""
Constructor
"""
super(SpellAction, self).__init__(*args)
self.triggered.connect(lambda x: self.correct.emit(self.text()))

View File

@ -1,145 +0,0 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2017 OpenLP Developers #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
Extend QTreeWidget to handle drag and drop functionality
"""
import os
from PyQt5 import QtCore, QtGui, QtWidgets
from openlp.core.common import is_win
from openlp.core.common.registry import Registry
class TreeWidgetWithDnD(QtWidgets.QTreeWidget):
"""
Provide a tree widget to store objects and handle drag and drop events
"""
def __init__(self, parent=None, name=''):
"""
Initialise the tree widget
"""
super(TreeWidgetWithDnD, self).__init__(parent)
self.mime_data_text = name
self.allow_internal_dnd = False
self.header().close()
self.default_indentation = self.indentation()
self.setIndentation(0)
self.setAnimated(True)
def activateDnD(self):
"""
Activate DnD of widget
"""
self.setAcceptDrops(True)
self.setDragDropMode(QtWidgets.QAbstractItemView.DragDrop)
Registry().register_function(('%s_dnd' % self.mime_data_text), self.parent().load_file)
Registry().register_function(('%s_dnd_internal' % self.mime_data_text), self.parent().dnd_move_internal)
def mouseMoveEvent(self, event):
"""
Drag and drop event does not care what data is selected as the recipient will use events to request the data
move just tell it what plugin to call
:param event: The event that occurred
"""
if event.buttons() != QtCore.Qt.LeftButton:
event.ignore()
return
if not self.selectedItems():
event.ignore()
return
drag = QtGui.QDrag(self)
mime_data = QtCore.QMimeData()
drag.setMimeData(mime_data)
mime_data.setText(self.mime_data_text)
drag.exec(QtCore.Qt.CopyAction)
def dragEnterEvent(self, event):
"""
Receive drag enter event, check if it is a file or internal object and allow it if it is.
:param event: The event that occurred
"""
if event.mimeData().hasUrls():
event.accept()
elif self.allow_internal_dnd:
event.accept()
else:
event.ignore()
def dragMoveEvent(self, event):
"""
Receive drag move event, check if it is a file or internal object and allow it if it is.
:param event: The event that occurred
"""
QtWidgets.QTreeWidget.dragMoveEvent(self, event)
if event.mimeData().hasUrls():
event.setDropAction(QtCore.Qt.CopyAction)
event.accept()
elif self.allow_internal_dnd:
event.setDropAction(QtCore.Qt.CopyAction)
event.accept()
else:
event.ignore()
def dropEvent(self, event):
"""
Receive drop event, check if it is a file or internal object and process it if it is.
:param event: Handle of the event pint passed
"""
# If we are on Windows, OpenLP window will not be set on top. For example, user can drag images to Library and
# the folder stays on top of the group creation box. This piece of code fixes this issue.
if is_win():
self.setWindowState(self.windowState() & ~QtCore.Qt.WindowMinimized | QtCore.Qt.WindowActive)
self.setWindowState(QtCore.Qt.WindowNoState)
if event.mimeData().hasUrls():
event.setDropAction(QtCore.Qt.CopyAction)
event.accept()
files = []
for url in event.mimeData().urls():
local_file = url.toLocalFile()
if os.path.isfile(local_file):
files.append(local_file)
elif os.path.isdir(local_file):
listing = os.listdir(local_file)
for file_name in listing:
files.append(os.path.join(local_file, file_name))
Registry().execute('%s_dnd' % self.mime_data_text, {'files': files, 'target': self.itemAt(event.pos())})
elif self.allow_internal_dnd:
event.setDropAction(QtCore.Qt.CopyAction)
event.accept()
Registry().execute('%s_dnd_internal' % self.mime_data_text, self.itemAt(event.pos()))
else:
event.ignore()
# Convenience methods for emulating a QListWidget. This helps keeping MediaManagerItem simple.
def addItem(self, item):
self.addTopLevelItem(item)
def count(self):
return self.topLevelItemCount()
def item(self, index):
return self.topLevelItem(index)

View File

@ -36,9 +36,9 @@ from PyQt5 import QtCore, QtWidgets, QtWebKit, QtWebKitWidgets, QtGui, QtMultime
from openlp.core.common import is_macosx, is_win
from openlp.core.common.applocation import AppLocation
from openlp.core.common.i18n import translate
from openlp.core.common.mixins import OpenLPMixin
from openlp.core.common.mixins import LogMixin, RegistryProperties
from openlp.core.common.path import path_to_str
from openlp.core.common.registry import Registry, RegistryProperties
from openlp.core.common.registry import Registry
from openlp.core.common.settings import Settings
from openlp.core.display.screens import ScreenList
from openlp.core.lib import ServiceItem, ImageSource, build_html, expand_tags, image_to_byte
@ -131,7 +131,7 @@ class Display(QtWidgets.QGraphicsView):
self.web_loaded = True
class MainDisplay(OpenLPMixin, Display, RegistryProperties):
class MainDisplay(Display, LogMixin, RegistryProperties):
"""
This is the display screen as a specialized class from the Display class
"""
@ -603,7 +603,7 @@ class MainDisplay(OpenLPMixin, Display, RegistryProperties):
self.web_view.setGeometry(0, 0, self.width(), self.height())
class AudioPlayer(OpenLPMixin, QtCore.QObject):
class AudioPlayer(LogMixin, QtCore.QObject):
"""
This Class will play audio only allowing components to work with a soundtrack independent of the user interface.
"""

View File

@ -41,7 +41,8 @@ from openlp.core.common.actions import ActionList, CategoryOrder
from openlp.core.common.applocation import AppLocation
from openlp.core.common.i18n import LanguageManager, UiStrings, translate
from openlp.core.common.path import Path, copyfile, create_paths, path_to_str, str_to_path
from openlp.core.common.registry import Registry, RegistryProperties
from openlp.core.common.mixins import RegistryProperties
from openlp.core.common.registry import Registry
from openlp.core.common.settings import Settings
from openlp.core.display.screens import ScreenList
from openlp.core.display.renderer import Renderer
@ -50,9 +51,8 @@ from openlp.core.lib.ui import create_action
from openlp.core.ui import AboutForm, SettingsForm, ServiceManager, ThemeManager, LiveController, PluginForm, \
ShortcutListForm, FormattingTagForm, PreviewController
from openlp.core.ui.firsttimeform import FirstTimeForm
from openlp.core.ui.lib.dockwidget import OpenLPDockWidget
from openlp.core.ui.lib.filedialog import FileDialog
from openlp.core.ui.lib.mediadockmanager import MediaDockManager
from openlp.core.widgets.dialogs import FileDialog
from openlp.core.widgets.docks import OpenLPDockWidget, MediaDockManager
from openlp.core.ui.media import MediaController
from openlp.core.ui.printserviceform import PrintServiceForm
from openlp.core.ui.projector.manager import ProjectorManager

View File

@ -31,8 +31,8 @@ from PyQt5 import QtCore, QtWidgets
from openlp.core.api.http import register_endpoint
from openlp.core.common import extension_loader
from openlp.core.common.i18n import UiStrings, translate
from openlp.core.common.mixins import OpenLPMixin, RegistryMixin
from openlp.core.common.registry import Registry, RegistryProperties
from openlp.core.common.mixins import LogMixin, RegistryProperties
from openlp.core.common.registry import Registry, RegistryBase
from openlp.core.common.settings import Settings
from openlp.core.lib import ItemCapabilities
from openlp.core.lib.ui import critical_error_message_box
@ -42,7 +42,7 @@ from openlp.core.ui.media.vendor.mediainfoWrapper import MediaInfoWrapper
from openlp.core.ui.media.mediaplayer import MediaPlayer
from openlp.core.ui.media import MediaState, MediaInfo, MediaType, get_media_players, set_media_players,\
parse_optical_path
from openlp.core.ui.lib.toolbar import OpenLPToolbar
from openlp.core.widgets.toolbar import OpenLPToolbar
log = logging.getLogger(__name__)
@ -91,7 +91,7 @@ class MediaSlider(QtWidgets.QSlider):
QtWidgets.QSlider.mouseReleaseEvent(self, event)
class MediaController(RegistryMixin, OpenLPMixin, RegistryProperties):
class MediaController(RegistryBase, LogMixin, RegistryProperties):
"""
The implementation of the Media Controller. The Media Controller adds an own class for every Player.
Currently these are QtWebkit, Phonon and Vlc. display_controllers are an array of controllers keyed on the

View File

@ -22,7 +22,7 @@
"""
The :mod:`~openlp.core.ui.media.mediaplayer` module contains the MediaPlayer class.
"""
from openlp.core.common.registry import RegistryProperties
from openlp.core.common.mixins import RegistryProperties
from openlp.core.ui.media import MediaState

View File

@ -23,6 +23,7 @@
The :mod:`~openlp.core.ui.media.playertab` module holds the configuration tab for the media stuff.
"""
import platform
from PyQt5 import QtCore, QtWidgets
from openlp.core.common.i18n import UiStrings, translate
@ -31,7 +32,7 @@ from openlp.core.common.settings import Settings
from openlp.core.lib import SettingsTab
from openlp.core.lib.ui import create_button
from openlp.core.ui.media import get_media_players, set_media_players
from openlp.core.ui.lib.colorbutton import ColorButton
from openlp.core.widgets.buttons import ColorButton
class MediaQCheckBox(QtWidgets.QCheckBox):

View File

@ -27,7 +27,7 @@ import logging
from PyQt5 import QtCore, QtWidgets
from openlp.core.common.i18n import translate
from openlp.core.common.registry import RegistryProperties
from openlp.core.common.mixins import RegistryProperties
from openlp.core.lib import PluginStatus
from openlp.core.ui.plugindialog import Ui_PluginViewDialog

View File

@ -26,7 +26,7 @@ from PyQt5 import QtCore, QtWidgets, QtPrintSupport
from openlp.core.common.i18n import UiStrings, translate
from openlp.core.lib import build_icon
from openlp.core.ui.lib import SpellTextEdit
from openlp.core.widgets.edits import SpellTextEdit
class ZoomSize(object):

View File

@ -30,7 +30,8 @@ from PyQt5 import QtCore, QtGui, QtWidgets, QtPrintSupport
from openlp.core.common.applocation import AppLocation
from openlp.core.common.i18n import UiStrings, translate
from openlp.core.common.registry import Registry, RegistryProperties
from openlp.core.common.mixins import RegistryProperties
from openlp.core.common.registry import Registry
from openlp.core.common.settings import Settings
from openlp.core.lib import get_text_file_string
from openlp.core.ui.printservicedialog import Ui_PrintServiceDialog, ZoomSize

View File

@ -30,8 +30,8 @@ import logging
from PyQt5 import QtCore, QtGui, QtWidgets
from openlp.core.common.i18n import translate
from openlp.core.common.mixins import OpenLPMixin, RegistryMixin
from openlp.core.common.registry import RegistryProperties
from openlp.core.common.mixins import LogMixin, RegistryProperties
from openlp.core.common.registry import RegistryBase
from openlp.core.common.settings import Settings
from openlp.core.lib.ui import create_widget_action
from openlp.core.lib.projector import DialogSourceStyle
@ -40,9 +40,9 @@ from openlp.core.lib.projector.constants import ERROR_MSG, ERROR_STRING, E_AUTHE
S_INITIALIZE, S_NOT_CONNECTED, S_OFF, S_ON, S_STANDBY, S_WARMUP
from openlp.core.lib.projector.db import ProjectorDB
from openlp.core.lib.projector.pjlink import PJLink, PJLinkUDP
from openlp.core.ui.lib import OpenLPToolbar
from openlp.core.ui.projector.editform import ProjectorEditForm
from openlp.core.ui.projector.sourceselectform import SourceSelectTabs, SourceSelectSingle
from openlp.core.widgets.toolbar import OpenLPToolbar
log = logging.getLogger(__name__)
log.debug('projectormanager loaded')
@ -276,7 +276,7 @@ class UiProjectorManager(object):
self.update_icons()
class ProjectorManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, UiProjectorManager, RegistryProperties):
class ProjectorManager(QtWidgets.QWidget, RegistryBase, UiProjectorManager, LogMixin, RegistryProperties):
"""
Manage the projectors.
"""
@ -288,7 +288,7 @@ class ProjectorManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, UiProjecto
:param projectordb: Database session inherited from superclass.
"""
log.debug('__init__()')
super().__init__(parent)
super(ProjectorManager, self).__init__(parent)
self.settings_section = 'projector'
self.projectordb = projectordb
self.projector_list = []

View File

@ -24,7 +24,8 @@ The service item edit dialog
"""
from PyQt5 import QtCore, QtWidgets
from openlp.core.common.registry import Registry, RegistryProperties
from openlp.core.common.mixins import RegistryProperties
from openlp.core.common.registry import Registry
from openlp.core.ui.serviceitemeditdialog import Ui_ServiceItemEditDialog

View File

@ -36,15 +36,15 @@ from openlp.core.common import ThemeLevel, split_filename, delete_file
from openlp.core.common.actions import ActionList, CategoryOrder
from openlp.core.common.applocation import AppLocation
from openlp.core.common.i18n import UiStrings, format_time, translate
from openlp.core.common.mixins import OpenLPMixin, RegistryMixin
from openlp.core.common.mixins import LogMixin, RegistryProperties
from openlp.core.common.path import Path, create_paths, path_to_str, str_to_path
from openlp.core.common.registry import Registry, RegistryProperties
from openlp.core.common.registry import Registry, RegistryBase
from openlp.core.common.settings import Settings
from openlp.core.lib import ServiceItem, ItemCapabilities, PluginStatus, build_icon
from openlp.core.lib.ui import critical_error_message_box, create_widget_action, find_and_set_in_combo_box
from openlp.core.ui import ServiceNoteForm, ServiceItemEditForm, StartTimeForm
from openlp.core.ui.lib import OpenLPToolbar
from openlp.core.ui.lib.filedialog import FileDialog
from openlp.core.widgets.dialogs import FileDialog
from openlp.core.widgets.toolbar import OpenLPToolbar
class ServiceManagerList(QtWidgets.QTreeWidget):
@ -56,8 +56,24 @@ class ServiceManagerList(QtWidgets.QTreeWidget):
Constructor
"""
super(ServiceManagerList, self).__init__(parent)
self.setDragDropMode(QtWidgets.QAbstractItemView.DragDrop)
self.setAlternatingRowColors(True)
self.setHeaderHidden(True)
self.setExpandsOnDoubleClick(False)
self.service_manager = service_manager
def dragEnterEvent(self, event):
"""
React to a drag enter event
"""
event.accept()
def dragMoveEvent(self, event):
"""
React to a drage move event
"""
event.accept()
def keyPressEvent(self, event):
"""
Capture Key press and respond accordingly.
@ -117,7 +133,7 @@ class Ui_ServiceManager(object):
self.layout.setSpacing(0)
self.layout.setContentsMargins(0, 0, 0, 0)
# Create the top toolbar
self.toolbar = OpenLPToolbar(widget)
self.toolbar = OpenLPToolbar(self)
self.toolbar.add_toolbar_action('newService', text=UiStrings().NewService, icon=':/general/general_new.png',
tooltip=UiStrings().CreateService, triggers=self.on_new_service_clicked)
self.toolbar.add_toolbar_action('openService', text=UiStrings().OpenService,
@ -147,73 +163,67 @@ class Ui_ServiceManager(object):
QtWidgets.QAbstractItemView.CurrentChanged |
QtWidgets.QAbstractItemView.DoubleClicked |
QtWidgets.QAbstractItemView.EditKeyPressed)
self.service_manager_list.setDragDropMode(QtWidgets.QAbstractItemView.DragDrop)
self.service_manager_list.setAlternatingRowColors(True)
self.service_manager_list.setHeaderHidden(True)
self.service_manager_list.setExpandsOnDoubleClick(False)
self.service_manager_list.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
self.service_manager_list.customContextMenuRequested.connect(self.context_menu)
self.service_manager_list.setObjectName('service_manager_list')
# enable drop
self.service_manager_list.__class__.dragEnterEvent = lambda x, event: event.accept()
self.service_manager_list.__class__.dragMoveEvent = lambda x, event: event.accept()
self.service_manager_list.__class__.dropEvent = self.drop_event
self.service_manager_list.dropEvent = self.drop_event
self.layout.addWidget(self.service_manager_list)
# Add the bottom toolbar
self.order_toolbar = OpenLPToolbar(widget)
action_list = ActionList.get_instance()
action_list.add_category(UiStrings().Service, CategoryOrder.standard_toolbar)
self.service_manager_list.move_top = self.order_toolbar.add_toolbar_action(
self.move_top_action = self.order_toolbar.add_toolbar_action(
'moveTop',
text=translate('OpenLP.ServiceManager', 'Move to &top'), icon=':/services/service_top.png',
tooltip=translate('OpenLP.ServiceManager', 'Move item to the top of the service.'),
can_shortcuts=True, category=UiStrings().Service, triggers=self.on_service_top)
self.service_manager_list.move_up = self.order_toolbar.add_toolbar_action(
self.move_up_action = self.order_toolbar.add_toolbar_action(
'moveUp',
text=translate('OpenLP.ServiceManager', 'Move &up'), icon=':/services/service_up.png',
tooltip=translate('OpenLP.ServiceManager', 'Move item up one position in the service.'),
can_shortcuts=True, category=UiStrings().Service, triggers=self.on_service_up)
self.service_manager_list.move_down = self.order_toolbar.add_toolbar_action(
self.move_down_action = self.order_toolbar.add_toolbar_action(
'moveDown',
text=translate('OpenLP.ServiceManager', 'Move &down'), icon=':/services/service_down.png',
tooltip=translate('OpenLP.ServiceManager', 'Move item down one position in the service.'),
can_shortcuts=True, category=UiStrings().Service, triggers=self.on_service_down)
self.service_manager_list.move_bottom = self.order_toolbar.add_toolbar_action(
self.move_bottom_action = self.order_toolbar.add_toolbar_action(
'moveBottom',
text=translate('OpenLP.ServiceManager', 'Move to &bottom'), icon=':/services/service_bottom.png',
tooltip=translate('OpenLP.ServiceManager', 'Move item to the end of the service.'),
can_shortcuts=True, category=UiStrings().Service, triggers=self.on_service_end)
self.service_manager_list.down = self.order_toolbar.add_toolbar_action(
self.down_action = self.order_toolbar.add_toolbar_action(
'down',
text=translate('OpenLP.ServiceManager', 'Move &down'), can_shortcuts=True,
tooltip=translate('OpenLP.ServiceManager', 'Moves the selection down the window.'), visible=False,
triggers=self.on_move_selection_down)
action_list.add_action(self.service_manager_list.down)
self.service_manager_list.up = self.order_toolbar.add_toolbar_action(
action_list.add_action(self.down_action)
self.up_action = self.order_toolbar.add_toolbar_action(
'up',
text=translate('OpenLP.ServiceManager', 'Move up'), can_shortcuts=True,
tooltip=translate('OpenLP.ServiceManager', 'Moves the selection up the window.'), visible=False,
triggers=self.on_move_selection_up)
action_list.add_action(self.service_manager_list.up)
action_list.add_action(self.up_action)
self.order_toolbar.addSeparator()
self.service_manager_list.delete = self.order_toolbar.add_toolbar_action(
self.delete_action = self.order_toolbar.add_toolbar_action(
'delete', can_shortcuts=True,
text=translate('OpenLP.ServiceManager', '&Delete From Service'), icon=':/general/general_delete.png',
tooltip=translate('OpenLP.ServiceManager', 'Delete the selected item from the service.'),
triggers=self.on_delete_from_service)
self.order_toolbar.addSeparator()
self.service_manager_list.expand = self.order_toolbar.add_toolbar_action(
self.expand_action = self.order_toolbar.add_toolbar_action(
'expand', can_shortcuts=True,
text=translate('OpenLP.ServiceManager', '&Expand all'), icon=':/services/service_expand_all.png',
tooltip=translate('OpenLP.ServiceManager', 'Expand all the service items.'),
category=UiStrings().Service, triggers=self.on_expand_all)
self.service_manager_list.collapse = self.order_toolbar.add_toolbar_action(
self.collapse_action = self.order_toolbar.add_toolbar_action(
'collapse', can_shortcuts=True,
text=translate('OpenLP.ServiceManager', '&Collapse all'), icon=':/services/service_collapse_all.png',
tooltip=translate('OpenLP.ServiceManager', 'Collapse all the service items.'),
category=UiStrings().Service, triggers=self.on_collapse_all)
self.order_toolbar.addSeparator()
self.service_manager_list.make_live = self.order_toolbar.add_toolbar_action(
self.make_live_action = self.order_toolbar.add_toolbar_action(
'make_live', can_shortcuts=True,
text=translate('OpenLP.ServiceManager', 'Go Live'), icon=':/general/general_live.png',
tooltip=translate('OpenLP.ServiceManager', 'Send the selected item to Live.'),
@ -254,7 +264,7 @@ class Ui_ServiceManager(object):
icon=':/media/auto-start_active.png',
triggers=self.on_auto_start)
# Add already existing delete action to the menu.
self.menu.addAction(self.service_manager_list.delete)
self.menu.addAction(self.delete_action)
self.create_custom_action = create_widget_action(self.menu,
text=translate('OpenLP.ServiceManager', 'Create New &Custom '
'Slide'),
@ -285,28 +295,20 @@ class Ui_ServiceManager(object):
self.preview_action = create_widget_action(self.menu, text=translate('OpenLP.ServiceManager', 'Show &Preview'),
icon=':/general/general_preview.png', triggers=self.make_preview)
# Add already existing make live action to the menu.
self.menu.addAction(self.service_manager_list.make_live)
self.menu.addAction(self.make_live_action)
self.menu.addSeparator()
self.theme_menu = QtWidgets.QMenu(translate('OpenLP.ServiceManager', '&Change Item Theme'))
self.menu.addMenu(self.theme_menu)
self.service_manager_list.addActions(
[self.service_manager_list.move_down,
self.service_manager_list.move_up,
self.service_manager_list.make_live,
self.service_manager_list.move_top,
self.service_manager_list.move_bottom,
self.service_manager_list.up,
self.service_manager_list.down,
self.service_manager_list.expand,
self.service_manager_list.collapse
])
self.service_manager_list.addActions([self.move_down_action, self.move_up_action, self.make_live_action,
self.move_top_action, self.move_bottom_action, self.up_action,
self.down_action, self.expand_action, self.collapse_action])
Registry().register_function('theme_update_list', self.update_theme_list)
Registry().register_function('config_screen_changed', self.regenerate_service_items)
Registry().register_function('theme_update_global', self.theme_change)
Registry().register_function('mediaitem_suffix_reset', self.reset_supported_suffixes)
class ServiceManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ServiceManager, RegistryProperties):
class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixin, RegistryProperties):
"""
Manages the services. This involves taking text strings from plugins and adding them to the service. This service
can then be zipped up with all the resources used into one OSZ or oszl file for use on any OpenLP v2 installation.
@ -320,7 +322,7 @@ class ServiceManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ServiceMa
"""
Sets up the service manager, toolbars, list view, et al.
"""
super(ServiceManager, self).__init__(parent)
super().__init__(parent)
self.active = build_icon(':/media/auto-start_active.png')
self.inactive = build_icon(':/media/auto-start_inactive.png')
self.service_items = []

View File

@ -25,9 +25,10 @@ The :mod:`~openlp.core.ui.servicenoteform` module contains the `ServiceNoteForm`
from PyQt5 import QtCore, QtWidgets
from openlp.core.common.i18n import translate
from openlp.core.common.registry import Registry, RegistryProperties
from openlp.core.ui.lib import SpellTextEdit
from openlp.core.common.mixins import RegistryProperties
from openlp.core.common.registry import Registry
from openlp.core.lib.ui import create_button_box
from openlp.core.widgets.edits import SpellTextEdit
class ServiceNoteForm(QtWidgets.QDialog, RegistryProperties):

View File

@ -27,7 +27,8 @@ import logging
from PyQt5 import QtCore, QtWidgets
from openlp.core.api import ApiTab
from openlp.core.common.registry import Registry, RegistryProperties
from openlp.core.common.mixins import RegistryProperties
from openlp.core.common.registry import Registry
from openlp.core.lib import build_icon
from openlp.core.ui import AdvancedTab, GeneralTab, ThemesTab
from openlp.core.ui.media import PlayerTab

View File

@ -29,7 +29,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets
from openlp.core.common.actions import ActionList
from openlp.core.common.i18n import translate
from openlp.core.common.registry import RegistryProperties
from openlp.core.common.mixins import RegistryProperties
from openlp.core.common.settings import Settings
from openlp.core.ui.shortcutlistdialog import Ui_ShortcutListDialog

View File

@ -22,7 +22,6 @@
"""
The :mod:`slidecontroller` module contains the most important part of OpenLP - the slide controller
"""
import copy
import os
from collections import deque
@ -33,15 +32,15 @@ from PyQt5 import QtCore, QtGui, QtWidgets
from openlp.core.common import SlideLimits
from openlp.core.common.actions import ActionList, CategoryOrder
from openlp.core.common.i18n import UiStrings, translate
from openlp.core.common.mixins import OpenLPMixin, RegistryMixin
from openlp.core.common.registry import Registry, RegistryProperties
from openlp.core.common.mixins import LogMixin, RegistryProperties
from openlp.core.common.registry import Registry, RegistryBase
from openlp.core.common.settings import Settings
from openlp.core.display.screens import ScreenList
from openlp.core.lib import ItemCapabilities, ServiceItem, ImageSource, ServiceItemAction, build_icon, build_html
from openlp.core.lib.ui import create_action
from openlp.core.ui.lib.toolbar import OpenLPToolbar
from openlp.core.ui.lib.listpreviewwidget import ListPreviewWidget
from openlp.core.ui import HideMode, MainDisplay, Display, DisplayControllerType
from openlp.core.widgets.toolbar import OpenLPToolbar
from openlp.core.widgets.views import ListPreviewWidget
# Threshold which has to be trespassed to toggle.
@ -82,11 +81,11 @@ class DisplayController(QtWidgets.QWidget):
"""
Controller is a general display controller widget.
"""
def __init__(self, parent):
def __init__(self, *args, **kwargs):
"""
Set up the general Controller.
"""
super(DisplayController, self).__init__(parent)
super().__init__(*args, **kwargs)
self.is_live = False
self.display = None
self.controller_type = None
@ -133,16 +132,16 @@ class InfoLabel(QtWidgets.QLabel):
super().setText(text)
class SlideController(DisplayController, RegistryProperties):
class SlideController(DisplayController, LogMixin, RegistryProperties):
"""
SlideController is the slide controller widget. This widget is what the
user uses to control the displaying of verses/slides/etc on the screen.
"""
def __init__(self, parent):
def __init__(self, *args, **kwargs):
"""
Set up the Slide Controller.
"""
super(SlideController, self).__init__(parent)
super().__init__(*args, **kwargs)
def post_set_up(self):
"""
@ -1505,7 +1504,7 @@ class SlideController(DisplayController, RegistryProperties):
self.display.audio_player.go_to(action.data())
class PreviewController(RegistryMixin, OpenLPMixin, SlideController):
class PreviewController(RegistryBase, SlideController):
"""
Set up the Preview Controller.
"""
@ -1513,11 +1512,12 @@ class PreviewController(RegistryMixin, OpenLPMixin, SlideController):
slidecontroller_preview_next = QtCore.pyqtSignal()
slidecontroller_preview_previous = QtCore.pyqtSignal()
def __init__(self, parent):
def __init__(self, *args, **kwargs):
"""
Set up the base Controller as a preview.
"""
super(PreviewController, self).__init__(parent)
self.__registry_name = 'preview_slidecontroller'
super().__init__(*args, **kwargs)
self.split = 0
self.type_prefix = 'preview'
self.category = 'Preview Toolbar'
@ -1529,7 +1529,7 @@ class PreviewController(RegistryMixin, OpenLPMixin, SlideController):
self.post_set_up()
class LiveController(RegistryMixin, OpenLPMixin, SlideController):
class LiveController(RegistryBase, SlideController):
"""
Set up the Live Controller.
"""
@ -1541,11 +1541,11 @@ class LiveController(RegistryMixin, OpenLPMixin, SlideController):
mediacontroller_live_pause = QtCore.pyqtSignal()
mediacontroller_live_stop = QtCore.pyqtSignal()
def __init__(self, parent):
def __init__(self, *args, **kwargs):
"""
Set up the base Controller as a live.
"""
super(LiveController, self).__init__(parent)
super().__init__(*args, **kwargs)
self.is_live = True
self.split = 1
self.type_prefix = 'live'

View File

@ -25,7 +25,8 @@ The actual start time form.
from PyQt5 import QtCore, QtWidgets
from openlp.core.common.i18n import UiStrings, translate
from openlp.core.common.registry import Registry, RegistryProperties
from openlp.core.common.mixins import RegistryProperties
from openlp.core.common.registry import Registry
from openlp.core.lib.ui import critical_error_message_box
from openlp.core.ui.starttimedialog import Ui_StartTimeDialog

View File

@ -28,7 +28,8 @@ from PyQt5 import QtCore, QtGui, QtWidgets
from openlp.core.common import get_images_filter, is_not_image_file
from openlp.core.common.i18n import UiStrings, translate
from openlp.core.common.registry import Registry, RegistryProperties
from openlp.core.common.mixins import RegistryProperties
from openlp.core.common.registry import Registry
from openlp.core.lib.theme import BackgroundType, BackgroundGradientType
from openlp.core.lib.ui import critical_error_message_box
from openlp.core.ui import ThemeLayoutForm

View File

@ -31,17 +31,17 @@ from PyQt5 import QtCore, QtGui, QtWidgets
from openlp.core.common import delete_file
from openlp.core.common.applocation import AppLocation
from openlp.core.common.i18n import UiStrings, translate, get_locale_key
from openlp.core.common.mixins import OpenLPMixin, RegistryMixin
from openlp.core.common.mixins import LogMixin, RegistryProperties
from openlp.core.common.path import Path, copyfile, create_paths, path_to_str, rmtree
from openlp.core.common.registry import Registry, RegistryProperties
from openlp.core.common.registry import Registry, RegistryBase
from openlp.core.common.settings import Settings
from openlp.core.lib import ImageSource, ValidationError, get_text_file_string, build_icon, \
check_item_selected, create_thumb, validate_thumb
from openlp.core.lib.theme import Theme, BackgroundType
from openlp.core.lib.ui import critical_error_message_box, create_widget_action
from openlp.core.ui import FileRenameForm, ThemeForm
from openlp.core.ui.lib import OpenLPToolbar
from openlp.core.ui.lib.filedialog import FileDialog
from openlp.core.widgets.dialogs import FileDialog
from openlp.core.widgets.toolbar import OpenLPToolbar
class Ui_ThemeManager(object):
@ -125,7 +125,7 @@ class Ui_ThemeManager(object):
self.theme_list_widget.currentItemChanged.connect(self.check_list_state)
class ThemeManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ThemeManager, RegistryProperties):
class ThemeManager(QtWidgets.QWidget, RegistryBase, Ui_ThemeManager, LogMixin, RegistryProperties):
"""
Manages the orders of Theme.
"""

View File

@ -29,7 +29,8 @@ from openlp.core.common.i18n import UiStrings, translate
from openlp.core.lib import build_icon
from openlp.core.lib.theme import HorizontalType, BackgroundType, BackgroundGradientType
from openlp.core.lib.ui import add_welcome_page, create_valign_selection_widgets
from openlp.core.ui.lib import ColorButton, PathEdit
from openlp.core.widgets.buttons import ColorButton
from openlp.core.widgets.edits import PathEdit
class Ui_ThemeWizard(object):

View File

@ -20,15 +20,41 @@
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
The media manager dock.
The :mod:`~openlp.core.widgets.docks` module contains a customised base dock widget and dock widgets
"""
import logging
from openlp.core.lib import StringContent
from PyQt5 import QtWidgets
from openlp.core.display.screens import ScreenList
from openlp.core.lib import StringContent, build_icon
log = logging.getLogger(__name__)
class OpenLPDockWidget(QtWidgets.QDockWidget):
"""
Custom DockWidget class to handle events
"""
def __init__(self, parent=None, name=None, icon=None):
"""
Initialise the DockWidget
"""
log.debug('Initialise the %s widget' % name)
super(OpenLPDockWidget, self).__init__(parent)
if name:
self.setObjectName(name)
if icon:
self.setWindowIcon(build_icon(icon))
# Sort out the minimum width.
screens = ScreenList()
main_window_docbars = screens.current['size'].width() // 5
if main_window_docbars > 300:
self.setMinimumWidth(300)
else:
self.setMinimumWidth(main_window_docbars)
class MediaDockManager(object):
"""
Provide a repository for MediaManagerItems

View File

@ -0,0 +1,573 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2017 OpenLP Developers #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
The :mod:`~openlp.core.widgets.edits` module contains all the customised edit widgets used in OpenLP
"""
import logging
import re
from PyQt5 import QtCore, QtGui, QtWidgets
from openlp.core.common.i18n import UiStrings, translate
from openlp.core.common.path import Path, path_to_str, str_to_path
from openlp.core.common.settings import Settings
from openlp.core.lib import FormattingTags, build_icon
from openlp.core.lib.ui import create_widget_action, create_action
from openlp.core.widgets.dialogs import FileDialog
from openlp.core.widgets.enums import PathEditType
try:
import enchant
from enchant import DictNotFoundError
from enchant.errors import Error
ENCHANT_AVAILABLE = True
except ImportError:
ENCHANT_AVAILABLE = False
log = logging.getLogger(__name__)
class SearchEdit(QtWidgets.QLineEdit):
"""
This is a specialised QLineEdit with a "clear" button inside for searches.
"""
searchTypeChanged = QtCore.pyqtSignal(QtCore.QVariant)
cleared = QtCore.pyqtSignal()
def __init__(self, parent, settings_section):
"""
Constructor.
"""
super().__init__(parent)
self.settings_section = settings_section
self._current_search_type = -1
self.clear_button = QtWidgets.QToolButton(self)
self.clear_button.setIcon(build_icon(':/system/clear_shortcut.png'))
self.clear_button.setCursor(QtCore.Qt.ArrowCursor)
self.clear_button.setStyleSheet('QToolButton { border: none; padding: 0px; }')
self.clear_button.resize(18, 18)
self.clear_button.hide()
self.clear_button.clicked.connect(self._on_clear_button_clicked)
self.textChanged.connect(self._on_search_edit_text_changed)
self._update_style_sheet()
self.setAcceptDrops(False)
def _update_style_sheet(self):
"""
Internal method to update the stylesheet depending on which widgets are available and visible.
"""
frame_width = self.style().pixelMetric(QtWidgets.QStyle.PM_DefaultFrameWidth)
right_padding = self.clear_button.width() + frame_width
if hasattr(self, 'menu_button'):
left_padding = self.menu_button.width()
stylesheet = 'QLineEdit {{ padding-left:{left}px; padding-right: {right}px; }} '.format(left=left_padding,
right=right_padding)
else:
stylesheet = 'QLineEdit {{ padding-right: {right}px; }} '.format(right=right_padding)
self.setStyleSheet(stylesheet)
msz = self.minimumSizeHint()
self.setMinimumSize(max(msz.width(), self.clear_button.width() + (frame_width * 2) + 2),
max(msz.height(), self.clear_button.height() + (frame_width * 2) + 2))
def resizeEvent(self, event):
"""
Reimplemented method to react to resizing of the widget.
:param event: The event that happened.
"""
size = self.clear_button.size()
frame_width = self.style().pixelMetric(QtWidgets.QStyle.PM_DefaultFrameWidth)
self.clear_button.move(self.rect().right() - frame_width - size.width(),
(self.rect().bottom() + 1 - size.height()) // 2)
if hasattr(self, 'menu_button'):
size = self.menu_button.size()
self.menu_button.move(self.rect().left() + frame_width + 2, (self.rect().bottom() + 1 - size.height()) // 2)
def current_search_type(self):
"""
Readonly property to return the current search type.
"""
return self._current_search_type
def set_current_search_type(self, identifier):
"""
Set a new current search type.
:param identifier: The search type identifier (int).
"""
menu = self.menu_button.menu()
for action in menu.actions():
if identifier == action.data():
self.setPlaceholderText(action.placeholder_text)
self.menu_button.setDefaultAction(action)
self._current_search_type = identifier
Settings().setValue('{section}/last used search type'.format(section=self.settings_section), identifier)
self.searchTypeChanged.emit(identifier)
return True
def set_search_types(self, items):
"""
A list of tuples to be used in the search type menu. The first item in the list will be preselected as the
default.
:param items: The list of tuples to use. The tuples should contain an integer identifier, an icon (QIcon
instance or string) and a title for the item in the menu. In short, they should look like this::
(<identifier>, <icon>, <title>, <place holder text>)
For instance::
(1, <QIcon instance>, "Titles", "Search Song Titles...")
Or::
(2, ":/songs/authors.png", "Authors", "Search Authors...")
"""
menu = QtWidgets.QMenu(self)
for identifier, icon, title, placeholder in items:
action = create_widget_action(
menu, text=title, icon=icon, data=identifier, triggers=self._on_menu_action_triggered)
action.placeholder_text = placeholder
if not hasattr(self, 'menu_button'):
self.menu_button = QtWidgets.QToolButton(self)
self.menu_button.setIcon(build_icon(':/system/clear_shortcut.png'))
self.menu_button.setCursor(QtCore.Qt.ArrowCursor)
self.menu_button.setPopupMode(QtWidgets.QToolButton.InstantPopup)
self.menu_button.setStyleSheet('QToolButton { border: none; padding: 0px 10px 0px 0px; }')
self.menu_button.resize(QtCore.QSize(28, 18))
self.menu_button.setMenu(menu)
self.set_current_search_type(
Settings().value('{section}/last used search type'.format(section=self.settings_section)))
self.menu_button.show()
self._update_style_sheet()
def _on_search_edit_text_changed(self, text):
"""
Internally implemented slot to react to when the text in the line edit has changed so that we can show or hide
the clear button.
:param text: A :class:`~PyQt5.QtCore.QString` instance which represents the text in the line edit.
"""
self.clear_button.setVisible(bool(text))
def _on_clear_button_clicked(self):
"""
Internally implemented slot to react to the clear button being clicked to clear the line edit. Once it has
cleared the line edit, it emits the ``cleared()`` signal so that an application can react to the clearing of the
line edit.
"""
self.clear()
self.cleared.emit()
def _on_menu_action_triggered(self):
"""
Internally implemented slot to react to the select of one of the search types in the menu. Once it has set the
correct action on the button, and set the current search type (using the list of identifiers provided by the
developer), the ``searchTypeChanged(int)`` signal is emitted with the identifier.
"""
for action in self.menu_button.menu().actions():
# Why is this needed?
action.setChecked(False)
self.set_current_search_type(self.sender().data())
class PathEdit(QtWidgets.QWidget):
"""
The :class:`~openlp.core.widgets.edits.PathEdit` class subclasses QWidget to create a custom widget for use when
a file or directory needs to be selected.
"""
pathChanged = QtCore.pyqtSignal(Path)
def __init__(self, parent=None, path_type=PathEditType.Files, default_path=None, dialog_caption=None,
show_revert=True):
"""
Initialise the PathEdit widget
:param QtWidget.QWidget | None: The parent of the widget. This is just passed to the super method.
:param str dialog_caption: Used to customise the caption in the QFileDialog.
:param openlp.core.common.path.Path default_path: The default path. This is set as the path when the revert
button is clicked
:param bool show_revert: Used to determine if the 'revert button' should be visible.
:rtype: None
"""
super().__init__(parent)
self.default_path = default_path
self.dialog_caption = dialog_caption
self._path_type = path_type
self._path = None
self.filters = '{all_files} (*)'.format(all_files=UiStrings().AllFiles)
self._setup(show_revert)
def _setup(self, show_revert):
"""
Set up the widget
:param bool show_revert: Show or hide the revert button
:rtype: None
"""
widget_layout = QtWidgets.QHBoxLayout()
widget_layout.setContentsMargins(0, 0, 0, 0)
self.line_edit = QtWidgets.QLineEdit(self)
widget_layout.addWidget(self.line_edit)
self.browse_button = QtWidgets.QToolButton(self)
self.browse_button.setIcon(build_icon(':/general/general_open.png'))
widget_layout.addWidget(self.browse_button)
self.revert_button = QtWidgets.QToolButton(self)
self.revert_button.setIcon(build_icon(':/general/general_revert.png'))
self.revert_button.setVisible(show_revert)
widget_layout.addWidget(self.revert_button)
self.setLayout(widget_layout)
# Signals and Slots
self.browse_button.clicked.connect(self.on_browse_button_clicked)
self.revert_button.clicked.connect(self.on_revert_button_clicked)
self.line_edit.editingFinished.connect(self.on_line_edit_editing_finished)
self.update_button_tool_tips()
@QtCore.pyqtProperty('QVariant')
def path(self):
"""
A property getter method to return the selected path.
:return: The selected path
:rtype: openlp.core.common.path.Path
"""
return self._path
@path.setter
def path(self, path):
"""
A Property setter method to set the selected path
:param openlp.core.common.path.Path path: The path to set the widget to
:rtype: None
"""
self._path = path
text = path_to_str(path)
self.line_edit.setText(text)
self.line_edit.setToolTip(text)
@property
def path_type(self):
"""
A property getter method to return the path_type. Path type allows you to sepecify if the user is restricted to
selecting a file or directory.
:return: The type selected
:rtype: PathType
"""
return self._path_type
@path_type.setter
def path_type(self, path_type):
"""
A Property setter method to set the path type
:param PathType path_type: The type of path to select
:rtype: None
"""
self._path_type = path_type
self.update_button_tool_tips()
def update_button_tool_tips(self):
"""
Called to update the tooltips on the buttons. This is changing path types, and when the widget is initalised
:rtype: None
"""
if self._path_type == PathEditType.Directories:
self.browse_button.setToolTip(translate('OpenLP.PathEdit', 'Browse for directory.'))
self.revert_button.setToolTip(translate('OpenLP.PathEdit', 'Revert to default directory.'))
else:
self.browse_button.setToolTip(translate('OpenLP.PathEdit', 'Browse for file.'))
self.revert_button.setToolTip(translate('OpenLP.PathEdit', 'Revert to default file.'))
def on_browse_button_clicked(self):
"""
A handler to handle a click on the browse button.
Show the QFileDialog and process the input from the user
:rtype: None
"""
caption = self.dialog_caption
path = None
if self._path_type == PathEditType.Directories:
if not caption:
caption = translate('OpenLP.PathEdit', 'Select Directory')
path = FileDialog.getExistingDirectory(self, caption, self._path, FileDialog.ShowDirsOnly)
elif self._path_type == PathEditType.Files:
if not caption:
caption = self.dialog_caption = translate('OpenLP.PathEdit', 'Select File')
path, filter_used = FileDialog.getOpenFileName(self, caption, self._path, self.filters)
if path:
self.on_new_path(path)
def on_revert_button_clicked(self):
"""
A handler to handle a click on the revert button.
Set the new path to the value of the default_path instance variable.
:rtype: None
"""
self.on_new_path(self.default_path)
def on_line_edit_editing_finished(self):
"""
A handler to handle when the line edit has finished being edited.
:rtype: None
"""
path = str_to_path(self.line_edit.text())
self.on_new_path(path)
def on_new_path(self, path):
"""
A method called to validate and set a new path.
Emits the pathChanged Signal
:param openlp.core.common.path.Path path: The new path
:rtype: None
"""
if self._path != path:
self._path = path
self.pathChanged.emit(path)
class SpellTextEdit(QtWidgets.QPlainTextEdit):
"""
Spell checking widget based on QPlanTextEdit.
Based on code from http://john.nachtimwald.com/2009/08/22/qplaintextedit-with-in-line-spell-check
"""
def __init__(self, parent=None, formatting_tags_allowed=True):
"""
Constructor.
"""
global ENCHANT_AVAILABLE
super(SpellTextEdit, self).__init__(parent)
self.formatting_tags_allowed = formatting_tags_allowed
# Default dictionary based on the current locale.
if ENCHANT_AVAILABLE:
try:
self.dictionary = enchant.Dict()
self.highlighter = Highlighter(self.document())
self.highlighter.spelling_dictionary = self.dictionary
except (Error, DictNotFoundError):
ENCHANT_AVAILABLE = False
log.debug('Could not load default dictionary')
def mousePressEvent(self, event):
"""
Handle mouse clicks within the text edit region.
"""
if event.button() == QtCore.Qt.RightButton:
# Rewrite the mouse event to a left button event so the cursor is moved to the location of the pointer.
event = QtGui.QMouseEvent(QtCore.QEvent.MouseButtonPress,
event.pos(), QtCore.Qt.LeftButton, QtCore.Qt.LeftButton, QtCore.Qt.NoModifier)
QtWidgets.QPlainTextEdit.mousePressEvent(self, event)
def contextMenuEvent(self, event):
"""
Provide the context menu for the text edit region.
"""
popup_menu = self.createStandardContextMenu()
# Select the word under the cursor.
cursor = self.textCursor()
# only select text if not already selected
if not cursor.hasSelection():
cursor.select(QtGui.QTextCursor.WordUnderCursor)
self.setTextCursor(cursor)
# Add menu with available languages.
if ENCHANT_AVAILABLE:
lang_menu = QtWidgets.QMenu(translate('OpenLP.SpellTextEdit', 'Language:'))
for lang in enchant.list_languages():
action = create_action(lang_menu, lang, text=lang, checked=lang == self.dictionary.tag)
lang_menu.addAction(action)
popup_menu.insertSeparator(popup_menu.actions()[0])
popup_menu.insertMenu(popup_menu.actions()[0], lang_menu)
lang_menu.triggered.connect(self.set_language)
# Check if the selected word is misspelled and offer spelling suggestions if it is.
if ENCHANT_AVAILABLE and self.textCursor().hasSelection():
text = self.textCursor().selectedText()
if not self.dictionary.check(text):
spell_menu = QtWidgets.QMenu(translate('OpenLP.SpellTextEdit', 'Spelling Suggestions'))
for word in self.dictionary.suggest(text):
action = SpellAction(word, spell_menu)
action.correct.connect(self.correct_word)
spell_menu.addAction(action)
# Only add the spelling suggests to the menu if there are suggestions.
if spell_menu.actions():
popup_menu.insertMenu(popup_menu.actions()[0], spell_menu)
tag_menu = QtWidgets.QMenu(translate('OpenLP.SpellTextEdit', 'Formatting Tags'))
if self.formatting_tags_allowed:
for html in FormattingTags.get_html_tags():
action = SpellAction(html['desc'], tag_menu)
action.correct.connect(self.html_tag)
tag_menu.addAction(action)
popup_menu.insertSeparator(popup_menu.actions()[0])
popup_menu.insertMenu(popup_menu.actions()[0], tag_menu)
popup_menu.exec(event.globalPos())
def set_language(self, action):
"""
Changes the language for this spelltextedit.
:param action: The action.
"""
self.dictionary = enchant.Dict(action.text())
self.highlighter.spelling_dictionary = self.dictionary
self.highlighter.highlightBlock(self.toPlainText())
self.highlighter.rehighlight()
def correct_word(self, word):
"""
Replaces the selected text with word.
"""
cursor = self.textCursor()
cursor.beginEditBlock()
cursor.removeSelectedText()
cursor.insertText(word)
cursor.endEditBlock()
def html_tag(self, tag):
"""
Replaces the selected text with word.
"""
tag = tag.replace('&', '')
for html in FormattingTags.get_html_tags():
if tag == html['desc']:
cursor = self.textCursor()
if self.textCursor().hasSelection():
text = cursor.selectedText()
cursor.beginEditBlock()
cursor.removeSelectedText()
cursor.insertText(html['start tag'])
cursor.insertText(text)
cursor.insertText(html['end tag'])
cursor.endEditBlock()
else:
cursor = self.textCursor()
cursor.insertText(html['start tag'])
cursor.insertText(html['end tag'])
class Highlighter(QtGui.QSyntaxHighlighter):
"""
Provides a text highlighter for pointing out spelling errors in text.
"""
WORDS = r'(?iu)[\w\']+'
def __init__(self, *args):
"""
Constructor
"""
super(Highlighter, self).__init__(*args)
self.spelling_dictionary = None
def highlightBlock(self, text):
"""
Highlight mis spelt words in a block of text.
Note, this is a Qt hook.
"""
if not self.spelling_dictionary:
return
text = str(text)
char_format = QtGui.QTextCharFormat()
char_format.setUnderlineColor(QtCore.Qt.red)
char_format.setUnderlineStyle(QtGui.QTextCharFormat.SpellCheckUnderline)
for word_object in re.finditer(self.WORDS, text):
if not self.spelling_dictionary.check(word_object.group()):
self.setFormat(word_object.start(), word_object.end() - word_object.start(), char_format)
class SpellAction(QtWidgets.QAction):
"""
A special QAction that returns the text in a signal.
"""
correct = QtCore.pyqtSignal(str)
def __init__(self, *args):
"""
Constructor
"""
super(SpellAction, self).__init__(*args)
self.triggered.connect(lambda x: self.correct.emit(self.text()))
class HistoryComboBox(QtWidgets.QComboBox):
"""
The :class:`~openlp.core.common.historycombobox.HistoryComboBox` widget emulates the QLineEdit ``returnPressed``
signal for when the :kbd:`Enter` or :kbd:`Return` keys are pressed, and saves anything that is typed into the edit
box into its list.
"""
returnPressed = QtCore.pyqtSignal()
def __init__(self, parent=None):
"""
Initialise the combo box, setting duplicates to False and the insert policy to insert items at the top.
:param parent: The parent widget
"""
super().__init__(parent)
self.setDuplicatesEnabled(False)
self.setEditable(True)
self.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Preferred)
self.setInsertPolicy(QtWidgets.QComboBox.InsertAtTop)
def keyPressEvent(self, event):
"""
Override the inherited keyPressEvent method to emit the ``returnPressed`` signal and to save the current text to
the dropdown list.
:param event: The keyboard event
"""
# Handle Enter and Return ourselves
if event.key() == QtCore.Qt.Key_Enter or event.key() == QtCore.Qt.Key_Return:
# Emit the returnPressed signal
self.returnPressed.emit()
# Save the current text to the dropdown list
if self.currentText() and self.findText(self.currentText()) == -1:
self.insertItem(0, self.currentText())
# Let the parent handle any keypress events
super().keyPressEvent(event)
def focusOutEvent(self, event):
"""
Override the inherited focusOutEvent to save the current text to the dropdown list.
:param event: The focus event
"""
# Save the current text to the dropdown list
if self.currentText() and self.findText(self.currentText()) == -1:
self.insertItem(0, self.currentText())
# Let the parent handle any keypress events
super().focusOutEvent(event)
def getItems(self):
"""
Get all the items from the history
:return: A list of strings
"""
return [self.itemText(i) for i in range(self.count())]

View File

@ -19,18 +19,12 @@
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
The :mod:`~openlp.core.widgets.enums` module contains enumerations used by the widgets
"""
from enum import Enum
from .colorbutton import ColorButton
from .listpreviewwidget import ListPreviewWidget
from .listwidgetwithdnd import ListWidgetWithDnD
from .mediadockmanager import MediaDockManager
from .dockwidget import OpenLPDockWidget
from .toolbar import OpenLPToolbar
from .wizard import OpenLPWizard, WizardStrings
from .pathedit import PathEdit, PathType
from .spelltextedit import SpellTextEdit
from .treewidgetwithdnd import TreeWidgetWithDnD
__all__ = ['ColorButton', 'ListPreviewWidget', 'ListWidgetWithDnD', 'MediaDockManager', 'OpenLPDockWidget',
'OpenLPToolbar', 'OpenLPWizard', 'PathEdit', 'PathType', 'SpellTextEdit', 'TreeWidgetWithDnD',
'WizardStrings']
class PathEditType(Enum):
Files = 1
Directories = 2

View File

@ -40,7 +40,7 @@ class OpenLPToolbar(QtWidgets.QToolBar):
"""
Initialise the toolbar.
"""
super(OpenLPToolbar, self).__init__(parent)
super().__init__(parent)
# useful to be able to reuse button icons...
self.setIconSize(QtCore.QSize(20, 20))
self.actions = {}

View File

@ -23,10 +23,14 @@
The :mod:`listpreviewwidget` is a widget that lists the slides in the slide controller.
It is based on a QTableWidget but represents its contents in list form.
"""
import os
from PyQt5 import QtCore, QtGui, QtWidgets
from openlp.core.common.registry import RegistryProperties
from openlp.core.common import is_win
from openlp.core.common.i18n import UiStrings
from openlp.core.common.mixins import RegistryProperties
from openlp.core.common.registry import Registry
from openlp.core.common.settings import Settings
from openlp.core.lib import ImageSource, ItemCapabilities, ServiceItem
@ -238,3 +242,241 @@ class ListPreviewWidget(QtWidgets.QTableWidget, RegistryProperties):
Returns the number of slides this widget holds.
"""
return super(ListPreviewWidget, self).rowCount()
class ListWidgetWithDnD(QtWidgets.QListWidget):
"""
Provide a list widget to store objects and handle drag and drop events
"""
def __init__(self, parent=None, name=''):
"""
Initialise the list widget
"""
super().__init__(parent)
self.mime_data_text = name
self.no_results_text = UiStrings().NoResults
self.setSpacing(1)
self.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
self.setAlternatingRowColors(True)
self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
def activateDnD(self):
"""
Activate DnD of widget
"""
self.setAcceptDrops(True)
self.setDragDropMode(QtWidgets.QAbstractItemView.DragDrop)
Registry().register_function(('%s_dnd' % self.mime_data_text), self.parent().load_file)
def clear(self, search_while_typing=False):
"""
Re-implement clear, so that we can customise feedback when using 'Search as you type'
:param search_while_typing: True if we want to display the customised message
:return: None
"""
if search_while_typing:
self.no_results_text = UiStrings().ShortResults
else:
self.no_results_text = UiStrings().NoResults
super().clear()
def mouseMoveEvent(self, event):
"""
Drag and drop event does not care what data is selected as the recipient will use events to request the data
move just tell it what plugin to call
"""
if event.buttons() != QtCore.Qt.LeftButton:
event.ignore()
return
if not self.selectedItems():
event.ignore()
return
drag = QtGui.QDrag(self)
mime_data = QtCore.QMimeData()
drag.setMimeData(mime_data)
mime_data.setText(self.mime_data_text)
drag.exec(QtCore.Qt.CopyAction)
def dragEnterEvent(self, event):
"""
When something is dragged into this object, check if you should be able to drop it in here.
"""
if event.mimeData().hasUrls():
event.accept()
else:
event.ignore()
def dragMoveEvent(self, event):
"""
Make an object droppable, and set it to copy the contents of the object, not move it.
"""
if event.mimeData().hasUrls():
event.setDropAction(QtCore.Qt.CopyAction)
event.accept()
else:
event.ignore()
def dropEvent(self, event):
"""
Receive drop event check if it is a file and process it if it is.
:param event: Handle of the event pint passed
"""
if event.mimeData().hasUrls():
event.setDropAction(QtCore.Qt.CopyAction)
event.accept()
files = []
for url in event.mimeData().urls():
local_file = os.path.normpath(url.toLocalFile())
if os.path.isfile(local_file):
files.append(local_file)
elif os.path.isdir(local_file):
listing = os.listdir(local_file)
for file in listing:
files.append(os.path.join(local_file, file))
Registry().execute('{mime_data}_dnd'.format(mime_data=self.mime_data_text),
{'files': files, 'target': self.itemAt(event.pos())})
else:
event.ignore()
def allItems(self):
"""
An generator to list all the items in the widget
:return: a generator
"""
for row in range(self.count()):
yield self.item(row)
def paintEvent(self, event):
"""
Re-implement paintEvent so that we can add 'No Results' text when the listWidget is empty.
:param event: A QPaintEvent
:return: None
"""
super().paintEvent(event)
if not self.count():
viewport = self.viewport()
painter = QtGui.QPainter(viewport)
font = QtGui.QFont()
font.setItalic(True)
painter.setFont(font)
painter.drawText(QtCore.QRect(0, 0, viewport.width(), viewport.height()),
(QtCore.Qt.AlignHCenter | QtCore.Qt.TextWordWrap), self.no_results_text)
class TreeWidgetWithDnD(QtWidgets.QTreeWidget):
"""
Provide a tree widget to store objects and handle drag and drop events
"""
def __init__(self, parent=None, name=''):
"""
Initialise the tree widget
"""
super(TreeWidgetWithDnD, self).__init__(parent)
self.mime_data_text = name
self.allow_internal_dnd = False
self.header().close()
self.default_indentation = self.indentation()
self.setIndentation(0)
self.setAnimated(True)
def activateDnD(self):
"""
Activate DnD of widget
"""
self.setAcceptDrops(True)
self.setDragDropMode(QtWidgets.QAbstractItemView.DragDrop)
Registry().register_function(('%s_dnd' % self.mime_data_text), self.parent().load_file)
Registry().register_function(('%s_dnd_internal' % self.mime_data_text), self.parent().dnd_move_internal)
def mouseMoveEvent(self, event):
"""
Drag and drop event does not care what data is selected as the recipient will use events to request the data
move just tell it what plugin to call
:param event: The event that occurred
"""
if event.buttons() != QtCore.Qt.LeftButton:
event.ignore()
return
if not self.selectedItems():
event.ignore()
return
drag = QtGui.QDrag(self)
mime_data = QtCore.QMimeData()
drag.setMimeData(mime_data)
mime_data.setText(self.mime_data_text)
drag.exec(QtCore.Qt.CopyAction)
def dragEnterEvent(self, event):
"""
Receive drag enter event, check if it is a file or internal object and allow it if it is.
:param event: The event that occurred
"""
if event.mimeData().hasUrls():
event.accept()
elif self.allow_internal_dnd:
event.accept()
else:
event.ignore()
def dragMoveEvent(self, event):
"""
Receive drag move event, check if it is a file or internal object and allow it if it is.
:param event: The event that occurred
"""
QtWidgets.QTreeWidget.dragMoveEvent(self, event)
if event.mimeData().hasUrls():
event.setDropAction(QtCore.Qt.CopyAction)
event.accept()
elif self.allow_internal_dnd:
event.setDropAction(QtCore.Qt.CopyAction)
event.accept()
else:
event.ignore()
def dropEvent(self, event):
"""
Receive drop event, check if it is a file or internal object and process it if it is.
:param event: Handle of the event pint passed
"""
# If we are on Windows, OpenLP window will not be set on top. For example, user can drag images to Library and
# the folder stays on top of the group creation box. This piece of code fixes this issue.
if is_win():
self.setWindowState(self.windowState() & ~QtCore.Qt.WindowMinimized | QtCore.Qt.WindowActive)
self.setWindowState(QtCore.Qt.WindowNoState)
if event.mimeData().hasUrls():
event.setDropAction(QtCore.Qt.CopyAction)
event.accept()
files = []
for url in event.mimeData().urls():
local_file = url.toLocalFile()
if os.path.isfile(local_file):
files.append(local_file)
elif os.path.isdir(local_file):
listing = os.listdir(local_file)
for file_name in listing:
files.append(os.path.join(local_file, file_name))
Registry().execute('%s_dnd' % self.mime_data_text, {'files': files, 'target': self.itemAt(event.pos())})
elif self.allow_internal_dnd:
event.setDropAction(QtCore.Qt.CopyAction)
event.accept()
Registry().execute('%s_dnd_internal' % self.mime_data_text, self.itemAt(event.pos()))
else:
event.ignore()
# Convenience methods for emulating a QListWidget. This helps keeping MediaManagerItem simple.
def addItem(self, item):
self.addTopLevelItem(item)
def count(self):
return self.topLevelItemCount()
def item(self, index):
return self.topLevelItem(index)

View File

@ -28,11 +28,12 @@ from PyQt5 import QtCore, QtGui, QtWidgets
from openlp.core.common import is_macosx
from openlp.core.common.i18n import UiStrings, translate
from openlp.core.common.registry import Registry, RegistryProperties
from openlp.core.common.mixins import RegistryProperties
from openlp.core.common.registry import Registry
from openlp.core.common.settings import Settings
from openlp.core.lib import build_icon
from openlp.core.lib.ui import add_welcome_page
from openlp.core.ui.lib.filedialog import FileDialog
from openlp.core.widgets.dialogs import FileDialog
log = logging.getLogger(__name__)

View File

@ -26,12 +26,12 @@ displaying of alerts.
from PyQt5 import QtCore
from openlp.core.common.i18n import translate
from openlp.core.common.mixins import OpenLPMixin, RegistryMixin
from openlp.core.common.registry import Registry, RegistryProperties
from openlp.core.common.mixins import LogMixin, RegistryProperties
from openlp.core.common.registry import Registry, RegistryBase
from openlp.core.common.settings import Settings
class AlertsManager(OpenLPMixin, RegistryMixin, QtCore.QObject, RegistryProperties):
class AlertsManager(QtCore.QObject, RegistryBase, LogMixin, RegistryProperties):
"""
AlertsManager manages the settings of Alerts.
"""

View File

@ -26,7 +26,7 @@ from openlp.core.common.i18n import UiStrings, translate
from openlp.core.common.settings import Settings
from openlp.core.lib import SettingsTab
from openlp.core.lib.ui import create_valign_selection_widgets
from openlp.core.ui.lib.colorbutton import ColorButton
from openlp.core.widgets.buttons import ColorButton
class AlertsTab(SettingsTab):

View File

@ -40,8 +40,8 @@ from openlp.core.common.settings import Settings
from openlp.core.lib.db import delete_database
from openlp.core.lib.exceptions import ValidationError
from openlp.core.lib.ui import critical_error_message_box
from openlp.core.ui.lib.pathedit import PathEdit
from openlp.core.ui.lib.wizard import OpenLPWizard, WizardStrings
from openlp.core.widgets.edits import PathEdit
from openlp.core.widgets.wizard import OpenLPWizard, WizardStrings
from openlp.plugins.bibles.lib.db import clean_filename
from openlp.plugins.bibles.lib.importers.http import CWExtract, BGExtract, BSExtract
from openlp.plugins.bibles.lib.manager import BibleFormat

View File

@ -26,7 +26,7 @@ import re
from PyQt5 import QtCore, QtWidgets
from openlp.core.common.i18n import UiStrings, translate
from openlp.core.common.registry import RegistryProperties
from openlp.core.common.mixins import RegistryProperties
from openlp.core.lib.ui import critical_error_message_box
from .editbibledialog import Ui_EditBibleDialog
from openlp.plugins.bibles.lib import BibleStrings

View File

@ -23,15 +23,15 @@
from lxml import etree, objectify
from zipfile import is_zipfile
from openlp.core.common.mixins import OpenLPMixin
from openlp.core.common.registry import Registry, RegistryProperties
from openlp.core.common.mixins import LogMixin, RegistryProperties
from openlp.core.common.registry import Registry
from openlp.core.common.i18n import get_language, translate
from openlp.core.lib import ValidationError
from openlp.core.lib.ui import critical_error_message_box
from openlp.plugins.bibles.lib.db import AlternativeBookNamesDB, BibleDB, BiblesResourcesDB
class BibleImport(OpenLPMixin, RegistryProperties, BibleDB):
class BibleImport(BibleDB, LogMixin, RegistryProperties):
"""
Helper class to import bibles from a third party source into OpenLP
"""
@ -96,6 +96,8 @@ class BibleImport(OpenLPMixin, RegistryProperties, BibleDB):
if language_form.exec(bible_name):
combo_box = language_form.language_combo_box
language_id = combo_box.itemData(combo_box.currentIndex())
else:
return False
if not language_id:
return None
self.save_meta('language_id', language_id)

View File

@ -19,7 +19,6 @@
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
import chardet
import logging
import os

View File

@ -32,7 +32,8 @@ from bs4 import BeautifulSoup, NavigableString, Tag
from openlp.core.common.httputils import get_web_page
from openlp.core.common.i18n import translate
from openlp.core.common.registry import Registry, RegistryProperties
from openlp.core.common.mixins import RegistryProperties
from openlp.core.common.registry import Registry
from openlp.core.lib.ui import critical_error_message_box
from openlp.plugins.bibles.lib import SearchResults
from openlp.plugins.bibles.lib.bibleimport import BibleImport

View File

@ -24,9 +24,8 @@ import logging
from openlp.core.common import delete_file
from openlp.core.common.applocation import AppLocation
from openlp.core.common.i18n import UiStrings, translate
from openlp.core.common.mixins import OpenLPMixin
from openlp.core.common.mixins import LogMixin, RegistryProperties
from openlp.core.common.path import Path
from openlp.core.common.registry import RegistryProperties
from openlp.core.common.settings import Settings
from openlp.plugins.bibles.lib import LanguageSelection, parse_reference
from openlp.plugins.bibles.lib.db import BibleDB, BibleMeta
@ -97,7 +96,7 @@ class BibleFormat(object):
]
class BibleManager(OpenLPMixin, RegistryProperties):
class BibleManager(LogMixin, RegistryProperties):
"""
The Bible manager which holds and manages all the Bibles.
"""

View File

@ -30,9 +30,9 @@ from openlp.core.common.i18n import UiStrings, translate, get_locale_key
from openlp.core.common.registry import Registry
from openlp.core.common.settings import Settings
from openlp.core.lib import MediaManagerItem, ItemCapabilities, ServiceItemContext
from openlp.core.lib.searchedit import SearchEdit
from openlp.core.lib.ui import set_case_insensitive_completer, create_horizontal_adjusting_combo_box, \
critical_error_message_box, find_and_set_in_combo_box, build_icon
from openlp.core.widgets.edits import SearchEdit
from openlp.plugins.bibles.forms.bibleimportform import BibleImportForm
from openlp.plugins.bibles.forms.editbibleform import EditBibleForm
from openlp.plugins.bibles.lib import DisplayStyle, LayoutStyle, VerseReferenceList, \

View File

@ -25,7 +25,7 @@ from PyQt5 import QtWidgets
from openlp.core.common.i18n import UiStrings, translate
from openlp.core.lib import build_icon
from openlp.core.lib.ui import create_button, create_button_box
from openlp.core.ui.lib import SpellTextEdit
from openlp.core.widgets.edits import SpellTextEdit
class Ui_CustomSlideEditDialog(object):

View File

@ -25,7 +25,7 @@ from PyQt5 import QtWidgets
from openlp.core.common.i18n import UiStrings, translate
from openlp.core.common.settings import Settings
from openlp.core.lib import SettingsTab
from openlp.core.ui.lib.colorbutton import ColorButton
from openlp.core.widgets.buttons import ColorButton
class ImageTab(SettingsTab):

View File

@ -33,7 +33,7 @@ from openlp.core.common.settings import Settings
from openlp.core.lib import ItemCapabilities, MediaManagerItem, ServiceItemContext, StringContent, build_icon, \
check_item_selected, create_thumb, validate_thumb
from openlp.core.lib.ui import create_widget_action, critical_error_message_box
from openlp.core.ui.lib.treewidgetwithdnd import TreeWidgetWithDnD
from openlp.core.widgets.views import TreeWidgetWithDnD
from openlp.plugins.images.forms import AddGroupForm, ChooseGroupForm
from openlp.plugins.images.lib.db import ImageFilenames, ImageGroups

View File

@ -29,7 +29,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets
from openlp.core.common import is_win, is_linux, is_macosx
from openlp.core.common.i18n import translate
from openlp.core.common.registry import RegistryProperties
from openlp.core.common.mixins import RegistryProperties
from openlp.plugins.media.forms.mediaclipselectordialog import Ui_MediaClipSelector
from openlp.core.lib.ui import critical_error_message_box
from openlp.core.ui.media.vlcplayer import get_vlc

View File

@ -28,7 +28,8 @@ from PyQt5 import QtCore, QtWidgets
from openlp.core.common.applocation import AppLocation
from openlp.core.common.i18n import UiStrings, translate, get_locale_key
from openlp.core.common.path import Path, path_to_str, create_paths
from openlp.core.common.registry import Registry, RegistryProperties
from openlp.core.common.mixins import RegistryProperties
from openlp.core.common.registry import Registry
from openlp.core.common.settings import Settings
from openlp.core.lib import ItemCapabilities, MediaManagerItem, MediaType, ServiceItem, ServiceItemContext, \
build_icon, check_item_selected

View File

@ -26,7 +26,7 @@ from openlp.core.common.i18n import UiStrings, translate
from openlp.core.common.settings import Settings
from openlp.core.lib import SettingsTab
from openlp.core.lib.ui import critical_error_message_box
from openlp.core.ui.lib import PathEdit
from openlp.core.widgets.edits import PathEdit
from openlp.plugins.presentations.lib.pdfcontroller import PdfController

View File

@ -29,8 +29,9 @@ import multiprocessing
from PyQt5 import QtCore, QtWidgets
from openlp.core.common.i18n import translate
from openlp.core.common.registry import Registry, RegistryProperties
from openlp.core.ui.lib.wizard import OpenLPWizard, WizardStrings
from openlp.core.common.mixins import RegistryProperties
from openlp.core.common.registry import Registry
from openlp.core.widgets.wizard import OpenLPWizard, WizardStrings
from openlp.plugins.songs.lib import delete_song
from openlp.plugins.songs.lib.db import Song
from openlp.plugins.songs.forms.songreviewwidget import SongReviewWidget

View File

@ -31,10 +31,11 @@ from PyQt5 import QtCore, QtWidgets
from openlp.core.common.applocation import AppLocation
from openlp.core.common.i18n import UiStrings, translate, get_natural_key
from openlp.core.common.path import create_paths, copyfile
from openlp.core.common.registry import Registry, RegistryProperties
from openlp.core.common.mixins import RegistryProperties
from openlp.core.common.registry import Registry
from openlp.core.lib import PluginStatus, MediaType, create_separated_list
from openlp.core.lib.ui import set_case_insensitive_completer, critical_error_message_box, find_and_set_in_combo_box
from openlp.core.ui.lib.filedialog import FileDialog
from openlp.core.widgets.dialogs import FileDialog
from openlp.plugins.songs.forms.editsongdialog import Ui_EditSongDialog
from openlp.plugins.songs.forms.editverseform import EditVerseForm
from openlp.plugins.songs.forms.mediafilesform import MediaFilesForm

View File

@ -26,7 +26,7 @@ from openlp.core.common.settings import Settings
from openlp.core.common.i18n import UiStrings, translate
from openlp.core.lib import build_icon
from openlp.core.lib.ui import create_button_box
from openlp.core.ui.lib import SpellTextEdit
from openlp.core.widgets.edits import SpellTextEdit
from openlp.plugins.songs.lib import VerseType

View File

@ -32,8 +32,9 @@ from openlp.core.common.registry import Registry
from openlp.core.common.settings import Settings
from openlp.core.lib import create_separated_list
from openlp.core.lib.ui import critical_error_message_box
from openlp.core.ui.lib import PathEdit, PathType
from openlp.core.ui.lib.wizard import OpenLPWizard, WizardStrings
from openlp.core.widgets.edits import PathEdit
from openlp.core.widgets.enums import PathEditType
from openlp.core.widgets.wizard import OpenLPWizard, WizardStrings
from openlp.plugins.songs.lib.db import Song
from openlp.plugins.songs.lib.openlyricsexport import OpenLyricsExport
@ -124,7 +125,7 @@ class SongExportForm(OpenLPWizard):
self.selected_list_widget.setObjectName('selected_list_widget')
self.grid_layout.addWidget(self.selected_list_widget, 1, 0, 1, 2)
self.output_directory_path_edit = PathEdit(
self.export_song_page, PathType.Directories,
self.export_song_page, PathEditType.Directories,
dialog_caption=translate('SongsPlugin.ExportWizardForm', 'Select Destination Folder'), show_revert=False)
self.output_directory_path_edit.path = Settings().value('songs/last directory export')
self.directory_label = QtWidgets.QLabel(self.export_song_page)

View File

@ -27,12 +27,13 @@ import logging
from PyQt5 import QtCore, QtWidgets
from openlp.core.common.i18n import UiStrings, translate
from openlp.core.common.registry import RegistryProperties
from openlp.core.common.mixins import RegistryProperties
from openlp.core.common.settings import Settings
from openlp.core.lib.ui import critical_error_message_box
from openlp.core.ui.lib import PathEdit, PathType
from openlp.core.ui.lib.filedialog import FileDialog
from openlp.core.ui.lib.wizard import OpenLPWizard, WizardStrings
from openlp.core.widgets.dialogs import FileDialog
from openlp.core.widgets.edits import PathEdit
from openlp.core.widgets.enums import PathEditType
from openlp.core.widgets.wizard import OpenLPWizard, WizardStrings
from openlp.plugins.songs.lib.importer import SongFormat, SongFormatSelect
log = logging.getLogger(__name__)
@ -383,10 +384,10 @@ class SongImportForm(OpenLPWizard, RegistryProperties):
file_path_label = QtWidgets.QLabel(import_widget)
file_path_layout.addWidget(file_path_label)
if select_mode == SongFormatSelect.SingleFile:
path_type = PathType.Files
path_type = PathEditType.Files
dialog_caption = WizardStrings.OpenTypeFile.format(file_type=format_name)
else:
path_type = PathType.Directories
path_type = PathEditType.Directories
dialog_caption = WizardStrings.OpenTypeFolder.format(folder_name=format_name)
path_edit = PathEdit(
parent=import_widget, path_type=path_type, dialog_caption=dialog_caption, show_revert=False)

View File

@ -25,7 +25,8 @@ from PyQt5 import QtCore, QtWidgets
from sqlalchemy.sql import and_
from openlp.core.common.i18n import UiStrings, translate, get_natural_key
from openlp.core.common.registry import Registry, RegistryProperties
from openlp.core.common.mixins import RegistryProperties
from openlp.core.common.registry import Registry
from openlp.core.lib.ui import critical_error_message_box
from openlp.plugins.songs.forms.authorsform import AuthorsForm
from openlp.plugins.songs.forms.topicsform import TopicsForm

View File

@ -28,7 +28,7 @@ from PyQt5 import QtCore, QtWidgets
from openlp.core.common.i18n import translate
from openlp.core.lib import build_icon
from openlp.core.ui import SingleColumnTableWidget
from openlp.core.ui.lib.historycombobox import HistoryComboBox
from openlp.core.widgets.edits import HistoryComboBox
class Ui_SongSelectDialog(object):

View File

@ -26,7 +26,7 @@ import logging
from openlp.core.common import is_win
from openlp.core.common.i18n import UiStrings, translate
from openlp.core.ui.lib.wizard import WizardStrings
from openlp.core.widgets.wizard import WizardStrings
from .importers.opensong import OpenSongImport
from .importers.easyslides import EasySlidesImport
from .importers.openlp import OpenLPSongImport

View File

@ -44,7 +44,7 @@ NOTE_REGEX = re.compile(r'\(.*?\)')
log = logging.getLogger(__name__)
class FieldDescEntry:
class FieldDescEntry(object):
def __init__(self, name, field_type, size):
self.name = name
self.field_type = field_type

View File

@ -88,7 +88,7 @@ import re
from lxml import etree, objectify
from openlp.core.common.i18n import translate
from openlp.core.ui.lib.wizard import WizardStrings
from openlp.core.widgets.wizard import WizardStrings
from openlp.plugins.songs.lib import clean_song, VerseType
from openlp.plugins.songs.lib.importers.songimport import SongImport
from openlp.plugins.songs.lib.db import Author, Book, Song, Topic

View File

@ -31,7 +31,7 @@ from sqlalchemy.orm.exc import UnmappedClassError
from openlp.core.common.i18n import translate
from openlp.core.lib.db import BaseModel
from openlp.core.ui.lib.wizard import WizardStrings
from openlp.core.widgets.wizard import WizardStrings
from openlp.plugins.songs.lib import clean_song
from openlp.plugins.songs.lib.db import Author, Book, Song, Topic, MediaFile
from .songimport import SongImport

View File

@ -27,7 +27,7 @@ import logging
from lxml import etree
from openlp.core.ui.lib.wizard import WizardStrings
from openlp.core.widgets.wizard import WizardStrings
from openlp.plugins.songs.lib.importers.songimport import SongImport
from openlp.plugins.songs.lib.ui import SongStrings
from openlp.plugins.songs.lib.openlyricsxml import OpenLyrics, OpenLyricsError

View File

@ -25,8 +25,8 @@ Powerpraise song files into the current database.
"""
from lxml import objectify
from openlp.core.ui.lib.wizard import WizardStrings
from .songimport import SongImport
from openlp.core.widgets.wizard import WizardStrings
from openlp.plugins.songs.lib.importers.songimport import SongImport
class PowerPraiseImport(SongImport):

View File

@ -29,8 +29,8 @@ from lxml import objectify, etree
from openlp.core.common.i18n import translate
from openlp.core.common import get_file_encoding
from openlp.core.ui.lib.wizard import WizardStrings
from .songimport import SongImport
from openlp.core.widgets.wizard import WizardStrings
from openlp.plugins.songs.lib.importers.songimport import SongImport
class PresentationManagerImport(SongImport):

View File

@ -27,9 +27,9 @@ import base64
import logging
from lxml import objectify
from openlp.core.ui.lib.wizard import WizardStrings
from openlp.core.widgets.wizard import WizardStrings
from openlp.plugins.songs.lib import strip_rtf
from .songimport import SongImport
from openlp.plugins.songs.lib.importers.songimport import SongImport
log = logging.getLogger(__name__)

View File

@ -29,7 +29,7 @@ from openlp.core.common.applocation import AppLocation
from openlp.core.common.i18n import translate
from openlp.core.common.path import copyfile, create_paths
from openlp.core.common.registry import Registry
from openlp.core.ui.lib.wizard import WizardStrings
from openlp.core.widgets.wizard import WizardStrings
from openlp.plugins.songs.lib import clean_song, VerseType
from openlp.plugins.songs.lib.db import Song, Author, Topic, Book, MediaFile
from openlp.plugins.songs.lib.ui import SongStrings

View File

@ -27,7 +27,7 @@ import logging
import re
import struct
from openlp.core.ui.lib.wizard import WizardStrings
from openlp.core.widgets.wizard import WizardStrings
from openlp.plugins.songs.lib import VerseType, retrieve_windows_encoding
from openlp.plugins.songs.lib.importers.songimport import SongImport

View File

@ -30,7 +30,7 @@ from lxml import etree
from openlp.core.common import clean_filename
from openlp.core.common.i18n import translate
from openlp.core.common.path import create_paths
from openlp.core.common.registry import RegistryProperties
from openlp.core.common.mixins import RegistryProperties
from openlp.plugins.songs.lib.openlyricsxml import OpenLyrics
log = logging.getLogger(__name__)

View File

@ -29,7 +29,7 @@ from openlp.core.common.i18n import translate
from openlp.core.common.path import Path
from openlp.core.common.registry import Registry
from openlp.core.lib.ui import critical_error_message_box
from openlp.core.ui.lib.filedialog import FileDialog
from openlp.core.widgets.dialogs import FileDialog
from openlp.plugins.songs.lib.db import Song
log = logging.getLogger(__name__)

View File

@ -23,7 +23,7 @@
from PyQt5 import QtCore, QtWidgets
from openlp.core.common.i18n import translate
from openlp.core.common.registry import RegistryProperties
from openlp.core.common.mixins import RegistryProperties
from openlp.plugins.songusage.lib.db import SongUsageItem
from openlp.plugins.songusage.forms.songusagedeletedialog import Ui_SongUsageDeleteDialog

View File

@ -24,7 +24,8 @@ from PyQt5 import QtCore, QtWidgets
from openlp.core.common.i18n import translate
from openlp.core.lib import build_icon
from openlp.core.lib.ui import create_button_box
from openlp.core.ui.lib import PathEdit, PathType
from openlp.core.widgets.edits import PathEdit
from openlp.core.widgets.enums import PathEditType
class Ui_SongUsageDetailDialog(object):
@ -68,7 +69,7 @@ class Ui_SongUsageDetailDialog(object):
self.file_horizontal_layout.setSpacing(8)
self.file_horizontal_layout.setContentsMargins(8, 8, 8, 8)
self.file_horizontal_layout.setObjectName('file_horizontal_layout')
self.report_path_edit = PathEdit(self.file_group_box, path_type=PathType.Directories, show_revert=False)
self.report_path_edit = PathEdit(self.file_group_box, path_type=PathEditType.Directories, show_revert=False)
self.file_horizontal_layout.addWidget(self.report_path_edit)
self.vertical_layout.addWidget(self.file_group_box)
self.button_box = create_button_box(song_usage_detail_dialog, 'button_box', ['cancel', 'ok'])

View File

@ -25,7 +25,7 @@ from PyQt5 import QtCore, QtWidgets
from sqlalchemy.sql import and_
from openlp.core.common.i18n import translate
from openlp.core.common.registry import RegistryProperties
from openlp.core.common.mixins import RegistryProperties
from openlp.core.common.settings import Settings
from openlp.core.common.path import create_paths
from openlp.core.lib.ui import critical_error_message_box

View File

@ -347,7 +347,7 @@ class TestInit(TestCase, TestMixin):
self.assertTrue(mocked_log.exception.called)
self.assertFalse(result, 'delete_file should return False when an OSError is raised')
def test_get_file_encoding_done_test(self):
def test_get_file_encoding_done(self):
"""
Test get_file_encoding when the detector sets done to True
"""
@ -368,7 +368,7 @@ class TestInit(TestCase, TestMixin):
mocked_universal_detector_inst.close.assert_called_once_with()
self.assertEqual(result, encoding_result)
def test_get_file_encoding_eof_test(self):
def test_get_file_encoding_eof(self):
"""
Test get_file_encoding when the end of the file is reached
"""
@ -390,7 +390,7 @@ class TestInit(TestCase, TestMixin):
mocked_universal_detector_inst.close.assert_called_once_with()
self.assertEqual(result, encoding_result)
def test_get_file_encoding_oserror_test(self):
def test_get_file_encoding_oserror(self):
"""
Test get_file_encoding when the end of the file is reached
"""

View File

@ -23,45 +23,76 @@
Package to test the openlp.core.common package.
"""
from unittest import TestCase
from unittest.mock import MagicMock, patch
from openlp.core.common.mixins import RegistryMixin
from openlp.core.common.mixins import RegistryProperties
from openlp.core.common.registry import Registry
class PlainStub(object):
def __init__(self):
pass
class MixinStub(RegistryMixin):
def __init__(self):
super().__init__(None)
class TestRegistryMixin(TestCase):
def test_registry_mixin_missing(self):
class TestRegistryProperties(TestCase, RegistryProperties):
"""
Test the registry creation and its usage
Test the functions in the ThemeManager module
"""
def setUp(self):
"""
Create the Register
"""
# GIVEN: A new registry
Registry.create()
# WHEN: I create an instance of a class that doesn't inherit from RegistryMixin
PlainStub()
# THEN: Nothing is registered with the registry
self.assertEqual(len(Registry().functions_list), 0), 'The function should not be in the dict anymore.'
def test_registry_mixin_present(self):
def test_no_application(self):
"""
Test the registry creation and its usage
Test property if no registry value assigned
"""
# GIVEN: A new registry
Registry.create()
# GIVEN an Empty Registry
# WHEN there is no Application
# THEN the application should be none
self.assertEqual(self.application, None, 'The application value should be None')
# WHEN: I create an instance of a class that inherits from RegistryMixin
MixinStub()
def test_application(self):
"""
Test property if registry value assigned
"""
# GIVEN an Empty Registry
application = MagicMock()
# THEN: The bootstrap methods should be registered
self.assertEqual(len(Registry().functions_list), 2), 'The bootstrap functions should be in the dict.'
# WHEN the application is registered
Registry().register('application', application)
# THEN the application should be none
self.assertEqual(self.application, application, 'The application value should match')
@patch('openlp.core.common.mixins.is_win')
def test_application_on_windows(self, mocked_is_win):
"""
Test property if registry value assigned on Windows
"""
# GIVEN an Empty Registry and we're on Windows
application = MagicMock()
mocked_is_win.return_value = True
# WHEN the application is registered
Registry().register('application', application)
# THEN the application should be none
self.assertEqual(self.application, application, 'The application value should match')
@patch('openlp.core.common.mixins.is_win')
def test_get_application_on_windows(self, mocked_is_win):
"""
Set that getting the application object on Windows happens dynamically
"""
# GIVEN an Empty Registry and we're on Windows
mocked_is_win.return_value = True
mock_application = MagicMock()
reg_props = RegistryProperties()
registry = Registry()
# WHEN the application is accessed
with patch.object(registry, 'get') as mocked_get:
mocked_get.return_value = mock_application
actual_application = reg_props.application
# THEN the application should be the mock object, and the correct function should have been called
self.assertEqual(mock_application, actual_application, 'The application value should match')
mocked_is_win.assert_called_with()
mocked_get.assert_called_with('application')

View File

@ -24,9 +24,9 @@ Package to test the openlp.core.lib package.
"""
import os
from unittest import TestCase
from unittest.mock import MagicMock, patch
from unittest.mock import MagicMock
from openlp.core.common.registry import Registry, RegistryProperties
from openlp.core.common.registry import Registry, RegistryBase
TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '../', '..', 'resources'))
@ -151,70 +151,40 @@ class TestRegistry(TestCase):
return "function_2"
class TestRegistryProperties(TestCase, RegistryProperties):
class PlainStub(object):
def __init__(self):
pass
class RegistryStub(RegistryBase):
def __init__(self):
super().__init__()
class TestRegistryBase(TestCase):
def test_registry_mixin_missing(self):
"""
Test the functions in the ThemeManager module
"""
def setUp(self):
"""
Create the Register
Test the registry creation and its usage
"""
# GIVEN: A new registry
Registry.create()
def test_no_application(self):
# WHEN: I create an instance of a class that doesn't inherit from RegistryMixin
PlainStub()
# THEN: Nothing is registered with the registry
self.assertEqual(len(Registry().functions_list), 0), 'The function should not be in the dict anymore.'
def test_registry_mixin_present(self):
"""
Test property if no registry value assigned
Test the registry creation and its usage
"""
# GIVEN an Empty Registry
# WHEN there is no Application
# THEN the application should be none
self.assertEqual(self.application, None, 'The application value should be None')
# GIVEN: A new registry
Registry.create()
def test_application(self):
"""
Test property if registry value assigned
"""
# GIVEN an Empty Registry
application = MagicMock()
# WHEN: I create an instance of a class that inherits from RegistryMixin
RegistryStub()
# WHEN the application is registered
Registry().register('application', application)
# THEN the application should be none
self.assertEqual(self.application, application, 'The application value should match')
@patch('openlp.core.common.registry.is_win')
def test_application_on_windows(self, mocked_is_win):
"""
Test property if registry value assigned on Windows
"""
# GIVEN an Empty Registry and we're on Windows
application = MagicMock()
mocked_is_win.return_value = True
# WHEN the application is registered
Registry().register('application', application)
# THEN the application should be none
self.assertEqual(self.application, application, 'The application value should match')
@patch('openlp.core.common.registry.is_win')
def test_get_application_on_windows(self, mocked_is_win):
"""
Set that getting the application object on Windows happens dynamically
"""
# GIVEN an Empty Registry and we're on Windows
mocked_is_win.return_value = True
mock_application = MagicMock()
reg_props = RegistryProperties()
registry = Registry()
# WHEN the application is accessed
with patch.object(registry, 'get') as mocked_get:
mocked_get.return_value = mock_application
actual_application = reg_props.application
# THEN the application should be the mock object, and the correct function should have been called
self.assertEqual(mock_application, actual_application, 'The application value should match')
mocked_is_win.assert_called_with()
mocked_get.assert_called_with('application')
# THEN: The bootstrap methods should be registered
self.assertEqual(len(Registry().functions_list), 2), 'The bootstrap functions should be in the dict.'

View File

@ -29,8 +29,6 @@ from PyQt5 import QtCore, QtWidgets
from openlp.core.app import OpenLP, parse_options
from openlp.core.common.settings import Settings
from tests.helpers.testmixin import TestMixin
TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'resources'))
@ -141,23 +139,33 @@ class TestOpenLP(TestCase):
"""
Test the OpenLP app class
"""
@patch('openlp.core.app.QtWidgets.QApplication.exec')
def test_exec(self, mocked_exec):
def setUp(self):
self.build_settings()
self.qapplication_patcher = patch('openlp.core.app.QtGui.QApplication')
self.mocked_qapplication = self.qapplication_patcher.start()
self.openlp = OpenLP([])
def tearDown(self):
self.qapplication_patcher.stop()
self.destroy_settings()
del self.openlp
self.openlp = None
def test_exec(self):
"""
Test the exec method
"""
# GIVEN: An app
app = OpenLP([])
app.shared_memory = MagicMock()
mocked_exec.return_value = False
self.openlp.shared_memory = MagicMock()
self.mocked_qapplication.exec.return_value = False
# WHEN: exec() is called
result = app.exec()
result = self.openlp.exec()
# THEN: The right things should be called
assert app.is_event_loop_active is True
mocked_exec.assert_called_once_with()
app.shared_memory.detach.assert_called_once_with()
assert self.openlp.is_event_loop_active is True
self.mocked_qapplication.exec.assert_called_once_with()
self.openlp.shared_memory.detach.assert_called_once_with()
assert result is False
@patch('openlp.core.app.QtCore.QSharedMemory')
@ -169,10 +177,9 @@ class TestOpenLP(TestCase):
mocked_shared_memory = MagicMock()
mocked_shared_memory.attach.return_value = False
MockedSharedMemory.return_value = mocked_shared_memory
app = OpenLP([])
# WHEN: is_already_running() is called
result = app.is_already_running()
result = self.openlp.is_already_running()
# THEN: The result should be false
MockedSharedMemory.assert_called_once_with('OpenLP')
@ -193,10 +200,9 @@ class TestOpenLP(TestCase):
MockedSharedMemory.return_value = mocked_shared_memory
MockedStandardButtons.return_value = 0
mocked_critical.return_value = QtWidgets.QMessageBox.Yes
app = OpenLP([])
# WHEN: is_already_running() is called
result = app.is_already_running()
result = self.openlp.is_already_running()
# THEN: The result should be false
MockedSharedMemory.assert_called_once_with('OpenLP')
@ -218,10 +224,9 @@ class TestOpenLP(TestCase):
MockedSharedMemory.return_value = mocked_shared_memory
MockedStandardButtons.return_value = 0
mocked_critical.return_value = QtWidgets.QMessageBox.No
app = OpenLP([])
# WHEN: is_already_running() is called
result = app.is_already_running()
result = self.openlp.is_already_running()
# THEN: The result should be false
MockedSharedMemory.assert_called_once_with('OpenLP')
@ -235,11 +240,9 @@ class TestOpenLP(TestCase):
Test that the app.process_events() method simply calls the Qt method
"""
# GIVEN: An app
app = OpenLP([])
# WHEN: process_events() is called
with patch.object(app, 'processEvents') as mocked_processEvents:
app.process_events()
with patch.object(self.openlp, 'processEvents') as mocked_processEvents:
self.openlp.process_events()
# THEN: processEvents was called
mocked_processEvents.assert_called_once_with()
@ -249,12 +252,10 @@ class TestOpenLP(TestCase):
Test that the set_busy_cursor() method sets the cursor
"""
# GIVEN: An app
app = OpenLP([])
# WHEN: set_busy_cursor() is called
with patch.object(app, 'setOverrideCursor') as mocked_setOverrideCursor, \
patch.object(app, 'processEvents') as mocked_processEvents:
app.set_busy_cursor()
with patch.object(self.openlp, 'setOverrideCursor') as mocked_setOverrideCursor, \
patch.object(self.openlp, 'processEvents') as mocked_processEvents:
self.openlp.set_busy_cursor()
# THEN: The cursor should have been set
mocked_setOverrideCursor.assert_called_once_with(QtCore.Qt.BusyCursor)
@ -265,29 +266,15 @@ class TestOpenLP(TestCase):
Test that the set_normal_cursor() method resets the cursor
"""
# GIVEN: An app
app = OpenLP([])
# WHEN: set_normal_cursor() is called
with patch.object(app, 'restoreOverrideCursor') as mocked_restoreOverrideCursor, \
patch.object(app, 'processEvents') as mocked_processEvents:
app.set_normal_cursor()
with patch.object(self.openlp, 'restoreOverrideCursor') as mocked_restoreOverrideCursor, \
patch.object(self.openlp, 'processEvents') as mocked_processEvents:
self.openlp.set_normal_cursor()
# THEN: The cursor should have been set
mocked_restoreOverrideCursor.assert_called_once_with()
mocked_processEvents.assert_called_once_with()
class TestInit(TestCase, TestMixin):
def setUp(self):
self.build_settings()
with patch('openlp.core.app.OpenLPMixin.__init__') as constructor:
constructor.return_value = None
self.openlp = OpenLP(list())
def tearDown(self):
self.destroy_settings()
del self.openlp
def test_event(self):
"""
Test the reimplemented event method

View File

@ -1,137 +0,0 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2017 OpenLP Developers #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
This module contains tests for the openlp.core.lib.listwidgetwithdnd module
"""
from unittest import TestCase
from unittest.mock import MagicMock, patch
from types import GeneratorType
from openlp.core.common.i18n import UiStrings
from openlp.core.ui.lib.listwidgetwithdnd import ListWidgetWithDnD
class TestListWidgetWithDnD(TestCase):
"""
Test the :class:`~openlp.core.lib.listwidgetwithdnd.ListWidgetWithDnD` class
"""
def test_clear(self):
"""
Test the clear method when called without any arguments.
"""
# GIVEN: An instance of ListWidgetWithDnD
widget = ListWidgetWithDnD()
# WHEN: Calling clear with out any arguments
widget.clear()
# THEN: The results text should be the standard 'no results' text.
self.assertEqual(widget.no_results_text, UiStrings().NoResults)
def test_clear_search_while_typing(self):
"""
Test the clear method when called with the search_while_typing argument set to True
"""
# GIVEN: An instance of ListWidgetWithDnD
widget = ListWidgetWithDnD()
# WHEN: Calling clear with search_while_typing set to True
widget.clear(search_while_typing=True)
# THEN: The results text should be the 'short results' text.
self.assertEqual(widget.no_results_text, UiStrings().ShortResults)
def test_all_items_no_list_items(self):
"""
Test allItems when there are no items in the list widget
"""
# GIVEN: An instance of ListWidgetWithDnD
widget = ListWidgetWithDnD()
with patch.object(widget, 'count', return_value=0), \
patch.object(widget, 'item', side_effect=lambda x: [][x]):
# WHEN: Calling allItems
result = widget.allItems()
# THEN: An instance of a Generator object should be returned. The generator should not yeild any results
self.assertIsInstance(result, GeneratorType)
self.assertEqual(list(result), [])
def test_all_items_list_items(self):
"""
Test allItems when the list widget contains some items.
"""
# GIVEN: An instance of ListWidgetWithDnD
widget = ListWidgetWithDnD()
with patch.object(widget, 'count', return_value=2), \
patch.object(widget, 'item', side_effect=lambda x: [5, 3][x]):
# WHEN: Calling allItems
result = widget.allItems()
# THEN: An instance of a Generator object should be returned. The generator should not yeild any results
self.assertIsInstance(result, GeneratorType)
self.assertEqual(list(result), [5, 3])
def test_paint_event(self):
"""
Test the paintEvent method when the list is not empty
"""
# GIVEN: An instance of ListWidgetWithDnD with a mocked out count methode which returns 1
# (i.e the list has an item)
widget = ListWidgetWithDnD()
with patch('openlp.core.ui.lib.listwidgetwithdnd.QtWidgets.QListWidget.paintEvent') as mocked_paint_event, \
patch.object(widget, 'count', return_value=1), \
patch.object(widget, 'viewport') as mocked_viewport:
mocked_event = MagicMock()
# WHEN: Calling paintEvent
widget.paintEvent(mocked_event)
# THEN: The overridden paintEvnet should have been called
mocked_paint_event.assert_called_once_with(mocked_event)
self.assertFalse(mocked_viewport.called)
def test_paint_event_no_items(self):
"""
Test the paintEvent method when the list is empty
"""
# GIVEN: An instance of ListWidgetWithDnD with a mocked out count methode which returns 0
# (i.e the list is empty)
widget = ListWidgetWithDnD()
mocked_painter_instance = MagicMock()
mocked_qrect = MagicMock()
with patch('openlp.core.ui.lib.listwidgetwithdnd.QtWidgets.QListWidget.paintEvent') as mocked_paint_event, \
patch.object(widget, 'count', return_value=0), \
patch.object(widget, 'viewport'), \
patch('openlp.core.ui.lib.listwidgetwithdnd.QtGui.QPainter',
return_value=mocked_painter_instance) as mocked_qpainter, \
patch('openlp.core.ui.lib.listwidgetwithdnd.QtCore.QRect', return_value=mocked_qrect):
mocked_event = MagicMock()
# WHEN: Calling paintEvent
widget.paintEvent(mocked_event)
# THEN: The overridden paintEvnet should have been called, and some text should be drawn.
mocked_paint_event.assert_called_once_with(mocked_event)
mocked_qpainter.assert_called_once_with(widget.viewport())
mocked_painter_instance.drawText.assert_called_once_with(mocked_qrect, 4100, 'No Search Results')

View File

@ -62,7 +62,7 @@ class TestFirstTimeForm(TestCase, TestMixin):
self.assertTrue('OpenLP 3.1.5 build 3000' in about_form.about_text_edit.toPlainText(),
"The build number should be set correctly")
def test_about_form_date_test(self):
def test_about_form_date(self):
"""
Test that the copyright date is included correctly
"""

View File

@ -26,10 +26,11 @@ import os
from unittest import TestCase
from unittest.mock import MagicMock, patch
from PyQt5 import QtWidgets
from PyQt5 import QtCore, QtWidgets
from openlp.core.common.i18n import UiStrings
from openlp.core.common.registry import Registry
from openlp.core.display.screens import ScreenList
from openlp.core.ui.mainwindow import MainWindow
from tests.helpers.testmixin import TestMixin
@ -37,8 +38,23 @@ from tests.utils.constants import TEST_RESOURCES_PATH
class TestMainWindow(TestCase, TestMixin):
"""
Test the main window
"""
def _create_mock_action(self, parent, name, **kwargs):
"""
Create a fake action with some "real" attributes
"""
action = QtWidgets.QAction(parent)
action.setObjectName(name)
if kwargs.get('triggers'):
action.triggered.connect(kwargs.pop('triggers'))
return action
def setUp(self):
"""
Set up the objects we need for all of the tests
"""
Registry.create()
self.registry = Registry()
self.setup_application()
@ -48,30 +64,18 @@ class TestMainWindow(TestCase, TestMixin):
self.app.args = []
Registry().register('application', self.app)
Registry().set_flag('no_web_server', False)
# Mock classes and methods used by mainwindow.
with patch('openlp.core.ui.mainwindow.SettingsForm') as mocked_settings_form, \
patch('openlp.core.ui.mainwindow.ImageManager') as mocked_image_manager, \
patch('openlp.core.ui.mainwindow.LiveController') as mocked_live_controller, \
patch('openlp.core.ui.mainwindow.PreviewController') as mocked_preview_controller, \
patch('openlp.core.ui.mainwindow.OpenLPDockWidget') as mocked_dock_widget, \
patch('openlp.core.ui.mainwindow.QtWidgets.QToolBox') as mocked_q_tool_box_class, \
patch('openlp.core.ui.mainwindow.QtWidgets.QMainWindow.addDockWidget') as mocked_add_dock_method, \
patch('openlp.core.ui.mainwindow.ThemeManager') as mocked_theme_manager, \
patch('openlp.core.ui.mainwindow.Renderer') as mocked_renderer, \
patch('openlp.core.ui.mainwindow.websockets.WebSocketServer'), \
patch('openlp.core.ui.mainwindow.server.HttpServer'):
self.mocked_settings_form = mocked_settings_form
self.mocked_image_manager = mocked_image_manager
self.mocked_live_controller = mocked_live_controller
self.mocked_preview_controller = mocked_preview_controller
self.mocked_dock_widget = mocked_dock_widget
self.mocked_q_tool_box_class = mocked_q_tool_box_class
self.mocked_add_dock_method = mocked_add_dock_method
self.mocked_theme_manager = mocked_theme_manager
self.mocked_renderer = mocked_renderer
self.add_toolbar_action_patcher = patch('openlp.core.ui.mainwindow.create_action')
self.mocked_add_toolbar_action = self.add_toolbar_action_patcher.start()
self.mocked_add_toolbar_action.side_effect = self._create_mock_action
with patch('openlp.core.display.screens.ScreenList.__instance__', spec=ScreenList) as mocked_screen_list:
mocked_screen_list.current = {'number': 0, 'size': QtCore.QSize(600, 800), 'primary': True}
self.main_window = MainWindow()
def tearDown(self):
"""
Delete all the C++ objects and stop all the patchers
"""
self.add_toolbar_action_patcher.stop()
del self.main_window
def test_cmd_line_file(self):
@ -81,13 +85,13 @@ class TestMainWindow(TestCase, TestMixin):
# GIVEN a service as an argument to openlp
service = os.path.join(TEST_RESOURCES_PATH, 'service', 'test.osz')
self.main_window.arguments = [service]
with patch('openlp.core.ui.servicemanager.ServiceManager.load_file') as mocked_load_path:
# WHEN the argument is processed
with patch.object(self.main_window.service_manager, 'load_file') as mocked_load_file:
self.main_window.open_cmd_line_files(service)
# THEN the service from the arguments is loaded
mocked_load_path.assert_called_with(service), 'load_path should have been called with the service\'s path'
mocked_load_file.assert_called_with(service)
def test_cmd_line_arg(self):
"""
@ -96,13 +100,13 @@ class TestMainWindow(TestCase, TestMixin):
# GIVEN a non service file as an argument to openlp
service = os.path.join('openlp.py')
self.main_window.arguments = [service]
with patch('openlp.core.ui.servicemanager.ServiceManager.load_file') as mocked_load_path:
with patch('openlp.core.ui.servicemanager.ServiceManager.load_file') as mocked_load_file:
# WHEN the argument is processed
self.main_window.open_cmd_line_files("")
# THEN the file should not be opened
assert not mocked_load_path.called, 'load_path should not have been called'
assert not mocked_load_file.called, 'load_file should not have been called'
def test_main_window_title(self):
"""
@ -151,14 +155,14 @@ class TestMainWindow(TestCase, TestMixin):
# WHEN: you check the started functions
# THEN: the following registry functions should have been registered
self.assertEqual(len(self.registry.service_list), 6, 'The registry should have 6 services.')
self.assertEqual(len(self.registry.functions_list), 18, 'The registry should have 18 functions')
self.assertTrue('application' in self.registry.service_list, 'The application should have been registered.')
self.assertTrue('main_window' in self.registry.service_list, 'The main_window should have been registered.')
self.assertTrue('media_controller' in self.registry.service_list, 'The media_controller should have been '
'registered.')
self.assertTrue('plugin_manager' in self.registry.service_list,
'The plugin_manager should have been registered.')
assert len(self.registry.service_list) == 12, \
'The registry should have 12 services, got {}'.format(self.registry.service_list.keys())
assert len(self.registry.functions_list) == 19, \
'The registry should have 19 functions, got {}'.format(self.registry.functions_list.keys())
assert 'application' in self.registry.service_list, 'The application should have been registered.'
assert 'main_window' in self.registry.service_list, 'The main_window should have been registered.'
assert 'media_controller' in self.registry.service_list, 'The media_controller should have been registered.'
assert 'plugin_manager' in self.registry.service_list, 'The plugin_manager should have been registered.'
def test_projector_manager_hidden_on_startup(self):
"""
@ -167,7 +171,7 @@ class TestMainWindow(TestCase, TestMixin):
# GIVEN: A built main window
# WHEN: OpenLP is started
# THEN: The projector manager should be hidden
self.main_window.projector_manager_dock.setVisible.assert_called_once_with(False)
assert self.main_window.projector_manager_dock.isVisible() is False
def test_on_search_shortcut_triggered_shows_media_manager(self):
"""
@ -203,56 +207,38 @@ class TestMainWindow(TestCase, TestMixin):
self.assertEqual(0, mocked_media_manager_dock.setVisible.call_count)
mocked_widget.on_focus.assert_called_with()
@patch('openlp.core.ui.mainwindow.MainWindow.plugin_manager')
@patch('openlp.core.ui.mainwindow.MainWindow.first_time')
@patch('openlp.core.ui.mainwindow.MainWindow.application')
@patch('openlp.core.ui.mainwindow.FirstTimeForm')
@patch('openlp.core.ui.mainwindow.QtWidgets.QMessageBox.warning')
@patch('openlp.core.ui.mainwindow.Settings')
def test_on_first_time_wizard_clicked_show_projectors_after(self, mocked_Settings, mocked_warning,
mocked_FirstTimeForm, mocked_application,
mocked_first_time,
mocked_plugin_manager):
def test_on_first_time_wizard_clicked_show_projectors_after(self, MockSettings, mocked_warning, MockWizard):
"""Test that the projector manager is shown after the FTW is run"""
# GIVEN: Main_window, patched things, patched "Yes" as confirmation to re-run wizard, settings to True.
mocked_Settings_obj = MagicMock()
mocked_Settings_obj.value.return_value = True
mocked_Settings.return_value = mocked_Settings_obj
MockSettings.return_value.value.return_value = True
mocked_warning.return_value = QtWidgets.QMessageBox.Yes
mocked_FirstTimeForm_obj = MagicMock()
mocked_FirstTimeForm_obj.was_cancelled = False
mocked_FirstTimeForm.return_value = mocked_FirstTimeForm_obj
mocked_plugin_manager.plugins = []
self.main_window.projector_manager_dock = MagicMock()
MockWizard.return_value.was_cancelled = False
with patch.object(self.main_window, 'projector_manager_dock') as mocked_dock, \
patch.object(self.registry, 'execute'), patch.object(self.main_window, 'theme_manager_contents'):
# WHEN: on_first_time_wizard_clicked is called
self.main_window.on_first_time_wizard_clicked()
# THEN: projector_manager_dock.setVisible should had been called once
self.main_window.projector_manager_dock.setVisible.assert_called_once_with(True)
mocked_dock.setVisible.assert_called_once_with(True)
@patch('openlp.core.ui.mainwindow.MainWindow.plugin_manager')
@patch('openlp.core.ui.mainwindow.MainWindow.first_time')
@patch('openlp.core.ui.mainwindow.MainWindow.application')
@patch('openlp.core.ui.mainwindow.FirstTimeForm')
@patch('openlp.core.ui.mainwindow.QtWidgets.QMessageBox.warning')
@patch('openlp.core.ui.mainwindow.Settings')
def test_on_first_time_wizard_clicked_hide_projectors_after(self, mocked_Settings, mocked_warning,
mocked_FirstTimeForm, mocked_application,
mocked_first_time,
mocked_plugin_manager):
def test_on_first_time_wizard_clicked_hide_projectors_after(self, MockSettings, mocked_warning, MockWizard):
"""Test that the projector manager is hidden after the FTW is run"""
# GIVEN: Main_window, patched things, patched "Yes" as confirmation to re-run wizard, settings to False.
mocked_Settings_obj = MagicMock()
mocked_Settings_obj.value.return_value = False
mocked_Settings.return_value = mocked_Settings_obj
MockSettings.return_value.value.return_value = False
mocked_warning.return_value = QtWidgets.QMessageBox.Yes
mocked_FirstTimeForm_obj = MagicMock()
mocked_FirstTimeForm_obj.was_cancelled = False
mocked_FirstTimeForm.return_value = mocked_FirstTimeForm_obj
mocked_plugin_manager.plugins = []
self.main_window.projector_manager_dock = MagicMock()
MockWizard.return_value.was_cancelled = False
# WHEN: on_first_time_wizard_clicked is called
with patch.object(self.main_window, 'projector_manager_dock') as mocked_dock, \
patch.object(self.registry, 'execute'), patch.object(self.main_window, 'theme_manager_contents'):
self.main_window.on_first_time_wizard_clicked()
# THEN: projector_manager_dock.setVisible should had been called once
self.main_window.projector_manager_dock.setVisible.assert_called_once_with(False)
mocked_dock.setVisible.assert_called_once_with(False)

Some files were not shown because too many files have changed in this diff Show More