Esempio n. 1
0
class Application(QObject):
    """Class to implement a Qt Application."""

    instance = None

    _logger = _module_logger.getChild('Application')

    ##############################################

    # Fixme: Singleton

    @classmethod
    def create(cls, *args, **kwargs):

        if cls.instance is not None:
            raise NameError('Instance exists')

        cls.instance = cls(*args, **kwargs)
        return cls.instance

    ##############################################

    def __init__(self):

        super().__init__()

        QtCore.qInstallMessageHandler(self._message_handler)

        self._parse_arguments()

        self._appplication = QGuiApplication(sys.argv)
        self._engine = QQmlApplicationEngine()
        self._qml_application = QmlApplication(self)

        logo_path = ':/icons/logo-256.png'
        self._appplication.setWindowIcon(QIcon(logo_path))

        self._platform = QtPlatform()
        # self._logger.info('\n' + str(self._platform))

        self._scene = None

        # self._load_translation()
        self._register_qml_types()
        self._set_context_properties()
        self._load_qml_main()

        # self._run_before_event_loop()

        QTimer.singleShot(0, self._post_init)

        # self._view = QQuickView()
        # self._view.setResizeMode(QQuickView.SizeRootObjectToView)
        # self._view.setSource(qml_url)

    ##############################################

    @property
    def args(self):
        return self._args

    @property
    def qml_application(self):
        return self._qml_application

    @property
    def platform(self):
        return self._platform

    ##############################################

    def _print_critical_message(self, message):

        print('\nCritical Error on {}'.format(datetime.datetime.now()))
        print('-' * 80)
        print(message)

        # Fixme: don't print ???
        # self._logger.critical(message)

        # Fixme: useless
        sys.stdout.flush()
        sys.stderr.flush()

    ##############################################

    def _message_handler(self, msg_type, context, msg):

        if msg_type == QtCore.QtDebugMsg:
            method = self._logger.debug
        elif msg_type == QtCore.QtInfoMsg:
            method = self._logger.info
        elif msg_type == QtCore.QtWarningMsg:
            method = self._logger.warning
        # elif msg_type == QtCore.QtCriticalMsg:
        #     method = self._logger.critical
        # elif msg_type == QtCore.QtFatalMsg:
        #     method = self._logger.critical
        elif msg_type in (QtCore.QtCriticalMsg, QtCore.QtFatalMsg):
            # method = self._logger.critical
            method = None

        # local_msg = msg.toLocal8Bit()
        # localMsg.constData()
        context_file = context.file
        if context_file is not None:
            file_path = Path(context_file).name
        else:
            file_path = ''
        message = '{1} {3} — {0}'.format(msg, file_path, context.line,
                                         context.function)
        if method is not None:
            method(message)
        else:
            self._print_critical_message(message)

    ##############################################

    def _on_critical_exception(self, exception):
        message = str(exception) + '\n' + traceback.format_exc()
        self._print_critical_message(message)
        sys.exit(1)

    ##############################################

    @classmethod
    def setup_gui_application(cls):

        # QGuiApplication.setApplicationName(APPLICATION_NAME)
        # QGuiApplication.setOrganizationName(ORGANISATION_NAME)
        QGuiApplication.setAttribute(Qt.AA_EnableHighDpiScaling)

        # QQuickStyle.setStyle('Material')

    ##############################################

    def _parse_arguments(self):

        parser = argparse.ArgumentParser(description='Patro', )

        # parser.add_argument(
        #     '--version',
        #     action='store_true', default=False,
        #     help="show version and exit",
        # )

        parser.add_argument(
            '--user-script',
            action=PathAction,
            default=None,
            help='user script to execute',
        )

        parser.add_argument(
            '--user-script-args',
            default='',
            help="user script args (don't forget to quote)",
        )

        self._args = parser.parse_args()

    ##############################################

    # def _load_translationt(self):

    #     locale = QLocale()

    #     if m_translator.load(locale, '...', '.', ':/translations', '.qm'):
    #         m_application.installTranslator(m_translator)
    #     else:
    #         raise "No translator for locale" locale.name()

    ##############################################

    def _register_qml_types(self):

        qmlRegisterUncreatableType(QmlApplication, 'Patro', 1, 0,
                                   'QmlApplication',
                                   'Cannot create QmlApplication')
        qmlRegisterUncreatableType(QtScene, 'Patro', 1, 0, 'QtScene',
                                   'Cannot create QtScene')
        # qmlRegisterType(QmlApplication, 'Patro', 1, 0, 'QmlApplication')
        # qmlRegisterType(QtScene, 'Patro', 1, 0, 'QtScene')

        qmlRegisterType(QtQuickPaintedSceneItem, 'Patro', 1, 0,
                        'PaintedSceneItem')

    ##############################################

    def _set_context_properties(self):
        context = self._engine.rootContext()
        context.setContextProperty('application', self._qml_application)

    ##############################################

    def _load_qml_main(self):

        # self._engine.addImportPath('qrc:///qml')

        qml_path = Path(__file__).parent.joinpath('qml', 'main.qml')
        self._qml_url = QUrl.fromLocalFile(str(qml_path))
        # QUrl('qrc:/qml/main.qml')
        self._engine.objectCreated.connect(self._check_qml_is_loaded)
        self._engine.load(self._qml_url)

    ##############################################

    def _check_qml_is_loaded(self, obj, url):
        # See https://bugreports.qt.io/browse/QTBUG-39469
        if (obj is None and url == self._qml_url):
            sys.exit(-1)

    ##############################################

    def exec_(self):
        # self._view.show()
        sys.exit(self._appplication.exec_())

    ##############################################

    def _post_init(self):
        # Fixme: ui refresh ???
        self._logger.info('post init')
        if self._args.user_script is not None:
            self.execute_user_script(self._args.user_script)

    ##############################################

    def execute_user_script(self, script_path):
        """Execute an user script provided by file *script_path* in a context where is defined a
        variable *application* that is a reference to the application instance.

        """

        script_path = Path(script_path).absolute()
        self._logger.info('Execute user script:\n  {}'.format(script_path))
        try:
            source = open(script_path).read()
        except FileNotFoundError:
            self._logger.info('File {} not found'.format(script_path))
            sys.exit(1)
        try:
            bytecode = compile(source, script_path, 'exec')
        except SyntaxError as exception:
            self._on_critical_exception(exception)
        try:
            exec(bytecode, {'application': self})
        except Exception as exception:
            self._on_critical_exception(exception)
        self._logger.info('User script done')
Esempio n. 2
0
class Application(QObject):
    """Class to implement a Qt Application."""

    instance = None

    _logger = _module_logger.getChild('Application')

    scanner_ready = Signal()

    ##############################################

    # Fixme: Singleton

    @classmethod
    def create(cls, *args, **kwargs):

        if cls.instance is not None:
            raise NameError('Instance exists')

        cls.instance = cls(*args, **kwargs)
        return cls.instance

    ##############################################

    def __init__(self):

        self._logger.info('Ctor')

        super().__init__()

        QtCore.qInstallMessageHandler(self._message_handler)

        self._parse_arguments()

        self._library = None
        self._book = None
        # Fixme: must be defined before QML
        if BookLibrary.is_library(self._args.path):
            self.load_library(self._args.path)
        else:
            self.load_book(self._args.path)

        # For Qt Labs Platform native widgets
        # self._application = QGuiApplication(sys.argv)
        # use QCoreApplication::instance() to get instance
        self._application = QApplication(sys.argv)
        self._application.main = self
        self._init_application()

        self._engine = QQmlApplicationEngine()
        self._qml_application = QmlApplication(self)
        self._application.qml_main = self._qml_application

        self._platform = QtPlatform()
        # self._logger.info('\n' + str(self._platform))

        self._load_translation()
        self._register_qml_types()
        self._set_context_properties()
        self._load_qml_main()

        # self._run_before_event_loop()

        self._thread_pool = QtCore.QThreadPool()
        self._logger.info("Multithreading with maximum {} threads".format(
            self._thread_pool.maxThreadCount()))

        self._scanner = None
        self._scanner_image_provider = ScannerImageProvider()
        self._engine.addImageProvider('scanner_image',
                                      self._scanner_image_provider)

        QTimer.singleShot(0, self._post_init)

        # self._view = QQuickView()
        # self._view.setResizeMode(QQuickView.SizeRootObjectToView)
        # self._view.setSource(qml_url)

    ##############################################

    @property
    def args(self):
        return self._args

    @property
    def platform(self):
        return self._platform

    @property
    def settings(self):
        return self._settings

    @property
    def qml_application(self):
        return self._qml_application

    @property
    def thread_pool(self):
        return self._thread_pool

    @property
    def scanner_image_provider(self):
        return self._scanner_image_provider

    @property
    def scanner(self):
        return self.init_scanner()

    @property
    def book(self):
        return self._book

    # @property
    # def book_path(self):
    #     return self._book.path

    @property
    def library(self):
        return self._library

    ##############################################

    def _print_critical_message(self, message):
        # print('\nCritical Error on {}'.format(datetime.datetime.now()))
        # print('-'*80)
        # print(message)
        self._logger.critical(message)

    ##############################################

    def _message_handler(self, msg_type, context, msg):

        if msg_type == QtCore.QtDebugMsg:
            method = self._logger.debug
        elif msg_type == QtCore.QtInfoMsg:
            method = self._logger.info
        elif msg_type == QtCore.QtWarningMsg:
            method = self._logger.warning
        elif msg_type in (QtCore.QtCriticalMsg, QtCore.QtFatalMsg):
            method = self._logger.critical
            # method = None

        # local_msg = msg.toLocal8Bit()
        # localMsg.constData()
        context_file = context.file
        if context_file is not None:
            file_path = Path(context_file).name
        else:
            file_path = ''
        message = '{1} {3} — {0}'.format(msg, file_path, context.line,
                                         context.function)
        if method is not None:
            method(message)
        else:
            self._print_critical_message(message)

    ##############################################

    def _on_critical_exception(self, exception):
        message = str(exception) + '\n' + traceback.format_exc()
        self._print_critical_message(message)
        self._qml_application.notify_error(exception)
        # sys.exit(1)

    ##############################################

    def _init_application(self):

        self._application.setOrganizationName(
            ApplicationMetadata.organisation_name)
        self._application.setOrganizationDomain(
            ApplicationMetadata.organisation_domain)

        self._application.setApplicationName(ApplicationMetadata.name)
        self._application.setApplicationDisplayName(
            ApplicationMetadata.display_name)
        self._application.setApplicationVersion(ApplicationMetadata.version)

        logo_path = ':/icons/logo/logo-256.png'
        self._application.setWindowIcon(QIcon(logo_path))

        QIcon.setThemeName('material')

        self._settings = ApplicationSettings()

    ##############################################

    @classmethod
    def setup_gui_application(self):

        # https://bugreports.qt.io/browse/QTBUG-55167
        # for path in (
        #         'qt.qpa.xcb.xcberror',
        # ):
        #     QtCore.QLoggingCategory.setFilterRules('{} = false'.format(path))
        QGuiApplication.setAttribute(Qt.AA_EnableHighDpiScaling)

        # QQuickStyle.setStyle('Material')

    ##############################################

    def _parse_arguments(self):

        parser = argparse.ArgumentParser(description='BookBrowser', )

        # parser.add_argument(
        #     '--version',
        #     action='store_true', default=False,
        #     help="show version and exit",
        # )

        # Fixme: should be able to start application without !!!
        parser.add_argument(
            'path',
            metavar='PATH',
            action=PathAction,
            help='book or library path',
        )

        parser.add_argument(
            '--dont-translate',
            action='store_true',
            default=False,
            help="Don't translate application",
        )

        parser.add_argument(
            '--watcher',
            action='store_true',
            default=False,
            help='start watcher',
        )

        parser.add_argument(
            '--fake-scanner',
            action='store_true',
            default=False,
            help='use a fake scanner',
        )

        parser.add_argument(
            '--user-script',
            action=PathAction,
            default=None,
            help='user script to execute',
        )

        parser.add_argument(
            '--user-script-args',
            default='',
            help="user script args (don't forget to quote)",
        )

        self._args = parser.parse_args()

    ##############################################

    def _load_translation(self):

        if self._args.dont_translate:
            return

        # Fixme: ConfigInstall
        # directory = ':/translations'
        directory = str(Path(__file__).parent.joinpath('rcc', 'translations'))

        locale = QtCore.QLocale()
        self._translator = QtCore.QTranslator()
        if self._translator.load(locale, 'book-browser', '.', directory,
                                 '.qm'):
            self._application.installTranslator(self._translator)
        else:
            raise NameError('No translator for locale {}'.format(
                locale.name()))

    ##############################################

    def _register_qml_types(self):

        qmlRegisterType(KeySequenceEditor, 'BookBrowser', 1, 0,
                        'KeySequenceEditor')

        qmlRegisterUncreatableType(Shortcut, 'BookBrowser', 1, 0, 'Shortcut',
                                   'Cannot create Shortcut')
        qmlRegisterUncreatableType(ApplicationSettings, 'BookBrowser', 1, 0,
                                   'ApplicationSettings',
                                   'Cannot create ApplicationSettings')
        qmlRegisterUncreatableType(QmlApplication, 'BookBrowser', 1, 0,
                                   'QmlApplication',
                                   'Cannot create QmlApplication')
        qmlRegisterUncreatableType(QmlBookCover, 'BookBrowser', 1, 0,
                                   'QmlBookCover',
                                   'Cannot create QmlBookCover')
        qmlRegisterUncreatableType(QmlBookLibrary, 'BookBrowser', 1, 0,
                                   'QmlBookLibrary', 'Cannot create QmlBookLi')
        qmlRegisterUncreatableType(QmlBook, 'BookBrowser', 1, 0, 'QmlBook',
                                   'Cannot create QmlBook')
        qmlRegisterUncreatableType(QmlBookPage, 'BookBrowser', 1, 0,
                                   'QmlBookPage', 'Cannot create QmlBookPage')
        qmlRegisterUncreatableType(QmlBookMetadata, 'BookBrowser', 1, 0,
                                   'QmlBookMetadata',
                                   'Cannot create QmlBookMetadata')
        qmlRegisterUncreatableType(QmlScannerConfig, 'BookBrowser', 1, 0,
                                   'QmlScannerConfig',
                                   'Cannot create QmlScannerConfig')
        qmlRegisterUncreatableType(QmlScanner, 'BookBrowser', 1, 0,
                                   'QmlScanner', 'Cannot create QmlScanner')

    ##############################################

    def _set_context_properties(self):
        context = self._engine.rootContext()
        context.setContextProperty('application', self._qml_application)
        context.setContextProperty('application_settings', self._settings)

    ##############################################

    def _load_qml_main(self):

        self._logger.info('Load QML...')

        qml_path = Path(__file__).parent.joinpath('qml')
        # qml_path = 'qrc:///qml'
        self._engine.addImportPath(str(qml_path))

        main_qml_path = qml_path.joinpath('main.qml')
        self._qml_url = QUrl.fromLocalFile(str(main_qml_path))
        # QUrl('qrc:/qml/main.qml')
        self._engine.objectCreated.connect(self._check_qml_is_loaded)
        self._engine.load(self._qml_url)

        self._logger.info('QML loaded')

    ##############################################

    def _check_qml_is_loaded(self, obj, url):
        # See https://bugreports.qt.io/browse/QTBUG-39469
        if (obj is None and url == self._qml_url):
            sys.exit(-1)

    ##############################################

    def exec_(self):
        # self._view.show()
        self._logger.info('Start event loop')
        sys.exit(self._application.exec_())

    ##############################################

    def _post_init(self):

        # Fixme: ui refresh ???

        self._logger.info('post Init...')

        if self._args.watcher:
            self._logger.info('Start watcher')
            self._book.start_watcher()  # QtCore.QFileSystemWatcher(self)

        if self._args.user_script is not None:
            self.execute_user_script(self._args.user_script)

        self._logger.info('Post Init Done')

    ##############################################

    def execute_user_script(self, script_path):
        """Execute an user script provided by file *script_path* in a context where is defined a
        variable *application* that is a reference to the application instance.

        """

        script_path = Path(script_path).absolute()
        self._logger.info('Execute user script:\n  {}'.format(script_path))
        try:
            source = open(script_path).read()
        except FileNotFoundError:
            self._logger.info('File {} not found'.format(script_path))
            sys.exit(1)
        try:
            bytecode = compile(source, script_path, 'exec')
        except SyntaxError as exception:
            self._on_critical_exception(exception)
        try:
            exec(bytecode, {'application': self})
        except Exception as exception:
            self._on_critical_exception(exception)
        self._logger.info('User script done')

    ##############################################

    def init_scanner(self):

        if self._scanner is None:
            # take time
            self._scanner = QmlScanner(fake=self._args.fake_scanner)
            self.scanner_ready.emit()
        return self._scanner

    ##############################################

    def load_book(self, path):
        self._logger.info('Load book {} ...'.format(path))
        self._book = QmlBook(path)
        self._logger.info('Book loaded')

    ##############################################

    def load_library(self, path):
        self._logger.info('Load library {} ...'.format(path))
        self._library = QmlBookLibrary(path)
        self._logger.info('library loaded')
Esempio n. 3
0
class Application(QObject):
    """Class to implement a Qt Application."""

    instance = None

    QmlApplication_CLS = QmlApplication

    _logger = _module_logger.getChild('Application')

    ##############################################

    # Fixme: Singleton

    @classmethod
    def create(cls, *args, **kwargs):

        if cls.instance is not None:
            raise NameError('Instance exists')

        cls.instance = cls(*args, **kwargs)
        return cls.instance

    ##############################################

    def __init__(self):

        self._logger.info('Ctor')

        super().__init__()

        QtCore.qInstallMessageHandler(self._message_handler)

        self._parse_arguments()

        # For Qt Labs Platform native widgets
        # self._application = QGuiApplication(sys.argv)
        # use QCoreApplication::instance() to get instance
        self._application = QApplication(sys.argv)
        self._application.main = self
        self._init_application()

        self._engine = QQmlApplicationEngine()
        self._qml_application = self.QmlApplication_CLS(self)
        self._application.qml_main = self._qml_application

        self._platform = QtPlatform()
        # self._logger.info('\n' + str(self._platform))

        #! self._load_translation()
        self.register_qml_types()
        self._set_context_properties()
        self._load_qml_main()

        # self._run_before_event_loop()

        self._application.aboutToQuit.connect(self.aboutToQuit)

        QTimer.singleShot(0, self.post_init)

    ##############################################

    @property
    def parser(self):
        return self._parser

    @property
    def args(self):
        return self._args

    @property
    def platform(self):
        return self._platform

    @property
    def settings(self):
        return self._settings

    @property
    def qml_application(self):
        return self._qml_application

    ##############################################

    def _print_critical_message(self, message):
        # print('\nCritical Error on {}'.format(datetime.datetime.now()))
        # print('-'*80)
        # print(message)
        self._logger.critical(message)

    ##############################################

    def _message_handler(self, msg_type, context, msg):

        if msg_type == QtCore.QtDebugMsg:
            method = self._logger.debug
        elif msg_type == QtCore.QtInfoMsg:
            method = self._logger.info
        elif msg_type == QtCore.QtWarningMsg:
            method = self._logger.warning
        elif msg_type in (QtCore.QtCriticalMsg, QtCore.QtFatalMsg):
            method = self._logger.critical
            # method = None

        # local_msg = msg.toLocal8Bit()
        # localMsg.constData()
        context_file = context.file
        if context_file is not None:
            file_path = Path(context_file).name
        else:
            file_path = ''
        message = '{1} {3} — {0}'.format(msg, file_path, context.line,
                                         context.function)
        if method is not None:
            method(message)
        else:
            self._print_critical_message(message)

    ##############################################

    def _on_critical_exception(self, exception):
        message = str(exception) + '\n' + traceback.format_exc()
        self._print_critical_message(message)
        self._qml_application.notify_error(exception)
        # sys.exit(1)

    ##############################################

    def _init_application(self):

        self._application.setOrganizationName(
            ApplicationMetadata.organisation_name)
        self._application.setOrganizationDomain(
            ApplicationMetadata.organisation_domain)

        self._application.setApplicationName(ApplicationMetadata.name)
        self._application.setApplicationDisplayName(
            ApplicationMetadata.display_name)
        self._application.setApplicationVersion(ApplicationMetadata.version)

        logo_path = ':/icons/logo/logo-256.png'
        self._application.setWindowIcon(QIcon(logo_path))

        QIcon.setThemeName('material')

        self._settings = ApplicationSettings()

    ##############################################

    @classmethod
    def setup_gui_application(self):

        QGuiApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
        # QQuickStyle.setStyle('Material')

    ##############################################

    def _parse_arguments(self):

        self._parser = argparse.ArgumentParser(description='CodeReview', )

        self.parse_arguments()

        self._args = self._parser.parse_args()

    ##############################################

    def parse_arguments(self):

        # self.parser.add_argument(
        #     '--version',
        #     action='store_true', default=False,
        #     help="show version and exit",
        # )

        self.parser.add_argument(
            '--dont-translate',
            action='store_true',
            default=False,
            help="Don't translate application",
        )

        self.parser.add_argument(
            '--user-script',
            action=PathAction,
            default=None,
            help='user script to execute',
        )

        self.parser.add_argument(
            '--user-script-args',
            default='',
            help="user script args (don't forget to quote)",
        )

    ##############################################

    def _load_translation(self):

        if self._args.dont_translate:
            return

        # Fixme: ConfigInstall
        # directory = ':/translations'
        directory = str(Path(__file__).parent.joinpath('rcc', 'translations'))

        locale = QtCore.QLocale()
        self._translator = QtCore.QTranslator()
        if self._translator.load(locale, 'code-review', '.', directory, '.qm'):
            self._application.installTranslator(self._translator)
        else:
            raise NameError('No translator for locale {}'.format(
                locale.name()))

    ##############################################

    def register_qml_types(self):

        qmlRegisterType(KeySequenceEditor, 'CodeReview', 1, 0,
                        'KeySequenceEditor')

        qmlRegisterUncreatableType(Shortcut, 'CodeReview', 1, 0, 'Shortcut',
                                   'Cannot create Shortcut')
        qmlRegisterUncreatableType(ApplicationSettings, 'CodeReview', 1, 0,
                                   'ApplicationSettings',
                                   'Cannot create ApplicationSettings')
        qmlRegisterUncreatableType(self.QmlApplication_CLS, 'CodeReview', 1, 0,
                                   'QmlApplication',
                                   'Cannot create QmlApplication')

    ##############################################

    def _set_context_properties(self):
        context = self._engine.rootContext()
        context.setContextProperty('application', self._qml_application)
        context.setContextProperty('application_settings', self._settings)

    ##############################################

    def _load_qml_main(self):

        self._logger.info('Load QML...')

        qml_path = Path(__file__).parent.joinpath('qml')
        # qml_path = 'qrc:///qml'
        self._engine.addImportPath(str(qml_path))

        main_qml_path = qml_path.joinpath('main.qml')
        self._qml_url = QUrl.fromLocalFile(str(main_qml_path))
        # QUrl('qrc:/qml/main.qml')
        self._engine.objectCreated.connect(self._check_qml_is_loaded)
        self._engine.load(self._qml_url)

        self._logger.info('QML loaded')

    ##############################################

    def _check_qml_is_loaded(self, obj, url):
        # See https://bugreports.qt.io/browse/QTBUG-39469
        if (obj is None and url == self._qml_url):
            sys.exit(-1)

    ##############################################

    def exec_(self):
        self._logger.info('Start event loop')
        rc = self._application.exec_()
        self._logger.info('Event loop done {}'.format(rc))
        del self._engine  # solve quit issue ?
        sys.exit(rc)

    ##############################################

    def aboutToQuit(self):
        self._logger.info('')

    ##############################################

    def post_init(self):

        # Fixme: ui refresh ???

        self._logger.info('post Init...')

        if self._args.user_script is not None:
            self.execute_user_script(self._args.user_script)

        self._logger.info('Post Init Done')

    ##############################################

    def execute_user_script(self, script_path):
        """Execute an user script provided by file *script_path* in a context where is defined a
        variable *application* that is a reference to the application instance.

        """

        script_path = Path(script_path).absolute()
        self._logger.info('Execute user script:\n  {}'.format(script_path))
        try:
            source = open(script_path).read()
        except FileNotFoundError:
            self._logger.info('File {} not found'.format(script_path))
            sys.exit(1)
        try:
            bytecode = compile(source, script_path, 'exec')
        except SyntaxError as exception:
            self._on_critical_exception(exception)
        try:
            exec(bytecode, {'application': self})
        except Exception as exception:
            self._on_critical_exception(exception)
        self._logger.info('User script done')