openlp/openlp/core/__init__.py

440 lines
20 KiB
Python
Raw Normal View History

# -*- coding: utf-8 -*-
2012-12-05 05:53:14 +00:00
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
2016-12-31 11:01:36 +00:00
# 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 #
2010-03-21 23:58:01 +00:00
###############################################################################
"""
The :mod:`core` module provides all core application functions
All the core functions of the OpenLP application including the GUI, settings,
logging and a plugin framework are contained within the openlp.core module.
"""
2015-10-15 18:03:57 +00:00
import argparse
2016-04-04 19:53:54 +00:00
import logging
import os
import shutil
2016-04-04 19:53:54 +00:00
import sys
import time
2016-04-04 19:53:54 +00:00
from traceback import format_exception
2015-11-07 00:49:40 +00:00
from PyQt5 import QtCore, QtGui, QtWidgets
2016-03-31 16:47:42 +00:00
from openlp.core.common import Registry, OpenLPMixin, AppLocation, LanguageManager, Settings, UiStrings, \
check_directory_exists, is_macosx, is_win, translate
from openlp.core.common.versionchecker import VersionThread, get_application_version
2013-12-13 17:44:05 +00:00
from openlp.core.lib import ScreenList
from openlp.core.resources import qInitResources
from openlp.core.ui import SplashScreen
2016-04-04 19:53:54 +00:00
from openlp.core.ui.exceptionform import ExceptionForm
from openlp.core.ui.firsttimeform import FirstTimeForm
from openlp.core.ui.firsttimelanguageform import FirstTimeLanguageForm
from openlp.core.ui.mainwindow import MainWindow
2013-08-31 18:17:38 +00:00
__all__ = ['OpenLP', 'main']
2010-06-10 01:57:59 +00:00
log = logging.getLogger()
2014-01-11 21:29:01 +00:00
WIN_REPAIR_STYLESHEET = """
QMainWindow::separator
{
border: none;
}
QDockWidget::title
{
border: 1px solid palette(dark);
padding-left: 5px;
padding-top: 2px;
margin: 1px 0;
}
QToolBar
{
border: none;
margin: 0;
padding: 0;
}
"""
2015-11-07 00:49:40 +00:00
class OpenLP(OpenLPMixin, QtWidgets.QApplication):
"""
The core application class. This class inherits from Qt's QApplication
class in order to provide the core of the application.
"""
args = []
2015-11-07 00:49:40 +00:00
def exec(self):
"""
Override exec method to allow the shared memory to be released on exit
"""
self.is_event_loop_active = True
2015-11-07 00:49:40 +00:00
result = QtWidgets.QApplication.exec()
self.shared_memory.detach()
return result
2013-02-03 15:06:17 +00:00
def run(self, args):
"""
Run the OpenLP application.
2014-01-11 21:29:01 +00:00
:param args: Some Args
"""
self.is_event_loop_active = False
2013-03-07 12:59:35 +00:00
# On Windows, the args passed into the constructor are ignored. Not very handy, so set the ones we want to use.
# On Linux and FreeBSD, in order to set the WM_CLASS property for X11, we pass "OpenLP" in as a command line
# argument. This interferes with files being passed in as command line arguments, so we remove it from the list.
if 'OpenLP' in args:
args.remove('OpenLP')
self.args.extend(args)
# Decide how many screens we have and their size
screens = ScreenList.create(self.desktop())
# First time checks in settings
2013-08-31 18:17:38 +00:00
has_run_wizard = Settings().value('core/has run wizard')
if not has_run_wizard:
ftw = FirstTimeForm()
ftw.initialize(screens)
2015-11-07 00:49:40 +00:00
if ftw.exec() == QtWidgets.QDialog.Accepted:
2013-08-31 18:17:38 +00:00
Settings().setValue('core/has run wizard', True)
elif ftw.was_cancelled:
QtCore.QCoreApplication.exit()
sys.exit()
# Correct stylesheet bugs
2013-08-31 18:17:38 +00:00
application_stylesheet = ''
if not Settings().value('advanced/alternate rows'):
2012-12-11 20:02:41 +00:00
base_color = self.palette().color(QtGui.QPalette.Active, QtGui.QPalette.Base)
2012-12-19 22:22:11 +00:00
alternate_rows_repair_stylesheet = \
2013-08-31 18:17:38 +00:00
'QTableWidget, QListWidget, QTreeWidget {alternate-background-color: ' + base_color.name() + ';}\n'
2012-12-19 22:22:11 +00:00
application_stylesheet += alternate_rows_repair_stylesheet
if is_win():
application_stylesheet += WIN_REPAIR_STYLESHEET
2012-12-10 23:35:53 +00:00
if application_stylesheet:
self.setStyleSheet(application_stylesheet)
can_show_splash = Settings().value('core/show splash')
if can_show_splash:
self.splash = SplashScreen()
self.splash.show()
# make sure Qt really display the splash screen
self.processEvents()
# Check if OpenLP has been upgrade and if a backup of data should be created
self.backup_on_upgrade(has_run_wizard, can_show_splash)
# start the main app window
2013-02-03 19:23:12 +00:00
self.main_window = MainWindow()
2013-08-31 18:17:38 +00:00
Registry().execute('bootstrap_initialise')
Registry().execute('bootstrap_post_set_up')
Registry().initialise = False
self.main_window.show()
if can_show_splash:
# now kill the splashscreen
self.splash.finish(self.main_window)
2013-08-31 18:17:38 +00:00
log.debug('Splashscreen closed')
# make sure Qt really display the splash screen
self.processEvents()
self.main_window.repaint()
self.processEvents()
if not has_run_wizard:
self.main_window.first_time()
# update_check = Settings().value('core/update check')
# if update_check:
# version = VersionThread(self.main_window)
# version.start()
2013-12-23 20:43:35 +00:00
self.main_window.is_display_blank()
self.main_window.app_startup()
2015-11-07 00:49:40 +00:00
return self.exec()
def is_already_running(self):
"""
2013-03-23 07:07:06 +00:00
Look to see if OpenLP is already running and ask if a 2nd instance is to be started.
"""
self.shared_memory = QtCore.QSharedMemory('OpenLP')
if self.shared_memory.attach():
2015-11-07 00:49:40 +00:00
status = QtWidgets.QMessageBox.critical(None, UiStrings().Error, UiStrings().OpenLPStart,
QtWidgets.QMessageBox.StandardButtons(QtWidgets.QMessageBox.Yes |
QtWidgets.QMessageBox.No))
if status == QtWidgets.QMessageBox.No:
return True
return False
else:
self.shared_memory.create(1)
return False
2016-10-17 04:17:33 +00:00
def is_data_path_missing(self):
"""
Check if the data folder path exists.
"""
data_folder_path = AppLocation.get_data_path()
if not os.path.exists(data_folder_path):
log.critical('Database was not found in: ' + data_folder_path)
2016-10-17 04:17:33 +00:00
status = QtWidgets.QMessageBox.critical(None, translate('OpenLP', 'Data Directory Error'),
translate('OpenLP', 'OpenLP data folder was not found in:\n\n{path}'
'\n\nThe location of the data folder was '
'previously changed from the OpenLP\'s '
2016-10-17 04:17:33 +00:00
'default location. If the data was stored on '
'removable device, that device needs to be '
'made available.\n\nYou may reset the data '
'location back to the default location, '
2016-10-17 04:17:33 +00:00
'or you can try to make the current location '
'available.\n\nDo you want to reset to the '
'default data location? If not, OpenLP will be '
'closed so you can try to fix the the problem.')
.format(path=data_folder_path),
2016-10-17 04:17:33 +00:00
QtWidgets.QMessageBox.StandardButtons(QtWidgets.QMessageBox.Yes |
QtWidgets.QMessageBox.No),
QtWidgets.QMessageBox.No)
if status == QtWidgets.QMessageBox.No:
# If answer was "No", return "True", it will shutdown OpenLP in def main
log.info('User requested termination')
return True
# If answer was "Yes", remove the custom data path thus resetting the default location.
Settings().remove('advanced/data path')
log.info('Database location has been reset to the default settings.')
return False
2014-01-11 21:29:01 +00:00
def hook_exception(self, exc_type, value, traceback):
2013-02-01 19:58:18 +00:00
"""
Add an exception hook so that any uncaught exceptions are displayed in this window rather than somewhere where
users cannot see it and cannot report when we encounter these problems.
2014-01-11 21:29:01 +00:00
:param exc_type: The class of exception.
:param value: The actual exception object.
:param traceback: A traceback object with the details of where the exception occurred.
2013-02-01 19:58:18 +00:00
"""
# We can't log.exception here because the last exception no longer exists, we're actually busy handling it.
2014-01-11 21:29:01 +00:00
log.critical(''.join(format_exception(exc_type, value, traceback)))
2013-08-31 18:17:38 +00:00
if not hasattr(self, 'exception_form'):
2013-07-19 20:56:31 +00:00
self.exception_form = ExceptionForm()
2014-01-11 21:29:01 +00:00
self.exception_form.exception_text_edit.setPlainText(''.join(format_exception(exc_type, value, traceback)))
2013-02-03 09:07:31 +00:00
self.set_normal_cursor()
is_splash_visible = False
if hasattr(self, 'splash') and self.splash.isVisible():
is_splash_visible = True
self.splash.hide()
2015-11-07 00:49:40 +00:00
self.exception_form.exec()
if is_splash_visible:
self.splash.show()
def backup_on_upgrade(self, has_run_wizard, can_show_splash):
"""
Check if OpenLP has been upgraded, and ask if a backup of data should be made
2014-10-13 19:24:19 +00:00
:param has_run_wizard: OpenLP has been run before
:param can_show_splash: Should OpenLP show the splash screen
"""
2014-10-14 07:52:59 +00:00
data_version = Settings().value('core/application version')
openlp_version = get_application_version()['version']
# New installation, no need to create backup
2014-10-13 19:24:19 +00:00
if not has_run_wizard:
2014-10-14 07:52:59 +00:00
Settings().setValue('core/application version', openlp_version)
# If data_version is different from the current version ask if we should backup the data folder
elif data_version != openlp_version:
if self.splash.isVisible():
self.splash.hide()
2015-11-07 00:49:40 +00:00
if QtWidgets.QMessageBox.question(None, translate('OpenLP', 'Backup'),
translate('OpenLP', 'OpenLP has been upgraded, do you want to create\n'
'a backup of the old data folder?'),
2015-11-07 00:49:40 +00:00
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No,
QtWidgets.QMessageBox.Yes) == QtWidgets.QMessageBox.Yes:
# Create copy of data folder
2016-10-17 04:29:08 +00:00
data_folder_path = AppLocation.get_data_path()
timestamp = time.strftime("%Y%m%d-%H%M%S")
data_folder_backup_path = data_folder_path + '-' + timestamp
try:
shutil.copytree(data_folder_path, data_folder_backup_path)
except OSError:
2015-11-07 00:49:40 +00:00
QtWidgets.QMessageBox.warning(None, translate('OpenLP', 'Backup'),
translate('OpenLP', 'Backup of the data folder failed!'))
return
message = translate('OpenLP',
2016-10-17 04:29:08 +00:00
'A backup of the data folder has been created at:\n\n'
2016-10-02 11:31:35 +00:00
'{text}').format(text=data_folder_backup_path)
QtWidgets.QMessageBox.information(None, translate('OpenLP', 'Backup'), message)
2014-10-13 14:08:06 +00:00
# Update the version in the settings
2014-10-14 07:52:59 +00:00
Settings().setValue('core/application version', openlp_version)
if can_show_splash:
self.splash.show()
2013-02-03 09:07:31 +00:00
def process_events(self):
"""
Wrapper to make ProcessEvents visible and named correctly
"""
self.processEvents()
def set_busy_cursor(self):
"""
Sets the Busy Cursor for the Application
"""
self.setOverrideCursor(QtCore.Qt.BusyCursor)
self.processEvents()
2013-02-03 09:07:31 +00:00
def set_normal_cursor(self):
"""
Sets the Normal Cursor for the Application
"""
self.restoreOverrideCursor()
2013-02-03 09:07:31 +00:00
self.processEvents()
def event(self, event):
"""
Enables platform specific event handling i.e. direct file opening on OS X
2014-01-11 21:29:01 +00:00
:param event: The event
"""
if event.type() == QtCore.QEvent.FileOpen:
file_name = event.file()
log.debug('Got open file event for {name}!'.format(name=file_name))
2012-05-17 18:57:01 +00:00
self.args.insert(0, file_name)
return True
# Mac OS X should restore app window when user clicked on the OpenLP icon
# in the Dock bar. However, OpenLP consists of multiple windows and this
# does not work. This workaround fixes that.
# The main OpenLP window is restored when it was previously minimized.
elif event.type() == QtCore.QEvent.ApplicationActivate:
if is_macosx() and hasattr(self, 'main_window'):
if self.main_window.isMinimized():
# Copied from QWidget.setWindowState() docs on how to restore and activate a minimized window
# while preserving its maximized and/or full-screen state.
self.main_window.setWindowState(self.main_window.windowState() & ~QtCore.Qt.WindowMinimized |
QtCore.Qt.WindowActive)
return True
2015-11-07 00:49:40 +00:00
return QtWidgets.QApplication.event(self, event)
2015-01-22 18:07:56 +00:00
2017-03-10 05:42:38 +00:00
def parse_options(args=None):
2015-01-22 15:05:03 +00:00
"""
Parse the command line arguments
:param args: list of command line arguments
:return: a tuple of parsed options of type optparse.Value and a list of remaining argsZ
"""
# Set up command line options.
2015-10-15 18:03:57 +00:00
parser = argparse.ArgumentParser(prog='openlp.py')
parser.add_argument('-e', '--no-error-form', dest='no_error_form', action='store_true',
help='Disable the error notification form.')
parser.add_argument('-l', '--log-level', dest='loglevel', default='warning', metavar='LEVEL',
help='Set logging to LEVEL level. Valid values are "debug", "info", "warning".')
parser.add_argument('-p', '--portable', dest='portable', action='store_true',
2015-10-16 16:17:38 +00:00
help='Specify if this should be run as a portable app, '
'off a USB flash drive (not implemented).')
2015-10-15 18:03:57 +00:00
parser.add_argument('-d', '--dev-version', dest='dev_version', action='store_true',
help='Ignore the version file and pull the version directly from Bazaar')
2015-11-07 00:49:40 +00:00
parser.add_argument('-s', '--style', dest='style', help='Set the Qt5 style (passed directly to Qt5).')
2015-10-15 18:03:57 +00:00
parser.add_argument('rargs', nargs='?', default=[])
2015-01-22 15:05:03 +00:00
# Parse command line options and deal with them. Use args supplied pragmatically if possible.
2015-10-28 21:21:05 +00:00
return parser.parse_args(args) if args else parser.parse_args()
2015-01-22 18:07:56 +00:00
2012-07-08 19:32:56 +00:00
def set_up_logging(log_path):
"""
Setup our logging using log_path
2014-01-11 21:29:01 +00:00
:param log_path: the path
"""
check_directory_exists(log_path, True)
2013-08-31 18:17:38 +00:00
filename = os.path.join(log_path, 'openlp.log')
2013-10-20 20:34:46 +00:00
logfile = logging.FileHandler(filename, 'w', encoding="UTF-8")
2013-08-31 18:17:38 +00:00
logfile.setFormatter(logging.Formatter('%(asctime)s %(name)-55s %(levelname)-8s %(message)s'))
log.addHandler(logfile)
if log.isEnabledFor(logging.DEBUG):
print('Logging to: {name}'.format(name=filename))
2015-10-28 21:21:05 +00:00
def main(args=None):
"""
The main function which parses command line options and then runs
2014-01-11 21:29:01 +00:00
:param args: Some args
"""
2015-10-28 21:21:05 +00:00
args = parse_options(args)
qt_args = []
2015-10-15 18:03:57 +00:00
if args and args.loglevel.lower() in ['d', 'debug']:
log.setLevel(logging.DEBUG)
2015-10-15 18:03:57 +00:00
elif args and args.loglevel.lower() in ['w', 'warning']:
log.setLevel(logging.WARNING)
else:
log.setLevel(logging.INFO)
2015-10-15 18:03:57 +00:00
if args and args.style:
qt_args.extend(['-style', args.style])
# Throw the rest of the arguments at Qt, just in case.
2015-10-15 18:03:57 +00:00
qt_args.extend(args.rargs)
# Bug #1018855: Set the WM_CLASS property in X11
if not is_win() and not is_macosx():
2012-08-31 08:39:38 +00:00
qt_args.append('OpenLP')
# Initialise the resources
qInitResources()
# Now create and actually run the application.
2013-02-03 17:42:31 +00:00
application = OpenLP(qt_args)
2013-08-31 18:17:38 +00:00
application.setOrganizationName('OpenLP')
application.setOrganizationDomain('openlp.org')
application.setAttribute(QtCore.Qt.AA_UseHighDpiPixmaps, True)
application.setAttribute(QtCore.Qt.AA_DontCreateNativeWidgetSiblings, True)
2015-10-15 18:03:57 +00:00
if args and args.portable:
2013-08-31 18:17:38 +00:00
application.setApplicationName('OpenLPPortable')
2012-05-23 17:13:22 +00:00
Settings.setDefaultFormat(Settings.IniFormat)
# Get location OpenLPPortable.ini
2013-02-03 17:42:31 +00:00
application_path = AppLocation.get_directory(AppLocation.AppDir)
2013-08-31 18:17:38 +00:00
set_up_logging(os.path.abspath(os.path.join(application_path, '..', '..', 'Other')))
log.info('Running portable')
portable_settings_file = os.path.abspath(os.path.join(application_path, '..', '..', 'Data', 'OpenLP.ini'))
# Make this our settings file
log.info('INI file: {name}'.format(name=portable_settings_file))
Settings.set_filename(portable_settings_file)
portable_settings = Settings()
# Set our data path
2013-08-31 18:17:38 +00:00
data_path = os.path.abspath(os.path.join(application_path, '..', '..', 'Data',))
log.info('Data path: {name}'.format(name=data_path))
# Point to our data path
2013-08-31 18:17:38 +00:00
portable_settings.setValue('advanced/data path', data_path)
portable_settings.setValue('advanced/is portable', True)
portable_settings.sync()
else:
2013-08-31 18:17:38 +00:00
application.setApplicationName('OpenLP')
set_up_logging(AppLocation.get_directory(AppLocation.CacheDir))
2013-02-01 19:58:18 +00:00
Registry.create()
2013-08-31 18:17:38 +00:00
Registry().register('application', application)
application.setApplicationVersion(get_application_version()['version'])
# Check if an instance of OpenLP is already running. Quit if there is a running instance and the user only wants one
if application.is_already_running():
sys.exit()
# If the custom data path is missing and the user wants to restore the data path, quit OpenLP.
if application.is_data_path_missing():
application.shared_memory.detach()
2013-02-03 15:06:17 +00:00
sys.exit()
2013-03-26 11:45:18 +00:00
# Remove/convert obsolete settings.
Settings().remove_obsolete_settings()
# First time checks in settings
2013-08-31 18:17:38 +00:00
if not Settings().value('core/has run wizard'):
2015-11-07 00:49:40 +00:00
if not FirstTimeLanguageForm().exec():
# if cancel then stop processing
sys.exit()
# i18n Set Language
language = LanguageManager.get_language()
translators = LanguageManager.get_translators(language)
for translator in translators:
if not translator.isEmpty():
application.installTranslator(translator)
if not translators:
log.debug('Could not find translators.')
2015-10-15 18:03:57 +00:00
if args and not args.no_error_form:
2013-02-03 17:42:31 +00:00
sys.excepthook = application.hook_exception
sys.exit(application.run(qt_args))