class KatajaMain(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. """ active_edge_color_changed = QtCore.pyqtSignal() color_themes_changed = QtCore.pyqtSignal() document_changed = QtCore.pyqtSignal() forest_changed = QtCore.pyqtSignal() parse_changed = QtCore.pyqtSignal() palette_changed = QtCore.pyqtSignal() scope_changed = QtCore.pyqtSignal() selection_changed = QtCore.pyqtSignal() ui_font_changed = QtCore.pyqtSignal() viewport_moved = QtCore.pyqtSignal() viewport_resized = QtCore.pyqtSignal() visualisation_changed = QtCore.pyqtSignal() def __init__(self, kataja_app, tree='', plugin='', image_out='', 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) silent = bool(image_out) self.init_done = False self._stored_init_state = True self.disable_signaling() self.outgoing = [] kataja_app.processEvents() self.use_tooltips = True self.setWindowTitle("Kataja") 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, 1024, 800) self.setMinimumSize(w, h) self.app = kataja_app self.classes = classes self.save_prefs = not no_prefs self.fontdb = QtGui.QFontDatabase() self.color_manager = PaletteManager(self) self.plugin_manager = PluginManager() self.document = None ctrl.late_init(self) # sets ctrl.main capture_stdout(log, self.log_stdout_as_debug, ctrl) classes.late_init() # make all default classes available prefs.import_node_classes(classes) # add node styles defined at class to prefs prefs.load_preferences(disable=reset_prefs or no_prefs) qt_prefs.late_init(running_environment, prefs, self.fontdb, log) self.plugin_manager.find_plugins(prefs.plugins_path or running_environment.plugins_path) self.setWindowIcon(qt_prefs.kataja_icon) self.print_manager = PrintManager() self.view_manager = ViewManager() self.graph_scene = GraphScene() self.recorder = Recorder(self.graph_scene) self.graph_view = GraphView(self.graph_scene) self.view_manager.late_init(self.graph_scene, self.graph_view) self.ui_manager = UIManager(self) if not silent: 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.create_default_document() self.color_manager.update_custom_colors() kataja_app.setPalette(self.color_manager.get_qt_palette()) self.change_color_theme(prefs.color_theme, force=True) self.update_style_sheet() self.graph_scene.late_init() if not silent: self.setCentralWidget(self.graph_view) self.setGeometry(x, y, w, h) self.show() self.raise_() kataja_app.processEvents() self.activateWindow() if tree: plugin = plugin or 'FreeDrawing' else: plugin = plugin or prefs.active_plugin_name or 'FreeDrawing' self.plugin_manager.enable_plugin(plugin) self.document.load_default_forests(tree=tree) self.document.play = not silent if not silent: self.enable_signaling() self.viewport_resized.emit() self.forest_changed.emit() self.action_finished(undoable=False, play=True) if self.forest: self.forest.undo_manager.flush_pile() else: self.action_finished(undoable=False, play=False) #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) if image_out: self.print_manager.print_all(running_environment.default_userspace_path, image_out) quit() @property def forest(self): return self.document.forest def update_style_sheet(self): c = ctrl.cm.drawing() ui = ctrl.cm.ui() f = qt_prefs.get_font(g.UI_FONT) fm = qt_prefs.get_font(g.MAIN_FONT) fc = qt_prefs.get_font(g.CONSOLE_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(), 'main_font': fm.family(), 'main_font_size': fm.pointSize(), 'heading_font_size': fm.pointSize() * 2, 'console_font': fc.family(), 'console_font_size': fc.pointSize(), }) def leaveEvent(self, event): ctrl.ui.force_hide_help() def disable_signaling(self): # shut down side effects self._stored_init_state = self.init_done self.init_done = False ctrl.disable_undo() self.blockSignals(True) # ---------------------- def enable_signaling(self): # resume with side effects self.blockSignals(False) ctrl.resume_undo() self.init_done = self._stored_init_state # ---------------------- # Preferences ################################### def reset_preferences(self): """ :return: """ prefs.restore_default_preferences(qt_prefs, running_environment, classes, log) self.color_themes_changed.emit() 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() # Document / Project ######################### def start_new_document(self, name='Example'): document = classes.KatajaDocument(name=name) self.set_document(document) return document def set_document(self, document): if document is not self.document: if self.document: self.document.retire_from_display() self.document = document self.document_changed.emit() if document: document.update_forest() plug = f'{prefs.active_plugin_name} — ' if prefs.active_plugin_name else '' self.setWindowTitle(f'Kataja — {plug}{document.name}') def create_default_document(self): """ Put empty Kataja document in place -- you want to do this after plugins have changed the classes that implement these. :return: """ self.start_new_document() def clear_document(self): """ Empty everything - maybe necessary before changing plugin """ self.set_document(None) # ### Visualization # ############################################################# def redraw(self): """ Call for forest redraw :return: None """ if self.forest and self.forest.in_display: self.forest.draw() def log_stdout_as_debug(self, text): self.outgoing.append(text) if text.endswith('\n') or text.endswith('\r'): log.debug((''.join(self.outgoing)).strip()) self.outgoing = [] 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) # ## Actions ####################################################### def action_finished(self, m='', undoable=True, error=None, play=False): """ 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 self.forest: if ctrl.action_redraw: self.forest.draw() if undoable and not error: self.forest.undo_manager.take_snapshot(m) if play: self.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: """ if self.init_done: action = self.ui_manager.actions[name] action.run_command(*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) # Color theme ################################# def change_color_theme(self, mode, force=False): """ triggered by color mode selector in colors panel :param mode: """ if self.document: if mode != self.document.settings.get('color_theme') or force: self.document.settings.set('color_theme', mode) self.update_colors() # Not called from anywhere yet, but useful def release_selected(self, **kw): """ :param kw: :return: """ for node in ctrl.get_selected_nodes(): node.release() self.action_finished() return True # ## Other window events ################################################### def timerEvent(self, event): """ Timer event only for printing, for 'snapshot' effect :param event: """ self.print_manager.snapframe_timer(event) 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') def update_colors(self, randomise=False, animate=True): """ This is the master palette change. Its effects should propagate to all objects in scene and ui, either through updated style sheets, paletteChanged -events or 'palette_changed' -signals. :param randomise: :param animate: :return: """ 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.main.palette_changed.emit() 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): self.forest.set_visualization(prefs.visualization) self.redraw() def resize_ui_font(self): qt_prefs.toggle_large_ui_font(prefs.large_ui_text, prefs.fonts) self.update_style_sheet()