openlp-core team mailing list archive
-
openlp-core team
-
Mailing list archive
-
Message #29009
[Merge] lp:~trb143/openlp/refactor26 into lp:openlp
Tim Bentley has proposed merging lp:~trb143/openlp/refactor26 into lp:openlp.
Requested reviews:
OpenLP Core (openlp-core)
For more details, see:
https://code.launchpad.net/~trb143/openlp/refactor26/+merge/290638
Started to move things around and make more logical in 2.2 and planned to do more in 2.4.
Never got a chance in 2.4 so here goes.
Move classes from utils to common
lp:~trb143/openlp/refactor26 (revision 2637)
[SUCCESS] https://ci.openlp.io/job/Branch-01-Pull/1358/
[SUCCESS] https://ci.openlp.io/job/Branch-02-Functional-Tests/1277/
[SUCCESS] https://ci.openlp.io/job/Branch-03-Interface-Tests/1216/
[SUCCESS] https://ci.openlp.io/job/Branch-04a-Windows_Functional_Tests/1049/
[SUCCESS] https://ci.openlp.io/job/Branch-04b-Windows_Interface_Tests/640/
[SUCCESS] https://ci.openlp.io/job/Branch-05a-Code_Analysis/707/
[SUCCESS] https://ci.openlp.io/job/Branch-05b-Test_Coverage/575/
next set is to sort out utils __init__ but that is bigger!
--
Your team OpenLP Core is requested to review the proposed merge of lp:~trb143/openlp/refactor26 into lp:openlp.
=== modified file 'openlp/core/__init__.py'
--- openlp/core/__init__.py 2016-01-16 20:13:41 +0000
+++ openlp/core/__init__.py 2016-03-31 17:04:24 +0000
@@ -36,8 +36,8 @@
import time
from PyQt5 import QtCore, QtGui, QtWidgets
-from openlp.core.common import Registry, OpenLPMixin, AppLocation, Settings, UiStrings, check_directory_exists, \
- is_macosx, is_win, translate
+from openlp.core.common import Registry, OpenLPMixin, AppLocation, LanguageManager, Settings, UiStrings, \
+ check_directory_exists, is_macosx, is_win, translate
from openlp.core.lib import ScreenList
from openlp.core.resources import qInitResources
from openlp.core.ui.mainwindow import MainWindow
@@ -45,7 +45,7 @@
from openlp.core.ui.firsttimeform import FirstTimeForm
from openlp.core.ui.exceptionform import ExceptionForm
from openlp.core.ui import SplashScreen
-from openlp.core.utils import LanguageManager, VersionThread, get_application_version
+from openlp.core.utils import VersionThread, get_application_version
__all__ = ['OpenLP', 'main']
=== modified file 'openlp/core/common/__init__.py'
--- openlp/core/common/__init__.py 2015-12-31 22:46:06 +0000
+++ openlp/core/common/__init__.py 2016-03-31 17:04:24 +0000
@@ -242,3 +242,5 @@
from .settings import Settings
from .applocation import AppLocation
from .historycombobox import HistoryComboBox
+from .actions import ActionList
+from .languagemanager import LanguageManager
=== added file 'openlp/core/common/actions.py'
--- openlp/core/common/actions.py 1970-01-01 00:00:00 +0000
+++ openlp/core/common/actions.py 2016-03-31 17:04:24 +0000
@@ -0,0 +1,388 @@
+# -*- coding: utf-8 -*-
+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
+
+###############################################################################
+# OpenLP - Open Source Lyrics Projection #
+# --------------------------------------------------------------------------- #
+# Copyright (c) 2008-2016 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.utils.actions` module provides action list classes used
+by the shortcuts system.
+"""
+import logging
+
+from PyQt5 import QtCore, QtGui, QtWidgets
+
+from openlp.core.common import Settings
+
+
+log = logging.getLogger(__name__)
+
+
+class ActionCategory(object):
+ """
+ The :class:`~openlp.core.utils.ActionCategory` class encapsulates a category for the
+ :class:`~openlp.core.utils.CategoryList` class.
+ """
+ def __init__(self, name, weight=0):
+ """
+ Constructor
+ """
+ self.name = name
+ self.weight = weight
+ self.actions = CategoryActionList()
+
+
+class CategoryActionList(object):
+ """
+ The :class:`~openlp.core.utils.CategoryActionList` class provides a sorted list of actions within a category.
+ """
+ def __init__(self):
+ """
+ Constructor
+ """
+ self.index = 0
+ self.actions = []
+
+ def __contains__(self, key):
+ """
+ Implement the __contains__() method to make this class a dictionary type
+ """
+ for weight, action in self.actions:
+ if action == key:
+ return True
+ return False
+
+ def __len__(self):
+ """
+ Implement the __len__() method to make this class a dictionary type
+ """
+ return len(self.actions)
+
+ def __iter__(self):
+ """
+ Implement the __getitem__() method to make this class iterable
+ """
+ return self
+
+ def __next__(self):
+ """
+ Python 3 "next" method.
+ """
+ if self.index >= len(self.actions):
+ self.index = 0
+ raise StopIteration
+ else:
+ self.index += 1
+ return self.actions[self.index - 1][1]
+
+ def append(self, action):
+ """
+ Append an action
+ """
+ weight = 0
+ if self.actions:
+ weight = self.actions[-1][0] + 1
+ self.add(action, weight)
+
+ def add(self, action, weight=0):
+ """
+ Add an action.
+ """
+ self.actions.append((weight, action))
+ self.actions.sort(key=lambda act: act[0])
+
+ def remove(self, action):
+ """
+ Remove an action
+ """
+ for item in self.actions:
+ if item[1] == action:
+ self.actions.remove(item)
+ return
+ raise ValueError('Action "%s" does not exist.' % action)
+
+
+class CategoryList(object):
+ """
+ The :class:`~openlp.core.utils.CategoryList` class encapsulates a category list for the
+ :class:`~openlp.core.utils.ActionList` class and provides an iterator interface for walking through the list of
+ actions in this category.
+ """
+
+ def __init__(self):
+ """
+ Constructor
+ """
+ self.index = 0
+ self.categories = []
+
+ def __getitem__(self, key):
+ """
+ Implement the __getitem__() method to make this class like a dictionary
+ """
+ for category in self.categories:
+ if category.name == key:
+ return category
+ raise KeyError('Category "%s" does not exist.' % key)
+
+ def __len__(self):
+ """
+ Implement the __len__() method to make this class like a dictionary
+ """
+ return len(self.categories)
+
+ def __iter__(self):
+ """
+ Implement the __iter__() method to make this class like a dictionary
+ """
+ return self
+
+ def __next__(self):
+ """
+ Python 3 "next" method for iterator.
+ """
+ if self.index >= len(self.categories):
+ self.index = 0
+ raise StopIteration
+ else:
+ self.index += 1
+ return self.categories[self.index - 1]
+
+ def __contains__(self, key):
+ """
+ Implement the __contains__() method to make this class like a dictionary
+ """
+ for category in self.categories:
+ if category.name == key:
+ return True
+ return False
+
+ def append(self, name, actions=None):
+ """
+ Append a category
+ """
+ weight = 0
+ if self.categories:
+ weight = self.categories[-1].weight + 1
+ self.add(name, weight, actions)
+
+ def add(self, name, weight=0, actions=None):
+ """
+ Add a category
+ """
+ category = ActionCategory(name, weight)
+ if actions:
+ for action in actions:
+ if isinstance(action, tuple):
+ category.actions.add(action[0], action[1])
+ else:
+ category.actions.append(action)
+ self.categories.append(category)
+ self.categories.sort(key=lambda cat: cat.weight)
+
+ def remove(self, name):
+ """
+ Remove a category
+ """
+ for category in self.categories:
+ if category.name == name:
+ self.categories.remove(category)
+ return
+ raise ValueError('Category "%s" does not exist.' % name)
+
+
+class ActionList(object):
+ """
+ The :class:`~openlp.core.utils.ActionList` class contains a list of menu actions and categories associated with
+ those actions. Each category also has a weight by which it is sorted when iterating through the list of actions or
+ categories.
+ """
+ instance = None
+ shortcut_map = {}
+
+ def __init__(self):
+ """
+ Constructor
+ """
+ self.categories = CategoryList()
+
+ @staticmethod
+ def get_instance():
+ """
+ Get the instance of this class.
+ """
+ if ActionList.instance is None:
+ ActionList.instance = ActionList()
+ return ActionList.instance
+
+ def add_action(self, action, category=None, weight=None):
+ """
+ Add an action to the list of actions.
+
+ **Note**: The action's objectName must be set when you want to add it!
+
+ :param action: The action to add (QAction). **Note**, the action must not have an empty ``objectName``.
+ :param category: The category this action belongs to. The category has to be a python string. . **Note**,
+ if the category is ``None``, the category and its actions are being hidden in the shortcut dialog. However,
+ if they are added, it is possible to avoid assigning shortcuts twice, which is important.
+ :param weight: The weight specifies how important a category is. However, this only has an impact on the order
+ the categories are displayed.
+ """
+ if category not in self.categories:
+ self.categories.append(category)
+ settings = Settings()
+ settings.beginGroup('shortcuts')
+ # Get the default shortcut from the config.
+ action.default_shortcuts = settings.get_default_value(action.objectName())
+ if weight is None:
+ self.categories[category].actions.append(action)
+ else:
+ self.categories[category].actions.add(action, weight)
+ # Load the shortcut from the config.
+ shortcuts = settings.value(action.objectName())
+ settings.endGroup()
+ if not shortcuts:
+ action.setShortcuts([])
+ return
+ # We have to do this to ensure that the loaded shortcut list e. g. STRG+O (German) is converted to CTRL+O,
+ # which is only done when we convert the strings in this way (QKeySequencet -> uncode).
+ shortcuts = list(map(QtGui.QKeySequence.toString, list(map(QtGui.QKeySequence, shortcuts))))
+ # Check the alternate shortcut first, to avoid problems when the alternate shortcut becomes the primary shortcut
+ # after removing the (initial) primary shortcut due to conflicts.
+ if len(shortcuts) == 2:
+ existing_actions = ActionList.shortcut_map.get(shortcuts[1], [])
+ # Check for conflicts with other actions considering the shortcut context.
+ if self._is_shortcut_available(existing_actions, action):
+ actions = ActionList.shortcut_map.get(shortcuts[1], [])
+ actions.append(action)
+ ActionList.shortcut_map[shortcuts[1]] = actions
+ else:
+ log.warning('Shortcut "%s" is removed from "%s" because another action already uses this shortcut.' %
+ (shortcuts[1], action.objectName()))
+ shortcuts.remove(shortcuts[1])
+ # Check the primary shortcut.
+ existing_actions = ActionList.shortcut_map.get(shortcuts[0], [])
+ # Check for conflicts with other actions considering the shortcut context.
+ if self._is_shortcut_available(existing_actions, action):
+ actions = ActionList.shortcut_map.get(shortcuts[0], [])
+ actions.append(action)
+ ActionList.shortcut_map[shortcuts[0]] = actions
+ else:
+ log.warning('Shortcut "%s" is removed from "%s" because another action already uses this shortcut.' %
+ (shortcuts[0], action.objectName()))
+ shortcuts.remove(shortcuts[0])
+ action.setShortcuts([QtGui.QKeySequence(shortcut) for shortcut in shortcuts])
+
+ def remove_action(self, action, category=None):
+ """
+ This removes an action from its category. Empty categories are automatically removed.
+
+ :param action: The ``QAction`` object to be removed.
+ :param category: The name (unicode string) of the category, which contains the action. Defaults to None.
+ """
+ if category not in self.categories:
+ return
+ self.categories[category].actions.remove(action)
+ # Remove empty categories.
+ if not self.categories[category].actions:
+ self.categories.remove(category)
+ shortcuts = list(map(QtGui.QKeySequence.toString, action.shortcuts()))
+ for shortcut in shortcuts:
+ # Remove action from the list of actions which are using this shortcut.
+ ActionList.shortcut_map[shortcut].remove(action)
+ # Remove empty entries.
+ if not ActionList.shortcut_map[shortcut]:
+ del ActionList.shortcut_map[shortcut]
+
+ def add_category(self, name, weight):
+ """
+ Add an empty category to the list of categories. This is only convenient for categories with a given weight.
+
+ :param name: The category's name.
+ :param weight: The category's weight (int).
+ """
+ if name in self.categories:
+ # Only change the weight and resort the categories again.
+ for category in self.categories:
+ if category.name == name:
+ category.weight = weight
+ self.categories.categories.sort(key=lambda cat: cat.weight)
+ return
+ self.categories.add(name, weight)
+
+ def update_shortcut_map(self, action, old_shortcuts):
+ """
+ Remove the action for the given ``old_shortcuts`` from the ``shortcut_map`` to ensure its up-to-dateness.
+ **Note**: The new action's shortcuts **must** be assigned to the given ``action`` **before** calling this
+ method.
+
+ :param action: The action whose shortcuts are supposed to be updated in the ``shortcut_map``.
+ :param old_shortcuts: A list of unicode key sequences.
+ """
+ for old_shortcut in old_shortcuts:
+ # Remove action from the list of actions which are using this shortcut.
+ ActionList.shortcut_map[old_shortcut].remove(action)
+ # Remove empty entries.
+ if not ActionList.shortcut_map[old_shortcut]:
+ del ActionList.shortcut_map[old_shortcut]
+ new_shortcuts = list(map(QtGui.QKeySequence.toString, action.shortcuts()))
+ # Add the new shortcuts to the map.
+ for new_shortcut in new_shortcuts:
+ existing_actions = ActionList.shortcut_map.get(new_shortcut, [])
+ existing_actions.append(action)
+ ActionList.shortcut_map[new_shortcut] = existing_actions
+
+ def _is_shortcut_available(self, existing_actions, action):
+ """
+ Checks if the given ``action`` may use its assigned shortcut(s) or not. Returns ``True`` or ``False.
+
+ :param existing_actions: A list of actions which already use a particular shortcut.
+ :param action: The action which wants to use a particular shortcut.
+ """
+ global_context = action.shortcutContext() in [QtCore.Qt.WindowShortcut, QtCore.Qt.ApplicationShortcut]
+ affected_actions = []
+ if global_context:
+ affected_actions = [a for a in self.get_all_child_objects(action.parent()) if isinstance(a,
+ QtWidgets.QAction)]
+ for existing_action in existing_actions:
+ if action is existing_action:
+ continue
+ if existing_action in affected_actions:
+ return False
+ if existing_action.shortcutContext() in [QtCore.Qt.WindowShortcut, QtCore.Qt.ApplicationShortcut]:
+ return False
+ elif action in self.get_all_child_objects(existing_action.parent()):
+ return False
+ return True
+
+ def get_all_child_objects(self, qobject):
+ """
+ Goes recursively through the children of ``qobject`` and returns a list of all child objects.
+ """
+ children = qobject.children()
+ # Append the children's children.
+ children.extend(list(map(self.get_all_child_objects, children)))
+ return children
+
+
+class CategoryOrder(object):
+ """
+ An enumeration class for category weights.
+ """
+ standard_menu = -20
+ standard_toolbar = -10
=== added file 'openlp/core/common/db.py'
--- openlp/core/common/db.py 1970-01-01 00:00:00 +0000
+++ openlp/core/common/db.py 2016-03-31 17:04:24 +0000
@@ -0,0 +1,71 @@
+# -*- coding: utf-8 -*-
+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
+
+###############################################################################
+# OpenLP - Open Source Lyrics Projection #
+# --------------------------------------------------------------------------- #
+# Copyright (c) 2008-2016 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:`db` module provides helper functions for database related methods.
+"""
+import sqlalchemy
+import logging
+
+from copy import deepcopy
+
+log = logging.getLogger(__name__)
+
+
+def drop_column(op, tablename, columnname):
+ drop_columns(op, tablename, [columnname])
+
+
+def drop_columns(op, tablename, columns):
+ """
+ Column dropping functionality for SQLite, as there is no DROP COLUMN support in SQLite
+
+ From https://github.com/klugjohannes/alembic-sqlite
+ """
+
+ # get the db engine and reflect database tables
+ engine = op.get_bind()
+ meta = sqlalchemy.MetaData(bind=engine)
+ meta.reflect()
+
+ # create a select statement from the old table
+ old_table = meta.tables[tablename]
+ select = sqlalchemy.sql.select([c for c in old_table.c if c.name not in columns])
+
+ # get remaining columns without table attribute attached
+ remaining_columns = [deepcopy(c) for c in old_table.columns if c.name not in columns]
+ for column in remaining_columns:
+ column.table = None
+
+ # create a temporary new table
+ new_tablename = '{0}_new'.format(tablename)
+ op.create_table(new_tablename, *remaining_columns)
+ meta.reflect()
+ new_table = meta.tables[new_tablename]
+
+ # copy data from old table
+ insert = sqlalchemy.sql.insert(new_table).from_select([c.name for c in remaining_columns], select)
+ engine.execute(insert)
+
+ # drop the old table and rename the new table to take the old tables
+ # position
+ op.drop_table(tablename)
+ op.rename_table(new_tablename, tablename)
=== added file 'openlp/core/common/languagemanager.py'
--- openlp/core/common/languagemanager.py 1970-01-01 00:00:00 +0000
+++ openlp/core/common/languagemanager.py 2016-03-31 17:04:24 +0000
@@ -0,0 +1,146 @@
+# -*- coding: utf-8 -*-
+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
+
+###############################################################################
+# OpenLP - Open Source Lyrics Projection #
+# --------------------------------------------------------------------------- #
+# Copyright (c) 2008-2016 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:`languagemanager` module provides all the translation settings and language file loading for OpenLP.
+"""
+import logging
+import re
+import sys
+
+from PyQt5 import QtCore, QtWidgets
+
+
+from openlp.core.common import AppLocation, Settings, translate, is_win, is_macosx
+
+log = logging.getLogger(__name__)
+
+
+class LanguageManager(object):
+ """
+ Helper for Language selection
+ """
+ __qm_list__ = {}
+ auto_language = False
+
+ @staticmethod
+ def get_translator(language):
+ """
+ Set up a translator to use in this instance of OpenLP
+
+ :param language: The language to load into the translator
+ """
+ if LanguageManager.auto_language:
+ language = QtCore.QLocale.system().name()
+ lang_path = AppLocation.get_directory(AppLocation.LanguageDir)
+ app_translator = QtCore.QTranslator()
+ app_translator.load(language, lang_path)
+ # A translator for buttons and other default strings provided by Qt.
+ if not is_win() and not is_macosx():
+ lang_path = QtCore.QLibraryInfo.location(QtCore.QLibraryInfo.TranslationsPath)
+ default_translator = QtCore.QTranslator()
+ default_translator.load('qt_%s' % language, lang_path)
+ return app_translator, default_translator
+
+ @staticmethod
+ def find_qm_files():
+ """
+ Find all available language files in this OpenLP install
+ """
+ log.debug('Translation files: %s', AppLocation.get_directory(AppLocation.LanguageDir))
+ trans_dir = QtCore.QDir(AppLocation.get_directory(AppLocation.LanguageDir))
+ file_names = trans_dir.entryList(['*.qm'], QtCore.QDir.Files, QtCore.QDir.Name)
+ # Remove qm files from the list which start with "qt_".
+ file_names = [file_ for file_ in file_names if not file_.startswith('qt_')]
+ return list(map(trans_dir.filePath, file_names))
+
+ @staticmethod
+ def language_name(qm_file):
+ """
+ Load the language name from a language file
+
+ :param qm_file: The file to obtain the name from
+ """
+ translator = QtCore.QTranslator()
+ translator.load(qm_file)
+ return translator.translate('OpenLP.MainWindow', 'English', 'Please add the name of your language here')
+
+ @staticmethod
+ def get_language():
+ """
+ Retrieve a saved language to use from settings
+ """
+ language = Settings().value('core/language')
+ language = str(language)
+ log.info('Language file: \'%s\' Loaded from conf file' % language)
+ if re.match(r'[[].*[]]', language):
+ LanguageManager.auto_language = True
+ language = re.sub(r'[\[\]]', '', language)
+ return language
+
+ @staticmethod
+ def set_language(action, message=True):
+ """
+ Set the language to translate OpenLP into
+
+ :param action: The language menu option
+ :param message: Display the message option
+ """
+ language = 'en'
+ if action:
+ action_name = str(action.objectName())
+ if action_name == 'autoLanguageItem':
+ LanguageManager.auto_language = True
+ else:
+ LanguageManager.auto_language = False
+ qm_list = LanguageManager.get_qm_list()
+ language = str(qm_list[action_name])
+ if LanguageManager.auto_language:
+ language = '[%s]' % language
+ Settings().setValue('core/language', language)
+ log.info('Language file: \'%s\' written to conf file' % language)
+ if message:
+ QtWidgets.QMessageBox.information(None,
+ translate('OpenLP.LanguageManager', 'Language'),
+ translate('OpenLP.LanguageManager',
+ 'Please restart OpenLP to use your new language setting.'))
+
+ @staticmethod
+ def init_qm_list():
+ """
+ Initialise the list of available translations
+ """
+ LanguageManager.__qm_list__ = {}
+ qm_files = LanguageManager.find_qm_files()
+ for counter, qmf in enumerate(qm_files):
+ reg_ex = QtCore.QRegExp("^.*i18n/(.*).qm")
+ if reg_ex.exactMatch(qmf):
+ name = '%s' % reg_ex.cap(1)
+ LanguageManager.__qm_list__['%#2i %s' % (counter + 1, LanguageManager.language_name(qmf))] = name
+
+ @staticmethod
+ def get_qm_list():
+ """
+ Return the list of available translations
+ """
+ if not LanguageManager.__qm_list__:
+ LanguageManager.init_qm_list()
+ return LanguageManager.__qm_list__
=== modified file 'openlp/core/lib/ui.py'
--- openlp/core/lib/ui.py 2015-12-31 22:46:06 +0000
+++ openlp/core/lib/ui.py 2016-03-31 17:04:24 +0000
@@ -27,8 +27,8 @@
from PyQt5 import QtCore, QtGui, QtWidgets
from openlp.core.common import Registry, UiStrings, translate, is_macosx
+from openlp.core.common.actions import ActionList
from openlp.core.lib import build_icon
-from openlp.core.utils.actions import ActionList
log = logging.getLogger(__name__)
=== modified file 'openlp/core/ui/firsttimelanguageform.py'
--- openlp/core/ui/firsttimelanguageform.py 2016-01-09 16:26:14 +0000
+++ openlp/core/ui/firsttimelanguageform.py 2016-03-31 17:04:24 +0000
@@ -25,7 +25,7 @@
from PyQt5 import QtCore, QtWidgets
from openlp.core.lib.ui import create_action
-from openlp.core.utils import LanguageManager
+from openlp.core.common import LanguageManager
from .firsttimelanguagedialog import Ui_FirstTimeLanguageDialog
=== modified file 'openlp/core/ui/mainwindow.py'
--- openlp/core/ui/mainwindow.py 2016-01-11 21:57:20 +0000
+++ openlp/core/ui/mainwindow.py 2016-03-31 17:04:24 +0000
@@ -24,30 +24,29 @@
"""
import logging
import os
+import shutil
import sys
-import shutil
+import time
+from datetime import datetime
from distutils import dir_util
from distutils.errors import DistutilsFileError
from tempfile import gettempdir
-import time
-from datetime import datetime
from PyQt5 import QtCore, QtGui, QtWidgets
-from openlp.core.common import Registry, RegistryProperties, AppLocation, Settings, check_directory_exists, translate, \
- is_win, is_macosx
+from openlp.core.common import Registry, RegistryProperties, AppLocation, LanguageManager, Settings, \
+ check_directory_exists, translate, is_win, is_macosx
+from openlp.core.common.actions import ActionList, CategoryOrder
from openlp.core.lib import Renderer, OpenLPDockWidget, PluginManager, ImageManager, PluginStatus, ScreenList, \
build_icon
from openlp.core.lib.ui import UiStrings, create_action
from openlp.core.ui import AboutForm, SettingsForm, ServiceManager, ThemeManager, LiveController, PluginForm, \
MediaDockManager, ShortcutListForm, FormattingTagForm, PreviewController
-
+from openlp.core.ui.firsttimeform import FirstTimeForm
from openlp.core.ui.media import MediaController
-from openlp.core.utils import LanguageManager, add_actions, get_application_version
-from openlp.core.utils.actions import ActionList, CategoryOrder
-from openlp.core.ui.firsttimeform import FirstTimeForm
+from openlp.core.ui.printserviceform import PrintServiceForm
from openlp.core.ui.projector.manager import ProjectorManager
-from openlp.core.ui.printserviceform import PrintServiceForm
+from openlp.core.utils import get_application_version, add_actions
log = logging.getLogger(__name__)
=== modified file 'openlp/core/ui/servicemanager.py'
--- openlp/core/ui/servicemanager.py 2016-01-19 07:02:47 +0000
+++ openlp/core/ui/servicemanager.py 2016-03-31 17:04:24 +0000
@@ -23,23 +23,22 @@
The service manager sets up, loads, saves and manages services.
"""
import html
+import json
import os
import shutil
import zipfile
-import json
+from datetime import datetime, timedelta
from tempfile import mkstemp
-from datetime import datetime, timedelta
from PyQt5 import QtCore, QtGui, QtWidgets
from openlp.core.common import Registry, RegistryProperties, AppLocation, Settings, ThemeLevel, OpenLPMixin, \
RegistryMixin, check_directory_exists, UiStrings, translate
+from openlp.core.common.actions import ActionList, CategoryOrder
from openlp.core.lib import OpenLPToolbar, 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.printserviceform import PrintServiceForm
from openlp.core.utils import delete_file, split_filename, format_time
-from openlp.core.utils.actions import ActionList, CategoryOrder
class ServiceManagerList(QtWidgets.QTreeWidget):
=== modified file 'openlp/core/ui/shortcutlistform.py'
--- openlp/core/ui/shortcutlistform.py 2016-02-04 21:25:06 +0000
+++ openlp/core/ui/shortcutlistform.py 2016-03-31 17:04:24 +0000
@@ -27,7 +27,7 @@
from PyQt5 import QtCore, QtGui, QtWidgets
from openlp.core.common import RegistryProperties, Settings, translate
-from openlp.core.utils.actions import ActionList
+from openlp.core.common.actions import ActionList
from .shortcutlistdialog import Ui_ShortcutListDialog
REMOVE_AMPERSAND = re.compile(r'&{1}')
=== modified file 'openlp/core/ui/slidecontroller.py'
--- openlp/core/ui/slidecontroller.py 2016-02-28 20:33:19 +0000
+++ openlp/core/ui/slidecontroller.py 2016-03-31 17:04:24 +0000
@@ -23,20 +23,20 @@
The :mod:`slidecontroller` module contains the most important part of OpenLP - the slide controller
"""
+import copy
import os
-import copy
from collections import deque
from threading import Lock
from PyQt5 import QtCore, QtGui, QtWidgets
from openlp.core.common import Registry, RegistryProperties, Settings, SlideLimits, UiStrings, translate, \
- RegistryMixin, OpenLPMixin, is_win
+ RegistryMixin, OpenLPMixin
+from openlp.core.common.actions import ActionList, CategoryOrder
from openlp.core.lib import OpenLPToolbar, ItemCapabilities, ServiceItem, ImageSource, ServiceItemAction, \
ScreenList, build_icon, build_html
+from openlp.core.lib.ui import create_action
from openlp.core.ui import HideMode, MainDisplay, Display, DisplayControllerType
-from openlp.core.lib.ui import create_action
-from openlp.core.utils.actions import ActionList, CategoryOrder
from openlp.core.ui.listpreviewwidget import ListPreviewWidget
# Threshold which has to be trespassed to toggle.
=== modified file 'openlp/core/utils/__init__.py'
--- openlp/core/utils/__init__.py 2015-12-31 22:46:06 +0000
+++ openlp/core/utils/__init__.py 2016-03-31 17:04:24 +0000
@@ -22,29 +22,28 @@
"""
The :mod:`openlp.core.utils` module provides the utility libraries for OpenLP.
"""
-from datetime import datetime
-from distutils.version import LooseVersion
-from http.client import HTTPException
+import locale
import logging
-import locale
import os
import platform
import re
import socket
+import sys
import time
-from shutil import which
-from subprocess import Popen, PIPE
-import sys
-import urllib.request
import urllib.error
import urllib.parse
+import urllib.request
+from datetime import datetime
+from distutils.version import LooseVersion
+from http.client import HTTPException
from random import randint
+from shutil import which
+from subprocess import Popen, PIPE
from PyQt5 import QtGui, QtCore
from openlp.core.common import Registry, AppLocation, Settings, is_win, is_macosx
-
if not is_win() and not is_macosx():
try:
from xdg import BaseDirectory
@@ -511,7 +510,7 @@
try:
if ICU_COLLATOR is None:
import icu
- from .languagemanager import LanguageManager
+ from openlp.core.common.languagemanager import LanguageManager
language = LanguageManager.get_language()
icu_locale = icu.Locale(language)
ICU_COLLATOR = icu.Collator.createInstance(icu_locale)
@@ -523,21 +522,18 @@
def get_natural_key(string):
"""
Generate a key for locale aware natural string sorting.
+
+ :param string: string to be sorted by
Returns a list of string compare keys and integers.
"""
key = DIGITS_OR_NONDIGITS.findall(string)
key = [int(part) if part.isdigit() else get_locale_key(part) for part in key]
# Python 3 does not support comparison of different types anymore. So make sure, that we do not compare str
# and int.
- if string[0].isdigit():
+ if string and string[0].isdigit():
return [b''] + key
return key
-
-from .languagemanager import LanguageManager
-from .actions import ActionList
-
-
-__all__ = ['ActionList', 'LanguageManager', 'get_application_version', 'check_latest_version',
+__all__ = ['get_application_version', 'check_latest_version',
'add_actions', 'get_filesystem_encoding', 'get_web_page', 'get_uno_command', 'get_uno_instance',
'delete_file', 'clean_filename', 'format_time', 'get_locale_key', 'get_natural_key']
=== removed file 'openlp/core/utils/actions.py'
--- openlp/core/utils/actions.py 2015-12-31 22:46:06 +0000
+++ openlp/core/utils/actions.py 1970-01-01 00:00:00 +0000
@@ -1,388 +0,0 @@
-# -*- coding: utf-8 -*-
-# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
-
-###############################################################################
-# OpenLP - Open Source Lyrics Projection #
-# --------------------------------------------------------------------------- #
-# Copyright (c) 2008-2016 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.utils.actions` module provides action list classes used
-by the shortcuts system.
-"""
-import logging
-
-from PyQt5 import QtCore, QtGui, QtWidgets
-
-from openlp.core.common import Settings
-
-
-log = logging.getLogger(__name__)
-
-
-class ActionCategory(object):
- """
- The :class:`~openlp.core.utils.ActionCategory` class encapsulates a category for the
- :class:`~openlp.core.utils.CategoryList` class.
- """
- def __init__(self, name, weight=0):
- """
- Constructor
- """
- self.name = name
- self.weight = weight
- self.actions = CategoryActionList()
-
-
-class CategoryActionList(object):
- """
- The :class:`~openlp.core.utils.CategoryActionList` class provides a sorted list of actions within a category.
- """
- def __init__(self):
- """
- Constructor
- """
- self.index = 0
- self.actions = []
-
- def __contains__(self, key):
- """
- Implement the __contains__() method to make this class a dictionary type
- """
- for weight, action in self.actions:
- if action == key:
- return True
- return False
-
- def __len__(self):
- """
- Implement the __len__() method to make this class a dictionary type
- """
- return len(self.actions)
-
- def __iter__(self):
- """
- Implement the __getitem__() method to make this class iterable
- """
- return self
-
- def __next__(self):
- """
- Python 3 "next" method.
- """
- if self.index >= len(self.actions):
- self.index = 0
- raise StopIteration
- else:
- self.index += 1
- return self.actions[self.index - 1][1]
-
- def append(self, action):
- """
- Append an action
- """
- weight = 0
- if self.actions:
- weight = self.actions[-1][0] + 1
- self.add(action, weight)
-
- def add(self, action, weight=0):
- """
- Add an action.
- """
- self.actions.append((weight, action))
- self.actions.sort(key=lambda act: act[0])
-
- def remove(self, action):
- """
- Remove an action
- """
- for item in self.actions:
- if item[1] == action:
- self.actions.remove(item)
- return
- raise ValueError('Action "%s" does not exist.' % action)
-
-
-class CategoryList(object):
- """
- The :class:`~openlp.core.utils.CategoryList` class encapsulates a category list for the
- :class:`~openlp.core.utils.ActionList` class and provides an iterator interface for walking through the list of
- actions in this category.
- """
-
- def __init__(self):
- """
- Constructor
- """
- self.index = 0
- self.categories = []
-
- def __getitem__(self, key):
- """
- Implement the __getitem__() method to make this class like a dictionary
- """
- for category in self.categories:
- if category.name == key:
- return category
- raise KeyError('Category "%s" does not exist.' % key)
-
- def __len__(self):
- """
- Implement the __len__() method to make this class like a dictionary
- """
- return len(self.categories)
-
- def __iter__(self):
- """
- Implement the __iter__() method to make this class like a dictionary
- """
- return self
-
- def __next__(self):
- """
- Python 3 "next" method for iterator.
- """
- if self.index >= len(self.categories):
- self.index = 0
- raise StopIteration
- else:
- self.index += 1
- return self.categories[self.index - 1]
-
- def __contains__(self, key):
- """
- Implement the __contains__() method to make this class like a dictionary
- """
- for category in self.categories:
- if category.name == key:
- return True
- return False
-
- def append(self, name, actions=None):
- """
- Append a category
- """
- weight = 0
- if self.categories:
- weight = self.categories[-1].weight + 1
- self.add(name, weight, actions)
-
- def add(self, name, weight=0, actions=None):
- """
- Add a category
- """
- category = ActionCategory(name, weight)
- if actions:
- for action in actions:
- if isinstance(action, tuple):
- category.actions.add(action[0], action[1])
- else:
- category.actions.append(action)
- self.categories.append(category)
- self.categories.sort(key=lambda cat: cat.weight)
-
- def remove(self, name):
- """
- Remove a category
- """
- for category in self.categories:
- if category.name == name:
- self.categories.remove(category)
- return
- raise ValueError('Category "%s" does not exist.' % name)
-
-
-class ActionList(object):
- """
- The :class:`~openlp.core.utils.ActionList` class contains a list of menu actions and categories associated with
- those actions. Each category also has a weight by which it is sorted when iterating through the list of actions or
- categories.
- """
- instance = None
- shortcut_map = {}
-
- def __init__(self):
- """
- Constructor
- """
- self.categories = CategoryList()
-
- @staticmethod
- def get_instance():
- """
- Get the instance of this class.
- """
- if ActionList.instance is None:
- ActionList.instance = ActionList()
- return ActionList.instance
-
- def add_action(self, action, category=None, weight=None):
- """
- Add an action to the list of actions.
-
- **Note**: The action's objectName must be set when you want to add it!
-
- :param action: The action to add (QAction). **Note**, the action must not have an empty ``objectName``.
- :param category: The category this action belongs to. The category has to be a python string. . **Note**,
- if the category is ``None``, the category and its actions are being hidden in the shortcut dialog. However,
- if they are added, it is possible to avoid assigning shortcuts twice, which is important.
- :param weight: The weight specifies how important a category is. However, this only has an impact on the order
- the categories are displayed.
- """
- if category not in self.categories:
- self.categories.append(category)
- settings = Settings()
- settings.beginGroup('shortcuts')
- # Get the default shortcut from the config.
- action.default_shortcuts = settings.get_default_value(action.objectName())
- if weight is None:
- self.categories[category].actions.append(action)
- else:
- self.categories[category].actions.add(action, weight)
- # Load the shortcut from the config.
- shortcuts = settings.value(action.objectName())
- settings.endGroup()
- if not shortcuts:
- action.setShortcuts([])
- return
- # We have to do this to ensure that the loaded shortcut list e. g. STRG+O (German) is converted to CTRL+O,
- # which is only done when we convert the strings in this way (QKeySequencet -> uncode).
- shortcuts = list(map(QtGui.QKeySequence.toString, list(map(QtGui.QKeySequence, shortcuts))))
- # Check the alternate shortcut first, to avoid problems when the alternate shortcut becomes the primary shortcut
- # after removing the (initial) primary shortcut due to conflicts.
- if len(shortcuts) == 2:
- existing_actions = ActionList.shortcut_map.get(shortcuts[1], [])
- # Check for conflicts with other actions considering the shortcut context.
- if self._is_shortcut_available(existing_actions, action):
- actions = ActionList.shortcut_map.get(shortcuts[1], [])
- actions.append(action)
- ActionList.shortcut_map[shortcuts[1]] = actions
- else:
- log.warning('Shortcut "%s" is removed from "%s" because another action already uses this shortcut.' %
- (shortcuts[1], action.objectName()))
- shortcuts.remove(shortcuts[1])
- # Check the primary shortcut.
- existing_actions = ActionList.shortcut_map.get(shortcuts[0], [])
- # Check for conflicts with other actions considering the shortcut context.
- if self._is_shortcut_available(existing_actions, action):
- actions = ActionList.shortcut_map.get(shortcuts[0], [])
- actions.append(action)
- ActionList.shortcut_map[shortcuts[0]] = actions
- else:
- log.warning('Shortcut "%s" is removed from "%s" because another action already uses this shortcut.' %
- (shortcuts[0], action.objectName()))
- shortcuts.remove(shortcuts[0])
- action.setShortcuts([QtGui.QKeySequence(shortcut) for shortcut in shortcuts])
-
- def remove_action(self, action, category=None):
- """
- This removes an action from its category. Empty categories are automatically removed.
-
- :param action: The ``QAction`` object to be removed.
- :param category: The name (unicode string) of the category, which contains the action. Defaults to None.
- """
- if category not in self.categories:
- return
- self.categories[category].actions.remove(action)
- # Remove empty categories.
- if not self.categories[category].actions:
- self.categories.remove(category)
- shortcuts = list(map(QtGui.QKeySequence.toString, action.shortcuts()))
- for shortcut in shortcuts:
- # Remove action from the list of actions which are using this shortcut.
- ActionList.shortcut_map[shortcut].remove(action)
- # Remove empty entries.
- if not ActionList.shortcut_map[shortcut]:
- del ActionList.shortcut_map[shortcut]
-
- def add_category(self, name, weight):
- """
- Add an empty category to the list of categories. This is only convenient for categories with a given weight.
-
- :param name: The category's name.
- :param weight: The category's weight (int).
- """
- if name in self.categories:
- # Only change the weight and resort the categories again.
- for category in self.categories:
- if category.name == name:
- category.weight = weight
- self.categories.categories.sort(key=lambda cat: cat.weight)
- return
- self.categories.add(name, weight)
-
- def update_shortcut_map(self, action, old_shortcuts):
- """
- Remove the action for the given ``old_shortcuts`` from the ``shortcut_map`` to ensure its up-to-dateness.
- **Note**: The new action's shortcuts **must** be assigned to the given ``action`` **before** calling this
- method.
-
- :param action: The action whose shortcuts are supposed to be updated in the ``shortcut_map``.
- :param old_shortcuts: A list of unicode key sequences.
- """
- for old_shortcut in old_shortcuts:
- # Remove action from the list of actions which are using this shortcut.
- ActionList.shortcut_map[old_shortcut].remove(action)
- # Remove empty entries.
- if not ActionList.shortcut_map[old_shortcut]:
- del ActionList.shortcut_map[old_shortcut]
- new_shortcuts = list(map(QtGui.QKeySequence.toString, action.shortcuts()))
- # Add the new shortcuts to the map.
- for new_shortcut in new_shortcuts:
- existing_actions = ActionList.shortcut_map.get(new_shortcut, [])
- existing_actions.append(action)
- ActionList.shortcut_map[new_shortcut] = existing_actions
-
- def _is_shortcut_available(self, existing_actions, action):
- """
- Checks if the given ``action`` may use its assigned shortcut(s) or not. Returns ``True`` or ``False.
-
- :param existing_actions: A list of actions which already use a particular shortcut.
- :param action: The action which wants to use a particular shortcut.
- """
- global_context = action.shortcutContext() in [QtCore.Qt.WindowShortcut, QtCore.Qt.ApplicationShortcut]
- affected_actions = []
- if global_context:
- affected_actions = [a for a in self.get_all_child_objects(action.parent()) if isinstance(a,
- QtWidgets.QAction)]
- for existing_action in existing_actions:
- if action is existing_action:
- continue
- if existing_action in affected_actions:
- return False
- if existing_action.shortcutContext() in [QtCore.Qt.WindowShortcut, QtCore.Qt.ApplicationShortcut]:
- return False
- elif action in self.get_all_child_objects(existing_action.parent()):
- return False
- return True
-
- def get_all_child_objects(self, qobject):
- """
- Goes recursively through the children of ``qobject`` and returns a list of all child objects.
- """
- children = qobject.children()
- # Append the children's children.
- children.extend(list(map(self.get_all_child_objects, children)))
- return children
-
-
-class CategoryOrder(object):
- """
- An enumeration class for category weights.
- """
- standard_menu = -20
- standard_toolbar = -10
=== removed file 'openlp/core/utils/db.py'
--- openlp/core/utils/db.py 2016-01-08 14:05:54 +0000
+++ openlp/core/utils/db.py 1970-01-01 00:00:00 +0000
@@ -1,71 +0,0 @@
-# -*- coding: utf-8 -*-
-# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
-
-###############################################################################
-# OpenLP - Open Source Lyrics Projection #
-# --------------------------------------------------------------------------- #
-# Copyright (c) 2008-2016 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:`db` module provides helper functions for database related methods.
-"""
-import sqlalchemy
-import logging
-
-from copy import deepcopy
-
-log = logging.getLogger(__name__)
-
-
-def drop_column(op, tablename, columnname):
- drop_columns(op, tablename, [columnname])
-
-
-def drop_columns(op, tablename, columns):
- """
- Column dropping functionality for SQLite, as there is no DROP COLUMN support in SQLite
-
- From https://github.com/klugjohannes/alembic-sqlite
- """
-
- # get the db engine and reflect database tables
- engine = op.get_bind()
- meta = sqlalchemy.MetaData(bind=engine)
- meta.reflect()
-
- # create a select statement from the old table
- old_table = meta.tables[tablename]
- select = sqlalchemy.sql.select([c for c in old_table.c if c.name not in columns])
-
- # get remaining columns without table attribute attached
- remaining_columns = [deepcopy(c) for c in old_table.columns if c.name not in columns]
- for column in remaining_columns:
- column.table = None
-
- # create a temporary new table
- new_tablename = '{0}_new'.format(tablename)
- op.create_table(new_tablename, *remaining_columns)
- meta.reflect()
- new_table = meta.tables[new_tablename]
-
- # copy data from old table
- insert = sqlalchemy.sql.insert(new_table).from_select([c.name for c in remaining_columns], select)
- engine.execute(insert)
-
- # drop the old table and rename the new table to take the old tables
- # position
- op.drop_table(tablename)
- op.rename_table(new_tablename, tablename)
=== removed file 'openlp/core/utils/languagemanager.py'
--- openlp/core/utils/languagemanager.py 2015-12-31 22:46:06 +0000
+++ openlp/core/utils/languagemanager.py 1970-01-01 00:00:00 +0000
@@ -1,146 +0,0 @@
-# -*- coding: utf-8 -*-
-# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
-
-###############################################################################
-# OpenLP - Open Source Lyrics Projection #
-# --------------------------------------------------------------------------- #
-# Copyright (c) 2008-2016 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:`languagemanager` module provides all the translation settings and language file loading for OpenLP.
-"""
-import logging
-import re
-import sys
-
-from PyQt5 import QtCore, QtWidgets
-
-
-from openlp.core.common import AppLocation, Settings, translate, is_win, is_macosx
-
-log = logging.getLogger(__name__)
-
-
-class LanguageManager(object):
- """
- Helper for Language selection
- """
- __qm_list__ = {}
- auto_language = False
-
- @staticmethod
- def get_translator(language):
- """
- Set up a translator to use in this instance of OpenLP
-
- :param language: The language to load into the translator
- """
- if LanguageManager.auto_language:
- language = QtCore.QLocale.system().name()
- lang_path = AppLocation.get_directory(AppLocation.LanguageDir)
- app_translator = QtCore.QTranslator()
- app_translator.load(language, lang_path)
- # A translator for buttons and other default strings provided by Qt.
- if not is_win() and not is_macosx():
- lang_path = QtCore.QLibraryInfo.location(QtCore.QLibraryInfo.TranslationsPath)
- default_translator = QtCore.QTranslator()
- default_translator.load('qt_%s' % language, lang_path)
- return app_translator, default_translator
-
- @staticmethod
- def find_qm_files():
- """
- Find all available language files in this OpenLP install
- """
- log.debug('Translation files: %s', AppLocation.get_directory(AppLocation.LanguageDir))
- trans_dir = QtCore.QDir(AppLocation.get_directory(AppLocation.LanguageDir))
- file_names = trans_dir.entryList(['*.qm'], QtCore.QDir.Files, QtCore.QDir.Name)
- # Remove qm files from the list which start with "qt_".
- file_names = [file_ for file_ in file_names if not file_.startswith('qt_')]
- return list(map(trans_dir.filePath, file_names))
-
- @staticmethod
- def language_name(qm_file):
- """
- Load the language name from a language file
-
- :param qm_file: The file to obtain the name from
- """
- translator = QtCore.QTranslator()
- translator.load(qm_file)
- return translator.translate('OpenLP.MainWindow', 'English', 'Please add the name of your language here')
-
- @staticmethod
- def get_language():
- """
- Retrieve a saved language to use from settings
- """
- language = Settings().value('core/language')
- language = str(language)
- log.info('Language file: \'%s\' Loaded from conf file' % language)
- if re.match(r'[[].*[]]', language):
- LanguageManager.auto_language = True
- language = re.sub(r'[\[\]]', '', language)
- return language
-
- @staticmethod
- def set_language(action, message=True):
- """
- Set the language to translate OpenLP into
-
- :param action: The language menu option
- :param message: Display the message option
- """
- language = 'en'
- if action:
- action_name = str(action.objectName())
- if action_name == 'autoLanguageItem':
- LanguageManager.auto_language = True
- else:
- LanguageManager.auto_language = False
- qm_list = LanguageManager.get_qm_list()
- language = str(qm_list[action_name])
- if LanguageManager.auto_language:
- language = '[%s]' % language
- Settings().setValue('core/language', language)
- log.info('Language file: \'%s\' written to conf file' % language)
- if message:
- QtWidgets.QMessageBox.information(None,
- translate('OpenLP.LanguageManager', 'Language'),
- translate('OpenLP.LanguageManager',
- 'Please restart OpenLP to use your new language setting.'))
-
- @staticmethod
- def init_qm_list():
- """
- Initialise the list of available translations
- """
- LanguageManager.__qm_list__ = {}
- qm_files = LanguageManager.find_qm_files()
- for counter, qmf in enumerate(qm_files):
- reg_ex = QtCore.QRegExp("^.*i18n/(.*).qm")
- if reg_ex.exactMatch(qmf):
- name = '%s' % reg_ex.cap(1)
- LanguageManager.__qm_list__['%#2i %s' % (counter + 1, LanguageManager.language_name(qmf))] = name
-
- @staticmethod
- def get_qm_list():
- """
- Return the list of available translations
- """
- if not LanguageManager.__qm_list__:
- LanguageManager.init_qm_list()
- return LanguageManager.__qm_list__
=== modified file 'openlp/plugins/alerts/alertsplugin.py'
--- openlp/plugins/alerts/alertsplugin.py 2016-01-04 00:18:01 +0000
+++ openlp/plugins/alerts/alertsplugin.py 2016-03-31 17:04:24 +0000
@@ -24,17 +24,16 @@
from PyQt5 import QtGui
-
from openlp.core.common import Settings, translate
+from openlp.core.common.actions import ActionList
from openlp.core.lib import Plugin, StringContent, build_icon
from openlp.core.lib.db import Manager
+from openlp.core.lib.theme import VerticalType
from openlp.core.lib.ui import create_action, UiStrings
-from openlp.core.lib.theme import VerticalType
from openlp.core.ui import AlertLocation
-from openlp.core.utils.actions import ActionList
+from openlp.plugins.alerts.forms import AlertForm
from openlp.plugins.alerts.lib import AlertsManager, AlertsTab
from openlp.plugins.alerts.lib.db import init_schema
-from openlp.plugins.alerts.forms import AlertForm
log = logging.getLogger(__name__)
=== modified file 'openlp/plugins/bibles/bibleplugin.py'
--- openlp/plugins/bibles/bibleplugin.py 2016-01-04 00:18:01 +0000
+++ openlp/plugins/bibles/bibleplugin.py 2016-03-31 17:04:24 +0000
@@ -24,13 +24,13 @@
from PyQt5 import QtWidgets
+from openlp.core.common.actions import ActionList
from openlp.core.lib import Plugin, StringContent, build_icon, translate
from openlp.core.lib.ui import UiStrings, create_action
-from openlp.core.utils.actions import ActionList
+from openlp.plugins.bibles.forms import BibleUpgradeForm
from openlp.plugins.bibles.lib import BibleManager, BiblesTab, BibleMediaItem, LayoutStyle, DisplayStyle, \
LanguageSelection
from openlp.plugins.bibles.lib.mediaitem import BibleSearch
-from openlp.plugins.bibles.forms import BibleUpgradeForm
log = logging.getLogger(__name__)
=== modified file 'openlp/plugins/songs/lib/upgrade.py'
--- openlp/plugins/songs/lib/upgrade.py 2016-01-07 22:12:03 +0000
+++ openlp/plugins/songs/lib/upgrade.py 2016-03-31 17:04:24 +0000
@@ -28,8 +28,8 @@
from sqlalchemy import Table, Column, ForeignKey, types
from sqlalchemy.sql.expression import func, false, null, text
+from openlp.core.common.db import drop_columns
from openlp.core.lib.db import get_upgrade_op
-from openlp.core.utils.db import drop_columns
log = logging.getLogger(__name__)
__version__ = 5
=== modified file 'openlp/plugins/songs/songsplugin.py'
--- openlp/plugins/songs/songsplugin.py 2016-01-04 12:21:58 +0000
+++ openlp/plugins/songs/songsplugin.py 2016-03-31 17:04:24 +0000
@@ -26,27 +26,26 @@
import logging
import os
+import sqlite3
from tempfile import gettempdir
-import sqlite3
from PyQt5 import QtCore, QtWidgets
from openlp.core.common import UiStrings, Registry, translate
+from openlp.core.common.actions import ActionList
from openlp.core.lib import Plugin, StringContent, build_icon
from openlp.core.lib.db import Manager
from openlp.core.lib.ui import create_action
-from openlp.core.utils.actions import ActionList
from openlp.plugins.songs.forms.duplicatesongremovalform import DuplicateSongRemovalForm
from openlp.plugins.songs.forms.songselectform import SongSelectForm
from openlp.plugins.songs.lib import clean_song, upgrade
from openlp.plugins.songs.lib.db import init_schema, Song
-from openlp.plugins.songs.lib.mediaitem import SongSearch
from openlp.plugins.songs.lib.importer import SongFormat
from openlp.plugins.songs.lib.importers.openlp import OpenLPSongImport
from openlp.plugins.songs.lib.mediaitem import SongMediaItem
+from openlp.plugins.songs.lib.mediaitem import SongSearch
from openlp.plugins.songs.lib.songstab import SongsTab
-
log = logging.getLogger(__name__)
__default_settings__ = {
'songs/db type': 'sqlite',
=== modified file 'openlp/plugins/songusage/songusageplugin.py'
--- openlp/plugins/songusage/songusageplugin.py 2016-02-11 21:05:41 +0000
+++ openlp/plugins/songusage/songusageplugin.py 2016-03-31 17:04:24 +0000
@@ -26,10 +26,10 @@
from PyQt5 import QtCore, QtWidgets
from openlp.core.common import Registry, Settings, translate
+from openlp.core.common.actions import ActionList
from openlp.core.lib import Plugin, StringContent, build_icon
from openlp.core.lib.db import Manager
from openlp.core.lib.ui import create_action
-from openlp.core.utils.actions import ActionList
from openlp.plugins.songusage.forms import SongUsageDetailForm, SongUsageDeleteForm
from openlp.plugins.songusage.lib import upgrade
from openlp.plugins.songusage.lib.db import init_schema, SongUsageItem
=== added file 'tests/functional/openlp_core/__init__.py'
=== modified file 'tests/functional/openlp_core/test_init.py'
--- tests/functional/openlp_core/test_init.py 2015-12-31 22:46:06 +0000
+++ tests/functional/openlp_core/test_init.py 2016-03-31 17:04:24 +0000
@@ -37,7 +37,7 @@
# GIVEN: a a set of system arguments.
sys.argv[1:] = []
# WHEN: We we parse them to expand to options
- args = parse_options()
+ args = parse_options(None)
# THEN: the following fields will have been extracted.
self.assertFalse(args.dev_version, 'The dev_version flag should be False')
self.assertEquals(args.loglevel, 'warning', 'The log level should be set to warning')
@@ -54,7 +54,7 @@
# GIVEN: a a set of system arguments.
sys.argv[1:] = ['-l debug']
# WHEN: We we parse them to expand to options
- args = parse_options()
+ args = parse_options(None)
# THEN: the following fields will have been extracted.
self.assertFalse(args.dev_version, 'The dev_version flag should be False')
self.assertEquals(args.loglevel, ' debug', 'The log level should be set to debug')
@@ -71,7 +71,7 @@
# GIVEN: a a set of system arguments.
sys.argv[1:] = ['--portable']
# WHEN: We we parse them to expand to options
- args = parse_options()
+ args = parse_options(None)
# THEN: the following fields will have been extracted.
self.assertFalse(args.dev_version, 'The dev_version flag should be False')
self.assertEquals(args.loglevel, 'warning', 'The log level should be set to warning')
@@ -88,7 +88,7 @@
# GIVEN: a a set of system arguments.
sys.argv[1:] = ['-l debug', '-d']
# WHEN: We we parse them to expand to options
- args = parse_options()
+ args = parse_options(None)
# THEN: the following fields will have been extracted.
self.assertTrue(args.dev_version, 'The dev_version flag should be True')
self.assertEquals(args.loglevel, ' debug', 'The log level should be set to debug')
@@ -105,7 +105,7 @@
# GIVEN: a a set of system arguments.
sys.argv[1:] = ['dummy_temp']
# WHEN: We we parse them to expand to options
- args = parse_options()
+ args = parse_options(None)
# THEN: the following fields will have been extracted.
self.assertFalse(args.dev_version, 'The dev_version flag should be False')
self.assertEquals(args.loglevel, 'warning', 'The log level should be set to warning')
@@ -122,7 +122,7 @@
# GIVEN: a a set of system arguments.
sys.argv[1:] = ['-l debug', 'dummy_temp']
# WHEN: We we parse them to expand to options
- args = parse_options()
+ args = parse_options(None)
# THEN: the following fields will have been extracted.
self.assertFalse(args.dev_version, 'The dev_version flag should be False')
self.assertEquals(args.loglevel, ' debug', 'The log level should be set to debug')
@@ -130,15 +130,3 @@
self.assertFalse(args.portable, 'The portable flag should be set to false')
self.assertEquals(args.style, None, 'There are no style flags to be processed')
self.assertEquals(args.rargs, 'dummy_temp', 'The service file should not be blank')
-
- def parse_options_two_files_test(self):
- """
- Test the parse options process works with a file
-
- """
- # GIVEN: a a set of system arguments.
- sys.argv[1:] = ['dummy_temp', 'dummy_temp2']
- # WHEN: We we parse them to expand to options
- args = parse_options()
- # THEN: the following fields will have been extracted.
- self.assertEquals(args, None, 'The args should be None')
=== added file 'tests/functional/openlp_core_common/test_actions.py'
--- tests/functional/openlp_core_common/test_actions.py 1970-01-01 00:00:00 +0000
+++ tests/functional/openlp_core_common/test_actions.py 2016-03-31 17:04:24 +0000
@@ -0,0 +1,244 @@
+# -*- coding: utf-8 -*-
+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
+
+###############################################################################
+# OpenLP - Open Source Lyrics Projection #
+# --------------------------------------------------------------------------- #
+# Copyright (c) 2008-2016 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 #
+###############################################################################
+"""
+Package to test the openlp.core.common.actions package.
+"""
+from unittest import TestCase
+
+from PyQt5 import QtGui, QtCore, QtWidgets
+
+from openlp.core.common import Settings
+from openlp.core.common.actions import CategoryActionList, ActionList
+from tests.functional import MagicMock
+from tests.helpers.testmixin import TestMixin
+
+
+class TestCategoryActionList(TestCase):
+ def setUp(self):
+ """
+ Create an instance and a few example actions.
+ """
+ self.action1 = MagicMock()
+ self.action1.text.return_value = 'first'
+ self.action2 = MagicMock()
+ self.action2.text.return_value = 'second'
+ self.list = CategoryActionList()
+
+ def tearDown(self):
+ """
+ Clean up
+ """
+ del self.list
+
+ def contains_test(self):
+ """
+ Test the __contains__() method
+ """
+ # GIVEN: The list.
+ # WHEN: Add an action
+ self.list.append(self.action1)
+
+ # THEN: The actions should (not) be in the list.
+ self.assertTrue(self.action1 in self.list)
+ self.assertFalse(self.action2 in self.list)
+
+ def len_test(self):
+ """
+ Test the __len__ method
+ """
+ # GIVEN: The list.
+ # WHEN: Do nothing.
+ # THEN: Check the length.
+ self.assertEqual(len(self.list), 0, "The length should be 0.")
+
+ # GIVEN: The list.
+ # WHEN: Append an action.
+ self.list.append(self.action1)
+
+ # THEN: Check the length.
+ self.assertEqual(len(self.list), 1, "The length should be 1.")
+
+ def append_test(self):
+ """
+ Test the append() method
+ """
+ # GIVEN: The list.
+ # WHEN: Append an action.
+ self.list.append(self.action1)
+ self.list.append(self.action2)
+
+ # THEN: Check if the actions are in the list and check if they have the correct weights.
+ self.assertTrue(self.action1 in self.list)
+ self.assertTrue(self.action2 in self.list)
+ self.assertEqual(self.list.actions[0], (0, self.action1))
+ self.assertEqual(self.list.actions[1], (1, self.action2))
+
+ def add_test(self):
+ """
+ Test the add() method
+ """
+ # GIVEN: The list and weights.
+ action1_weight = 42
+ action2_weight = 41
+
+ # WHEN: Add actions and their weights.
+ self.list.add(self.action1, action1_weight)
+ self.list.add(self.action2, action2_weight)
+
+ # THEN: Check if they were added and have the specified weights.
+ self.assertTrue(self.action1 in self.list)
+ self.assertTrue(self.action2 in self.list)
+ # Now check if action1 is second and action2 is first (due to their weights).
+ self.assertEqual(self.list.actions[0], (41, self.action2))
+ self.assertEqual(self.list.actions[1], (42, self.action1))
+
+ def remove_test(self):
+ """
+ Test the remove() method
+ """
+ # GIVEN: The list
+ self.list.append(self.action1)
+
+ # WHEN: Delete an item from the list.
+ self.list.remove(self.action1)
+
+ # THEN: Now the element should not be in the list anymore.
+ self.assertFalse(self.action1 in self.list)
+
+ # THEN: Check if an exception is raised when trying to remove a not present action.
+ self.assertRaises(ValueError, self.list.remove, self.action2)
+
+
+class TestActionList(TestCase, TestMixin):
+ """
+ Test the ActionList class
+ """
+
+ def setUp(self):
+ """
+ Prepare the tests
+ """
+ self.action_list = ActionList.get_instance()
+ self.build_settings()
+ self.settings = Settings()
+ self.settings.beginGroup('shortcuts')
+
+ def tearDown(self):
+ """
+ Clean up
+ """
+ self.settings.endGroup()
+ self.destroy_settings()
+
+ def test_add_action_same_parent(self):
+ """
+ ActionList test - Tests the add_action method. The actions have the same parent, the same shortcuts and both
+ have the QtCore.Qt.WindowShortcut shortcut context set.
+ """
+ # GIVEN: Two actions with the same shortcuts.
+ parent = QtCore.QObject()
+ action1 = QtWidgets.QAction(parent)
+ action1.setObjectName('action1')
+ action_with_same_shortcuts1 = QtWidgets.QAction(parent)
+ action_with_same_shortcuts1.setObjectName('action_with_same_shortcuts1')
+ # Add default shortcuts to Settings class.
+ default_shortcuts = {
+ 'shortcuts/action1': [QtGui.QKeySequence(QtCore.Qt.Key_A), QtGui.QKeySequence(QtCore.Qt.Key_B)],
+ 'shortcuts/action_with_same_shortcuts1': [QtGui.QKeySequence(QtCore.Qt.Key_B),
+ QtGui.QKeySequence(QtCore.Qt.Key_A)]
+ }
+ Settings.extend_default_settings(default_shortcuts)
+
+ # WHEN: Add the two actions to the action list.
+ self.action_list.add_action(action1, 'example_category')
+ self.action_list.add_action(action_with_same_shortcuts1, 'example_category')
+ # Remove the actions again.
+ self.action_list.remove_action(action1, 'example_category')
+ self.action_list.remove_action(action_with_same_shortcuts1, 'example_category')
+
+ # THEN: As both actions have the same shortcuts, they should be removed from one action.
+ assert len(action1.shortcuts()) == 2, 'The action should have two shortcut assigned.'
+ assert len(action_with_same_shortcuts1.shortcuts()) == 0, 'The action should not have a shortcut assigned.'
+
+ def test_add_action_different_parent(self):
+ """
+ ActionList test - Tests the add_action method. The actions have the different parent, the same shortcuts and
+ both have the QtCore.Qt.WindowShortcut shortcut context set.
+ """
+ # GIVEN: Two actions with the same shortcuts.
+ parent = QtCore.QObject()
+ action2 = QtWidgets.QAction(parent)
+ action2.setObjectName('action2')
+ second_parent = QtCore.QObject()
+ action_with_same_shortcuts2 = QtWidgets.QAction(second_parent)
+ action_with_same_shortcuts2.setObjectName('action_with_same_shortcuts2')
+ # Add default shortcuts to Settings class.
+ default_shortcuts = {
+ 'shortcuts/action2': [QtGui.QKeySequence(QtCore.Qt.Key_C), QtGui.QKeySequence(QtCore.Qt.Key_D)],
+ 'shortcuts/action_with_same_shortcuts2': [QtGui.QKeySequence(QtCore.Qt.Key_D),
+ QtGui.QKeySequence(QtCore.Qt.Key_C)]
+ }
+ Settings.extend_default_settings(default_shortcuts)
+
+ # WHEN: Add the two actions to the action list.
+ self.action_list.add_action(action2, 'example_category')
+ self.action_list.add_action(action_with_same_shortcuts2, 'example_category')
+ # Remove the actions again.
+ self.action_list.remove_action(action2, 'example_category')
+ self.action_list.remove_action(action_with_same_shortcuts2, 'example_category')
+
+ # THEN: As both actions have the same shortcuts, they should be removed from one action.
+ assert len(action2.shortcuts()) == 2, 'The action should have two shortcut assigned.'
+ assert len(action_with_same_shortcuts2.shortcuts()) == 0, 'The action should not have a shortcut assigned.'
+
+ def test_add_action_different_context(self):
+ """
+ ActionList test - Tests the add_action method. The actions have the different parent, the same shortcuts and
+ both have the QtCore.Qt.WidgetShortcut shortcut context set.
+ """
+ # GIVEN: Two actions with the same shortcuts.
+ parent = QtCore.QObject()
+ action3 = QtWidgets.QAction(parent)
+ action3.setObjectName('action3')
+ action3.setShortcutContext(QtCore.Qt.WidgetShortcut)
+ second_parent = QtCore.QObject()
+ action_with_same_shortcuts3 = QtWidgets.QAction(second_parent)
+ action_with_same_shortcuts3.setObjectName('action_with_same_shortcuts3')
+ action_with_same_shortcuts3.setShortcutContext(QtCore.Qt.WidgetShortcut)
+ # Add default shortcuts to Settings class.
+ default_shortcuts = {
+ 'shortcuts/action3': [QtGui.QKeySequence(QtCore.Qt.Key_E), QtGui.QKeySequence(QtCore.Qt.Key_F)],
+ 'shortcuts/action_with_same_shortcuts3': [QtGui.QKeySequence(QtCore.Qt.Key_E),
+ QtGui.QKeySequence(QtCore.Qt.Key_F)]
+ }
+ Settings.extend_default_settings(default_shortcuts)
+
+ # WHEN: Add the two actions to the action list.
+ self.action_list.add_action(action3, 'example_category2')
+ self.action_list.add_action(action_with_same_shortcuts3, 'example_category2')
+ # Remove the actions again.
+ self.action_list.remove_action(action3, 'example_category2')
+ self.action_list.remove_action(action_with_same_shortcuts3, 'example_category2')
+
+ # THEN: Both action should keep their shortcuts.
+ assert len(action3.shortcuts()) == 2, 'The action should have two shortcut assigned.'
+ assert len(action_with_same_shortcuts3.shortcuts()) == 2, 'The action should have two shortcuts assigned.'
=== added file 'tests/functional/openlp_core_common/test_db.py'
--- tests/functional/openlp_core_common/test_db.py 1970-01-01 00:00:00 +0000
+++ tests/functional/openlp_core_common/test_db.py 2016-03-31 17:04:24 +0000
@@ -0,0 +1,104 @@
+# -*- coding: utf-8 -*-
+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
+
+###############################################################################
+# OpenLP - Open Source Lyrics Projection #
+# --------------------------------------------------------------------------- #
+# Copyright (c) 2008-2016 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 #
+###############################################################################
+"""
+Package to test the openlp.core.common.db package.
+"""
+import gc
+import os
+import shutil
+import time
+from tempfile import mkdtemp
+from unittest import TestCase
+
+import sqlalchemy
+
+from openlp.core.common.db import drop_column, drop_columns
+from openlp.core.lib.db import init_db, get_upgrade_op
+from tests.utils.constants import TEST_RESOURCES_PATH
+
+
+class TestUtilsDBFunctions(TestCase):
+
+ def setUp(self):
+ """
+ Create temp folder for keeping db file
+ """
+ self.tmp_folder = mkdtemp()
+ db_path = os.path.join(TEST_RESOURCES_PATH, 'songs', 'songs-1.9.7.sqlite')
+ self.db_tmp_path = os.path.join(self.tmp_folder, 'songs-1.9.7.sqlite')
+ shutil.copyfile(db_path, self.db_tmp_path)
+ db_url = 'sqlite:///' + self.db_tmp_path
+ self.session, metadata = init_db(db_url)
+ self.op = get_upgrade_op(self.session)
+
+ def tearDown(self):
+ """
+ Clean up
+ """
+ self.session.close()
+ self.session = None
+ gc.collect()
+ retries = 0
+ while retries < 5:
+ try:
+ if os.path.exists(self.tmp_folder):
+ shutil.rmtree(self.tmp_folder)
+ break
+ except:
+ time.sleep(1)
+ retries += 1
+
+ def delete_column_test(self):
+ """
+ Test deleting a single column in a table
+ """
+ # GIVEN: A temporary song db
+
+ # WHEN: Deleting a columns in a table
+ drop_column(self.op, 'songs', 'song_book_id')
+
+ # THEN: The column should have been deleted
+ meta = sqlalchemy.MetaData(bind=self.op.get_bind())
+ meta.reflect()
+ columns = meta.tables['songs'].columns
+
+ for column in columns:
+ if column.name == 'song_book_id':
+ self.fail("The column 'song_book_id' should have been deleted.")
+
+ def delete_columns_test(self):
+ """
+ Test deleting multiple columns in a table
+ """
+ # GIVEN: A temporary song db
+
+ # WHEN: Deleting a columns in a table
+ drop_columns(self.op, 'songs', ['song_book_id', 'song_number'])
+
+ # THEN: The columns should have been deleted
+ meta = sqlalchemy.MetaData(bind=self.op.get_bind())
+ meta.reflect()
+ columns = meta.tables['songs'].columns
+
+ for column in columns:
+ if column.name == 'song_book_id' or column.name == 'song_number':
+ self.fail("The column '%s' should have been deleted." % column.name)
=== removed file 'tests/functional/openlp_core_utils/test_actions.py'
--- tests/functional/openlp_core_utils/test_actions.py 2015-12-31 22:46:06 +0000
+++ tests/functional/openlp_core_utils/test_actions.py 1970-01-01 00:00:00 +0000
@@ -1,245 +0,0 @@
-# -*- coding: utf-8 -*-
-# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
-
-###############################################################################
-# OpenLP - Open Source Lyrics Projection #
-# --------------------------------------------------------------------------- #
-# Copyright (c) 2008-2016 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 #
-###############################################################################
-"""
-Package to test the openlp.core.utils.actions package.
-"""
-from unittest import TestCase
-
-from PyQt5 import QtGui, QtCore, QtWidgets
-
-from openlp.core.common import Settings
-from openlp.core.utils import ActionList
-from openlp.core.utils.actions import CategoryActionList
-from tests.functional import MagicMock
-from tests.helpers.testmixin import TestMixin
-
-
-class TestCategoryActionList(TestCase):
- def setUp(self):
- """
- Create an instance and a few example actions.
- """
- self.action1 = MagicMock()
- self.action1.text.return_value = 'first'
- self.action2 = MagicMock()
- self.action2.text.return_value = 'second'
- self.list = CategoryActionList()
-
- def tearDown(self):
- """
- Clean up
- """
- del self.list
-
- def contains_test(self):
- """
- Test the __contains__() method
- """
- # GIVEN: The list.
- # WHEN: Add an action
- self.list.append(self.action1)
-
- # THEN: The actions should (not) be in the list.
- self.assertTrue(self.action1 in self.list)
- self.assertFalse(self.action2 in self.list)
-
- def len_test(self):
- """
- Test the __len__ method
- """
- # GIVEN: The list.
- # WHEN: Do nothing.
- # THEN: Check the length.
- self.assertEqual(len(self.list), 0, "The length should be 0.")
-
- # GIVEN: The list.
- # WHEN: Append an action.
- self.list.append(self.action1)
-
- # THEN: Check the length.
- self.assertEqual(len(self.list), 1, "The length should be 1.")
-
- def append_test(self):
- """
- Test the append() method
- """
- # GIVEN: The list.
- # WHEN: Append an action.
- self.list.append(self.action1)
- self.list.append(self.action2)
-
- # THEN: Check if the actions are in the list and check if they have the correct weights.
- self.assertTrue(self.action1 in self.list)
- self.assertTrue(self.action2 in self.list)
- self.assertEqual(self.list.actions[0], (0, self.action1))
- self.assertEqual(self.list.actions[1], (1, self.action2))
-
- def add_test(self):
- """
- Test the add() method
- """
- # GIVEN: The list and weights.
- action1_weight = 42
- action2_weight = 41
-
- # WHEN: Add actions and their weights.
- self.list.add(self.action1, action1_weight)
- self.list.add(self.action2, action2_weight)
-
- # THEN: Check if they were added and have the specified weights.
- self.assertTrue(self.action1 in self.list)
- self.assertTrue(self.action2 in self.list)
- # Now check if action1 is second and action2 is first (due to their weights).
- self.assertEqual(self.list.actions[0], (41, self.action2))
- self.assertEqual(self.list.actions[1], (42, self.action1))
-
- def remove_test(self):
- """
- Test the remove() method
- """
- # GIVEN: The list
- self.list.append(self.action1)
-
- # WHEN: Delete an item from the list.
- self.list.remove(self.action1)
-
- # THEN: Now the element should not be in the list anymore.
- self.assertFalse(self.action1 in self.list)
-
- # THEN: Check if an exception is raised when trying to remove a not present action.
- self.assertRaises(ValueError, self.list.remove, self.action2)
-
-
-class TestActionList(TestCase, TestMixin):
- """
- Test the ActionList class
- """
-
- def setUp(self):
- """
- Prepare the tests
- """
- self.action_list = ActionList.get_instance()
- self.build_settings()
- self.settings = Settings()
- self.settings.beginGroup('shortcuts')
-
- def tearDown(self):
- """
- Clean up
- """
- self.settings.endGroup()
- self.destroy_settings()
-
- def test_add_action_same_parent(self):
- """
- ActionList test - Tests the add_action method. The actions have the same parent, the same shortcuts and both
- have the QtCore.Qt.WindowShortcut shortcut context set.
- """
- # GIVEN: Two actions with the same shortcuts.
- parent = QtCore.QObject()
- action1 = QtWidgets.QAction(parent)
- action1.setObjectName('action1')
- action_with_same_shortcuts1 = QtWidgets.QAction(parent)
- action_with_same_shortcuts1.setObjectName('action_with_same_shortcuts1')
- # Add default shortcuts to Settings class.
- default_shortcuts = {
- 'shortcuts/action1': [QtGui.QKeySequence(QtCore.Qt.Key_A), QtGui.QKeySequence(QtCore.Qt.Key_B)],
- 'shortcuts/action_with_same_shortcuts1': [QtGui.QKeySequence(QtCore.Qt.Key_B),
- QtGui.QKeySequence(QtCore.Qt.Key_A)]
- }
- Settings.extend_default_settings(default_shortcuts)
-
- # WHEN: Add the two actions to the action list.
- self.action_list.add_action(action1, 'example_category')
- self.action_list.add_action(action_with_same_shortcuts1, 'example_category')
- # Remove the actions again.
- self.action_list.remove_action(action1, 'example_category')
- self.action_list.remove_action(action_with_same_shortcuts1, 'example_category')
-
- # THEN: As both actions have the same shortcuts, they should be removed from one action.
- assert len(action1.shortcuts()) == 2, 'The action should have two shortcut assigned.'
- assert len(action_with_same_shortcuts1.shortcuts()) == 0, 'The action should not have a shortcut assigned.'
-
- def test_add_action_different_parent(self):
- """
- ActionList test - Tests the add_action method. The actions have the different parent, the same shortcuts and
- both have the QtCore.Qt.WindowShortcut shortcut context set.
- """
- # GIVEN: Two actions with the same shortcuts.
- parent = QtCore.QObject()
- action2 = QtWidgets.QAction(parent)
- action2.setObjectName('action2')
- second_parent = QtCore.QObject()
- action_with_same_shortcuts2 = QtWidgets.QAction(second_parent)
- action_with_same_shortcuts2.setObjectName('action_with_same_shortcuts2')
- # Add default shortcuts to Settings class.
- default_shortcuts = {
- 'shortcuts/action2': [QtGui.QKeySequence(QtCore.Qt.Key_C), QtGui.QKeySequence(QtCore.Qt.Key_D)],
- 'shortcuts/action_with_same_shortcuts2': [QtGui.QKeySequence(QtCore.Qt.Key_D),
- QtGui.QKeySequence(QtCore.Qt.Key_C)]
- }
- Settings.extend_default_settings(default_shortcuts)
-
- # WHEN: Add the two actions to the action list.
- self.action_list.add_action(action2, 'example_category')
- self.action_list.add_action(action_with_same_shortcuts2, 'example_category')
- # Remove the actions again.
- self.action_list.remove_action(action2, 'example_category')
- self.action_list.remove_action(action_with_same_shortcuts2, 'example_category')
-
- # THEN: As both actions have the same shortcuts, they should be removed from one action.
- assert len(action2.shortcuts()) == 2, 'The action should have two shortcut assigned.'
- assert len(action_with_same_shortcuts2.shortcuts()) == 0, 'The action should not have a shortcut assigned.'
-
- def test_add_action_different_context(self):
- """
- ActionList test - Tests the add_action method. The actions have the different parent, the same shortcuts and
- both have the QtCore.Qt.WidgetShortcut shortcut context set.
- """
- # GIVEN: Two actions with the same shortcuts.
- parent = QtCore.QObject()
- action3 = QtWidgets.QAction(parent)
- action3.setObjectName('action3')
- action3.setShortcutContext(QtCore.Qt.WidgetShortcut)
- second_parent = QtCore.QObject()
- action_with_same_shortcuts3 = QtWidgets.QAction(second_parent)
- action_with_same_shortcuts3.setObjectName('action_with_same_shortcuts3')
- action_with_same_shortcuts3.setShortcutContext(QtCore.Qt.WidgetShortcut)
- # Add default shortcuts to Settings class.
- default_shortcuts = {
- 'shortcuts/action3': [QtGui.QKeySequence(QtCore.Qt.Key_E), QtGui.QKeySequence(QtCore.Qt.Key_F)],
- 'shortcuts/action_with_same_shortcuts3': [QtGui.QKeySequence(QtCore.Qt.Key_E),
- QtGui.QKeySequence(QtCore.Qt.Key_F)]
- }
- Settings.extend_default_settings(default_shortcuts)
-
- # WHEN: Add the two actions to the action list.
- self.action_list.add_action(action3, 'example_category2')
- self.action_list.add_action(action_with_same_shortcuts3, 'example_category2')
- # Remove the actions again.
- self.action_list.remove_action(action3, 'example_category2')
- self.action_list.remove_action(action_with_same_shortcuts3, 'example_category2')
-
- # THEN: Both action should keep their shortcuts.
- assert len(action3.shortcuts()) == 2, 'The action should have two shortcut assigned.'
- assert len(action_with_same_shortcuts3.shortcuts()) == 2, 'The action should have two shortcuts assigned.'
=== removed file 'tests/functional/openlp_core_utils/test_db.py'
--- tests/functional/openlp_core_utils/test_db.py 2016-01-15 19:14:24 +0000
+++ tests/functional/openlp_core_utils/test_db.py 1970-01-01 00:00:00 +0000
@@ -1,104 +0,0 @@
-# -*- coding: utf-8 -*-
-# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
-
-###############################################################################
-# OpenLP - Open Source Lyrics Projection #
-# --------------------------------------------------------------------------- #
-# Copyright (c) 2008-2016 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 #
-###############################################################################
-"""
-Package to test the openlp.core.utils.db package.
-"""
-from tempfile import mkdtemp
-from unittest import TestCase
-import gc
-import os
-import shutil
-import sqlalchemy
-import time
-
-from openlp.core.utils.db import drop_column, drop_columns
-from openlp.core.lib.db import init_db, get_upgrade_op
-
-from tests.utils.constants import TEST_RESOURCES_PATH
-
-
-class TestUtilsDBFunctions(TestCase):
-
- def setUp(self):
- """
- Create temp folder for keeping db file
- """
- self.tmp_folder = mkdtemp()
- db_path = os.path.join(TEST_RESOURCES_PATH, 'songs', 'songs-1.9.7.sqlite')
- self.db_tmp_path = os.path.join(self.tmp_folder, 'songs-1.9.7.sqlite')
- shutil.copyfile(db_path, self.db_tmp_path)
- db_url = 'sqlite:///' + self.db_tmp_path
- self.session, metadata = init_db(db_url)
- self.op = get_upgrade_op(self.session)
-
- def tearDown(self):
- """
- Clean up
- """
- self.session.close()
- self.session = None
- gc.collect()
- retries = 0
- while retries < 5:
- try:
- if os.path.exists(self.tmp_folder):
- shutil.rmtree(self.tmp_folder)
- break
- except:
- time.sleep(1)
- retries += 1
-
- def delete_column_test(self):
- """
- Test deleting a single column in a table
- """
- # GIVEN: A temporary song db
-
- # WHEN: Deleting a columns in a table
- drop_column(self.op, 'songs', 'song_book_id')
-
- # THEN: The column should have been deleted
- meta = sqlalchemy.MetaData(bind=self.op.get_bind())
- meta.reflect()
- columns = meta.tables['songs'].columns
-
- for column in columns:
- if column.name == 'song_book_id':
- self.fail("The column 'song_book_id' should have been deleted.")
-
- def delete_columns_test(self):
- """
- Test deleting multiple columns in a table
- """
- # GIVEN: A temporary song db
-
- # WHEN: Deleting a columns in a table
- drop_columns(self.op, 'songs', ['song_book_id', 'song_number'])
-
- # THEN: The columns should have been deleted
- meta = sqlalchemy.MetaData(bind=self.op.get_bind())
- meta.reflect()
- columns = meta.tables['songs'].columns
-
- for column in columns:
- if column.name == 'song_book_id' or column.name == 'song_number':
- self.fail("The column '%s' should have been deleted." % column.name)
=== modified file 'tests/functional/openlp_core_utils/test_utils.py'
--- tests/functional/openlp_core_utils/test_utils.py 2015-12-31 22:46:06 +0000
+++ tests/functional/openlp_core_utils/test_utils.py 2016-03-31 17:04:24 +0000
@@ -241,7 +241,7 @@
"""
Test the get_locale_key(string) function
"""
- with patch('openlp.core.utils.languagemanager.LanguageManager.get_language') as mocked_get_language:
+ with patch('openlp.core.common.languagemanager.LanguageManager.get_language') as mocked_get_language:
# GIVEN: The language is German
# 0x00C3 (A with diaresis) should be sorted as "A". 0x00DF (sharp s) should be sorted as "ss".
mocked_get_language.return_value = 'de'
@@ -258,7 +258,7 @@
"""
Test the get_natural_key(string) function
"""
- with patch('openlp.core.utils.languagemanager.LanguageManager.get_language') as mocked_get_language:
+ with patch('openlp.core.common.languagemanager.LanguageManager.get_language') as mocked_get_language:
# GIVEN: The language is English (a language, which sorts digits before letters)
mocked_get_language.return_value = 'en'
unsorted_list = ['item 10a', 'item 3b', '1st item']
Follow ups