import sys, os from typing import Dict, List from PyQt5.QtCore import QPoint, Qt from PyQt5.QtGui import QTransform from PyQt5.QtWidgets import QApplication, QWidget, QGridLayout, QSlider, QDoubleSpinBox from PyQt5.QtWidgets import QMenu, QHBoxLayout, QVBoxLayout sys.path.append(os.path.dirname(os.path.dirname(__file__))) from graph.DataType import Arrow, Point, Line from graph.directed_acyclic_graph.DAGPresent import DAGActiveView from graph.undirected_graph.UDGPresent import UDGPresent from parse.StoryMap import StoryMap, XAST_ParseTool, ArticleSlice from parse.ast_load import global_ast_path class FragmentPoint(Point): def __init__(self, name: str): Point.__init__(self, name) pass class StorylinesView(QWidget): def __init__(self, parent): QWidget.__init__(self, parent) layout = QVBoxLayout(self) self.fragment_view = DAGActiveView(self) layout.setContentsMargins(0,0,0,0) layout.addWidget(self.fragment_view) self.fragment_view.nodes_clicked.connect(self.highlight_node_path) self.present_graph: Dict[str, StoryMap] = {} pass pass def present_stories_graph(self, graph: Dict[str, StoryMap]) -> None: self.present_graph = graph arrows: List[Arrow] = [] for story_name in graph: fragments = graph[story_name].slice_list for fragi in range(1, len(fragments)): start_frag = fragments[fragi - 1] start_node_label = f"{start_frag.parent_story}&{start_frag.is_define_node[1]}" if not start_frag.is_define_node[0]: start_node_label = f"{start_frag.story_refer}&{start_frag.fragm_refer}" if fragi == 1: start_node_label = story_name end_frag = fragments[fragi] end_node_label = f"{end_frag.parent_story}&{end_frag.is_define_node[1]}" if not end_frag.is_define_node[0]: end_node_label = f"{end_frag.story_refer}&{end_frag.fragm_refer}" arrows.append(Arrow( FragmentPoint(start_node_label), FragmentPoint(end_node_label)) ) pass pass self.fragment_view.update_with_edges(arrows) pass def highlight_node_path(self, xpos, ypos, list): if len(list) == 0: return if len(list) > 1: pass else: node_x = list[0] if node_x[0].startswith("node"): node_sections = [s for s in node_x[1].split("&") if s != ""] if len(node_sections) == 1: story = self.present_graph[node_sections[0]] node_names = [node_sections[0]] for slice in story.slice_list[1:]: if slice.is_define_node[0]: node_names.append(f"{slice.parent_story}&{slice.is_define_node[1]}") else: node_names.append(f"{slice.story_refer}&{slice.fragm_refer}") pass pass self.fragment_view.highlight_graph_link(node_names) pass elif len(node_sections) > 1: story_node = self.present_graph[node_sections[0]] fragm_node = story_node.get_fragment_defined(node_sections[1]) story_list = [node_sections[0]] for vsname in fragm_node.refers_slice: if vsname not in story_list: story_list.append(vsname) pass pass if len(story_list) == 1: self.highlight_node_path(xpos, ypos, [("node", story_list[0])]) elif len(story_list) > 1: menu = QMenu() for story in story_list: def trigger(story_name:str): return lambda : self.highlight_node_path(xpos, ypos, [("node", story_name)]) menu.addAction(f"story/{story}", trigger(story)) pass menu.exec(self.fragment_view.mapToGlobal(QPoint(xpos, ypos))) pass else: print(story_list) pass pass pass pass pass class ArticleRefsView(QWidget): def __init__(self, parent): QWidget.__init__(self, parent) self.refer_view = UDGPresent(self) layout = QGridLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) layout.addWidget(self.refer_view) self.ppu_slider = QSlider(Qt.Orientation.Horizontal, self) self.scala_slider = QSlider(Qt.Orientation.Vertical, self) self.scala_slider.setRange(0, 1000) self.scala_slider.setValue(100) layout.addWidget(self.scala_slider, 0, 1) slide_panel = QWidget(self) layout.addWidget(slide_panel, 1, 0, 1, 2) bottom_layout = QHBoxLayout(slide_panel) bottom_layout.setSpacing(0) bottom_layout.setContentsMargins(0, 0, 0, 0) bottom_layout.addWidget(self.ppu_slider) self.ppu_max = QDoubleSpinBox(self) self.ppu_max.setRange(0, 2**31) self.ppu_max.setValue(10000) self.ppu_slider.setRange(0, 10000) self.ppu_slider.setValue(int(self.refer_view.pixel_per_unit)) bottom_layout.addWidget(self.ppu_max) self.refer_view.node_clicked.connect(self.highlight_sibling_nodes) def scala_view(times:float): tm = QTransform() tm.scale(times, times) return tm self.scala_slider.valueChanged.connect(lambda iproc: self.refer_view.setTransform(scala_view(iproc/100.0))) self.ppu_slider.valueChanged.connect(lambda ppu:self.refer_view.refresh_with_ppu(ppu)) self.ppu_slider.sliderReleased.connect(lambda : self.refer_view.update_scene_rect()) pass def present_volumes_graph(self, ref_graph: List[ArticleSlice]): node_edges = [] for line in ref_graph: for target in line.refer_target: node_edges.append(Line(Point(line.article_fullname), Point(target))) pass pass self.refer_view.rebuild_from_edges(node_edges) pass def highlight_sibling_nodes(self, nodename: str): self.refer_view.highlight_sibling_nodes(nodename) pass if __name__ == "__main__": app = QApplication(sys.argv) view1 = StorylinesView(None) view2 = ArticleRefsView(None) view1.show() view2.show() tool = XAST_ParseTool(global_ast_path) view1.present_stories_graph(tool.get_story_graph()) view2.present_volumes_graph(tool.get_article_nodes()) # view.fragment_view.highlight_graph_link(["血脉的源头", "血脉的源头&待续"]) app.exec()