Пример #1
0
    def create_forests(self, treelist=None):
        """ This will read sentences to parse. One sentence per line, no periods etc. 

        :param treelist: lines of file like above.
        """
        if not treelist:
            treelist = []

        # Clear this screen before we start creating a mess
        ctrl.disable_undo()  # disable tracking of changes (e.g. undo)
        if self.forest:
            self.forest.retire_from_drawing()
        self.forests = []

        start = 0
        end = 10

        for line in treelist:
            sentence = line.strip()
            if (not sentence) or sentence.startswith('#'):
                continue
            forest = Forest(gloss_text=sentence)
            self.forests.append(forest)
            parser = Parser(grammar.g, -0.0001, forest=forest)
            my_success, my_dnodes = parser.parse(sentence=sentence, start='C')
            print(my_success)
        self.current_index = 0
        self.forest = self.forests[0]
        # allow change tracking (undo) again
        ctrl.resume_undo()
Пример #2
0
    def create_forests(self, filename=None, clear=False):
        """ This will read sentences to parse. One sentence per line, no periods etc.

        :param filename: not used
        :param clear: start with empty
        """
        if clear:
            treelist = ['hello']
        else:
            treelist = self.load_treelist_from_text_file(self.__class__.default_treeset_file) or \
                       ['hello']

        # Clear this screen before we start creating a mess
        ctrl.disable_undo()  # disable tracking of changes (e.g. undo)
        if self.forest:
            self.forest.retire_from_drawing()
        self.forests = []

        grammar = load_grammar(filename=running_environment.plugins_path +
                               '/mgtdbpE/mg0.txt')

        for line in treelist:
            sentence = line.strip()
            if (not sentence) or sentence.startswith('#'):
                continue
            syn = classes.get('SyntaxConnection')()
            syn.sentence = sentence
            syn.lexicon = grammar
            forest = Forest(gloss_text=sentence, syntax=syn)
            self.forests.append(forest)
        self.current_index = 0
        self.forest = self.forests[0]
        # allow change tracking (undo) again
        ctrl.resume_undo()
Пример #3
0
    def create_forests(self, filename=None, clear=False):
        """ This will read example sentences in form used by Ginsburg / Fong parser

        1 Tom read a book. (Chomsky 2015:10) ['C','Tpres',['D','n','Tom'],'v*','read','a','n',
        'book']
        2 They expected John to win. (Chomsky 2015:10) ['C','Tpast',['D','n','they'],'v*','expect',
        'toT',['D','n','John'],'vUnerg','win']
        3 Who do you expect to win. (Chomsky 2015:10) ['C_Q','Tpres',['D','n','you'],'v*','expect',
        'toT',['Q','n','who'],'vUnerg','win']

        :param treelist: lines of file like above. Lines that don't begin with number are ignored.
        """
        if clear:
            treelist = []
        else:
            treelist = self.load_treelist_from_text_file(
                self.__class__.default_treeset_file) or []

        # Clear this screen before we start creating a mess
        ctrl.disable_undo()  # disable tracking of changes (e.g. undo)
        if self.forest:
            self.forest.retire_from_drawing()
        self.forests = []

        start = 0
        end = 10
        ug = Generate()

        for line in treelist:
            if "Japanese" in line:
                ug.language = "Japanese"
            elif "English" in line:
                ug.language = "English"
            sentence, lbracket, target_str = line.partition('[')
            if not (sentence and lbracket and target_str):
                continue
            sentence = sentence.strip()
            sentence_number = sentence[0]

            if not sentence_number.isdigit():
                continue
            sentence_number = int(sentence_number)
            if end and sentence_number > end:
                break
            elif sentence_number < start:
                continue
            sentence = sentence[2:]  # remove number and the space after it
            target_example = eval(lbracket + target_str)
            ug.out(sentence_number, sentence, target_example)
            forest = Forest(gloss_text=sentence)
            self.forests.append(forest)
            so = ug.generate_derivation(target_example, forest=forest)
            ug.out("MRGOperations", ug.merge_counter)
            ug.out("FTInheritanceOp", ug.inheritance_counter)
            ug.out("FTCheckOp", ug.feature_check_counter)
        self.current_index = 0
        self.forest = self.forests[0]
        # allow change tracking (undo) again
        ctrl.resume_undo()
Пример #4
0
 def __init__(self, name=None, filename=None, clear=False):
     super().__init__()
     self.name = name or filename or 'New project'
     self.filename = filename
     self.forests = [Forest()]
     self.current_index = 0
     self.forest = None
     self.lexicon = {}
     self.structures = OrderedDict()
     self.constituents = OrderedDict()
     self.features = OrderedDict()
Пример #5
0
 def new_forest(self):
     """ Add a new forest after the current one.
     :return: tuple (current_index (int), selected forest (Forest)
     """
     ctrl.undo_pile = set()
     #ctrl.undo_disabled = True
     if self.forest:
         self.forest.retire_from_drawing()
     forest = Forest()
     self.current_index += 1
     self.poke(
         'forests')  # <-- announce change in watched list-like attribute
     self.forests.insert(self.current_index, forest)
     self.forest = forest  # <-- at this point the signal is sent to update UI
     #ctrl.undo_disabled = False
     return self.current_index, self.forest
Пример #6
0
    def create_forests(self, filename=None, clear=False):
        """ This will read list of strings where each line defines a trees or an element of trees.
        This can be used to reset the KatajaDocument if no treeset or an empty treeset is given.

        It is common to override this method in plugins to provide custom commands for e.g.
        running parsers.

        Example of tree this can read:

        [.AspP [.Asp\\Ininom] [.vP [.KP [.K\\ng ] [.DP [.D´ [.D ] [.NP\\lola ]] [.KP [.K\\ng]
        [.DP [.D´ [.D ] [.NP\\alila ] ] [.KP\\{ni Maria} ]]]]] [.v´ [.v ] [.VP [.V ] [.KP\\{ang tubig}]]]]]
        Ininom = drank
        ng = NG
        ng = NG
        lola = grandma
        alila = servant
        ni Maria = NG Maria
        ang tubig = ANG water
        'Maria's grandmother's servant drank the water'

        :param filename: (optional) file to load from
        :param clear: (optional) if True, start with an empty treeset and don't attempt to load
        examples
        """
        print('************* create forests ****************')

        if clear:
            treelist = []
        else:
            treelist = self.load_treelist_from_text_file(
                self.__class__.default_treeset_file) or []

        # Clear this screen before we start creating a mess
        ctrl.disable_undo()  # disable tracking of changes (e.g. undo)
        if self.forest:
            self.forest.retire_from_drawing()
        self.forests = []

        # buildstring is the bracket trees or trees.
        buildstring = []
        # definitions includes given definitions for constituents of this trees
        definitions = {}
        # gloss_text is the gloss for whole trees
        gloss_text = ''
        # comments are internal notes about the trees, displayed as help text or something
        comments = []
        started_forest = False

        syntax_class = classes.get('SyntaxConnection')

        for line in treelist:
            line = line.strip()
            #line.split('=', 1)
            parts = line.split('=', 1)
            # comment line
            if line.startswith('#'):
                if started_forest:
                    comments.append(line[1:])
            # Definition line
            elif len(parts) > 1 and not line.startswith('['):
                started_forest = True
                word = parts[0].strip()
                values = parts[1]
                definitions[word] = values
            # Gloss text:
            elif line.startswith("'"):
                if started_forest:
                    if line.endswith("'"):
                        line = line[:-1]
                    gloss_text = line[1:]
            # empty line: finalize this forest
            elif started_forest and not line:
                syn = syntax_class()
                syn.sentence = buildstring
                syn.lexicon = definitions
                forest = Forest(gloss_text=gloss_text,
                                comments=comments,
                                syntax=syn)
                self.forests.append(forest)
                started_forest = False
            # trees definition starts a new forest
            elif line and not started_forest:
                started_forest = True
                buildstring = line
                definitions = {}
                gloss_text = ''
                comments = []
            # another trees definition, append to previous
            elif line:
                buildstring += '\n' + line
        if started_forest:  # make sure that the last forest is also added
            syn = syntax_class()
            syn.sentence = buildstring
            syn.lexicon = definitions
            forest = Forest(gloss_text=gloss_text,
                            comments=comments,
                            syntax=syn)
            self.forests.append(forest)
        if not self.forests:
            syn = syntax_class()
            forest = Forest(gloss_text='', comments=[], syntax=syn)
            self.forests.append(forest)
        self.current_index = 0
        self.forest = self.forests[0]
        # allow change tracking (undo) again
        ctrl.resume_undo()
Пример #7
0
    def __init__(self, kataja_app, no_prefs=False, reset_prefs=False):
        """ KatajaMain initializes all its children and connects itself to
        be the main window of the given application. Receives launch arguments:
        :param no_prefs: bool, don't load or save preferences
        :param reset_prefs: bool, don't attempt to load preferences, use defaults instead

        """
        QtWidgets.QMainWindow.__init__(self)
        kataja_app.processEvents()
        SavedObject.__init__(self)
        self.use_tooltips = True
        self.available_plugins = {}
        self.setDockOptions(QtWidgets.QMainWindow.AnimatedDocks)
        self.setCorner(QtCore.Qt.TopLeftCorner, QtCore.Qt.LeftDockWidgetArea)
        self.setCorner(QtCore.Qt.TopRightCorner, QtCore.Qt.RightDockWidgetArea)
        self.setCorner(QtCore.Qt.BottomLeftCorner,
                       QtCore.Qt.LeftDockWidgetArea)
        self.setCorner(QtCore.Qt.BottomRightCorner,
                       QtCore.Qt.RightDockWidgetArea)
        x, y, w, h = (50, 50, 1152, 720)
        self.setMinimumSize(w, h)
        self.app = kataja_app
        self.save_prefs = not no_prefs
        self.forest = None
        self.fontdb = QtGui.QFontDatabase()
        self.color_manager = PaletteManager()
        self.settings_manager = Settings()
        self.forest_keepers = []
        self.forest_keeper = None
        ctrl.late_init(self)
        classes.late_init()
        prefs.import_node_classes(classes)
        self.syntax = SyntaxConnection()
        prefs.load_preferences(disable=reset_prefs or no_prefs)
        qt_prefs.late_init(running_environment, prefs, self.fontdb, log)
        self.settings_manager.set_prefs(prefs)
        self.color_manager.update_custom_colors()
        self.find_plugins(prefs.plugins_path
                          or running_environment.plugins_path)
        self.setWindowIcon(qt_prefs.kataja_icon)
        self.graph_scene = GraphScene(main=self, graph_view=None)
        self.graph_view = GraphView(main=self, graph_scene=self.graph_scene)
        self.graph_scene.graph_view = self.graph_view
        self.ui_manager = UIManager(self)
        self.settings_manager.set_ui_manager(self.ui_manager)
        self.ui_manager.populate_ui_elements()
        # make empty forest and forest keeper so initialisations don't fail because of their absence
        self.visualizations = VISUALIZATIONS
        self.init_forest_keepers()
        self.settings_manager.set_document(self.forest_keeper)
        kataja_app.setPalette(self.color_manager.get_qt_palette())
        self.forest = Forest()
        self.settings_manager.set_forest(self.forest)
        self.change_color_theme(prefs.color_theme, force=True)
        self.update_style_sheet()
        self.graph_scene.late_init()
        self.setCentralWidget(self.graph_view)
        self.setGeometry(x, y, w, h)
        self.setWindowTitle(self.tr("Kataja"))
        self.print_started = False
        self.show()
        self.raise_()
        kataja_app.processEvents()
        self.activateWindow()
        self.status_bar = self.statusBar()
        self.install_plugins()
        self.load_initial_treeset()
        log.info('Welcome to Kataja! (h) for help')
        #ctrl.call_watchers(self.forest_keeper, 'forest_changed')
        # toolbar = QtWidgets.QToolBar()
        # toolbar.setFixedSize(480, 40)
        # self.addToolBar(toolbar)
        gestures = [
            QtCore.Qt.TapGesture, QtCore.Qt.TapAndHoldGesture,
            QtCore.Qt.PanGesture, QtCore.Qt.PinchGesture,
            QtCore.Qt.SwipeGesture, QtCore.Qt.CustomGesture
        ]
        #for gesture in gestures:
        #    self.grabGesture(gesture)
        self.action_finished(undoable=False)
        self.forest.undo_manager.flush_pile()
Пример #8
0
class KatajaMain(SavedObject, QtWidgets.QMainWindow):
    """ Qt's main window. When this is closed, application closes. Graphics are
    inside this, in scene objects with view widgets. This window also manages
    keypresses and menus. """
    unique = True

    def __init__(self, kataja_app, no_prefs=False, reset_prefs=False):
        """ KatajaMain initializes all its children and connects itself to
        be the main window of the given application. Receives launch arguments:
        :param no_prefs: bool, don't load or save preferences
        :param reset_prefs: bool, don't attempt to load preferences, use defaults instead

        """
        QtWidgets.QMainWindow.__init__(self)
        kataja_app.processEvents()
        SavedObject.__init__(self)
        self.use_tooltips = True
        self.available_plugins = {}
        self.setDockOptions(QtWidgets.QMainWindow.AnimatedDocks)
        self.setCorner(QtCore.Qt.TopLeftCorner, QtCore.Qt.LeftDockWidgetArea)
        self.setCorner(QtCore.Qt.TopRightCorner, QtCore.Qt.RightDockWidgetArea)
        self.setCorner(QtCore.Qt.BottomLeftCorner,
                       QtCore.Qt.LeftDockWidgetArea)
        self.setCorner(QtCore.Qt.BottomRightCorner,
                       QtCore.Qt.RightDockWidgetArea)
        x, y, w, h = (50, 50, 1152, 720)
        self.setMinimumSize(w, h)
        self.app = kataja_app
        self.save_prefs = not no_prefs
        self.forest = None
        self.fontdb = QtGui.QFontDatabase()
        self.color_manager = PaletteManager()
        self.settings_manager = Settings()
        self.forest_keepers = []
        self.forest_keeper = None
        ctrl.late_init(self)
        classes.late_init()
        prefs.import_node_classes(classes)
        self.syntax = SyntaxConnection()
        prefs.load_preferences(disable=reset_prefs or no_prefs)
        qt_prefs.late_init(running_environment, prefs, self.fontdb, log)
        self.settings_manager.set_prefs(prefs)
        self.color_manager.update_custom_colors()
        self.find_plugins(prefs.plugins_path
                          or running_environment.plugins_path)
        self.setWindowIcon(qt_prefs.kataja_icon)
        self.graph_scene = GraphScene(main=self, graph_view=None)
        self.graph_view = GraphView(main=self, graph_scene=self.graph_scene)
        self.graph_scene.graph_view = self.graph_view
        self.ui_manager = UIManager(self)
        self.settings_manager.set_ui_manager(self.ui_manager)
        self.ui_manager.populate_ui_elements()
        # make empty forest and forest keeper so initialisations don't fail because of their absence
        self.visualizations = VISUALIZATIONS
        self.init_forest_keepers()
        self.settings_manager.set_document(self.forest_keeper)
        kataja_app.setPalette(self.color_manager.get_qt_palette())
        self.forest = Forest()
        self.settings_manager.set_forest(self.forest)
        self.change_color_theme(prefs.color_theme, force=True)
        self.update_style_sheet()
        self.graph_scene.late_init()
        self.setCentralWidget(self.graph_view)
        self.setGeometry(x, y, w, h)
        self.setWindowTitle(self.tr("Kataja"))
        self.print_started = False
        self.show()
        self.raise_()
        kataja_app.processEvents()
        self.activateWindow()
        self.status_bar = self.statusBar()
        self.install_plugins()
        self.load_initial_treeset()
        log.info('Welcome to Kataja! (h) for help')
        #ctrl.call_watchers(self.forest_keeper, 'forest_changed')
        # toolbar = QtWidgets.QToolBar()
        # toolbar.setFixedSize(480, 40)
        # self.addToolBar(toolbar)
        gestures = [
            QtCore.Qt.TapGesture, QtCore.Qt.TapAndHoldGesture,
            QtCore.Qt.PanGesture, QtCore.Qt.PinchGesture,
            QtCore.Qt.SwipeGesture, QtCore.Qt.CustomGesture
        ]
        #for gesture in gestures:
        #    self.grabGesture(gesture)
        self.action_finished(undoable=False)
        self.forest.undo_manager.flush_pile()

    def update_style_sheet(self):
        c = ctrl.cm.drawing()
        ui = ctrl.cm.ui()
        f = qt_prefs.get_font(g.UI_FONT)
        self.setStyleSheet(
            stylesheet % {
                'draw': c.name(),
                'lighter': c.lighter().name(),
                'paper': ctrl.cm.paper().name(),
                'ui': ui.name(),
                'ui_lighter': ui.lighter().name(),
                'ui_font': f.family(),
                'ui_font_size': f.pointSize(),
                'ui_font_larger': int(f.pointSize() * 1.2),
                'ui_darker': ui.darker().name()
            })

    def find_plugins(self, plugins_path):
        """ Find the plugins dir for the running configuration and read the metadata of plugins.
        Don't try to load actual python code yet
        :return: None
        """
        if not plugins_path:
            return
        self.available_plugins = {}
        plugins_path = os.path.normpath(plugins_path)
        os.makedirs(plugins_path, exist_ok=True)
        sys.path.append(plugins_path)
        base_ends = len(plugins_path.split('/'))
        for root, dirs, files in os.walk(plugins_path):
            path_parts = root.split('/')
            if len(path_parts) == base_ends + 1 and not path_parts[base_ends].startswith('__') \
                    and 'plugin.json' in files:
                success = False
                try:
                    plugin_file = open(root + '/plugin.json', 'r')
                    data = json.load(plugin_file)
                    plugin_file.close()
                    success = True
                except:
                    log.error(sys.exc_info())
                    print(sys.exc_info())
                if success:
                    mod_name = path_parts[base_ends]
                    data['module_name'] = mod_name
                    data['module_path'] = root
                    self.available_plugins[mod_name] = data

    def enable_plugin(self, plugin_key, reload=False):
        """ Start one plugin: save data, replace required classes with plugin classes, load data.

        """
        self.active_plugin_setup = self.load_plugin(plugin_key)
        if not self.active_plugin_setup:
            return
        self.clear_all()
        ctrl.disable_undo()
        if reload:
            available = []
            for key in sys.modules:
                if key.startswith(plugin_key):
                    available.append(key)
            if getattr(self.active_plugin_setup, 'reload_order', None):
                to_reload = [
                    x for x in self.active_plugin_setup.reload_order
                    if x in available
                ]
            else:
                to_reload = sorted(available)
            for mod_name in to_reload:
                importlib.reload(sys.modules[mod_name])
                print('reloaded ', mod_name)
                log.info('reloaded module %s' % mod_name)

        if hasattr(self.active_plugin_setup, 'plugin_parts'):
            for classobj in self.active_plugin_setup.plugin_parts:
                base_class = classes.find_base_model(classobj)
                if base_class:
                    classes.add_mapping(base_class, classobj)
                    m = "replacing %s with %s " % (base_class.__name__,
                                                   classobj.__name__)
                else:
                    m = "adding %s " % classobj.__name__
                log.info(m)
                print(m)
        if hasattr(self.active_plugin_setup, 'help_file'):
            dir_path = os.path.dirname(
                os.path.realpath(self.active_plugin_setup.__file__))
            print(dir_path)
            self.ui_manager.set_help_source(dir_path,
                                            self.active_plugin_setup.help_file)
        if hasattr(self.active_plugin_setup, 'start_plugin'):
            self.active_plugin_setup.start_plugin(self, ctrl, prefs)
        self.init_forest_keepers()
        ctrl.resume_undo()
        prefs.active_plugin_name = plugin_key

    def disable_current_plugin(self):
        """ Disable the current plugin and load the default trees instead.
        :param clear: if True, have empty treeset, if False, try to load default kataja treeset."""
        if not self.active_plugin_setup:
            return
        ctrl.disable_undo()
        if hasattr(self.active_plugin_setup, 'tear_down_plugin'):
            self.active_plugin_setup.tear_down_plugin(self, ctrl, prefs)
        self.clear_all()
        # print(classes.base_name_to_plugin_class)
        # if hasattr(self.active_plugin_setup, 'plugin_parts'):
        #     for classobj in self.active_plugin_setup.plugin_parts:
        #         class_name = classobj.__name__
        #         if class_name:
        #             log.info(f'removing {class_name}')
        #             print(f'removing {class_name}')
        #             classes.remove_class(class_name)
        classes.restore_default_classes()
        self.init_forest_keepers()
        ctrl.resume_undo()
        prefs.active_plugin_name = ''

    def load_plugin(self, plugin_module):
        setup = None
        importlib.invalidate_caches()
        if plugin_module in self.available_plugins:
            retry = True
            while retry:
                try:
                    setup = importlib.import_module(plugin_module + ".setup")
                    retry = False
                except:
                    e = sys.exc_info()
                    error_dialog = ErrorDialog(self)
                    error_dialog.set_error(
                        '%s, line %s\n%s: %s' %
                        (plugin_module + ".setup.py", e[2].tb_lineno,
                         e[0].__name__, e[1]))
                    error_dialog.set_traceback(traceback.format_exc())
                    retry = error_dialog.exec_()
                    setup = None
        return setup

    def install_plugins(self):
        """ If there are plugins defined in preferences to be used, activate them now.
        :return: None
        """
        if prefs.active_plugin_name:
            log.info('Installing plugin %s...' % prefs.active_plugin_name)
            self.enable_plugin(prefs.active_plugin_name, reload=False)
        self.ui_manager.update_plugin_menu()

    def reset_preferences(self):
        """

        :return:
        """
        prefs.restore_default_preferences(qt_prefs, running_environment,
                                          classes)
        ctrl.call_watchers(self, 'color_themes_changed')
        if self.ui_manager.preferences_dialog:
            self.ui_manager.preferences_dialog.close()
        self.ui_manager.preferences_dialog = PreferencesDialog(self)
        self.ui_manager.preferences_dialog.open()
        self.ui_manager.preferences_dialog.trigger_all_updates()

    def init_forest_keepers(self):
        """ Put empty forest keepers (Kataja documents) in place -- you want to do this after
        plugins have changed the classes that implement these.
        :return:
        """
        self.forest_keepers = [classes.get('KatajaDocument')()]
        self.forest_keeper = self.forest_keepers[0]
        ctrl.call_watchers(self.forest_keeper, 'document_changed')

    def load_initial_treeset(self):
        """ Loads and initializes a new set of trees. Has to be done before
        the program can do anything sane.
        """

        self.forest_keeper.create_forests(clear=False)
        self.change_forest()
        self.ui_manager.update_projects_menu()

    def create_new_project(self):
        names = [fk.name for fk in self.forest_keepers]
        name_base = 'New project'
        name = 'New project'
        c = 1
        while name in names:
            name = '%s %s' % (name_base, c)
            c += 1
        self.forest.retire_from_drawing()
        self.forest_keepers.append(classes.KatajaDocument(name=name))
        self.forest_keeper = self.forest_keepers[-1]
        ctrl.call_watchers(self.forest_keeper, 'document_changed')
        self.change_forest()
        self.ui_manager.update_projects_menu()
        return self.forest_keeper

    def switch_project(self, i):
        self.forest.retire_from_drawing()
        self.forest_keeper = self.forest_keepers[i]
        ctrl.call_watchers(self.forest_keeper, 'document_changed')
        self.change_forest()
        self.ui_manager.update_projects_menu()
        return self.forest_keeper

    # ### Visualization
    # #############################################################

    def change_forest(self):
        """ Tells the scene to remove current trees and related data and
        change it to a new one. Signal 'forest_changed' is already sent by forest keeper.
        """
        ctrl.disable_undo()
        if self.forest:
            self.forest.retire_from_drawing()
        if not self.forest_keeper.forest:
            self.forest_keeper.create_forests(clear=True)
        self.forest = self.forest_keeper.forest
        self.settings_manager.set_forest(self.forest)
        if self.forest.is_parsed:
            if self.forest.derivation_steps:
                ds = self.forest.derivation_steps
                if not ds.activated:
                    print('jumping to derivation step: ',
                          ds.derivation_step_index)
                    ds.jump_to_derivation_step(ds.derivation_step_index)
            else:
                print('no derivation steps')
        self.forest.prepare_for_drawing()
        ctrl.resume_undo()
        #if self.forest.undo_manager.

    def redraw(self):
        """ Call for forest redraw
        :return: None
        """
        self.forest.draw()

    def attach_widget_to_log_handler(self, browserwidget):
        """ This has to be done once: we have a logger set up before there is any output widget,
        once the widget is created it is connected to logger.
        :param browserwidget:
        :return:
        """
        self.app.log_handler.set_widget(browserwidget)

    #    def mousePressEvent(self, event):
#        """ KatajaMain doesn't do anything with mousePressEvents, it delegates
#        :param event:
#        them downwards. This is for debugging. """
#        QtWidgets.QMainWindow.mousePressEvent(self, event)

#    def keyPressEvent(self, event):
#        # if not self.key_manager.receive_key_press(event):
#        """
#
#        :param event:
#        :return:
#        """
#        return QtWidgets.QMainWindow.keyPressEvent(self, event)

# ## Menu management #######################################################

    def action_finished(self, m='', undoable=True, error=None):
        """ Write action to undo stack, report back to user and redraw trees
        if necessary
        :param m: message for undo
        :param undoable: are we supposed to take a snapshot of changes after
        this action.
        :param error message
        """
        if error:
            log.error(error)
        elif m:
            log.info(m)
        if ctrl.action_redraw:
            ctrl.forest.draw()
        if undoable and not error:
            ctrl.forest.undo_manager.take_snapshot(m)
        ctrl.graph_scene.start_animations()
        ctrl.ui.update_actions()

    def trigger_action(self, name, *args, **kwargs):
        """ Helper for programmatically triggering actions (for tests and plugins)
        :param name: action name
        :param kwargs: keyword parameters
        :return:
        """
        action = self.ui_manager.actions[name]
        action.action_triggered(*args, **kwargs)

    def trigger_but_suppress_undo(self, name, *args, **kwargs):
        """ Helper for programmatically triggering actions (for tests and plugins)
        :param name: action name
        :param kwargs: keyword parameters
        :return:
        """
        action = self.ui_manager.actions[name]
        action.trigger_but_suppress_undo(*args, **kwargs)

    def enable_actions(self):
        """ Restores menus """
        for action in self.ui_manager.actions.values():
            action.setDisabled(False)

    def disable_actions(self):
        """ Actions shouldn't be initiated when there is other multi-phase
        action going on """
        for action in self.ui_manager.actions.values():
            action.setDisabled(True)

    def change_color_theme(self, mode, force=False):
        """
        triggered by color mode selector in colors panel

        :param mode:
        """
        if mode != ctrl.settings.get('color_theme') or force:
            if ctrl.settings.document:
                ctrl.settings.set('color_theme', mode, level=g.DOCUMENT)
            ctrl.settings.set('color_theme', mode, level=g.PREFS)
            self.update_colors()

    def timerEvent(self, event):
        """ Timer event only for printing, for 'snapshot' effect
        :param event:
        """
        def find_path(fixed_part, extension, counter=0):
            """ Generate file names until free one is found
            :param fixed_part: blah
            :param extension: blah
            :param counter: blah
            """
            if not counter:
                fpath = fixed_part + extension
            else:
                fpath = fixed_part + str(counter) + extension
            if os.path.exists(fpath):
                fpath = find_path(fixed_part, extension, counter + 1)
            return fpath

        if not self.print_started:
            return
        else:
            self.print_started = False
        self.killTimer(event.timerId())
        # Prepare file and path
        path = prefs.print_file_path or prefs.userspace_path or \
            running_environment.default_userspace_path
        if not path.endswith('/'):
            path += '/'
        if not os.path.exists(path):
            print("bad path for printing (print_file_path in preferences) , "
                  "using '.' instead.")
            path = './'
        filename = prefs.print_file_name
        if filename.endswith(('.pdf', '.png')):
            filename = filename[:-4]
        # Prepare image
        self.graph_scene.removeItem(self.graph_scene.photo_frame)
        self.graph_scene.photo_frame = None
        # Prepare printer
        png = prefs.print_format == 'png'
        source = self.graph_scene.print_rect()

        if png:
            full_path = find_path(path + filename, '.png', 0)
            scale = 4
            target = QtCore.QRectF(QtCore.QPointF(0, 0), source.size() * scale)
            writer = QtGui.QImage(target.size().toSize(),
                                  QtGui.QImage.Format_ARGB32_Premultiplied)
            writer.fill(QtCore.Qt.transparent)
            painter = QtGui.QPainter()
            painter.begin(writer)
            painter.setRenderHint(QtGui.QPainter.Antialiasing)
            self.graph_scene.render(painter, target=target, source=source)
            painter.end()
            iwriter = QtGui.QImageWriter(full_path)
            iwriter.write(writer)
            log.info(
                "printed to %s as PNG (%spx x %spx, %sx size)." %
                (full_path, int(target.width()), int(target.height()), scale))

        else:
            dpi = 25.4
            full_path = find_path(path + filename, '.pdf', 0)
            target = QtCore.QRectF(0, 0,
                                   source.width() / 2.0,
                                   source.height() / 2.0)

            writer = QtGui.QPdfWriter(full_path)
            writer.setResolution(dpi)
            writer.setPageSizeMM(target.size())
            writer.setPageMargins(QtCore.QMarginsF(0, 0, 0, 0))
            painter = QtGui.QPainter()
            painter.begin(writer)
            self.graph_scene.render(painter, target=target, source=source)
            painter.end()
            log.info("printed to %s as PDF with %s dpi." % (full_path, dpi))

        # Thank you!
        # Restore image
        self.graph_scene.setBackgroundBrush(self.color_manager.gradient)

    # Not called from anywhere yet, but useful
    def release_selected(self, **kw):
        """

        :param kw:
        :return:
        """
        for node in ctrl.selected:
            node.release()
        self.action_finished()
        return True

    def clear_all(self):
        """ Empty everything - maybe necessary before loading new data. """
        if self.forest:
            self.forest.retire_from_drawing()
        self.forest_keeper = None
        # Garbage collection doesn't mix well with animations that are still running
        #print('garbage stats:', gc.get_count())
        #gc.collect()
        #print('after collection:', gc.get_count())
        #if gc.garbage:
        #    print('garbage:', gc.garbage)
        self.forest_keepers.append(classes.KatajaDocument(clear=True))
        self.forest_keeper = self.forest_keepers[-1]
        self.settings_manager.set_document(self.forest_keeper)
        self.forest = None

    # ## Other window events
    ###################################################

    def closeEvent(self, event):
        """ Shut down the program, give some debug info
        :param event:
        """
        QtWidgets.QMainWindow.closeEvent(self, event)
        if ctrl.print_garbage:
            # import objgraph
            log.debug('garbage stats: ' + str(gc.get_count()))
            gc.collect()
            log.debug('after collection: ' + str(gc.get_count()))
            if gc.garbage:
                log.debug('garbage: ' + str(gc.garbage))

                # objgraph.show_most_common_types(limit =40)
        if self.save_prefs:
            prefs.save_preferences()
        log.info('...done')

    @time_me
    def create_save_data(self):
        """
        Make a large dictionary of all objects with all of the complex stuff
        and circular references stripped out.
        :return: dict
        """
        savedata = {}
        open_references = {}
        savedata['save_scheme_version'] = 0.4
        self.save_object(savedata, open_references)
        max_rounds = 10
        c = 0
        while open_references and c < max_rounds:
            c += 1
            #print(len(savedata))
            #print('---------------------------')
            for obj in list(open_references.values()):
                if hasattr(obj, 'uid'):
                    obj.save_object(savedata, open_references)
                else:
                    print('cannot save open reference object ', obj)
        assert (c < max_rounds)
        print('total savedata: %s chars in %s items.' %
              (len(str(savedata)), len(savedata)))
        # print(savedata)
        return savedata

    def update_colors(self, randomise=False, animate=True):
        t = time.time()
        cm = self.color_manager
        old_gradient_base = cm.paper()
        cm.update_colors(randomise=randomise)
        self.app.setPalette(cm.get_qt_palette())
        self.update_style_sheet()
        ctrl.call_watchers(self, 'palette_changed')
        if cm.gradient:
            if old_gradient_base != cm.paper() and animate:
                self.graph_scene.fade_background_gradient(
                    old_gradient_base, cm.paper())
            else:
                self.graph_scene.setBackgroundBrush(cm.gradient)
        else:
            self.graph_scene.setBackgroundBrush(qt_prefs.no_brush)
        self.update()

    ### Applying specific preferences globally.
    # These are on_change -methods for various preferences -- these are called if changing a
    # preference should have immediate consequences. They are hosted here because they need to
    # have access to prefs, qt_prefs, main etc.

    def prepare_easing_curve(self):
        qt_prefs.prepare_easing_curve(prefs.curve, prefs.move_frames)

    def update_color_theme(self):
        self.change_color_theme(prefs.color_theme, force=True)

    def update_visualization(self):
        ctrl.forest.set_visualization(prefs.visualization)
        self.main.redraw()

    def resize_ui_font(self):
        qt_prefs.toggle_large_ui_font(prefs.large_ui_text, prefs.fonts)
        self.update_style_sheet()

    def watch_alerted(self, obj, signal, field_name, value):
        """ Receives alerts from signals that this object has chosen to listen. These signals
         are declared in 'self.watchlist'.

         This method will try to sort out the received signals and act accordingly.

        :param obj: the object causing the alarm
        :param signal: identifier for type of the alarm
        :param field_name: name of the field of the object causing the alarm
        :param value: value given to the field
        :return:
        """
        pass

    # ############## #
    #                #
    #  Save support  #
    #                #
    # ############## #

    forest_keeper = SavedField("forest_keeper")
    forest = SavedField("forest")
Пример #9
0
    def __init__(self, kataja_app, no_prefs=False, reset_prefs=False):
        """ KatajaMain initializes all its children and connects itself to
        be the main window of the given application. Receives launch arguments:
        :param no_prefs: bool, don't load or save preferences
        :param reset_prefs: bool, don't attempt to load preferences, use defaults instead

        """
        QtWidgets.QMainWindow.__init__(self)
        kataja_app.processEvents()
        SavedObject.__init__(self)
        self.use_tooltips = True
        self.available_plugins = {}
        self.setDockOptions(QtWidgets.QMainWindow.AnimatedDocks)
        self.setCorner(QtCore.Qt.TopLeftCorner, QtCore.Qt.LeftDockWidgetArea)
        self.setCorner(QtCore.Qt.TopRightCorner, QtCore.Qt.RightDockWidgetArea)
        self.setCorner(QtCore.Qt.BottomLeftCorner, QtCore.Qt.LeftDockWidgetArea)
        self.setCorner(QtCore.Qt.BottomRightCorner,
                       QtCore.Qt.RightDockWidgetArea)
        x, y, w, h = (50, 50, 1152, 720)
        self.setMinimumSize(w, h)
        self.app = kataja_app
        self.save_prefs = not no_prefs
        self.forest = None
        self.fontdb = QtGui.QFontDatabase()
        self.color_manager = PaletteManager()
        self.settings_manager = Settings()
        self.forest_keepers = []
        self.forest_keeper = None
        ctrl.late_init(self)
        classes.late_init()
        prefs.import_node_classes(classes)
        self.syntax = SyntaxConnection()
        prefs.load_preferences(disable=reset_prefs or no_prefs)
        qt_prefs.late_init(running_environment, prefs, self.fontdb, log)
        self.settings_manager.set_prefs(prefs)
        self.color_manager.update_custom_colors()
        self.find_plugins(prefs.plugins_path or running_environment.plugins_path)
        self.setWindowIcon(qt_prefs.kataja_icon)
        self.graph_scene = GraphScene(main=self, graph_view=None)
        self.graph_view = GraphView(main=self, graph_scene=self.graph_scene)
        self.graph_scene.graph_view = self.graph_view
        ctrl.add_watcher(self, 'ui_font_changed')
        self.ui_manager = UIManager(self)
        self.settings_manager.set_ui_manager(self.ui_manager)
        self.ui_manager.populate_ui_elements()
        # make empty forest and forest keeper so initialisations don't fail because of their absence
        self.visualizations = VISUALIZATIONS
        self.init_forest_keepers()
        self.settings_manager.set_document(self.forest_keeper)
        kataja_app.setPalette(self.color_manager.get_qt_palette())
        self.forest = Forest()
        self.settings_manager.set_forest(self.forest)
        self.change_color_theme(prefs.color_theme, force=True)
        self.update_style_sheet()
        self.graph_scene.late_init()
        self.setCentralWidget(self.graph_view)
        self.setGeometry(x, y, w, h)
        self.setWindowTitle(self.tr("Kataja"))
        self.print_started = False
        self.show()
        self.raise_()
        kataja_app.processEvents()
        self.activateWindow()
        self.status_bar = self.statusBar()
        self.install_plugins()
        self.load_initial_treeset()
        log.info('Welcome to Kataja! (h) for help')
        #ctrl.call_watchers(self.forest_keeper, 'forest_changed')
        # toolbar = QtWidgets.QToolBar()
        # toolbar.setFixedSize(480, 40)
        # self.addToolBar(toolbar)
        gestures = [QtCore.Qt.TapGesture, QtCore.Qt.TapAndHoldGesture, QtCore.Qt.PanGesture,
                    QtCore.Qt.PinchGesture, QtCore.Qt.SwipeGesture, QtCore.Qt.CustomGesture]
        #for gesture in gestures:
        #    self.grabGesture(gesture)
        self.action_finished(undoable=False)
        self.forest.undo_manager.flush_pile()
Пример #10
0
class KatajaMain(SavedObject, QtWidgets.QMainWindow):
    """ Qt's main window. When this is closed, application closes. Graphics are
    inside this, in scene objects with view widgets. This window also manages
    keypresses and menus. """
    unique = True

    def __init__(self, kataja_app, no_prefs=False, reset_prefs=False):
        """ KatajaMain initializes all its children and connects itself to
        be the main window of the given application. Receives launch arguments:
        :param no_prefs: bool, don't load or save preferences
        :param reset_prefs: bool, don't attempt to load preferences, use defaults instead

        """
        QtWidgets.QMainWindow.__init__(self)
        kataja_app.processEvents()
        SavedObject.__init__(self)
        self.use_tooltips = True
        self.available_plugins = {}
        self.setDockOptions(QtWidgets.QMainWindow.AnimatedDocks)
        self.setCorner(QtCore.Qt.TopLeftCorner, QtCore.Qt.LeftDockWidgetArea)
        self.setCorner(QtCore.Qt.TopRightCorner, QtCore.Qt.RightDockWidgetArea)
        self.setCorner(QtCore.Qt.BottomLeftCorner, QtCore.Qt.LeftDockWidgetArea)
        self.setCorner(QtCore.Qt.BottomRightCorner,
                       QtCore.Qt.RightDockWidgetArea)
        x, y, w, h = (50, 50, 1152, 720)
        self.setMinimumSize(w, h)
        self.app = kataja_app
        self.save_prefs = not no_prefs
        self.forest = None
        self.fontdb = QtGui.QFontDatabase()
        self.color_manager = PaletteManager()
        self.settings_manager = Settings()
        self.forest_keepers = []
        self.forest_keeper = None
        ctrl.late_init(self)
        classes.late_init()
        prefs.import_node_classes(classes)
        self.syntax = SyntaxConnection()
        prefs.load_preferences(disable=reset_prefs or no_prefs)
        qt_prefs.late_init(running_environment, prefs, self.fontdb, log)
        self.settings_manager.set_prefs(prefs)
        self.color_manager.update_custom_colors()
        self.find_plugins(prefs.plugins_path or running_environment.plugins_path)
        self.setWindowIcon(qt_prefs.kataja_icon)
        self.graph_scene = GraphScene(main=self, graph_view=None)
        self.graph_view = GraphView(main=self, graph_scene=self.graph_scene)
        self.graph_scene.graph_view = self.graph_view
        ctrl.add_watcher(self, 'ui_font_changed')
        self.ui_manager = UIManager(self)
        self.settings_manager.set_ui_manager(self.ui_manager)
        self.ui_manager.populate_ui_elements()
        # make empty forest and forest keeper so initialisations don't fail because of their absence
        self.visualizations = VISUALIZATIONS
        self.init_forest_keepers()
        self.settings_manager.set_document(self.forest_keeper)
        kataja_app.setPalette(self.color_manager.get_qt_palette())
        self.forest = Forest()
        self.settings_manager.set_forest(self.forest)
        self.change_color_theme(prefs.color_theme, force=True)
        self.update_style_sheet()
        self.graph_scene.late_init()
        self.setCentralWidget(self.graph_view)
        self.setGeometry(x, y, w, h)
        self.setWindowTitle(self.tr("Kataja"))
        self.print_started = False
        self.show()
        self.raise_()
        kataja_app.processEvents()
        self.activateWindow()
        self.status_bar = self.statusBar()
        self.install_plugins()
        self.load_initial_treeset()
        log.info('Welcome to Kataja! (h) for help')
        #ctrl.call_watchers(self.forest_keeper, 'forest_changed')
        # toolbar = QtWidgets.QToolBar()
        # toolbar.setFixedSize(480, 40)
        # self.addToolBar(toolbar)
        gestures = [QtCore.Qt.TapGesture, QtCore.Qt.TapAndHoldGesture, QtCore.Qt.PanGesture,
                    QtCore.Qt.PinchGesture, QtCore.Qt.SwipeGesture, QtCore.Qt.CustomGesture]
        #for gesture in gestures:
        #    self.grabGesture(gesture)
        self.action_finished(undoable=False)
        self.forest.undo_manager.flush_pile()

    def update_style_sheet(self):
        c = ctrl.cm.drawing()
        ui = ctrl.cm.ui()
        f = qt_prefs.get_font(g.UI_FONT)
        self.setStyleSheet(stylesheet % {'draw': c.name(), 'lighter': c.lighter().name(),
                                         'paper': ctrl.cm.paper().name(),
                                         'ui': ui.name(), 'ui_lighter': ui.lighter().name(),
                                         'ui_font': f.family(), 'ui_font_size': f.pointSize(),
                                         'ui_font_larger': int(f.pointSize() * 1.2)})

    def find_plugins(self, plugins_path):
        """ Find the plugins dir for the running configuration and read the metadata of plugins.
        Don't try to load actual python code yet
        :return: None
        """
        if not plugins_path:
            return
        self.available_plugins = {}
        plugins_path = os.path.normpath(plugins_path)
        os.makedirs(plugins_path, exist_ok=True)
        sys.path.append(plugins_path)
        base_ends = len(plugins_path.split('/'))
        for root, dirs, files in os.walk(plugins_path):
            path_parts = root.split('/')
            if len(path_parts) == base_ends + 1 and not path_parts[base_ends].startswith('__') \
                    and 'plugin.json' in files:
                success = False
                try:
                    plugin_file = open(root + '/plugin.json', 'r')
                    data = json.load(plugin_file)
                    plugin_file.close()
                    success = True
                except:
                    log.error(sys.exc_info())
                    print(sys.exc_info())
                if success:
                    mod_name = path_parts[base_ends]
                    data['module_name'] = mod_name
                    data['module_path'] = root
                    self.available_plugins[mod_name] = data

    def enable_plugin(self, plugin_key, reload=False):
        """ Start one plugin: save data, replace required classes with plugin classes, load data.

        """
        self.active_plugin_setup = self.load_plugin(plugin_key)
        if not self.active_plugin_setup:
            return
        self.clear_all()
        ctrl.disable_undo()
        if reload:
            available = []
            for key in sys.modules:
                if key.startswith(plugin_key):
                    available.append(key)
            if getattr(self.active_plugin_setup, 'reload_order', None):
                to_reload = [x for x in self.active_plugin_setup.reload_order if x in available]
            else:
                to_reload = sorted(available)
            for mod_name in to_reload:
                importlib.reload(sys.modules[mod_name])
                print('reloaded ', mod_name)
                log.info('reloaded module %s' % mod_name)

        if hasattr(self.active_plugin_setup, 'plugin_parts'):
            for classobj in self.active_plugin_setup.plugin_parts:
                base_class = classes.find_base_model(classobj)
                if base_class:
                    classes.add_mapping(base_class, classobj)
                    m = "replacing %s with %s " % (base_class.__name__, classobj.__name__)
                else:
                    m = "adding %s " % classobj.__name__
                log.info(m)
                print(m)
        if hasattr(self.active_plugin_setup, 'help_file'):
            dir_path = os.path.dirname(os.path.realpath(self.active_plugin_setup.__file__))
            print(dir_path)
            self.ui_manager.set_help_source(dir_path, self.active_plugin_setup.help_file)
        if hasattr(self.active_plugin_setup, 'start_plugin'):
            self.active_plugin_setup.start_plugin(self, ctrl, prefs)
        self.init_forest_keepers()
        ctrl.resume_undo()
        prefs.active_plugin_name = plugin_key

    def disable_current_plugin(self):
        """ Disable the current plugin and load the default trees instead.
        :param clear: if True, have empty treeset, if False, try to load default kataja treeset."""
        if not self.active_plugin_setup:
            return
        ctrl.disable_undo()
        if hasattr(self.active_plugin_setup, 'tear_down_plugin'):
            self.active_plugin_setup.tear_down_plugin(self, ctrl, prefs)
        self.clear_all()
        # print(classes.base_name_to_plugin_class)
        # if hasattr(self.active_plugin_setup, 'plugin_parts'):
        #     for classobj in self.active_plugin_setup.plugin_parts:
        #         class_name = classobj.__name__
        #         if class_name:
        #             log.info(f'removing {class_name}')
        #             print(f'removing {class_name}')
        #             classes.remove_class(class_name)
        classes.restore_default_classes()
        self.init_forest_keepers()
        ctrl.resume_undo()
        prefs.active_plugin_name = ''

    def load_plugin(self, plugin_module):
        setup = None
        importlib.invalidate_caches()
        if plugin_module in self.available_plugins:
            retry = True
            while retry:
                try:
                    setup = importlib.import_module(plugin_module + ".setup")
                    retry = False
                except:
                    e = sys.exc_info()
                    error_dialog = ErrorDialog(self)
                    error_dialog.set_error('%s, line %s\n%s: %s' % (
                    plugin_module + ".setup.py", e[2].tb_lineno, e[0].__name__, e[1]))
                    error_dialog.set_traceback(traceback.format_exc())
                    retry = error_dialog.exec_()
                    setup = None
        return setup

    def install_plugins(self):
        """ If there are plugins defined in preferences to be used, activate them now.
        :return: None
        """
        if prefs.active_plugin_name:
            log.info('Installing plugin %s...' % prefs.active_plugin_name)
            self.enable_plugin(prefs.active_plugin_name, reload=False)
        self.ui_manager.update_plugin_menu()

    def reset_preferences(self):
        """

        :return:
        """
        prefs.restore_default_preferences(qt_prefs, running_environment, classes)
        ctrl.call_watchers(self, 'color_themes_changed')
        if self.ui_manager.preferences_dialog:
            self.ui_manager.preferences_dialog.close()
        self.ui_manager.preferences_dialog = PreferencesDialog(self)
        self.ui_manager.preferences_dialog.open()
        self.ui_manager.preferences_dialog.trigger_all_updates()


    def init_forest_keepers(self):
        """ Put empty forest keepers (Kataja documents) in place -- you want to do this after
        plugins have changed the classes that implement these.
        :return:
        """
        self.forest_keepers = [classes.get('KatajaDocument')()]
        self.forest_keeper = self.forest_keepers[0]
        ctrl.call_watchers(self.forest_keeper, 'document_changed')

    def load_initial_treeset(self):
        """ Loads and initializes a new set of trees. Has to be done before
        the program can do anything sane.
        """

        self.forest_keeper.create_forests(clear=False)
        self.change_forest()
        self.ui_manager.update_projects_menu()

    def create_new_project(self):
        names = [fk.name for fk in self.forest_keepers]
        name_base = 'New project'
        name = 'New project'
        c = 1
        while name in names:
            name = '%s %s' % (name_base, c)
            c += 1
        self.forest.retire_from_drawing()
        self.forest_keepers.append(classes.KatajaDocument(name=name))
        self.forest_keeper = self.forest_keepers[-1]
        ctrl.call_watchers(self.forest_keeper, 'document_changed')
        self.change_forest()
        self.ui_manager.update_projects_menu()
        return self.forest_keeper

    def switch_project(self, i):
        self.forest.retire_from_drawing()
        self.forest_keeper = self.forest_keepers[i]
        ctrl.call_watchers(self.forest_keeper, 'document_changed')
        self.change_forest()
        self.ui_manager.update_projects_menu()
        return self.forest_keeper

    # ### Visualization
    # #############################################################

    def change_forest(self):
        """ Tells the scene to remove current trees and related data and
        change it to a new one. Signal 'forest_changed' is already sent by forest keeper.
        """
        ctrl.disable_undo()
        if self.forest:
            self.forest.retire_from_drawing()
        if not self.forest_keeper.forest:
            self.forest_keeper.create_forests(clear=True)
        self.forest = self.forest_keeper.forest
        self.settings_manager.set_forest(self.forest)
        if self.forest.is_parsed:
            if self.forest.derivation_steps:
                ds = self.forest.derivation_steps
                if not ds.activated:
                    print('jumping to derivation step: ', ds.derivation_step_index)
                    ds.jump_to_derivation_step(ds.derivation_step_index)
            else:
                print('no derivation steps')
        self.forest.prepare_for_drawing()
        ctrl.resume_undo()

    def redraw(self):
        """ Call for forest redraw
        :return: None
        """
        self.forest.draw()

    def attach_widget_to_log_handler(self, browserwidget):
        """ This has to be done once: we have a logger set up before there is any output widget,
        once the widget is created it is connected to logger.
        :param browserwidget:
        :return:
        """
        self.app.log_handler.set_widget(browserwidget)

    #    def mousePressEvent(self, event):
#        """ KatajaMain doesn't do anything with mousePressEvents, it delegates
#        :param event:
#        them downwards. This is for debugging. """
#        QtWidgets.QMainWindow.mousePressEvent(self, event)

#    def keyPressEvent(self, event):
#        # if not self.key_manager.receive_key_press(event):
#        """
#
#        :param event:
#        :return:
#        """
#        return QtWidgets.QMainWindow.keyPressEvent(self, event)

    # ## Menu management #######################################################

    def action_finished(self, m='', undoable=True, error=None):
        """ Write action to undo stack, report back to user and redraw trees
        if necessary
        :param m: message for undo
        :param undoable: are we supposed to take a snapshot of changes after
        this action.
        :param error message
        """
        if error:
            log.error(error)
        elif m:
            log.info(m)
        if ctrl.action_redraw:
            ctrl.forest.draw()
        if undoable and not error:
            ctrl.forest.undo_manager.take_snapshot(m)
        ctrl.graph_scene.start_animations()
        ctrl.ui.update_actions()

    def trigger_action(self, name, *args, **kwargs):
        """ Helper for programmatically triggering actions (for tests and plugins)
        :param name: action name
        :param kwargs: keyword parameters
        :return:
        """
        action = self.ui_manager.actions[name]
        action.action_triggered(*args, **kwargs)

    def trigger_but_suppress_undo(self, name, *args, **kwargs):
        """ Helper for programmatically triggering actions (for tests and plugins)
        :param name: action name
        :param kwargs: keyword parameters
        :return:
        """
        action = self.ui_manager.actions[name]
        action.trigger_but_suppress_undo(*args, **kwargs)

    def enable_actions(self):
        """ Restores menus """
        for action in self.ui_manager.actions.values():
            action.setDisabled(False)

    def disable_actions(self):
        """ Actions shouldn't be initiated when there is other multi-phase
        action going on """
        for action in self.ui_manager.actions.values():
            action.setDisabled(True)

    def change_color_theme(self, mode, force=False):
        """
        triggered by color mode selector in colors panel

        :param mode:
        """
        if mode != ctrl.settings.get('color_theme') or force:
            if ctrl.settings.document:
                ctrl.settings.set('color_theme', mode, level=g.DOCUMENT)
            ctrl.settings.set('color_theme', mode, level=g.PREFS)
            self.update_colors()

    def timerEvent(self, event):
        """ Timer event only for printing, for 'snapshot' effect
        :param event:
        """
        def find_path(fixed_part, extension, counter=0):
            """ Generate file names until free one is found
            :param fixed_part: blah
            :param extension: blah
            :param counter: blah
            """
            if not counter:
                fpath = fixed_part + extension
            else:
                fpath = fixed_part + str(counter) + extension
            if os.path.exists(fpath):
                fpath = find_path(fixed_part, extension, counter + 1)
            return fpath

        if not self.print_started:
            return
        else:
            self.print_started = False
        self.killTimer(event.timerId())
        # Prepare file and path
        path = prefs.print_file_path or prefs.userspace_path or \
            running_environment.default_userspace_path
        if not path.endswith('/'):
            path += '/'
        if not os.path.exists(path):
            print(
                "bad path for printing (print_file_path in preferences) , "
                "using '.' instead.")
            path = './'
        filename = prefs.print_file_name
        if filename.endswith(('.pdf', '.png')):
            filename = filename[:-4]
        # Prepare image
        self.graph_scene.removeItem(self.graph_scene.photo_frame)
        self.graph_scene.photo_frame = None
        # Prepare printer
        png = prefs.print_format == 'png'
        source = self.graph_scene.print_rect()

        if png:
            full_path = find_path(path + filename, '.png', 0)
            scale = 4
            target = QtCore.QRectF(QtCore.QPointF(0, 0), source.size() * scale)
            writer = QtGui.QImage(target.size().toSize(),
                                  QtGui.QImage.Format_ARGB32_Premultiplied)
            writer.fill(QtCore.Qt.transparent)
            painter = QtGui.QPainter()
            painter.begin(writer)
            painter.setRenderHint(QtGui.QPainter.Antialiasing)
            self.graph_scene.render(painter, target=target, source=source)
            painter.end()
            iwriter = QtGui.QImageWriter(full_path)
            iwriter.write(writer)
            log.info("printed to %s as PNG (%spx x %spx, %sx size)." % (
                     full_path, int(target.width()), int(target.height()), scale))

        else:
            dpi = 25.4
            full_path = find_path(path + filename, '.pdf', 0)
            target = QtCore.QRectF(0, 0, source.width() / 2.0,
                                   source.height() / 2.0)

            writer = QtGui.QPdfWriter(full_path)
            writer.setResolution(dpi)
            writer.setPageSizeMM(target.size())
            writer.setPageMargins(QtCore.QMarginsF(0, 0, 0, 0))
            painter = QtGui.QPainter()
            painter.begin(writer)
            self.graph_scene.render(painter, target=target, source=source)
            painter.end()
            log.info("printed to %s as PDF with %s dpi." % (full_path, dpi))

        # Thank you!
        # Restore image
        self.graph_scene.setBackgroundBrush(self.color_manager.gradient)

    # Not called from anywhere yet, but useful
    def release_selected(self, **kw):
        """

        :param kw:
        :return:
        """
        for node in ctrl.selected:
            node.release()
        self.action_finished()
        return True

    def clear_all(self):
        """ Empty everything - maybe necessary before loading new data. """
        if self.forest:
            self.forest.retire_from_drawing()
        self.forest_keeper = None
        # Garbage collection doesn't mix well with animations that are still running
        #print('garbage stats:', gc.get_count())
        #gc.collect()
        #print('after collection:', gc.get_count())
        #if gc.garbage:
        #    print('garbage:', gc.garbage)
        self.forest_keepers.append(classes.KatajaDocument(clear=True))
        self.forest_keeper = self.forest_keepers[-1]
        self.settings_manager.set_document(self.forest_keeper)
        self.forest = None

    # ## Other window events
    ###################################################

    def closeEvent(self, event):
        """ Shut down the program, give some debug info
        :param event:
        """
        QtWidgets.QMainWindow.closeEvent(self, event)
        if ctrl.print_garbage:
            # import objgraph
            log.debug('garbage stats: ' + str(gc.get_count()))
            gc.collect()
            log.debug('after collection: ' + str(gc.get_count()))
            if gc.garbage:
                log.debug('garbage: ' + str(gc.garbage))

                # objgraph.show_most_common_types(limit =40)
        if self.save_prefs:
            prefs.save_preferences()
        log.info('...done')

    @time_me
    def create_save_data(self):
        """
        Make a large dictionary of all objects with all of the complex stuff
        and circular references stripped out.
        :return: dict
        """
        savedata = {}
        open_references = {}
        savedata['save_scheme_version'] = 0.4
        self.save_object(savedata, open_references)
        max_rounds = 10
        c = 0
        while open_references and c < max_rounds:
            c += 1
            #print(len(savedata))
            #print('---------------------------')
            for obj in list(open_references.values()):
                if hasattr(obj, 'uid'):
                    obj.save_object(savedata, open_references)
                else:
                    print('cannot save open reference object ', obj)
        assert(c < max_rounds)
        print('total savedata: %s chars in %s items.' % (
        len(str(savedata)), len(savedata)))
        # print(savedata)
        return savedata

    def update_colors(self, randomise=False, animate=True):
        cm = self.color_manager
        old_gradient_base = cm.paper()
        cm.update_colors(randomise=randomise)
        self.app.setPalette(cm.get_qt_palette())
        self.update_style_sheet()
        ctrl.call_watchers(self, 'palette_changed')
        if cm.gradient:
            if old_gradient_base != cm.paper() and animate:
                self.graph_scene.fade_background_gradient(old_gradient_base, cm.paper())
            else:
                self.graph_scene.setBackgroundBrush(cm.gradient)
        else:
            self.graph_scene.setBackgroundBrush(qt_prefs.no_brush)
        self.update()

    def watch_alerted(self, obj, signal, field_name, value):
        """ Receives alerts from signals that this object has chosen to listen. These signals
         are declared in 'self.watchlist'.

         This method will try to sort out the received signals and act accordingly.

        :param obj: the object causing the alarm
        :param signal: identifier for type of the alarm
        :param field_name: name of the field of the object causing the alarm
        :param value: value given to the field
        :return:
        """
        if signal == 'ui_font_changed':
            self.update_style_sheet()

    # ############## #
    #                #
    #  Save support  #
    #                #
    # ############## #

    forest_keeper = SavedField("forest_keeper")
    forest = SavedField("forest")