def delete_unnecessary_merger(self, node): """ :param node: :raise ForestError: """ if not isinstance(node, ConstituentNode): raise ForestError("Trying to treat wrong kind of node as ConstituentNode and " "forcing it to binary merge") if hasattr(node, 'index'): i = node.index else: i = '' children = list(node.get_children(similar=True, visible=False)) trees = set(node.trees) for child in list(children): parents = node.get_parents(similar=True, visible=False) parents_children = set() bad_parents = [] good_parents = [] for parent in list(parents): if child in parent.get_children(similar=True, visible=False): bad_parents.append(parent) else: good_parents.append(parent) if not (bad_parents or good_parents): self.disconnect_node(node, child) else: if bad_parents: # more complex case m = "Removing node would make parent to have same node as " \ "both left and right child. " + "Removing parent too." log.info(m) self.disconnect_node(node, child) for parent in list(bad_parents): for grandparent in list(parent.get_parents()): self.disconnect_node(grandparent, parent) self.disconnect_node(parent, child) self.connect_node(grandparent, child) if good_parents: # normal case self.disconnect_node(node, child) for parent in list(good_parents): edge = parent.get_edge_to(node) direction = edge.direction() self.disconnect_node(parent, node) self.connect_node(parent, child, direction=direction) if i: child.set_index(i) self.delete_node(node) for parent in list(bad_parents): self.delete_node(parent) # if right.is_placeholder(): # self.delete_node(right) # if left.is_placeholder(): # self.delete_node(left) for tree in list(trees): tree.update_items()
def method(self): """ Change color for selection or in currently active edge type. :return: None """ selector = self.sender() color_key = selector.receive_color_selection() if not color_key: return # Update color for selected nodes if ctrl.ui.scope_is_selection: for node in ctrl.selected: if isinstance(node, Node): ctrl.settings.set_node_setting('color_id', color_key, node=node) node.color_id = color_key node.update_label() # ... or update color for all nodes of this type else: ctrl.settings.set_node_setting('color_id', color_key, node_type=ctrl.ui.active_node_type, level=FOREST) for node in ctrl.forest.nodes.values(): node.update_label() if color_key: log.info('(s) Changed node color to: %s' % ctrl.cm.get_color_name(color_key))
def method(self): """ Create new Forest, insert it after the current one and select it. :return: None """ i, forest = ctrl.main.forest_keeper.new_forest() ctrl.main.change_forest() log.info('(Cmd-n) New forest, n.%s' % (i + 1))
def save_file(filename): save_format, zipped = deduce_format(filename) all_data = ctrl.document.create_save_data() t = time.time() pickle_format = 4 if save_format == 'pickle': if zipped: f = gzip.open(filename, 'wb') else: f = open(filename, 'wb') pickle_worker = pickle.Pickler(f, protocol=pickle_format) pickle_worker.dump(all_data) elif save_format == 'dict': if zipped: f = gzip.open(filename, 'wt') else: f = open(filename, 'w') pp = pprint.PrettyPrinter(indent=1, stream=f) pp.pprint(all_data) elif save_format == 'json': if zipped: f = gzip.open(filename, 'wt') else: f = open(filename, 'w') json.dump(all_data, f, indent="\t", sort_keys=False) else: log.info("Failed to save '%s', no proper format given." % filename) return f.close() log.info("Saved to '%s'. Took %s seconds." % (filename, time.time() - t))
def method(self, index): """ Switch to another project. The action description is generated dynamically, not in code below. :param index: """ project = ctrl.main.switch_project(index) log.info("Switched to project '%s'" % project.name)
def load_data_from_file(self, filename): save_format, zipped = deduce_format(filename) if zipped: if save_format == 'json' or save_format == 'dict': f = gzip.open(filename, 'rt') elif save_format == 'pickle': f = gzip.open(filename, 'rb') else: log.info("Failed to load '%s'. Unknown format." % filename) return else: if save_format == 'pickle': f = open(filename, 'rb') else: f = open(filename, 'r') # import codecs # f = codecs.open(filename, 'rb', encoding = 'utf-8') if save_format == 'pickle': pickle_worker = pickle.Unpickler(f) data = pickle_worker.load() elif save_format == 'dict': data = ast.literal_eval(f.read()) # data = eval(f.read()) elif save_format == 'json': data = json.load(f) else: log.info("Failed to load '%s'. Unknown format." % filename) f.close() return data
def redo(self): """ Move forward in the undo stack :return: None """ if self._current < len(self._stack) - 1: self._current += 1 else: log.info('redo [%s]: In last action' % self._current) return ctrl.disable_undo() ctrl.multiselection_start() ctrl.forest.halt_drawing = True msg, snapshot = self._stack[self._current] affected = set() for obj, transitions, transition_type in snapshot.values(): obj.move_to_later(transitions) if transition_type == CREATED: ctrl.forest.add_to_scene(obj) elif transition_type == DELETED: ctrl.free_drawing.delete_item(obj, ignore_consequences=True) affected.add(obj) ctrl.forest.edge_visibility_check() for obj, transitions, transition_type in snapshot.values(): obj.after_model_update(transitions.keys(), transition_type) if getattr(obj.__class__, 'syntactic_object', False): node = ctrl.forest.nodes_from_synobs.get(obj.uid, None) if node and node not in affected: node.after_model_update([], transition_type) ctrl.forest.flush_and_rebuild_temporary_items() log.info('redo [%s]: %s' % (self._current, msg)) ctrl.multiselection_end() ctrl.resume_undo() ctrl.forest.halt_drawing = False print('------redo finished: ', msg, self._current)
def reselect(self): """ Linearization has """ hs = self.get_data('height_steps') hs += 1 if self.get_data('max_height_steps') < hs: hs = 1 self.set_data('height_steps', hs) log.info('Set height: %s' % hs)
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 method(self, group_uid, color_key): """ """ if ctrl.ui.selection_group and ctrl.ui.selection_group.uid == group_uid: group = ctrl.ui.selection_group elif group_uid in ctrl.forest.groups: group = ctrl.forest.groups[group_uid] group.set_color_key(color_key) log.info('Group color changed to %s' % ctrl.cm.get_color_name(color_key))
def method(self, visualization_key=None): sender = self.sender() if visualization_key is None and isinstance(sender, QtWidgets.QComboBox): visualization_key = str(sender.currentData()) action = ctrl.ui.actions[action_key(visualization_key)] action.setChecked(True) if visualization_key: ctrl.forest.set_visualization(visualization_key) log.info(visualization_key)
def method(self): """ If triggered node is triangle node, restore it to normal :return: None """ ctrl.release_editor_focus() node = self.get_host() if not node: return log.info('unfolding from %s' % node.as_bracket_string()) ctrl.free_drawing.remove_triangle_from(node) ctrl.deselect_objects()
def method(self): """ Generic add node, gets the node type as an argument. :return: None """ sender = self.sender() ntype = sender.data pos = QtCore.QPoint(random.random() * 60 - 25, random.random() * 60 - 25) node = ctrl.free_drawing.create_node(pos=pos, node_type=ntype) nclass = classes.nodes[ntype] log.info('Added new %s.' % nclass.display_name[0])
def method(self): """ Generic add node, gets the node type as an argument. :return: None """ ntype = self.__class__.node_type pos = QtCore.QPoint(random.random() * 60 - 25, random.random() * 60 - 25) label = ctrl.drawing.next_free_label() ctrl.drawing.create_node(label=label, pos=pos, node_type=ntype) nclass = classes.nodes[ntype] log.info('Added new %s.' % nclass.display_name[0]) ctrl.forest.forest_edited()
def delete_unnecessary_merger(self, node): """ :param node: :raise ForestError: """ if not isinstance(node, ConstituentNode): raise ForestError( "Trying to treat wrong kind of node as ConstituentNode and " "forcing it to binary merge") if hasattr(node, 'index'): i = node.index else: i = '' children = list(node.get_children(similar=True, visible=False)) for child in list(children): parents = node.get_parents(similar=True, visible=False) bad_parents = [] good_parents = [] for parent in list(parents): if child in parent.get_children(similar=True, visible=False): bad_parents.append(parent) else: good_parents.append(parent) if not (bad_parents or good_parents): self.disconnect_node(node, child) else: if bad_parents: # more complex case m = "Removing node would make parent to have same node as " \ "both left and right child. " + "Removing parent too." log.info(m) self.disconnect_node(node, child) for parent in list(bad_parents): for grandparent in list(parent.get_parents()): self.disconnect_node(grandparent, parent) self.disconnect_node(parent, child) self.connect_node(grandparent, child) if good_parents: # normal case self.disconnect_node(node, child) for parent in list(good_parents): edge = parent.get_edge_to(node) direction = edge.direction() self.disconnect_node(parent, node) self.connect_node(parent, child, direction=direction) if i: child.set_index(i) self.delete_node(node) for parent in list(bad_parents): self.delete_node(parent)
def method(self): """ """ sender = self.sender() if sender: group = self.get_host() group.include_children = sender.isChecked() group.update_selection(group.selection) group.update_shape() if group.include_children: log.info("Group includes children of its orginal members") else: log.info("Group does not include children")
def method(self): """ """ sender = self.sender() if sender: group = self.get_host() group.allow_overlap = sender.isChecked() group.update_selection(group.selection) group.update_shape() if group.allow_overlap: log.info("Group can overlap with other groups") else: log.info("Group cannot overlap with other groups")
def method(self): """ """ sender = self.sender() if sender: group = self.get_host() group.allow_overlap = sender.isChecked() group.update_selection(group.selection) group.update_shape() if group.allow_overlap: log.info('Group can overlap with other groups') else: log.info('Group cannot overlap with other groups')
def method(self): """ If triggered node is triangle node, restore it to normal :return: None """ ctrl.release_editor_focus() node = self.get_host() if not node: return log.info('unfolding from %s' % node.as_bracket_string()) ctrl.free_drawing.remove_triangle_from(node) ctrl.deselect_objects() node.update_label()
def method(self): """ """ sender = self.sender() if sender: group = self.get_host() group.include_children = sender.isChecked() group.update_selection(group.selection) group.update_shape() if group.include_children: log.info('Group includes children of its orginal members') else: log.info('Group does not include children')
def enable_plugin(self, plugin_key, reload=False): """ Start one plugin: save data, replace required classes with plugin classes, load data. """ ctrl.plugin_settings = {} self.active_plugin_setup = self.load_plugin(plugin_key) if not self.active_plugin_setup: return ctrl.main.disable_signaling() ctrl.main.clear_document() 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]) log.info('reloaded module %s' % mod_name) if hasattr(self.active_plugin_setup, 'plugin_classes'): for classobj in self.active_plugin_setup.plugin_classes: 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) actions_module = getattr(self.active_plugin_setup, 'plugin_actions', None) if actions_module: classes.replaced_actions = {} classes.added_actions = [] ctrl.ui.load_actions_from_module(actions_module, added=classes.added_actions, replaced=classes.replaced_actions) dir_path = os.path.dirname(os.path.realpath(self.active_plugin_setup.__file__)) if hasattr(self.active_plugin_setup, 'help_file'): ctrl.ui.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) ctrl.main.create_default_document() ctrl.main.enable_signaling() self.active_plugin_path = dir_path prefs.active_plugin_name = plugin_key ctrl.ui.update_plugin_menu()
def method(self): """ Turn triggering node into triangle node :return: None """ ctrl.release_editor_focus() node = self.get_host() if not node: return log.info('folding in %s' % node.as_bracket_string()) ctrl.free_drawing.add_triangle_to(node) node.update_label() ctrl.deselect_objects()
def method(self): """ Turn triggering node into triangle node :return: None """ ctrl.release_editor_focus() node = self.get_host() if not node: return log.info('folding in %s' % node.as_bracket_string()) ctrl.free_drawing.add_or_update_triangle_for(node) ctrl.deselect_objects() node.update_label()
def restore_derivation_step(self): if self.derivation_steps: uid, frozen_data, msg = self.derivation_steps[self.derivation_step_index] d_step = DerivationStep(uid=uid) d_step.load_objects(frozen_data, ctrl.main) self.activated = True self.current = d_step synobjs_to_nodes(self.forest, d_step.synobjs, d_step.numeration, d_step.other, d_step.msg, d_step.gloss, d_step.transferred, d_step.mover) if msg: log.info(msg)
def method(self): """ Toggle between fullscreen mode and windowed mode :return: None """ if ctrl.main.isFullScreen(): ctrl.main.showNormal() log.info('(Cmd+f) windowed') ctrl.ui.restore_panel_positions() else: ctrl.ui.store_panel_positions() ctrl.main.showFullScreen() log.info('(Cmd+f) fullscreen') ctrl.graph_scene.fit_to_window(force=True)
def method(self): """ Toggle between fullscreen mode and windowed mode :return: None """ if ctrl.main.isFullScreen(): ctrl.main.showNormal() log.info('(Cmd+f) windowed') ctrl.ui.restore_panel_positions() else: ctrl.ui.store_panel_positions() ctrl.main.showFullScreen() log.info('(Cmd+f) fullscreen') ctrl.view_manager.update_viewport(ViewUpdateReason.FIT_IN_TRIGGERED)
def method(self): """ """ sender = self.sender() if sender: group = self.get_host() color_key = sender.currentData() sender.model().selected_color = color_key if color_key: group.update_colors(color_key) embed = sender.parent() if embed and hasattr(embed, "update_colors"): embed.update_colors() log.info("Group color changed to %s" % ctrl.cm.get_color_name(color_key))
def method(self): """ """ sender = self.sender() if sender: group = self.get_host() color_key = sender.currentData() sender.model().selected_color = color_key if color_key: group.update_colors(color_key) embed = sender.parent() if embed and hasattr(embed, 'update_colors'): embed.update_colors() log.info('Group color changed to %s' % ctrl.cm.get_color_name(color_key))
def restore_derivation_step(self): if self.derivation_steps: uid, frozen_data, msg, i = self.derivation_steps[self.derivation_step_index] d_step = DerivationStep(None, uid=uid) d_step.load_objects(frozen_data) self.activated = True self.current = d_step syntactic_state_to_nodes(self.forest, d_step.to_syn_state()) if d_step.msg: log.info(f'<b>msg: {d_step.msg}</b>') for log_msg in d_step.log: if log_msg.strip(): log_msg = log_msg.replace("\t", " ") log.info(f'<font color="#859900">{log_msg}</font>')
def delete_unnecessary_merger(self, node): """ :param node: :raise ForestError: """ if node.node_type != g.CONSTITUENT_NODE: raise ForestError("Trying to treat wrong kind of node as ConstituentNode and " "forcing it to binary merge") i = node.index or '' children = list(node.get_children(similar=True, visible=False)) for child in list(children): parents = node.get_parents(similar=True, visible=False) bad_parents = [] good_parents = [] for parent in list(parents): if child in parent.get_children(similar=True, visible=False): bad_parents.append(parent) else: good_parents.append(parent) if not (bad_parents or good_parents): self.disconnect_node(node, child) else: if bad_parents: # more complex case m = "Removing node would make parent to have same node as " \ "both left and right child. " + "Removing parent too." log.info(m) self.disconnect_node(node, child) for parent in list(bad_parents): for grandparent in list(parent.get_parents()): self.disconnect_node(grandparent, parent) self.disconnect_node(parent, child) self.connect_node(grandparent, child) if good_parents: # normal case self.disconnect_node(node, child) for parent in list(good_parents): edge = parent.get_edge_to(node) direction = edge.direction() self.disconnect_node(parent, node) self.connect_node(parent, child, direction=direction) if i: child.set_index(i) self.delete_node(node) for parent in list(bad_parents): self.delete_node(parent)
def method(self): """ """ sender = self.sender() if sender: embed = sender.parent() group = self.get_host() or ctrl.ui.selection_group ctrl.ui.close_group_label_editing(group) group.set_label_text(embed.input_line_edit.text()) group.update_shape() name = group.get_label_text() or ctrl.cm.get_color_name(group.color_key) if not group.persistent: ctrl.forest.drawing.turn_selection_group_to_group(group) ctrl.deselect_objects() log.info("Saved group '%s'" % name)
def method(self): """ Generic add node, gets the node type as an argument. :return: None """ sender = self.sender() ntype = sender.data pos = QtCore.QPoint(random.random() * 60 - 25, random.random() * 60 - 25) label = ctrl.free_drawing.next_free_label() node = ctrl.free_drawing.create_node(label=label, pos=pos, node_type=ntype) nclass = classes.nodes[ntype] log.info('Added new %s.' % nclass.display_name[0]) ctrl.forest.forest_edited()
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 method(self): """ """ sender = self.sender() if sender: embed = sender.parent() group = self.get_host() or ctrl.ui.selection_group ctrl.ui.close_group_label_editing(group) group.set_label_text(embed.input_line_edit.text()) group.update_shape() name = group.get_label_text() or ctrl.cm.get_color_name( group.color_key) if not group.persistent: ctrl.forest.free_drawing.turn_selection_group_to_group(group) ctrl.deselect_objects() log.info("Saved group '%s'" % name)
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 _write_png(self, source, write_path): 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) painter.setRenderHint(QtGui.QPainter.SmoothPixmapTransform) ctrl.graph_scene.render(painter, target=target, source=source) painter.end() iwriter = QtGui.QImageWriter(write_path) iwriter.write(writer) msg = f"printed to {write_path} as PNG ({int(target.width())}px x {int(target.height())}px, {scale}x size)." print(msg) log.info(msg)
def launch_from_command_line(): parser = argparse.ArgumentParser(description='Launch Kataja visualisation environment.') parser.add_argument('--reset_prefs', action='store_true', default=False, help='reset the current preferences file to default') parser.add_argument('--no_prefs', action='store_true', default=False, help="don't use preferences file -- don't save it either") parser.add_argument('-image_out', type=str, help="draw tree into given file (name.pdf or name.png) and exit") parser.add_argument('-plugin', type=str, default='', help="start with the given plugin") parser.add_argument('tree', type=str, nargs='?', help='bracket tree or source tree filename') kwargs = vars(parser.parse_args()) silent = True if kwargs['image_out'] else False print(f"Launching Kataja {kataja.__version__} with Python {sys.version_info.major}.{sys.version_info.minor}") app = prepare_app() log.info('Starting Kataja...') if not silent: splash_color = QtGui.QColor(238, 232, 213) splash_pix = QtGui.QPixmap(os.path.join(running_environment.resources_path, 'katajalogo.png')) splash = QtWidgets.QSplashScreen(splash_pix) splash.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint | QtCore.Qt.SplashScreen | QtCore.Qt.FramelessWindowHint | QtCore.Qt.NoDropShadowWindowHint) splash.showMessage(f'{kataja.__author__} | Fetching version...', QtCore.Qt.AlignBottom | QtCore.Qt.AlignHCenter, splash_color) app.processEvents() splash.show() app.processEvents() version_str = load_version() if running_environment.run_mode == 'source': version_str = bump_and_save_version(version_str) splash.showMessage(f'{kataja.__author__} | {version_str}', QtCore.Qt.AlignBottom | QtCore.Qt.AlignHCenter, splash_color) splash.repaint() app.processEvents() # importing KatajaMain here because it is slow, and splash screen is now up from kataja.KatajaMain import KatajaMain window = KatajaMain(app, **kwargs) if not silent: splash.finish(window) app.setActiveWindow(window) app.processEvents() app.exec_()
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 undo(self): """ Move backward in the undo stack :return: None """ if not self._stack: return if self._current == 0: log.info('undo [%s]: Cannot undo further' % self._current) return ctrl.disable_undo() ctrl.multiselection_start() ctrl.forest.halt_drawing = True msg, snapshot = self._stack[self._current] affected = set() for obj, transitions, transition_type in snapshot.values(): obj.revert_to_earlier(transitions) if transition_type == CREATED: ctrl.free_drawing.delete_item(obj, ignore_consequences=True) elif transition_type == DELETED: ctrl.forest.add_to_scene(obj) affected.add(obj) if hasattr(obj, 'update_visibility'): obj.update_visibility() ctrl.forest.edge_visibility_check() for obj, transitions, transition_type in snapshot.values(): if transition_type == CREATED: revtt = DELETED elif transition_type == DELETED: revtt = CREATED else: revtt = transition_type obj.after_model_update(transitions.keys(), revtt) if getattr(obj.__class__, 'syntactic_object', False): node = ctrl.forest.nodes_from_synobs.get(obj.uid, None) if node and node not in affected: node.after_model_update([], revtt) ctrl.forest.flush_and_rebuild_temporary_items() log.info('undo [%s]: %s' % (self._current, msg)) ctrl.multiselection_end() ctrl.resume_undo() self._current -= 1 ctrl.forest.halt_drawing = False print('-------undo finished', self._current)
def method(self, filename=''): """ Open file browser to load a kataja data file :param filename: optional filename, if given, no file dialog is displayed :return: None """ if not filename: filename = self.get_filename_from_dialog() if not filename: return data = self.load_data_from_file(filename) if not data: return # prefs.update(data['preferences'].__dict__) # qt_prefs.update(prefs) main = ctrl.main main.disable_signaling() required_plugin = data.get('kataja_plugin_name', '') if required_plugin != prefs.active_plugin_name: if required_plugin: print('having to switch plugins') ctrl.main.enable_plugin(required_plugin, reload=False) else: print('has to disable plugin') ctrl.main.disable_current_plugin() doc = main.start_new_document(filename) print('created empty document') doc.load_objects(data) doc.has_filename = True print('done load it with saved data') print(f'received {len(doc.forests)} forests, with: ') for i, forest in enumerate(doc.forests): print(f'Forest {i}: {len(forest.nodes)} nodes and {len(forest.edges)} edges.)') main.enable_signaling() main.set_document(doc) print('done setting it as active document') ctrl.main.document_changed.emit() print('document changed, emit') doc.update_forest() print('forest changed') log.info("Loaded '%s'." % filename)
def _write_pdf(self, source, write_path): dpi = 25.4 target = QtCore.QRectF(0, 0, source.width() / 2.0, source.height() / 2.0) writer = QtGui.QPdfWriter(write_path) writer.setResolution(dpi) writer.setPageSizeMM(target.size()) writer.setPageMargins(QtCore.QMarginsF(0, 0, 0, 0)) ctrl.printing = True painter = QtGui.QPainter() painter.begin(writer) # painter.setRenderHint(QtGui.QPainter.Antialiasing) # painter.setRenderHint(QtGui.QPainter.SmoothPixmapTransform) ctrl.graph_scene.render(painter, target=target, source=source) painter.end() ctrl.printing = False msg = f"printed to {write_path} as PDF with {dpi} dpi." print(msg) log.info(msg)
def save(self, filename): save_format = 'pickle' zipped = False for key, value, in file_extensions.items(): if filename.endswith(value): i = key.split('.') zipped = len(i) == 2 save_format = i[0] break all_data = ctrl.main.create_save_data() t = time.time() pickle_format = 4 print(filename) if save_format == 'pickle': if zipped: f = gzip.open(filename, 'wb') else: f = open(filename, 'wb') pickle_worker = pickle.Pickler(f, protocol=pickle_format) pickle_worker.dump(all_data) elif save_format == 'dict': if zipped: f = gzip.open(filename, 'wt') else: f = open(filename, 'w') pp = pprint.PrettyPrinter(indent=1, stream=f) pp.pprint(all_data) elif save_format == 'json': if zipped: f = gzip.open(filename, 'wt') else: f = open(filename, 'w') json.dump(all_data, f, indent="\t", sort_keys=False) else: log.info("Failed to save '%s', no proper format given." % filename) return f.close() log.info("Saved to '%s'. Took %s seconds." % (filename, time.time() - t))
def method(self): """ Switch between multidomination, showing traces and a view where traces are grouped to their original position :return: None """ grouped_traces = ctrl.settings.get('traces_are_grouped_together') multidomination = ctrl.settings.get('uses_multidomination') if grouped_traces and not multidomination: ctrl.settings.set('uses_multidomination', True, level=FOREST) log.info('use multidominance') elif (not grouped_traces) and not multidomination: ctrl.settings.set('traces_are_grouped_together', True, level=FOREST) ctrl.settings.set('uses_multidomination', False, level=FOREST) log.info('use traces, group them to one spot') ctrl.action_redraw = False elif multidomination: ctrl.settings.set('traces_are_grouped_together', False, level=FOREST) ctrl.settings.set('uses_multidomination', False, level=FOREST) log.info( 'use traces, show constituents in their base merge positions') ctrl.forest.forest_edited()
def redo(self): """ Move forward in the undo stack :return: None """ if self._current < len(self._stack) - 1: self._current += 1 else: log.info('redo [%s]: In last action' % self._current) return ctrl.disable_undo() ctrl.multiselection_start() ctrl.forest.halt_drawing = True msg, snapshot = self._stack[self._current] for obj, transitions, transition_type in snapshot.values(): obj.move_to_later(transitions, transition_type) ctrl.forest.edge_visibility_check() ctrl.forest.flush_and_rebuild_temporary_items() log.info('redo [%s]: %s' % (self._current, msg)) ctrl.multiselection_end() ctrl.resume_undo() ctrl.forest.halt_drawing = False print('------redo finished: ', msg, self._current)
def method(self): """ Change edge shape for selection or in currently active edge type. :return: None """ sender = self.sender() shape = sender.currentData() if ctrl.ui.scope_is_selection: for edge in ctrl.selected: if isinstance(edge, Edge): edge.shape_name = shape edge.update_shape() else: ctrl.settings.set_edge_setting('shape_name', shape, edge_type=ctrl.ui.active_edge_type, level=FOREST) for edge in ctrl.forest.edges.values(): edge.update_shape() line_options = ctrl.ui.get_panel('LineOptionsPanel') if line_options: line_options.update_panel() log.info('(s) Changed relation shape to: %s' % shape)
def method(self): """ Change edge shape for selection or in currently active edge type. :return: None """ selector = self.sender() color_key = selector.receive_color_selection() if not color_key: return # Update color for selected edges if ctrl.ui.scope_is_selection: for edge in ctrl.selected: if isinstance(edge, Edge): edge.color_id = color_key edge.update() # ... or update color for all edges of this type else: ctrl.settings.set_edge_setting('color_id', color_key, edge_type=ctrl.ui.active_edge_type, level=FOREST) for edge in ctrl.forest.edges.values(): edge.update() ctrl.call_watchers(self, 'active_edge_color_changed') # shape_selector needs this if color_key: log.info('(s) Changed relation color to: %s' % ctrl.cm.get_color_name(color_key))
def undo(self): """ Move backward in the undo stack :return: None """ if not self._stack: return #if self._current == 0: # log.info('undo [%s]: Cannot undo further' % self._current) # return ctrl.disable_undo() ctrl.multiselection_start() ctrl.forest.halt_drawing = True msg, snapshot = self._stack[self._current] for obj, transitions, transition_type in snapshot.values(): obj.revert_to_earlier(transitions, transition_type) ctrl.forest.edge_visibility_check() ctrl.forest.flush_and_rebuild_temporary_items() log.info('undo [%s]: %s' % (self._current, msg)) ctrl.multiselection_end() ctrl.resume_undo() self._current -= 1 ctrl.forest.halt_drawing = False print('-------undo finished', self._current)
def method(self, filename=''): """ Open file browser to load a kataja data file :param filename: optional filename, if given, no file dialog is displayed :return: None """ m = ctrl.main # fileName = QtGui.QFileDialog.getOpenFileName(self, # self.tr("Open File"), # QtCore.QDir.currentPath()) file_help = """All (*.kataja *.zkataja *.dict *.zdict *.json *.zjson);; Kataja files (*.kataja);; Packed Kataja files (*.zkataja);; Python dict dumps (*.dict);; Packed python dicts (*.zdict);; JSON dumps (*.json);; Packed JSON (*.zjson);; Text files containing bracket trees (*.txt, *.tex)""" # inspection doesn't recognize that getOpenFileName is static, switch it # off: # noinspection PyTypeChecker,PyCallByClass if not filename: filename, filetypes = QtWidgets.QFileDialog.getOpenFileName(ctrl.main, "Open " "KatajaMain " "trees", "", file_help) if not filename: return save_format = 'dict' zipped = False for key, value, in file_extensions.items(): if filename.endswith(value): i = key.split('.') zipped = len(i) == 2 save_format = i[0] break m.clear_all() if zipped: if save_format == 'json' or save_format == 'dict': f = gzip.open(filename, 'rt') elif save_format == 'pickle': f = gzip.open(filename, 'rb') else: log.info("Failed to load '%s'. Unknown format." % filename) return else: if save_format == 'pickle': f = open(filename, 'rb') else: f = open(filename, 'r') # import codecs # f = codecs.open(filename, 'rb', encoding = 'utf-8') if save_format == 'pickle': pickle_worker = pickle.Unpickler(f) data = pickle_worker.load() elif save_format == 'dict': data = ast.literal_eval(f.read()) # data = eval(f.read()) elif save_format == 'json': data = json.load(f) else: f.close() log.info("Failed to load '%s'. Unknown format." % filename) return f.close() # prefs.update(data['preferences'].__dict__) # qt_prefs.update(prefs) ctrl.disable_undo() m.load_objects(data, m) ctrl.resume_undo() ctrl.call_watchers(self.forest_keeper, 'document_changed') m.change_forest() log.info("Loaded '%s'." % filename)
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)
def launch_kataja(): rp = running_environment.resources_path if running_environment.platform == 'mac' and running_environment.code_mode == 'build': dir_path = os.path.dirname(os.path.abspath(sys.argv[0])) print('adding library path ' + dir_path + '/../plugins') # noinspection PyTypeChecker,PyCallByClass QtCore.QCoreApplication.addLibraryPath(dir_path + '/../plugins') author = 'Jukka Purma' parser = argparse.ArgumentParser( description='Launch Kataja visualisation environment.') parser.add_argument('--reset_prefs', action='store_true', default=False, help='reset the current preferences file to default') parser.add_argument( '--no_prefs', action='store_true', default=False, help="don't use preferences file -- don't save it either") kwargs = vars(parser.parse_args()) print("Launching Kataja with Python %s.%s" % (sys.version_info.major, sys.version_info.minor)) app = prepare_app() log.info('Starting Kataja...') splash_color = QtGui.QColor(238, 232, 213) splash_pix = QtGui.QPixmap(rp + 'katajalogo.png') splash = QtWidgets.QSplashScreen(splash_pix) splash.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint | QtCore.Qt.SplashScreen | QtCore.Qt.FramelessWindowHint | QtCore.Qt.NoDropShadowWindowHint) splash.showMessage('%s | Fetching version...' % author, QtCore.Qt.AlignBottom | QtCore.Qt.AlignHCenter, splash_color) app.processEvents() splash.show() app.processEvents() # Update version number in file version = None if running_environment.code_mode == 'python': try: version_file = open(rp + 'version.txt', 'r') version = version_file.readlines() version_file.close() except FileNotFoundError: pass if version: date, running_number, version_name = version[0].split(' | ', 2) running_number = int(running_number[2:]) date = str(datetime.datetime.now()) running_number += 1 else: date = str(datetime.datetime.now()) running_number = 1 version_name = '' if running_environment.code_mode == 'python': try: version_file = open(rp + 'version.txt', 'w') version_file.write('%s | v. %s | %s' % (date, running_number, version_name.strip())) version_file.close() except IOError: print('write failed') pass splash.showMessage( '%s | %s | v. %s | %s' % (author, date, running_number, version_name.strip()), QtCore.Qt.AlignBottom | QtCore.Qt.AlignHCenter, splash_color) splash.repaint() app.processEvents() # importing KatajaMain here because it is slow, and splash screen is now up from kataja.saved.KatajaMain import KatajaMain window = KatajaMain(app, **kwargs) splash.finish(window) app.setActiveWindow(window) app.processEvents() app.exec_()
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 method(self): """ Create new project, replaces the current project at the moment. :return: None """ project = ctrl.main.create_new_project() log.info("Starting a new project '%s'" % project.name)