章节节点引用视图构建完成

This commit is contained in:
codeboss 2024-08-02 17:07:41 +08:00
parent d4bf6b186c
commit 9b59b0a458
7 changed files with 286 additions and 98 deletions

View File

@ -5,14 +5,10 @@
</component> </component>
<component name="ChangeListManager"> <component name="ChangeListManager">
<list default="true" id="f609c0f2-cd0d-4eea-87f1-8caf02d3f04f" name="Changes" comment=""> <list default="true" id="f609c0f2-cd0d-4eea-87f1-8caf02d3f04f" name="Changes" comment="">
<change afterPath="$PROJECT_DIR$/graph/undirected_graph/UDGLayout.py" afterDir="false" />
<change afterPath="$PROJECT_DIR$/graph/undirected_graph/UDGPresent.py" afterDir="false" />
<change afterPath="$PROJECT_DIR$/graph/undirected_graph/__init__.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" /> <change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/entry.py" beforeDir="false" afterPath="$PROJECT_DIR$/entry.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/frame/ContentView.py" beforeDir="false" afterPath="$PROJECT_DIR$/frame/ContentView.py" afterDir="false" /> <change beforePath="$PROJECT_DIR$/frame/ContentView.py" beforeDir="false" afterPath="$PROJECT_DIR$/frame/ContentView.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/graph/DataType.py" beforeDir="false" afterPath="$PROJECT_DIR$/graph/DataType.py" afterDir="false" /> <change beforePath="$PROJECT_DIR$/graph/undirected_graph/UDGPresent.py" beforeDir="false" afterPath="$PROJECT_DIR$/graph/undirected_graph/UDGPresent.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/graph/directed_acyclic_graph/DAGPresent.py" beforeDir="false" afterPath="$PROJECT_DIR$/graph/directed_acyclic_graph/DAGPresent.py" afterDir="false" /> <change beforePath="$PROJECT_DIR$/parse/StoryMap.py" beforeDir="false" afterPath="$PROJECT_DIR$/parse/StoryMap.py" afterDir="false" />
</list> </list>
<option name="SHOW_DIALOG" value="false" /> <option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" /> <option name="HIGHLIGHT_CONFLICTS" value="true" />
@ -40,40 +36,40 @@
<option name="hideEmptyMiddlePackages" value="true" /> <option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" /> <option name="showLibraryContents" value="true" />
</component> </component>
<component name="PropertiesComponent"><![CDATA[{ <component name="PropertiesComponent">{
"keyToString": { &quot;keyToString&quot;: {
"Python.CompareViews.executor": "Run", &quot;Python.CompareViews.executor&quot;: &quot;Run&quot;,
"Python.CompareWindow.executor": "Run", &quot;Python.CompareWindow.executor&quot;: &quot;Run&quot;,
"Python.ContentView.executor": "Run", &quot;Python.ContentView.executor&quot;: &quot;Debug&quot;,
"Python.DAGGraph (1).executor": "Run", &quot;Python.DAGGraph (1).executor&quot;: &quot;Run&quot;,
"Python.DAGGraph.executor": "Run", &quot;Python.DAGGraph.executor&quot;: &quot;Run&quot;,
"Python.DAGLayout (1).executor": "Run", &quot;Python.DAGLayout (1).executor&quot;: &quot;Run&quot;,
"Python.DAGLayout.executor": "Run", &quot;Python.DAGLayout.executor&quot;: &quot;Run&quot;,
"Python.DAGPresent.executor": "Run", &quot;Python.DAGPresent.executor&quot;: &quot;Run&quot;,
"Python.MergeView.executor": "Run", &quot;Python.MergeView.executor&quot;: &quot;Run&quot;,
"Python.MileStone.executor": "Run", &quot;Python.MileStone.executor&quot;: &quot;Run&quot;,
"Python.NovelManage.executor": "Debug", &quot;Python.NovelManage.executor&quot;: &quot;Debug&quot;,
"Python.ReferView.executor": "Run", &quot;Python.ReferView.executor&quot;: &quot;Run&quot;,
"Python.StoryMap.executor": "Run", &quot;Python.StoryMap.executor&quot;: &quot;Run&quot;,
"Python.UDGLayout.executor": "Run", &quot;Python.UDGLayout.executor&quot;: &quot;Run&quot;,
"Python.UDGPresent.executor": "Run", &quot;Python.UDGPresent.executor&quot;: &quot;Run&quot;,
"Python.ast_load.executor": "Debug", &quot;Python.ast_load.executor&quot;: &quot;Debug&quot;,
"Python.entry.executor": "Run", &quot;Python.entry.executor&quot;: &quot;Run&quot;,
"Python.test.executor": "Run", &quot;Python.test.executor&quot;: &quot;Run&quot;,
"RunOnceActivity.OpenProjectViewOnStart": "true", &quot;RunOnceActivity.OpenProjectViewOnStart&quot;: &quot;true&quot;,
"RunOnceActivity.ShowReadmeOnStart": "true", &quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;,
"git-widget-placeholder": "master", &quot;git-widget-placeholder&quot;: &quot;master&quot;,
"last_opened_file_path": "D:/Projects/Python/StoryTools", &quot;last_opened_file_path&quot;: &quot;D:/Projects/Python/StoryTools&quot;,
"settings.editor.selected.configurable": "com.jetbrains.python.configuration.PyActiveSdkModuleConfigurable" &quot;settings.editor.selected.configurable&quot;: &quot;debugger.dataViews&quot;
} }
}]]></component> }</component>
<component name="PyDebuggerOptionsProvider"> <component name="PyDebuggerOptionsProvider">
<option name="mySaveCallSignatures" value="true" /> <option name="mySaveCallSignatures" value="true" />
<option name="mySupportGeventDebugging" value="true" /> <option name="mySupportGeventDebugging" value="true" />
<option name="myDropIntoDebuggerOnFailedTests" value="true" /> <option name="myDropIntoDebuggerOnFailedTests" value="true" />
<option name="myPyQtBackend" value="pyqt5" /> <option name="myPyQtBackend" value="pyqt5" />
</component> </component>
<component name="RunManager" selected="Python.UDGPresent"> <component name="RunManager" selected="Python.ContentView">
<configuration name="ContentView" type="PythonConfigurationType" factoryName="Python" temporary="true" nameIsGenerated="true"> <configuration name="ContentView" type="PythonConfigurationType" factoryName="Python" temporary="true" nameIsGenerated="true">
<module name="StoryTools" /> <module name="StoryTools" />
<option name="ENV_FILES" value="" /> <option name="ENV_FILES" value="" />
@ -193,8 +189,8 @@
</list> </list>
<recent_temporary> <recent_temporary>
<list> <list>
<item itemvalue="Python.UDGPresent" />
<item itemvalue="Python.ContentView" /> <item itemvalue="Python.ContentView" />
<item itemvalue="Python.UDGPresent" />
<item itemvalue="Python.test" /> <item itemvalue="Python.test" />
<item itemvalue="Python.UDGLayout" /> <item itemvalue="Python.UDGLayout" />
<item itemvalue="Python.DAGPresent" /> <item itemvalue="Python.DAGPresent" />
@ -222,4 +218,15 @@
<component name="UnknownFeatures"> <component name="UnknownFeatures">
<option featureType="com.intellij.fileTypeFactory" implementationName="*.bat" /> <option featureType="com.intellij.fileTypeFactory" implementationName="*.bat" />
</component> </component>
<component name="XDebuggerManager">
<breakpoint-manager>
<breakpoints>
<line-breakpoint enabled="true" suspend="THREAD" type="python-line">
<url>file://$PROJECT_DIR$/graph/undirected_graph/UDGPresent.py</url>
<line>239</line>
<option name="timeStamp" value="2" />
</line-breakpoint>
</breakpoints>
</breakpoint-manager>
</component>
</project> </project>

View File

@ -1,13 +1,16 @@
import sys import sys, os
from typing import Dict, List from typing import Dict, List
from PyQt5.QtCore import QPoint from PyQt5.QtCore import QPoint, Qt
from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QVBoxLayout from PyQt5.QtGui import QTransform
from PyQt5.QtWidgets import QMenu from PyQt5.QtWidgets import QApplication, QWidget, QGridLayout, QSlider, QDoubleSpinBox
from PyQt5.QtWidgets import QMenu, QHBoxLayout
from graph.DataType import Arrow, Point 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.directed_acyclic_graph.DAGPresent import DAGActiveView
from parse.StoryMap import StoryMap, XAST_ParseTool from graph.undirected_graph.UDGPresent import UDGPresent
from parse.StoryMap import StoryMap, XAST_ParseTool, ArticleSlice
from parse.ast_load import global_ast_path from parse.ast_load import global_ast_path
@ -24,7 +27,7 @@ class StorylinesView(QWidget):
self.fragment_view = DAGActiveView(self) self.fragment_view = DAGActiveView(self)
layout.setContentsMargins(0,0,0,0) layout.setContentsMargins(0,0,0,0)
layout.addWidget(self.fragment_view) layout.addWidget(self.fragment_view)
self.fragment_view.nodes_clicked.connect(self.highlisth_node_path) self.fragment_view.nodes_clicked.connect(self.highlight_node_path)
self.present_graph: Dict[str, StoryMap] = {} self.present_graph: Dict[str, StoryMap] = {}
pass pass
pass pass
@ -58,7 +61,7 @@ class StorylinesView(QWidget):
self.fragment_view.update_with_edges(arrows) self.fragment_view.update_with_edges(arrows)
pass pass
def highlisth_node_path(self, xpos, ypos, list): def highlight_node_path(self, xpos, ypos, list):
if len(list) == 0: if len(list) == 0:
return return
@ -92,12 +95,12 @@ class StorylinesView(QWidget):
pass pass
if len(story_list) == 1: if len(story_list) == 1:
self.highlisth_node_path(xpos, ypos, [("node", story_list[0])]) self.highlight_node_path(xpos, ypos, [("node", story_list[0])])
elif len(story_list) > 1: elif len(story_list) > 1:
menu = QMenu() menu = QMenu()
for story in story_list: for story in story_list:
def trigger(story_name:str): def trigger(story_name:str):
return lambda : self.highlisth_node_path(xpos, ypos, [("node", story_name)]) return lambda : self.highlight_node_path(xpos, ypos, [("node", story_name)])
menu.addAction(f"story/{story}", trigger(story)) menu.addAction(f"story/{story}", trigger(story))
pass pass
@ -116,16 +119,70 @@ class StorylinesView(QWidget):
class ArticleRefsView(QWidget): class ArticleRefsView(QWidget):
def __init__(self, parent): def __init__(self, parent):
QWidget.__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 pass
if __name__ == "__main__": if __name__ == "__main__":
app = QApplication(sys.argv) app = QApplication(sys.argv)
view = StorylinesView(None) #view = StorylinesView(None)
view = ArticleRefsView(None)
view.show() view.show()
tool = XAST_ParseTool(global_ast_path) tool = XAST_ParseTool(global_ast_path)
view.present_stories_graph(tool.get_story_graph()) # view.present_stories_graph(tool.get_story_graph())
view.present_volumes_graph(tool.get_article_nodes())
# view.fragment_view.highlight_graph_link(["血脉的源头", "血脉的源头&待续"]) # view.fragment_view.highlight_graph_link(["血脉的源头", "血脉的源头&待续"])

View File

@ -1,4 +1,4 @@
import sys import sys, os
from abc import abstractmethod from abc import abstractmethod
from enum import Enum from enum import Enum
from typing import List, Dict, Tuple from typing import List, Dict, Tuple
@ -7,7 +7,9 @@ import networkx as nx
from PyQt5.QtCore import QPointF, QRectF, Qt, pyqtSignal from PyQt5.QtCore import QPointF, QRectF, Qt, pyqtSignal
from PyQt5.QtGui import QFont, QPainterPath, QPen, QPainter, QMouseEvent from PyQt5.QtGui import QFont, QPainterPath, QPen, QPainter, QMouseEvent
from PyQt5.QtWidgets import QGraphicsItem, QGraphicsView, QApplication, QGraphicsScene from PyQt5.QtWidgets import QGraphicsItem, QGraphicsView, QApplication, QGraphicsScene
from PyQt5.QtWidgets import QSplitter, QHBoxLayout
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(__file__))))
from graph.DataType import Point, Line from graph.DataType import Point, Line
@ -37,14 +39,19 @@ class GraphNode:
def pos(self) -> QPointF: def pos(self) -> QPointF:
raise NotImplementedError("pos") raise NotImplementedError("pos")
@abstractmethod
def relayout_exec(self, ppu: float = 1):
raise NotImplementedError("relayout_exec")
class PresentNode(QGraphicsItem, GraphNode): class PresentNode(QGraphicsItem, GraphNode):
def __init__(self, name: str, font: QFont, parent): def __init__(self, name: str, font: QFont, prim_x:float, prim_y:float, parent):
QGraphicsItem.__init__(self, parent) QGraphicsItem.__init__(self, parent)
self.node_name = name self.node_name = name
self.__is_highlight_mark = False self.__is_highlight_mark = False
self.__font_bind = font self.__font_bind = font
self.__sibling_list = [] self.__sibling_list = []
self.__prim_pos: Tuple[float, float] = (prim_x, prim_y)
self.setZValue(10) self.setZValue(10)
pass pass
@ -85,7 +92,7 @@ class PresentNode(QGraphicsItem, GraphNode):
painter.save() painter.save()
painter.setRenderHint(QPainter.Antialiasing) painter.setRenderHint(QPainter.Antialiasing)
painter.drawRect(outline) #painter.drawRect(outline)
if self.__is_highlight_mark: if self.__is_highlight_mark:
brush = Qt.red brush = Qt.red
@ -102,6 +109,13 @@ class PresentNode(QGraphicsItem, GraphNode):
painter.restore() painter.restore()
pass pass
def relayout_exec(self, ppu: float = 1):
pos_x = ppu * self.__prim_pos[0]
pos_y = ppu * self.__prim_pos[1]
self.setPos(pos_x, pos_y)
self.update()
pass
class ConnectionNode(QGraphicsItem, GraphNode): class ConnectionNode(QGraphicsItem, GraphNode):
def __init__(self, p0: GraphNode, p1: GraphNode, parent): def __init__(self, p0: GraphNode, p1: GraphNode, parent):
@ -124,7 +138,7 @@ class ConnectionNode(QGraphicsItem, GraphNode):
return self.__highlight_mark return self.__highlight_mark
pass pass
def relayout_exec(self): def relayout_exec(self, ppi: float = 1):
start_pos = self.__point0.pos() start_pos = self.__point0.pos()
end_pos = self.__point1.pos() end_pos = self.__point1.pos()
@ -135,6 +149,7 @@ class ConnectionNode(QGraphicsItem, GraphNode):
self.setPos(QPointF(start_x, start_y)) self.setPos(QPointF(start_x, start_y))
self.__outline = QRectF(0, 0, end_x - start_x, end_y - start_y) self.__outline = QRectF(0, 0, end_x - start_x, end_y - start_y)
self.update()
pass pass
def boundingRect(self): def boundingRect(self):
@ -176,6 +191,8 @@ class UDGPresent(QGraphicsView):
def __init__(self, parent): def __init__(self, parent):
QGraphicsView.__init__(self, parent) QGraphicsView.__init__(self, parent)
self.pixel_per_unit = 5000
self.__highlight_nodes: List[GraphNode] = [] self.__highlight_nodes: List[GraphNode] = []
self.node_set: Dict[str, GraphNode] = {} self.node_set: Dict[str, GraphNode] = {}
self.__layout_graph = nx.Graph() self.__layout_graph = nx.Graph()
@ -190,69 +207,38 @@ class UDGPresent(QGraphicsView):
def rebuild_from_edges(self, line_set: List[Line]): def rebuild_from_edges(self, line_set: List[Line]):
self.node_set.clear() self.node_set.clear()
edge_set: Dict[str, Tuple[GraphNode, GraphNode]] = {}
for line in line_set: for line in line_set:
start_node = line.points()[0] start_node = line.points()[0]
if start_node.point_name not in self.node_set:
self.node_set[start_node.point_name] = PresentNode(start_node.point_name, self.font(), None)
pass
self.__layout_graph.add_node(start_node.point_name) self.__layout_graph.add_node(start_node.point_name)
end_node = line.points()[1] end_node = line.points()[1]
if start_node.point_name == end_node.point_name:
continue
if end_node.point_name not in self.node_set:
self.node_set[end_node.point_name] = PresentNode(end_node.point_name, self.font(), None)
pass
self.__layout_graph.add_node(end_node.point_name) self.__layout_graph.add_node(end_node.point_name)
self.__layout_graph.add_edge(start_node.point_name, end_node.point_name) self.__layout_graph.add_edge(start_node.point_name, end_node.point_name)
start_force_point: PresentNode = self.node_set[start_node.point_name]
other_force_point: PresentNode = self.node_set[end_node.point_name]
if other_force_point not in start_force_point.sibling_nodes():
start_force_point.sibling_append(other_force_point)
if start_force_point not in other_force_point.sibling_nodes():
other_force_point.sibling_append(start_force_point)
pass pass
pos_map = nx.spring_layout(self.__layout_graph) pos_map = nx.spring_layout(self.__layout_graph)
scala_value:float = 0 for node_name in pos_map:
for name in pos_map: node_prim_pos = pos_map[node_name]
primitive_pos = pos_map[name] targetx_node = PresentNode(node_name, self.font(), node_prim_pos[0], node_prim_pos[1], None)
target_gnode: PresentNode = self.node_set[name] self.node_set[node_name] = targetx_node
sibling_nodes = target_gnode.sibling_nodes() self.scene().addItem(targetx_node)
for sib in sibling_nodes: targetx_node.relayout_exec(self.pixel_per_unit)
sib_primitive_pos = pos_map[sib.node_name]
prim_x_span = primitive_pos[0] - sib_primitive_pos[0]
prim_y_span = primitive_pos[1] - sib_primitive_pos[1]
target_rect = target_gnode.boundingRect()
scala_value = max(scala_value, target_rect.width()/prim_x_span)
scala_value = max(scala_value, target_rect.height()/prim_y_span)
pass
pass
for name in pos_map:
primitive_pos = pos_map[name]
target_gnode: PresentNode = self.node_set[name]
target_gnode.setPos(primitive_pos[0] * scala_value, primitive_pos[1] * scala_value)
self.__scene_bind.addItem(target_gnode)
pass pass
for edge in nx.edges(self.__layout_graph): for edge in nx.edges(self.__layout_graph):
edge_start = edge[0] edge_start = edge[0]
edge_end = edge[1] edge_end = edge[1]
node_one = self.node_set[edge_start] node_one: PresentNode = self.node_set[edge_start]
node_two = self.node_set[edge_end] node_two: PresentNode = self.node_set[edge_end]
node_one.sibling_append(node_two)
node_two.sibling_append(node_one)
connection = ConnectionNode(node_one, node_two, None) connection = ConnectionNode(node_one, node_two, None)
self.scene().addItem(connection) self.scene().addItem(connection)
connection.relayout_exec() connection.relayout_exec(self.pixel_per_unit)
self.node_set[f"conn::{edge_start}&{edge_end}"] = connection
pass pass
pass pass
@ -260,7 +246,7 @@ class UDGPresent(QGraphicsView):
QGraphicsView.mousePressEvent(self, event) QGraphicsView.mousePressEvent(self, event)
if event.button() == Qt.MouseButton.LeftButton: if event.button() == Qt.MouseButton.LeftButton:
item: GraphNode = self.itemAt(event.pos()) item: GraphNode = self.itemAt(event.pos())
if item.node_type() == PresentNodeType.PresentNode: if item is not None and item.node_type() == PresentNodeType.PresentNode:
vnode: PresentNode = item vnode: PresentNode = item
self.node_clicked.emit(vnode.node_name) self.node_clicked.emit(vnode.node_name)
print(vnode.node_name) print(vnode.node_name)
@ -268,6 +254,74 @@ class UDGPresent(QGraphicsView):
pass pass
pass pass
def refresh_with_ppu(self, ppu: float):
self.pixel_per_unit = ppu
for node in self.node_set.values():
if node.node_type() == PresentNodeType.PresentNode:
node.relayout_exec(ppu)
pass
pass
for node in self.node_set.values():
if node.node_type() == PresentNodeType.ConnectionNode:
node.relayout_exec(ppu)
pass
pass
self.scene().update()
self.update()
pass
def update_scene_rect(self):
minx = 2*64
miny = 2*64
maxx = -2**64
maxy = -2**64
for item in self.node_set.values():
minx = min(item.pos().x(), minx)
miny = min(item.pos().y(), miny)
maxx = max(item.pos().x() + item.boundingRect().width(), maxx)
maxy = max(item.pos().y() + item.boundingRect().height(), maxy)
pass
self.scene().setSceneRect(minx, miny, maxx - minx, maxy - miny)
pass
def highlight_sibling_nodes(self, center_node: str):
for node in self.__highlight_nodes:
node.highlight(False)
pass
self.__highlight_nodes.clear()
target_node: PresentNode = self.node_set[center_node]
if target_node is None:
return
target_node.highlight(True)
self.__highlight_nodes.append(target_node)
for sib_node in target_node.sibling_nodes():
sib_node.highlight(True)
self.__highlight_nodes.append(sib_node)
sib_name = sib_node.node_name
constr1 = f"conn::{center_node}&{sib_name}"
constr2 = f"conn::{sib_name}&{center_node}"
if constr1 in self.node_set:
self.node_set[constr1].highlight(True)
self.__highlight_nodes.append(self.node_set[constr1])
elif constr2 in self.node_set:
self.node_set[constr2].highlight(True)
self.__highlight_nodes.append(self.node_set[constr2])
pass
pass
self.scene().update()
self.update()
pass
if __name__ == "__main__": if __name__ == "__main__":
app = QApplication(sys.argv) app = QApplication(sys.argv)

View File

@ -27,6 +27,24 @@ class FragmentSlice(EmptyNode):
pass pass
class ArticleSlice(EmptyNode):
def __init__(self, a_name: str, v_name: str):
self.article_name = a_name
self.volume_blongs = v_name
self.article_fullname = f"{v_name}@{a_name}"
self.refer_target: List[str] = [] # 引用切面
self.text_sections: List[str] = [] # 文本段落
pass
def get_from_memory(self) -> str:
return "\n".join(self.text_sections)
def set_to_memory(self, content: str):
self.text_sections = content.split("\n")
pass
class StoryMap: class StoryMap:
def __init__(self, name: str): def __init__(self, name: str):
self.story_name = name self.story_name = name
@ -44,7 +62,7 @@ class StoryMap:
self.slice_list.append(node) self.slice_list.append(node)
pass pass
def get_fragment_defined(self, name: str) -> FragmentSlice: def get_fragment_defined(self, name: str) -> FragmentSlice|None:
for fit in self.slice_list: for fit in self.slice_list:
if fit.is_define_node[0]: if fit.is_define_node[0]:
if fit.is_define_node[1] == name: if fit.is_define_node[1] == name:
@ -140,6 +158,58 @@ class XAST_ParseTool:
self.storylines_plait(story_dict) self.storylines_plait(story_dict)
return story_dict return story_dict
def get_article_nodes(self) -> List[ArticleSlice]:
retvalues = []
hangout_nodes = ArticleSlice("悬空节点", "")
retvalues.append(hangout_nodes)
fragments = self.__get_all_fragment_names()
volumes = self.dom_tree.getElementsByTagName("volume")
for vnode in volumes:
child_articles = self.__volume_node_parse(vnode)
retvalues.extend(child_articles)
for child in child_articles:
for refn in child.refer_target:
if refn in fragments:
fragments.remove(refn)
pass
pass
pass
pass
hangout_nodes.refer_target.extend(fragments)
return retvalues
def __get_all_fragment_names(self) -> List[str]:
values = []
frags = self.dom_tree.getElementsByTagName("fragment")
for frag in frags:
story: mdom.Element = frag.parentNode
values.append(f"{story.getAttribute("name")}&{frag.getAttribute("name")}")
pass
return values
def __volume_node_parse(self, vnode: mdom.Element)-> List[ArticleSlice]:
retvalues = []
vname = vnode.getAttribute("name")
articles = vnode.getElementsByTagName("article")
for anode in articles:
aname = anode.getAttribute("name")
node_inst = ArticleSlice(aname, vname)
retvalues.append(node_inst)
refsnode = anode.getElementsByTagName("refer")
for refnode in refsnode:
ref_story = refnode.getAttribute("story")
ref_fragment = refnode.getAttribute("fragment")
node_inst.refer_target.append(f"{ref_story}&{ref_fragment}")
pass
pass
return retvalues
pass pass