""" CompareViews: 对比Storylines基线和最新Storylines编译结果差异对比 """ import sys import os from typing import List, Dict, Tuple from PyQt5.QtCore import QModelIndex from PyQt5.QtGui import QStandardItemModel, QStandardItem from PyQt5.QtWidgets import QWidget, QVBoxLayout, QTableView, QSplitter, QApplication, QMainWindow sys.path.append(os.path.dirname(os.path.dirname(__file__))) from frame.MergeView import LinesMergeView, MergeDestination from manage.MileStone import base_store_path, current_store_path from parse.StoryMap import FragmentSlice, StoryMap, XAST_ParseTool, storyline_list2map from parse.StorylineCmp import ModifyReason, CmpTool class ResultList(QTableView): def __init__(self, parent): QTableView.__init__(self, parent) self.model = QStandardItemModel(self) self.model.setHorizontalHeaderLabels(["故事名称", "情节名称", "状态"]) self.setModel(self.model) self.setSelectionMode(QTableView.SelectionMode.SingleSelection) self.setSelectionBehavior(QTableView.SelectionBehavior.SelectRows) pass def present_affected_fragments(self, graph_new: Dict[str, StoryMap], cmp_result: Dict[ModifyReason, List[FragmentSlice]]): """ 展示影响节点 :return: """ old_remove = cmp_result[ModifyReason.Removed] affected_list = [] for node in old_remove: affected_list = affected_list + self.__find_remove_affected_slice_within_new(graph_new, node) pass fragm_items = self.__filter_and_related_defines_seek(affected_list) self.__append_fragments_list(fragm_items, "REMOVE") curr_append = cmp_result[ModifyReason.Append] affected_list = self.__find_append_and_changed_affected_slice(curr_append) fragm_items = self.__filter_and_related_defines_seek(affected_list) self.__append_fragments_list(fragm_items, "APPEND") curr_changed = cmp_result[ModifyReason.Changed] affected_list = self.__find_append_and_changed_affected_slice(curr_changed) fragm_items = self.__filter_and_related_defines_seek(affected_list) self.__append_fragments_list(fragm_items, "UPDATE") pass def __find_remove_affected_slice_within_new(self, graph_new: Dict[str, StoryMap], peers_old: FragmentSlice) -> List[FragmentSlice]: """ 获取受到移除节点直接影响的节点 :param graph_new: 新图 :param peers_old: 旧图节点 :return: """ retvs = [] story_name = peers_old.parent_story # 移除了情节定义 if peers_old.is_define_node[0]: refer_list = peers_old.refers_slice for refer_slice_prev in refer_list.values(): retvs = retvs + self.__find_remove_affect_within_new(graph_new, refer_slice_prev) pass story_curr = graph_new[story_name] # 新图内故事线存在,非最后一个节点,计算影响 if story_curr is not None and peers_old.next_node is not None: o_frag_def = peers_old.next_node frag_curr_def = story_curr.get_fragment_defined(o_frag_def.is_define_node[1]) if frag_curr_def is not None: retvs.append(frag_curr_def) pass pass else: refer_story_new = graph_new[story_name] slice_affected = peers_old.next_node # 非最后一个,需要计算影响,新图中的故事线存在,计算影响 if slice_affected is not None and refer_story_new is not None: v_s_name = slice_affected.story_refer v_f_name = slice_affected.fragm_refer # 获取收到影响的情节片段 slice_refer_now = CmpTool.__locate_fragment_refer__(refer_story_new.slice_list[0], v_s_name, v_f_name) if slice_refer_now is not None: retvs.append(slice_refer_now) pass pass return retvs def __find_append_and_changed_affected_slice(self, peers_new: List[FragmentSlice]) -> List[FragmentSlice]: retvs = [] for curr_node in peers_new: next_slice = curr_node.next_node if next_slice is not None: retvs.append(next_slice) return retvs def __filter_and_related_defines_seek(self, affect_directed: List[FragmentSlice]) -> List[Tuple[str, str]]: story_fragment_names: Dict[str, Tuple[str, str]] = {} for node in affect_directed: # 情节定义节点 if node.is_define_node[0]: fragm_key = f"{node.parent_story}#{node.is_define_node[1]}" story_fragment_names[fragm_key] = (node.parent_story, node.is_define_node[1]) # 情节引用节点 else: fragm_key = f"{node.story_refer}#{node.fragm_refer}" story_fragment_names[fragm_key] = (node.story_refer, node.fragm_refer) pass pass return list(story_fragment_names.values()) def __append_fragments_list(self, fragment_keys: List[Tuple[str, str]], comp_type: str) -> None: for key_def in fragment_keys: row = [ QStandardItem(key_def[0]), QStandardItem(key_def[1]), QStandardItem(comp_type) ] for cell in row: cell.setEditable(False) self.model.appendRow(row) pass pass pass class CompareWindow(QWidget): def __init__(self, graph_new: Dict[str, StoryMap], cmp_result: Dict[ModifyReason, List[FragmentSlice]], parent): QWidget.__init__(self, parent) self.graph_new_refer = graph_new layout = QVBoxLayout(self) self.splitter = QSplitter(self) layout.addWidget(self.splitter) self.result_list = ResultList(self) self.splitter.addWidget(self.result_list) self.splitter.addWidget(QWidget(self)) self.result_list.present_affected_fragments(graph_new, cmp_result) self.result_list.clicked.connect(self.present_fragment_merge_content) pass def present_fragment_merge_content(self, index: QModelIndex): if not index.isValid(): return story_name = self.result_list.model.item(index.row(), 0).text() fragm_name = self.result_list.model.item(index.row(), 1).text() story_node = self.graph_new_refer[story_name] fragm_node = story_node.get_fragment_defined(fragm_name) main_point = MergeDestination(story_name, fragm_node.prev_node, fragm_node) elist = [] for refstory in fragm_node.refers_slice.keys(): refslice = fragm_node.refers_slice[refstory] elist.append(MergeDestination(refstory, refslice.prev_node, refslice)) new_view = LinesMergeView(main_point, elist, self) new_view.load_from_mem() self.splitter.replaceWidget(1, new_view) pass pass if __name__ == "__main__": app = QApplication(sys.argv) win = QMainWindow() ast_old = XAST_ParseTool(base_store_path) ast_new = XAST_ParseTool(current_store_path) map_old = storyline_list2map(ast_old.story_list) map_new = storyline_list2map(ast_new.story_list) ast_old.storylines_plait(map_old) ast_new.storylines_plait(map_new) cmp_tool = CmpTool() all_changed = cmp_tool.graph_compare(map_new, map_old) view = CompareWindow(map_new, all_changed, win) win.setCentralWidget(view) win.show() app.exec()