Exemplo n.º 1
0
def set_up_fault_handling():
    """
    Set up the Python fault handler
    """
    # Create the cache directory if it doesn't exist, and enable the fault handler to log to an error log file
    create_paths(AppLocation.get_directory(AppLocation.CacheDir))
    faulthandler.enable((AppLocation.get_directory(AppLocation.CacheDir) / 'error.log').open('wb'))
Exemplo n.º 2
0
    def check_installed(self):
        """
        Check the viewer is installed.

        :return: True if program to open PDF-files was found, otherwise False.
        """
        log.debug('check_installed Pdf')
        self.mudrawbin = None
        self.mutoolbin = None
        self.gsbin = None
        self.also_supports = []
        # Use the user defined program if given
        if Registry().get('settings').value(
                'presentations/enable_pdf_program'):
            program_path = Registry().get('settings').value(
                'presentations/pdf_program')
            program_type = self.process_check_binary(program_path)
            if program_type == 'gs':
                self.gsbin = program_path
            elif program_type == 'mudraw':
                self.mudrawbin = program_path
            elif program_type == 'mutool':
                self.mutoolbin = program_path
        elif PYMUPDF_AVAILABLE:
            self.also_supports = ['xps', 'oxps', 'epub', 'cbz', 'fb2']
            return True
        else:
            # Fallback to autodetection
            application_path = AppLocation.get_directory(AppLocation.AppDir)
            if is_win():
                # for windows we only accept mudraw.exe or mutool.exe in the base folder
                if (application_path / 'mudraw.exe').is_file():
                    self.mudrawbin = application_path / 'mudraw.exe'
                elif (application_path / 'mutool.exe').is_file():
                    self.mutoolbin = application_path / 'mutool.exe'
            else:
                # First try to find mudraw
                self.mudrawbin = which('mudraw')
                # if mudraw isn't installed, try mutool
                if not self.mudrawbin:
                    self.mutoolbin = which('mutool')
                    # Check we got a working mutool
                    if not self.mutoolbin or self.process_check_binary(
                            self.mutoolbin) != 'mutool':
                        self.gsbin = which('gs')
                # Last option: check if mudraw or mutool is placed in OpenLP base folder
                if not self.mudrawbin and not self.mutoolbin and not self.gsbin:
                    application_path = AppLocation.get_directory(
                        AppLocation.AppDir)
                    if (application_path / 'mudraw').is_file():
                        self.mudrawbin = application_path / 'mudraw'
                    elif (application_path / 'mutool').is_file():
                        self.mutoolbin = application_path / 'mutool'
        if self.mudrawbin or self.mutoolbin:
            self.also_supports = ['xps', 'oxps', 'epub', 'cbz', 'fb2']
            return True
        elif self.gsbin:
            return True
        return False
Exemplo n.º 3
0
 def find_qm_files():
     """
     Find all available language files in this OpenLP install
     """
     log.debug('Translation files: {files}'.format(
         files=AppLocation.get_directory(AppLocation.LanguageDir)))
     trans_dir = QtCore.QDir(
         str(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))
Exemplo n.º 4
0
def get_version():
    """
    Returns the application version of the running instance of OpenLP::

        {'full': '1.9.4-bzr1249', 'version': '1.9.4', 'build': 'bzr1249'}
    """
    global APPLICATION_VERSION
    if APPLICATION_VERSION:
        return APPLICATION_VERSION
    file_path = AppLocation.get_directory(AppLocation.VersionDir) / '.version'
    try:
        full_version = file_path.read_text().rstrip()
    except OSError:
        log.exception('Error in version file.')
        full_version = '0.0.0'
    bits = full_version.split('-')
    APPLICATION_VERSION = {
        'full': full_version,
        'version': bits[0],
        'build': bits[1] if len(bits) > 1 else None
    }
    if APPLICATION_VERSION['build']:
        log.info('Openlp version {version} build {build}'.format(
            version=APPLICATION_VERSION['version'],
            build=APPLICATION_VERSION['build']))
    else:
        log.info('Openlp version {version}'.format(
            version=APPLICATION_VERSION['version']))
    return APPLICATION_VERSION
Exemplo n.º 5
0
def get_version():
    """
    Returns the application version of the running instance of OpenLP::

        {'full': '2.9.0.dev2963+97ba02d1f', 'version': '2.9.0', 'build': '97ba02d1f'}
        or
        {'full': '2.9.0', 'version': '2.9.0', 'build': None}
    """
    global APPLICATION_VERSION
    if APPLICATION_VERSION:
        return APPLICATION_VERSION
    file_path = AppLocation.get_directory(AppLocation.VersionDir) / '.version'
    try:
        full_version = file_path.read_text().rstrip()
    except OSError:
        log.exception('Error in version file.')
        full_version = '0.0.0'
    bits = full_version.split('.dev')
    APPLICATION_VERSION = {
        'full': full_version,
        'version': bits[0],
        'build': full_version.split('+')[1] if '+' in full_version else None
    }
    if APPLICATION_VERSION['build']:
        log.info('OpenLP version {version} build {build}'.format(
            version=APPLICATION_VERSION['version'],
            build=APPLICATION_VERSION['build']))
    else:
        log.info('OpenLP version {version}'.format(
            version=APPLICATION_VERSION['version']))
    return APPLICATION_VERSION
Exemplo n.º 6
0
def extension_loader(glob_pattern, excluded_files=None):
    """
    A utility function to find and load OpenLP extensions, such as plugins, presentation and media controllers and
    importers.

    :param str glob_pattern: A glob pattern used to find the extension(s) to be imported. Should be relative to the
        application directory. i.e. plugins/*/*plugin.py
    :param list[str] | None excluded_files: A list of file names to exclude that the glob pattern may find.
    :rtype: None
    """
    from openlp.core.common.applocation import AppLocation
    app_dir = AppLocation.get_directory(AppLocation.AppDir)
    for extension_path in app_dir.glob(glob_pattern):
        extension_path = extension_path.relative_to(app_dir)
        if extension_path.name in (excluded_files or []):
            continue
        log.debug('Attempting to import %s', extension_path)
        module_name = path_to_module(extension_path)
        try:
            importlib.import_module(module_name)
        except (ImportError, OSError):
            # On some platforms importing vlc.py might cause OSError exceptions. (e.g. Mac OS X)
            log.exception(
                'Failed to import {module_name} on path {extension_path}'.
                format(module_name=module_name, extension_path=extension_path))
Exemplo n.º 7
0
def set_up_fault_handling():
    """
    Set up the Python fault handler
    """
    global error_log_file
    # Create the cache directory if it doesn't exist, and enable the fault handler to log to an error log file
    try:
        create_paths(AppLocation.get_directory(AppLocation.CacheDir))
        error_log_file = (AppLocation.get_directory(AppLocation.CacheDir) /
                          'error.log').open('wb')
        atexit.register(tear_down_fault_handling)
        faulthandler.enable(error_log_file)
    except OSError:
        log.exception('An exception occurred when enabling the fault handler')
        atexit.unregister(tear_down_fault_handling)
        if error_log_file:
            error_log_file.close()
Exemplo n.º 8
0
Arquivo: db.py Projeto: simhnna/openlp
 def get_cursor():
     """
     Return the cursor object. Instantiate one if it doesn't exist yet.
     """
     if BiblesResourcesDB.cursor is None:
         file_path = \
             AppLocation.get_directory(AppLocation.PluginsDir) / 'bibles' / 'resources' / 'bibles_resources.sqlite'
         conn = sqlite3.connect(str(file_path))
         BiblesResourcesDB.cursor = conn.cursor()
     return BiblesResourcesDB.cursor
Exemplo n.º 9
0
 def __init__(self):
     """
     Initialise the theme object.
     """
     # basic theme object with defaults
     json_path = AppLocation.get_directory(
         AppLocation.AppDir) / 'core' / 'lib' / 'json' / 'theme.json'
     jsn = get_text_file_string(json_path)
     self.load_theme(jsn)
     self.background_filename = None
Exemplo n.º 10
0
 def __init__(self, parent=None):
     """
     The constructor for the plugin manager. Passes the controllers on to
     the plugins for them to interact with via their ServiceItems.
     """
     super(PluginManager, self).__init__(parent)
     self.log_info('Plugin manager Initialising')
     self.log_debug('Base path {path}'.format(
         path=AppLocation.get_directory(AppLocation.PluginsDir)))
     self.plugins = []
     self.log_info('Plugin manager Initialised')
Exemplo n.º 11
0
 def javaScriptConsoleMessage(self, level, message, line_number, source_id):
     """
     Override the parent method in order to log the messages in OpenLP
     """
     # The JS log has the entire file location, which we don't really care about
     app_dir = AppLocation.get_directory(AppLocation.AppDir).parent
     source_id = source_id.replace(
         'file://{app_dir}/'.format(app_dir=app_dir), '')
     # Log the JS messages to the Python logger
     log.log(
         LOG_LEVELS[level], '{source_id}:{line_number} {message}'.format(
             source_id=source_id, line_number=line_number, message=message))
Exemplo n.º 12
0
def test_get_directory_for_app_dir(mocked_get_frozen_path):
    """
    Test the AppLocation.get_directory() method for AppLocation.AppDir
    """
    # GIVEN: A mocked out _get_frozen_path function
    mocked_get_frozen_path.return_value = Path('app', 'dir')

    # WHEN: We call AppLocation.get_directory
    directory = AppLocation.get_directory(AppLocation.AppDir)

    # THEN: check that the correct directory is returned
    assert directory == Path('app', 'dir'), 'Directory should be "app/dir"'
Exemplo n.º 13
0
 def check_pre_conditions(self):
     """
     Check it we have a valid environment.
     :return: true or false
     """
     log.debug('check_installed Mediainfo')
     # Try to find mediainfo in the path
     exists = process_check_binary(Path('mediainfo'))
     # If mediainfo is not in the path, try to find it in the application folder
     if not exists:
         exists = process_check_binary(
             AppLocation.get_directory(AppLocation.AppDir) / 'mediainfo')
     return exists
Exemplo n.º 14
0
 def _start_server(self):
     """
     Start a LibreOfficeServer
     """
     libreoffice_python = Path(
         '/Applications/LibreOffice.app/Contents/Resources/python')
     libreoffice_server = AppLocation.get_directory(
         AppLocation.PluginsDir).joinpath('presentations', 'lib',
                                          'libreofficeserver.py')
     if libreoffice_python.exists():
         self.server_process = Popen(
             [str(libreoffice_python),
              str(libreoffice_server)])
Exemplo n.º 15
0
 def __init__(self, parent=None, screen=None, can_show_startup_screen=True):
     """
     Create the display window
     """
     super(DisplayWindow, self).__init__(parent)
     # Gather all flags for the display window
     flags = QtCore.Qt.FramelessWindowHint | QtCore.Qt.Tool | QtCore.Qt.WindowStaysOnTopHint
     if self.settings.value('advanced/x11 bypass wm'):
         flags |= QtCore.Qt.X11BypassWindowManagerHint
     # Need to import this inline to get around a QtWebEngine issue
     from openlp.core.display.webengine import WebEngineView
     self._is_initialised = False
     self._can_show_startup_screen = can_show_startup_screen
     self._fbo = None
     self.setWindowTitle(translate('OpenLP.DisplayWindow', 'Display Window'))
     self.setWindowFlags(flags)
     self.setAttribute(QtCore.Qt.WA_TranslucentBackground)
     self.setAutoFillBackground(True)
     self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
     self.layout = QtWidgets.QVBoxLayout(self)
     self.layout.setContentsMargins(0, 0, 0, 0)
     self.webview = WebEngineView(self)
     self.webview.setAttribute(QtCore.Qt.WA_TranslucentBackground)
     self.webview.page().setBackgroundColor(QtCore.Qt.transparent)
     self.layout.addWidget(self.webview)
     self.webview.loadFinished.connect(self.after_loaded)
     display_base_path = AppLocation.get_directory(AppLocation.AppDir) / 'core' / 'display' / 'html'
     self.display_path = display_base_path / 'display.html'
     self.checkerboard_path = display_base_path / 'checkerboard.png'
     self.openlp_splash_screen_path = display_base_path / 'openlp-splash-screen.png'
     self.set_url(QtCore.QUrl.fromLocalFile(path_to_str(self.display_path)))
     self.channel = QtWebChannel.QWebChannel(self)
     self.media_watcher = MediaWatcher(self)
     self.channel.registerObject('mediaWatcher', self.media_watcher)
     self.display_watcher = DisplayWatcher(self)
     self.channel.registerObject('displayWatcher', self.display_watcher)
     self.webview.page().setWebChannel(self.channel)
     self.display_watcher.initialised.connect(self.on_initialised)
     self.is_display = False
     self.scale = 1
     self.hide_mode = None
     self.__script_done = True
     self.__script_result = None
     if screen and screen.is_display:
         Registry().register_function('live_display_hide', self.hide_display)
         Registry().register_function('live_display_show', self.show_display)
         self.update_from_screen(screen)
         self.is_display = True
         # Only make visible on single monitor setup if setting enabled.
         if len(ScreenList()) > 1 or self.settings.value('core/display on monitor'):
             self.show()
Exemplo n.º 16
0
    def gs_get_resolution(self, size):
        """
        Only used when using ghostscript
        Ghostscript can't scale automatically while keeping aspect like mupdf, so we need
        to get the ratio between the screen size and the PDF to scale

        :param size: Size struct containing the screen size.
        :return: The resolution dpi to be used.
        """
        # Use a postscript script to get size of the pdf. It is assumed that all pages have same size
        gs_resolution_script = AppLocation.get_directory(
            AppLocation.PluginsDir
        ) / 'presentations' / 'lib' / 'ghostscript_get_resolution.ps'
        # Run the script on the pdf to get the size
        runlog = []
        try:
            runlog = check_output([
                str(self.controller.gsbin), '-dNOPAUSE', '-dNODISPLAY',
                '-dBATCH',
                '-sFile={file_path}'.format(file_path=self.file_path),
                str(gs_resolution_script)
            ],
                                  startupinfo=self.startupinfo)
        except CalledProcessError as e:
            log.debug(' '.join(e.cmd))
            log.debug(e.output)
        # Extract the pdf resolution from output, the format is " Size: x: <width>, y: <height>"
        width = 0.0
        height = 0.0
        for line in runlog.splitlines():
            try:
                width = float(
                    re.search(r'.*Size: x: (\d+\.?\d*), y: \d+.*',
                              line.decode()).group(1))
                height = float(
                    re.search(r'.*Size: x: \d+\.?\d*, y: (\d+\.?\d*).*',
                              line.decode()).group(1))
                break
            except AttributeError:
                continue
        # Calculate the ratio from pdf to screen
        if width > 0 and height > 0:
            width_ratio = size.width() / width
            height_ratio = size.height() / height
            # return the resolution that should be used. 72 is default.
            if width_ratio > height_ratio:
                return int(height_ratio * 72)
            else:
                return int(width_ratio * 72)
        else:
            return 72
Exemplo n.º 17
0
Arquivo: db.py Projeto: simhnna/openlp
 def get_cursor():
     """
     Return the cursor object. Instantiate one if it doesn't exist yet.
     If necessary loads up the database and creates the tables if the database doesn't exist.
     """
     if AlternativeBookNamesDB.cursor is None:
         file_path = AppLocation.get_directory(AppLocation.DataDir) / 'bibles' / 'alternative_book_names.sqlite'
         AlternativeBookNamesDB.conn = sqlite3.connect(str(file_path))
         if not file_path.exists():
             # create new DB, create table alternative_book_names
             AlternativeBookNamesDB.conn.execute(
                 'CREATE TABLE alternative_book_names(id INTEGER NOT NULL, '
                 'book_reference_id INTEGER, language_id INTEGER, name VARCHAR(50), PRIMARY KEY (id))')
         AlternativeBookNamesDB.cursor = AlternativeBookNamesDB.conn.cursor()
     return AlternativeBookNamesDB.cursor
Exemplo n.º 18
0
def test_get_directory_for_plugins_dir(mocked_sys, mocked_split, mocked_abspath, mocked_get_frozen_path):
    """
    Test the AppLocation.get_directory() method for AppLocation.PluginsDir
    """
    # GIVEN: _get_frozen_path, abspath, split and sys are mocked out
    mocked_abspath.return_value = os.path.join('plugins', 'dir')
    mocked_split.return_value = ['openlp']
    mocked_get_frozen_path.return_value = Path('dir')
    mocked_sys.frozen = 1
    mocked_sys.argv = ['openlp']

    # WHEN: We call AppLocation.get_directory
    directory = AppLocation.get_directory(AppLocation.PluginsDir)

    # THEN: The correct directory should be returned
    assert directory == Path('dir', 'plugins'), 'Directory should be "dir/plugins"'
Exemplo n.º 19
0
    def get_translators(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 = str(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)
        # As of Qt5, the core translations come in 2 files per language
        default_translator = QtCore.QTranslator()
        default_translator.load('qt_%s' % language, lang_path)
        base_translator = QtCore.QTranslator()
        base_translator.load('qtbase_%s' % language, lang_path)
        return app_translator, default_translator, base_translator
Exemplo n.º 20
0
def main(args=None):
    """
    The main function which parses command line options and then runs

    :param args: Some args
    """
    args = parse_options(args)
    qt_args = []
    if args and args.loglevel.lower() in ['d', 'debug']:
        log.setLevel(logging.DEBUG)
    elif args and args.loglevel.lower() in ['w', 'warning']:
        log.setLevel(logging.WARNING)
    else:
        log.setLevel(logging.INFO)
    # Throw the rest of the arguments at Qt, just in case.
    qt_args.extend(args.rargs)
    # Bug #1018855: Set the WM_CLASS property in X11
    if not is_win() and not is_macosx():
        qt_args.append('OpenLP')
    # Initialise the resources
    qInitResources()
    # Now create and actually run the application.
    application = OpenLP(qt_args)
    application.setOrganizationName('OpenLP')
    application.setOrganizationDomain('openlp.org')
    application.setAttribute(QtCore.Qt.AA_UseHighDpiPixmaps, True)
    application.setAttribute(QtCore.Qt.AA_DontCreateNativeWidgetSiblings, True)
    if args.portable:
        application.setApplicationName('OpenLPPortable')
        Settings.setDefaultFormat(Settings.IniFormat)
        # Get location OpenLPPortable.ini
        portable_path = (AppLocation.get_directory(AppLocation.AppDir) / '..' /
                         '..').resolve()
        data_path = portable_path / 'Data'
        set_up_logging(portable_path / 'Other')
        log.info('Running portable')
        portable_settings_path = data_path / 'OpenLP.ini'
        # Make this our settings file
        log.info('INI file: {name}'.format(name=portable_settings_path))
        Settings.set_filename(str(portable_settings_path))
        portable_settings = Settings()
        # Set our data path
        log.info('Data path: {name}'.format(name=data_path))
        # Point to our data path
        portable_settings.setValue('advanced/data path', data_path)
        portable_settings.setValue('advanced/is portable', True)
        portable_settings.sync()
    else:
        application.setApplicationName('OpenLP')
        set_up_logging(AppLocation.get_directory(AppLocation.CacheDir))
    Registry.create()
    Registry().register('application', application)
    Registry().set_flag('no_web_server', args.no_web_server)
    application.setApplicationVersion(get_version()['version'])
    # Check if an instance of OpenLP is already running. Quit if there is a running instance and the user only wants one
    server = Server()
    if server.is_another_instance_running():
        application.is_already_running()
        server.post_to_server(qt_args)
        sys.exit()
    else:
        server.start_server()
        application.server = server
    # If the custom data path is missing and the user wants to restore the data path, quit OpenLP.
    if application.is_data_path_missing():
        server.close_server()
        sys.exit()
    # Upgrade settings.
    settings = Settings()
    if settings.can_upgrade():
        now = datetime.now()
        # Only back up if OpenLP has previously run.
        if settings.value('core/has run wizard'):
            back_up_path = AppLocation.get_data_path() / (
                now.strftime('%Y-%m-%d %H-%M') + '.conf')
            log.info(
                'Settings about to be upgraded. Existing settings are being backed up to {back_up_path}'
                .format(back_up_path=back_up_path))
            QtWidgets.QMessageBox.information(
                None, translate('OpenLP', 'Settings Upgrade'),
                translate(
                    'OpenLP',
                    'Your settings are about to be upgraded. A backup will be created at '
                    '{back_up_path}').format(back_up_path=back_up_path))
            settings.export(back_up_path)
        settings.upgrade_settings()
    # First time checks in settings
    if not Settings().value('core/has run wizard'):
        if not FirstTimeLanguageForm().exec():
            # if cancel then stop processing
            server.close_server()
            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.')
    if args and not args.no_error_form:
        sys.excepthook = application.hook_exception
    sys.exit(application.run(qt_args))
Exemplo n.º 21
0
    def do_import_file(self, file):
        """
        Process the SingingTheFaith file - pass in a file-like object, not a file path.
        """
        hints_file_name = 'singingthefaith-hints.tag'
        singing_the_faith_version = 1
        self.set_defaults()
        # Setup variables
        line_number = 0
        old_indent = 0
        # The chorus indent is how many spaces the chorus is indented - it might be 6,
        # but we test for >= and I do not know how consistent to formatting of the
        # exported songs is.
        chorus_indent = 5
        # Initialise the song title - the format of the title finally produced can be affected
        #  by the SongbookNumberInTitle option in the hints file
        song_title = 'STF000 -'
        song_number = '0'
        ccli = '0'
        current_verse = ''
        current_verse_type = 'v'
        current_verse_number = 1
        # Potentially we could try to track current chorus number to automatically handle
        # more than 1 chorus, currently unused.
        # current_chorus_number = 1
        has_chorus = False
        chorus_written = False
        auto_verse_order_ok = False
        copyright = ''
        # the check_flag is prepended to the title, removed if the import should be OK
        # all the songs which need manual editing should sort below all the OK songs
        check_flag = 'z'

        self.add_comment(
            'Imported with Singing The Faith Importer  v{no}'.format(no=singing_the_faith_version))

        # Get the file_song_number - so we can use it for hints
        filename = Path(file.name)
        song_number_file = filename.stem
        song_number_match = re.search(r'\d+', song_number_file)
        if song_number_match:
            song_number_file = song_number_match.group()

        # See if there is a hints file in the same location as the file
        dir_path = filename.parent
        hints_file_path = dir_path / hints_file_name
        try:
            with hints_file_path.open('r') as hints_file:
                hints_available = self.read_hints(hints_file, song_number_file)
        except FileNotFoundError:
            # Look for the hints file in the Plugins directory
            hints_file_path = AppLocation.get_directory(AppLocation.PluginsDir) / 'songs' / 'lib' / \
                'importers' / hints_file_name
            try:
                with hints_file_path.open('r') as hints_file:
                    hints_available = self.read_hints(hints_file, song_number_file)
            except FileNotFoundError:
                hints_available = False

        try:
            for line in file:
                line_number += 1
                # Strip out leftover formatting (\i and \b)
                line = line.replace('\\i', '')
                line = line.replace('\\b', '')
                if hints_available and str(line_number) in self.hint_line:
                    hint = self.hint_line[str(line_number)]
                    # Set to false if this hint does not replace the line
                    line_replaced = True
                    if hint == 'Comment':
                        line.strip()
                        self.add_comment(line)
                        continue
                    elif hint == 'Ignore':
                        continue
                    elif hint == 'Author':
                        # add as a raw author - do not split
                        line.strip()
                        self.add_author(line)
                        line_number += 1
                        next(file)
                        continue
                    elif hint.startswith('VariantVerse'):
                        vv, hintverse, replace = hint.split(' ', 2)
                        this_verse = self.verses[int(hintverse) - 1]
                        this_verse_str = this_verse[1]
                        new_verse = this_verse_str
                        # There might be multiple replace pairs separated by |
                        replaces = replace.split('|')
                        for rep in replaces:
                            source_str, dest_str = rep.split('/')
                            new_verse = new_verse.replace(source_str, dest_str)
                        self.add_verse(new_verse, 'v')
                        self.verse_order_list.append('v{}'.format(str(current_verse_number)))
                        current_verse_number += 1
                        line_number += 1
                        next(file)
                        continue
                    elif hint == 'AddSpaceAfterSemi':
                        line = line.replace(';', '; ')
                        line_replaced = False
                        # note - do not use contine here as the line should now be processed as normal.
                    elif hint == 'AddSpaceAfterColon':
                        line = line.replace(':', ': ')
                        line_replaced = False
                    elif hint == 'BlankLine':
                        line = ' *Blank*'
                        line_replaced = False
                    elif hint == 'BoldLine':
                        # processing of the hint is deferred, but pick it up as a known hint here
                        line_replaced = False
                    else:
                        self.log_error(translate('SongsPlugin.SingingTheFaithImport',
                                       'File {file})'.format(file=file.name)),
                                       translate('SongsPlugin.SingingTheFaithImport',
                                       'Unknown hint {hint}').format(hint=hint))
                    if line_replaced:
                        return
                # STF exported lines have a leading verse number at the start of each verse.
                #  remove them - note that we want to track the indent as that shows a chorus
                # so will deal with that before stripping all leading spaces.
                indent = 0
                if line.strip():
                    # One hymn has one line which starts '* 6' at the start of a verse
                    # Strip this out
                    if line.startswith('* 6'):
                        line = line.lstrip('* ')
                    verse_num_match = re.search(r'^\d+', line)
                    if verse_num_match:
                        # Could extract the verse number and check it against the calculated
                        # verse number - TODO
                        # verse_num = verse_num_match.group()
                        line = line.lstrip('0123456789')
                    indent_match = re.search(r'^\s+', line)
                    if indent_match:
                        indent = len(indent_match.group())
                # Assuming we have sorted out what is verse and what is chorus, strip lines,
                # unless ignoreIndent
                if self.hint_ignore_indent:
                    line = line.rstrip()
                else:
                    line = line.strip()
                if line_number == 2:
                    # note that songs seem to start with a blank line so the title is line 2
                    # Also we strip blanks from the title, even if ignoring indent.
                    song_title = line.strip()
                # Process possible line formatting hints after the verse number has been removed
                if hints_available and str(line_number) in self.hint_line and hint == 'BoldLine':
                    line = '{{st}}{0}{{/st}}'.format(line)
                # Detect the 'Reproduced from Singing the Faith Electronic Words Edition' line
                if line.startswith('Reproduced from Singing the Faith Electronic Words Edition'):
                    song_number_match = re.search(r'\d+', line)
                    if song_number_match:
                        song_number = song_number_match.group()
                        continue
                elif indent == 0:
                    # If the indent is 0 and it contains '(c)' then it is a Copyright line
                    if '(c)' in line:
                        copyright = line
                        continue
                    elif (line.startswith('Liturgical ') or line.startswith('From The ') or
                          line.startswith('From Common ') or line.startswith('Based on Psalm ')):
                        self.add_comment(line)
                        continue
                    # If indent is 0 it may be the author, unless it was one of the cases covered above
                    elif len(line) > 0:
                        # May have more than one author, separated by ' and '
                        authors = line.split(' and ')
                        for a in authors:
                            self.parse_author(a)
                        continue
                # If a blank line has bee replaced by *Blank* then put it back to being
                # a simple space since this is past stripping blanks
                if '*Blank*' in line:
                    line = ' '
                if line == '':
                    if current_verse != '':
                        self.add_verse(current_verse, current_verse_type)
                        self.verse_order_list.append(current_verse_type + str(current_verse_number))
                        if current_verse_type == 'c':
                            chorus_written = True
                        else:
                            current_verse_number += 1
                    current_verse = ''
                    if chorus_written:
                        current_verse_type = 'v'
                else:
                    # If the line is indented more than or equal chorus_indent then assume it is a chorus
                    # If the indent has just changed then start a new verse just like hitting a blank line
                    if not self.hint_ignore_indent and ((indent >= chorus_indent) and (old_indent < indent)):
                        if current_verse != '':
                            self.add_verse(current_verse, current_verse_type)
                            self.verse_order_list.append(current_verse_type + str(current_verse_number))
                            if current_verse_type == 'v':
                                current_verse_number += 1
                        current_verse = line
                        current_verse_type = 'c'
                        old_indent = indent
                        chorus_written = False
                        has_chorus = True
                        continue
                    if current_verse == '':
                        current_verse += line
                    else:
                        current_verse += '\n' + line
                old_indent = indent
        except Exception as e:
            self.log_error(translate('SongsPlugin.SingingTheFaithImport', 'File {file}').format(file=file.name),
                           translate('SongsPlugin.SingingTheFaithImport', 'Error: {error}').format(error=e))
            return

        if self.hint_song_title:
            song_title = self.hint_song_title
        self.title = '{}STF{} - {title}'.format(check_flag, song_number.zfill(3), title=song_title)
        self.song_book_name = 'Singing The Faith'
        self.song_number = song_number
        self.ccli_number = ccli
        self.add_copyright(copyright)
        # If we have a chorus then the generated Verse order can not be used directly, but we can generate
        #  one for two special cases - Verse followed by one chorus (to be repeated after every verse)
        #  of Chorus, followed by verses. If hints for ManualCheck or VerseOrder are supplied ignore this
        if has_chorus and not self.hint_verse_order and not self.checks_needed:
            auto_verse_order_ok = False
            # Popular case V1 C2 V2 ...
            if self.verse_order_list:         # protect against odd cases
                if self.verse_order_list[0] == 'v1' and self.verse_order_list[1] == 'c2':
                    new_verse_order_list = ['v1', 'c1']
                    i = 2
                    auto_verse_order_ok = True
                elif self.verse_order_list[0] == 'c1' and self.verse_order_list[1] == 'v1':
                    new_verse_order_list = ['c1', 'v1', 'c1']
                    i = 2
                    auto_verse_order_ok = True
                # if we are in a case we can deal with
                if auto_verse_order_ok:
                    while i < len(self.verse_order_list):
                        if self.verse_order_list[i].startswith('v'):
                            new_verse_order_list.append(self.verse_order_list[i])
                            new_verse_order_list.append('c1')
                        else:
                            auto_verse_order_ok = False
                            self.add_comment('Importer detected unexpected verse order entry {}'.format(
                                self.verse_order_list[i]))
                        i += 1
                    self.verse_order_list = new_verse_order_list
            else:
                if not auto_verse_order_ok:
                    self.verse_order_list = []
        if self.hint_verse_order:
            self.verse_order_list = self.hint_verse_order.split(',')
        if self.hint_comments:
            self.add_comment(self.hint_comments)
        if self.hint_ccli:
            self.ccli_number = self.hint_ccli
        # Write the title last as by now we will know if we need checks
        if hints_available and not self.checks_needed:
            check_flag = ''
        elif not hints_available and not has_chorus:
            check_flag = ''
        elif not hints_available and has_chorus and auto_verse_order_ok:
            check_flag = ''
        if self.hint_songbook_number_in_title:
            self.title = '{}STF{} - {title}'.format(check_flag, song_number.zfill(3), title=song_title)
        else:
            self.title = '{}{title}'.format(check_flag, title=song_title)
        if not self.finish():
            self.log_error(file.name)
Exemplo n.º 22
0
 def setup_ui(self):
     """
     Configure the UI elements for the tab.
     """
     self.setObjectName('AdvancedTab')
     super(AdvancedTab, self).setup_ui()
     self.ui_group_box = QtWidgets.QGroupBox(self.left_column)
     self.ui_group_box.setObjectName('ui_group_box')
     self.ui_layout = QtWidgets.QFormLayout(self.ui_group_box)
     self.ui_layout.setObjectName('ui_layout')
     self.recent_label = QtWidgets.QLabel(self.ui_group_box)
     self.recent_label.setObjectName('recent_label')
     self.recent_spin_box = QtWidgets.QSpinBox(self.ui_group_box)
     self.recent_spin_box.setObjectName('recent_spin_box')
     self.recent_spin_box.setMinimum(0)
     self.ui_layout.addRow(self.recent_label, self.recent_spin_box)
     self.media_plugin_check_box = QtWidgets.QCheckBox(self.ui_group_box)
     self.media_plugin_check_box.setObjectName('media_plugin_check_box')
     self.ui_layout.addRow(self.media_plugin_check_box)
     self.hide_mouse_check_box = QtWidgets.QCheckBox(self.ui_group_box)
     self.hide_mouse_check_box.setObjectName('hide_mouse_check_box')
     self.ui_layout.addRow(self.hide_mouse_check_box)
     self.double_click_live_check_box = QtWidgets.QCheckBox(
         self.ui_group_box)
     self.double_click_live_check_box.setObjectName(
         'double_click_live_check_box')
     self.ui_layout.addRow(self.double_click_live_check_box)
     self.single_click_preview_check_box = QtWidgets.QCheckBox(
         self.ui_group_box)
     self.single_click_preview_check_box.setObjectName(
         'single_click_preview_check_box')
     self.ui_layout.addRow(self.single_click_preview_check_box)
     self.single_click_service_preview_check_box = QtWidgets.QCheckBox(
         self.ui_group_box)
     self.single_click_service_preview_check_box.setObjectName(
         'single_click_service_preview_check_box')
     self.ui_layout.addRow(self.single_click_service_preview_check_box)
     self.expand_service_item_check_box = QtWidgets.QCheckBox(
         self.ui_group_box)
     self.expand_service_item_check_box.setObjectName(
         'expand_service_item_check_box')
     self.ui_layout.addRow(self.expand_service_item_check_box)
     self.slide_max_height_label = QtWidgets.QLabel(self.ui_group_box)
     self.slide_max_height_label.setObjectName('slide_max_height_label')
     self.slide_max_height_combo_box = QtWidgets.QComboBox(
         self.ui_group_box)
     self.slide_max_height_combo_box.addItem('', userData=0)
     self.slide_max_height_combo_box.addItem('', userData=-4)
     # Generate numeric values for combo box dynamically
     for px in range(60, 801, 5):
         self.slide_max_height_combo_box.addItem(str(px) + 'px',
                                                 userData=px)
     self.slide_max_height_combo_box.setObjectName(
         'slide_max_height_combo_box')
     self.ui_layout.addRow(self.slide_max_height_label,
                           self.slide_max_height_combo_box)
     self.autoscroll_label = QtWidgets.QLabel(self.ui_group_box)
     self.autoscroll_label.setObjectName('autoscroll_label')
     self.autoscroll_combo_box = QtWidgets.QComboBox(self.ui_group_box)
     self.autoscroll_combo_box.addItems(
         ['', '', '', '', '', '', '', '', '', '', '', ''])
     self.autoscroll_combo_box.setObjectName('autoscroll_combo_box')
     self.ui_layout.addRow(self.autoscroll_label)
     self.ui_layout.addRow(self.autoscroll_combo_box)
     self.search_as_type_check_box = QtWidgets.QCheckBox(self.ui_group_box)
     self.search_as_type_check_box.setObjectName('SearchAsType_check_box')
     self.ui_layout.addRow(self.search_as_type_check_box)
     self.enable_auto_close_check_box = QtWidgets.QCheckBox(
         self.ui_group_box)
     self.enable_auto_close_check_box.setObjectName(
         'enable_auto_close_check_box')
     self.ui_layout.addRow(self.enable_auto_close_check_box)
     self.experimental_check_box = QtWidgets.QCheckBox(self.ui_group_box)
     self.experimental_check_box.setObjectName('experimental_check_box')
     self.ui_layout.addRow(self.experimental_check_box)
     self.left_layout.addWidget(self.ui_group_box)
     if HAS_DARK_STYLE:
         self.use_dark_style_checkbox = QtWidgets.QCheckBox(
             self.ui_group_box)
         self.use_dark_style_checkbox.setObjectName(
             'use_dark_style_checkbox')
         self.ui_layout.addRow(self.use_dark_style_checkbox)
     # Service Item Slide Limits
     self.slide_group_box = QtWidgets.QGroupBox(self.left_column)
     self.slide_group_box.setObjectName('slide_group_box')
     self.slide_layout = QtWidgets.QVBoxLayout(self.slide_group_box)
     self.slide_layout.setObjectName('slide_layout')
     self.slide_label = QtWidgets.QLabel(self.slide_group_box)
     self.slide_label.setWordWrap(True)
     self.slide_layout.addWidget(self.slide_label)
     self.end_slide_radio_button = QtWidgets.QRadioButton(
         self.slide_group_box)
     self.end_slide_radio_button.setObjectName('end_slide_radio_button')
     self.slide_layout.addWidget(self.end_slide_radio_button)
     self.wrap_slide_radio_button = QtWidgets.QRadioButton(
         self.slide_group_box)
     self.wrap_slide_radio_button.setObjectName('wrap_slide_radio_button')
     self.slide_layout.addWidget(self.wrap_slide_radio_button)
     self.next_item_radio_button = QtWidgets.QRadioButton(
         self.slide_group_box)
     self.next_item_radio_button.setObjectName('next_item_radio_button')
     self.slide_layout.addWidget(self.next_item_radio_button)
     self.left_layout.addWidget(self.slide_group_box)
     # Data Directory
     self.data_directory_group_box = QtWidgets.QGroupBox(self.left_column)
     self.data_directory_group_box.setObjectName('data_directory_group_box')
     self.data_directory_layout = QtWidgets.QFormLayout(
         self.data_directory_group_box)
     self.data_directory_layout.setObjectName('data_directory_layout')
     self.data_directory_new_label = QtWidgets.QLabel(
         self.data_directory_group_box)
     self.data_directory_new_label.setObjectName(
         'data_directory_current_label')
     self.data_directory_path_edit = PathEdit(
         self.data_directory_group_box,
         path_type=PathEditType.Directories,
         default_path=AppLocation.get_directory(AppLocation.DataDir))
     self.data_directory_layout.addRow(self.data_directory_new_label,
                                       self.data_directory_path_edit)
     self.new_data_directory_has_files_label = QtWidgets.QLabel(
         self.data_directory_group_box)
     self.new_data_directory_has_files_label.setObjectName(
         'new_data_directory_has_files_label')
     self.new_data_directory_has_files_label.setWordWrap(True)
     self.data_directory_cancel_button = QtWidgets.QToolButton(
         self.data_directory_group_box)
     self.data_directory_cancel_button.setObjectName(
         'data_directory_cancel_button')
     self.data_directory_cancel_button.setIcon(UiIcons().delete)
     self.data_directory_copy_check_layout = QtWidgets.QHBoxLayout()
     self.data_directory_copy_check_layout.setObjectName(
         'data_directory_copy_check_layout')
     self.data_directory_copy_check_box = QtWidgets.QCheckBox(
         self.data_directory_group_box)
     self.data_directory_copy_check_box.setObjectName(
         'data_directory_copy_check_box')
     self.data_directory_copy_check_layout.addWidget(
         self.data_directory_copy_check_box)
     self.data_directory_copy_check_layout.addStretch()
     self.data_directory_copy_check_layout.addWidget(
         self.data_directory_cancel_button)
     self.data_directory_layout.addRow(
         self.data_directory_copy_check_layout)
     self.data_directory_layout.addRow(
         self.new_data_directory_has_files_label)
     self.left_layout.addWidget(self.data_directory_group_box)
     # Display Workarounds
     self.display_workaround_group_box = QtWidgets.QGroupBox(
         self.right_column)
     self.display_workaround_group_box.setObjectName(
         'display_workaround_group_box')
     self.display_workaround_layout = QtWidgets.QVBoxLayout(
         self.display_workaround_group_box)
     self.display_workaround_layout.setObjectName(
         'display_workaround_layout')
     self.ignore_aspect_ratio_check_box = QtWidgets.QCheckBox(
         self.display_workaround_group_box)
     self.ignore_aspect_ratio_check_box.setObjectName(
         'ignore_aspect_ratio_check_box')
     self.display_workaround_layout.addWidget(
         self.ignore_aspect_ratio_check_box)
     self.x11_bypass_check_box = QtWidgets.QCheckBox(
         self.display_workaround_group_box)
     self.x11_bypass_check_box.setObjectName('x11_bypass_check_box')
     self.display_workaround_layout.addWidget(self.x11_bypass_check_box)
     self.alternate_rows_check_box = QtWidgets.QCheckBox(
         self.display_workaround_group_box)
     self.alternate_rows_check_box.setObjectName('alternate_rows_check_box')
     self.display_workaround_layout.addWidget(self.alternate_rows_check_box)
     self.right_layout.addWidget(self.display_workaround_group_box)
     # Default service name
     self.service_name_group_box = QtWidgets.QGroupBox(self.right_column)
     self.service_name_group_box.setObjectName('service_name_group_box')
     self.service_name_layout = QtWidgets.QFormLayout(
         self.service_name_group_box)
     self.service_name_check_box = QtWidgets.QCheckBox(
         self.service_name_group_box)
     self.service_name_check_box.setObjectName('service_name_check_box')
     self.service_name_layout.setObjectName('service_name_layout')
     self.service_name_layout.addRow(self.service_name_check_box)
     self.service_name_time_label = QtWidgets.QLabel(
         self.service_name_group_box)
     self.service_name_time_label.setObjectName('service_name_time_label')
     self.service_name_day = QtWidgets.QComboBox(
         self.service_name_group_box)
     self.service_name_day.addItems(['', '', '', '', '', '', '', ''])
     self.service_name_day.setObjectName('service_name_day')
     self.service_name_time = QtWidgets.QTimeEdit(
         self.service_name_group_box)
     self.service_name_time.setObjectName('service_name_time')
     self.service_name_time_layout = QtWidgets.QHBoxLayout()
     self.service_name_time_layout.setObjectName('service_name_time_layout')
     self.service_name_time_layout.addWidget(self.service_name_day)
     self.service_name_time_layout.addWidget(self.service_name_time)
     self.service_name_layout.addRow(self.service_name_time_label,
                                     self.service_name_time_layout)
     self.service_name_label = QtWidgets.QLabel(self.service_name_group_box)
     self.service_name_label.setObjectName('service_name_label')
     self.service_name_edit = QtWidgets.QLineEdit(
         self.service_name_group_box)
     self.service_name_edit.setObjectName('service_name_edit')
     self.service_name_edit.setValidator(
         QtGui.QRegExpValidator(QtCore.QRegExp(r'[^/\\?*|<>\[\]":+]+'),
                                self))
     self.service_name_revert_button = QtWidgets.QToolButton(
         self.service_name_group_box)
     self.service_name_revert_button.setObjectName(
         'service_name_revert_button')
     self.service_name_revert_button.setIcon(UiIcons().undo)
     self.service_name_button_layout = QtWidgets.QHBoxLayout()
     self.service_name_button_layout.setObjectName(
         'service_name_button_layout')
     self.service_name_button_layout.addWidget(self.service_name_edit)
     self.service_name_button_layout.addWidget(
         self.service_name_revert_button)
     self.service_name_layout.addRow(self.service_name_label,
                                     self.service_name_button_layout)
     self.service_name_example_label = QtWidgets.QLabel(
         self.service_name_group_box)
     self.service_name_example_label.setObjectName(
         'service_name_example_label')
     self.service_name_example = QtWidgets.QLabel(
         self.service_name_group_box)
     self.service_name_example.setObjectName('service_name_example')
     self.service_name_layout.addRow(self.service_name_example_label,
                                     self.service_name_example)
     self.right_layout.addWidget(self.service_name_group_box)
     # Proxies
     self.proxy_widget = ProxyWidget(self.right_column)
     self.right_layout.addWidget(self.proxy_widget)
     # After the last item on each side, add some spacing
     self.left_layout.addStretch()
     self.right_layout.addStretch()
     # Set up all the connections and things
     self.should_update_service_name_example = False
     self.service_name_check_box.toggled.connect(
         self.service_name_check_box_toggled)
     self.service_name_day.currentIndexChanged.connect(
         self.on_service_name_day_changed)
     self.service_name_time.timeChanged.connect(
         self.update_service_name_example)
     self.service_name_edit.textChanged.connect(
         self.update_service_name_example)
     self.service_name_revert_button.clicked.connect(
         self.on_service_name_revert_button_clicked)
     self.alternate_rows_check_box.toggled.connect(
         self.on_alternate_rows_check_box_toggled)
     self.data_directory_path_edit.pathChanged.connect(
         self.on_data_directory_path_edit_path_changed)
     self.data_directory_cancel_button.clicked.connect(
         self.on_data_directory_cancel_button_clicked)
     self.data_directory_copy_check_box.toggled.connect(
         self.on_data_directory_copy_check_box_toggled)
     self.end_slide_radio_button.clicked.connect(
         self.on_end_slide_button_clicked)
     self.wrap_slide_radio_button.clicked.connect(
         self.on_wrap_slide_button_clicked)
     self.next_item_radio_button.clicked.connect(
         self.on_next_item_button_clicked)
     self.search_as_type_check_box.stateChanged.connect(
         self.on_search_as_type_check_box_changed)
Exemplo n.º 23
0
 def load(self):
     """
     These are the font icons used in the code.
     """
     font_path = AppLocation.get_directory(AppLocation.AppDir) / 'core' / 'ui' / 'fonts' / 'OpenLP.ttf'
     charmap_path = AppLocation.get_directory(AppLocation.AppDir) / 'core' / 'ui' / 'fonts' / 'openlp-charmap.json'
     qta.load_font('op', font_path, charmap_path)
     palette = QtWidgets.QApplication.palette()
     qta.set_defaults(color=palette.color(QtGui.QPalette.Active,
                                          QtGui.QPalette.ButtonText),
                      color_disabled=palette.color(QtGui.QPalette.Disabled,
                                                   QtGui.QPalette.ButtonText))
     icon_list = {
         'active': {'icon': 'fa.child'},
         'add': {'icon': 'fa.plus-circle'},
         'alert': {'icon': 'fa.exclamation-triangle'},
         'arrow_down': {'icon': 'fa.arrow-down'},
         'arrow_left': {'icon': 'fa.arrow-left'},
         'arrow_right': {'icon': 'fa.arrow-right'},
         'arrow_up': {'icon': 'fa.arrow-up'},
         'audio': {'icon': 'fa.file-sound-o'},
         'authentication': {'icon': 'fa.exclamation-triangle', 'attr': 'red'},
         'address': {'icon': 'fa.book'},
         'back': {'icon': 'fa.step-backward'},
         'bible': {'icon': 'fa.book'},
         'blank': {'icon': 'fa.times-circle'},
         'blank_theme': {'icon': 'fa.file-image-o'},
         'book': {'icon': 'fa.book'},
         'bottom': {'icon': 'fa.angle-double-down'},
         'box': {'icon': 'fa.briefcase'},
         'clapperboard': {'icon': 'fa.chess-board'},
         'clock': {'icon': 'fa.clock-o'},
         'clone': {'icon': 'fa.clone'},
         'close': {'icon': 'fa.times-circle-o'},
         'copy': {'icon': 'fa.copy'},
         'copyright': {'icon': 'fa.copyright'},
         'database': {'icon': 'fa.database'},
         'default': {'icon': 'fa.info-circle'},
         'desktop': {'icon': 'fa.desktop'},
         'delete': {'icon': 'fa.trash'},
         'download': {'icon': 'fa.download'},
         'edit': {'icon': 'fa.edit'},
         'email': {'icon': 'fa.envelope'},
         'error': {'icon': 'fa.exclamation', 'attr': 'red'},
         'exception': {'icon': 'fa.times-circle'},
         'exit': {'icon': 'fa.sign-out'},
         'group': {'icon': 'fa.object-group'},
         'inactive': {'icon': 'fa.child', 'attr': 'lightGray'},
         'info': {'icon': 'fa.info'},
         'light_bulb': {'icon': 'fa.lightbulb-o'},
         'live': {'icon': 'fa.eye'},
         'manual': {'icon': 'fa.graduation-cap'},
         'media': {'icon': 'fa.fax'},
         'minus': {'icon': 'fa.minus'},
         'music': {'icon': 'fa.music'},
         'new': {'icon': 'fa.file'},
         'new_group': {'icon': 'fa.folder'},
         'notes': {'icon': 'fa.sticky-note'},
         'open': {'icon': 'fa.folder-open'},
         'optical': {'icon': 'fa.file-video-o'},
         'pause': {'icon': 'fa.pause'},
         'play': {'icon': 'fa.play'},
         'player': {'icon': 'fa.tablet'},
         'plugin_list': {'icon': 'fa.puzzle-piece'},
         'plus': {'icon': 'fa.plus'},
         'presentation': {'icon': 'fa.bar-chart'},
         'preview': {'icon': 'fa.laptop'},
         'projector': {'icon': 'op.video'},
         'projector_connect': {'icon': 'fa.plug'},
         'projector_cooldown': {'icon': 'fa.video-camera', 'attr': 'blue'},
         'projector_disconnect': {'icon': 'fa.plug', 'attr': 'lightGray'},
         'projector_error': {'icon': 'fa.video-camera', 'attr': 'red'},
         'projector_hdmi': {'icon': 'op.hdmi'},
         'projector_off': {'icon': 'fa.video-camera', 'attr': 'black'},
         'projector_on': {'icon': 'fa.video-camera', 'attr': 'green'},
         'projector_warmup': {'icon': 'fa.video-camera', 'attr': 'yellow'},
         'picture': {'icon': 'fa.picture-o'},
         'print': {'icon': 'fa.print'},
         'remote': {'icon': 'fa.rss'},
         'repeat': {'icon': 'fa.repeat'},
         'save': {'icon': 'fa.save'},
         'search': {'icon': 'fa.search'},
         'search_ccli': {'icon': 'op.search-CCLI'},
         'search_comb': {'icon': 'fa.columns'},
         'search_lyrcs': {'icon': 'op.search-lyrcs'},
         'search_minus': {'icon': 'fa.search-minus'},
         'search_plus': {'icon': 'fa.search-plus'},
         'search_ref': {'icon': 'fa.institution'},
         'search_text': {'icon': 'op.search-text'},
         'settings': {'icon': 'fa.cogs'},
         'shortcuts': {'icon': 'fa.wrench'},
         'song_usage': {'icon': 'fa.line-chart'},
         'song_usage_active': {'icon': 'op.plus_sign'},
         'song_usage_inactive': {'icon': 'op.minus_sign'},
         'sort': {'icon': 'fa.sort'},
         'stop': {'icon': 'fa.stop'},
         'square': {'icon': 'fa.square'},
         'text': {'icon': 'op.file-text'},
         'time': {'icon': 'fa.history'},
         'theme': {'icon': 'fa.paint-brush'},
         'top': {'icon': 'fa.angle-double-up'},
         'undo': {'icon': 'fa.undo'},
         'upload': {'icon': 'fa.upload'},
         'user': {'icon': 'fa.user'},
         'usermo': {'icon': 'op.users'},
         'users': {'icon': 'fa.users'},
         'video': {'icon': 'fa.file-video-o'},
         'volunteer': {'icon': 'fa.group'}
     }
     self.load_icons(self, icon_list)