コード例 #1
0
ファイル: Permutations.py プロジェクト: scrum-1/Pyslvs-PyQt5
class NumberAndTypeSynthesis(QWidget, Ui_Form):
    
    """Number and type synthesis widget."""
    
    def __init__(self, parent):
        super(NumberAndTypeSynthesis, self).__init__(parent)
        self.setupUi(self)
        self.outputTo = parent.outputTo
        self.saveReplyBox = parent.saveReplyBox
        self.inputFrom = parent.inputFrom
        self.splitter.setStretchFactor(0, 2)
        self.splitter.setStretchFactor(1, 15)
        self.answer = []
        self.save_edges_auto_label.setStatusTip(self.save_edges_auto.statusTip())
        self.NL_input.valueChanged.connect(self.adjust_NJ_NL_dof)
        self.NJ_input.valueChanged.connect(self.adjust_NJ_NL_dof)
        self.graph_engine.addItems(EngineList)
        self.graph_engine.setCurrentIndex(2)
        self.graph_link_as_node.clicked.connect(self.on_reload_atlas_clicked)
        self.graph_engine.currentIndexChanged.connect(
            self.on_reload_atlas_clicked
        )
        self.Topologic_result.customContextMenuRequested.connect(
            self.Topologic_result_context_menu
        )
        """Context menu
        
        + Add to collections
        + Copy edges
        + Copy image
        """
        self.popMenu_topo = QMenu(self)
        self.add_collection = QAction(
            QIcon(QPixmap(":/icons/collections.png")),
            "Add to collections",
            self
        )
        self.copy_edges = QAction("Copy edges", self)
        self.copy_image = QAction("Copy image", self)
        self.popMenu_topo.addActions([
            self.add_collection,
            self.copy_edges,
            self.copy_image
        ])
        self.jointDataFunc = parent.Entities_Point.data
        self.linkDataFunc = parent.Entities_Link.data
        self.clear()
    
    def clear(self):
        """Clear all sub-widgets."""
        self.answer.clear()
        self.Expression_edges.clear()
        self.Expression_number.clear()
        self.Topologic_result.clear()
        self.time_label.setText("")
        self.NL_input.setValue(0)
        self.NJ_input.setValue(0)
        self.NL_input_old_value = 0
        self.NJ_input_old_value = 0
        self.DOF.setValue(1)
    
    @pyqtSlot()
    def on_ReloadMechanism_clicked(self):
        """Reload button: Auto-combine the mechanism from the workbook."""
        jointData = self.jointDataFunc()
        linkData = self.linkDataFunc()
        if jointData and linkData:
            self.Expression_edges.setText(str(v_to_graph(jointData, linkData)))
        else:
            self.Expression_edges.setText("")
        keep_dof_checked = self.keep_dof.isChecked()
        self.keep_dof.setChecked(False)
        self.NL_input.setValue(
            sum(len(vlink.points)>1 for vlink in linkData)+
            sum(
                len(vpoint.links)-1 for vpoint in jointData
                if (vpoint.type == 2) and (len(vpoint.links) > 1)
            )
        )
        self.NJ_input.setValue(sum(
            (len(vpoint.links)-1 + int(vpoint.type == 2))
            for vpoint in jointData if len(vpoint.links)>1
        ))
        self.keep_dof.setChecked(keep_dof_checked)
    
    def adjust_NJ_NL_dof(self):
        """Update NJ and NL values.
        
        If user don't want to keep the DOF:
        Change the DOF then exit.
        """
        if not self.keep_dof.isChecked():
            self.DOF.setValue(
                3 * (self.NL_input.value() - 1) -
                2 * self.NJ_input.value()
            )
            return
        """Prepare the input value.
        
        + N2: Get the user's adjusted value.
        + NL_func: Get the another value of parameters (N1) by
            degrees of freedom formula.
        + is_above: Is value increase or decrease?
        """
        if self.sender() == self.NJ_input:
            N2 = self.NJ_input.value()
            NL_func = lambda: float(((self.DOF.value() + 2*N2) / 3) + 1)
            is_above = N2 > self.NJ_input_old_value
        else:
            N2 = self.NL_input.value()
            NL_func = lambda: float((3*(N2-1) - self.DOF.value()) / 2)
            is_above = N2 > self.NL_input_old_value
        N1 = NL_func()
        while not N1.is_integer():
            N2 += 1 if is_above else -1
            N1 = NL_func()
            if (N1 == 0) or (N2 == 0):
                break
        """Return the result values.
        
        + Value of widgets.
        + Setting old value record.
        """
        if self.sender() == self.NL_input:
            self.NJ_input.setValue(N1)
            self.NL_input.setValue(N2)
            self.NJ_input_old_value = N1
            self.NL_input_old_value = N2
        else:
            self.NJ_input.setValue(N2)
            self.NL_input.setValue(N1)
            self.NJ_input_old_value = N2
            self.NL_input_old_value = N1
    
    @pyqtSlot()
    def on_Combine_number_clicked(self):
        """Show number of links with different number of joints."""
        self.Expression_number.clear()
        NS_result = NumberSynthesis(self.NL_input.value(), self.NJ_input.value())
        if type(NS_result) == str:
            item = QListWidgetItem(NS_result)
            item.links = None
            self.Expression_number.addItem(item)
        else:
            for result in NS_result:
                item = QListWidgetItem(", ".join(
                    "NL{} = {}".format(i+2, result[i])
                    for i in range(len(result))
                ))
                item.links = result
                self.Expression_number.addItem(item)
        self.Expression_number.setCurrentRow(0)
    
    @pyqtSlot()
    def on_Combine_type_clicked(self):
        """Type synthesis.
        
        If there has no data of number synthesis,
        execute number synthesis first.
        """
        row = self.Expression_number.currentRow()
        if not row>-1:
            self.on_Combine_number_clicked()
            row = self.Expression_number.currentRow()
        if self.Expression_number.currentItem() is None:
            return
        answer = self.combineType(row)
        if answer:
            self.answer = answer
            self.on_reload_atlas_clicked()
    
    @pyqtSlot()
    def on_Combine_type_all_clicked(self):
        """Type synthesis - find all.
        
        If the data of number synthesis has multiple results,
        execute type synthesis one by one.
        """
        if not self.Expression_number.currentRow()>-1:
            self.on_Combine_number_clicked()
        if self.Expression_number.currentItem().links is None:
            return
        answers = []
        break_point = False
        for row in range(self.Expression_number.count()):
            answer = self.combineType(row)
            if answer:
                answers += answer
            else:
                break_point = True
                break
        if not answers:
            return
        if break_point:
            reply = QMessageBox.question(self,
                "Type synthesis - abort",
                "Do you want to keep the results?"
            )
            if reply != QMessageBox.Yes:
                return
        self.answer = answers
        self.on_reload_atlas_clicked()
    
    def combineType(self, row: int):
        """Combine and show progress dialog."""
        item = self.Expression_number.item(row)
        progdlg = QProgressDialog(
            "Analysis of the topology...",
            "Cancel",
            0,
            100,
            self
        )
        progdlg.setWindowTitle("Type synthesis - ({})".format(item.text()))
        progdlg.setMinimumSize(QSize(500, 120))
        progdlg.setModal(True)
        progdlg.show()
        #Call in every loop.
        def stopFunc():
            QCoreApplication.processEvents()
            progdlg.setValue(progdlg.value() + 1)
            return progdlg.wasCanceled()
        def setjobFunc(job, maximum):
            progdlg.setLabelText(job)
            progdlg.setValue(0)
            progdlg.setMaximum(maximum+1)
        answer, time = topo(
            item.links,
            not self.graph_degenerate.isChecked(),
            setjobFunc,
            stopFunc
        )
        self.time_label.setText("{}[min] {:.2f}[s]".format(
            int(time // 60),
            time % 60
        ))
        progdlg.setValue(progdlg.maximum())
        if answer:
            return [Graph(G.edges) for G in answer]
    
    @pyqtSlot()
    @pyqtSlot(str)
    def on_reload_atlas_clicked(self, p0=None):
        """Reload the atlas. Regardless there has any old data."""
        self.engine = self.graph_engine.currentText().split(" - ")[1]
        self.Topologic_result.clear()
        if self.answer:
            progdlg = QProgressDialog(
                "Drawing atlas...",
                "Cancel",
                0,
                len(self.answer),
                self
            )
            progdlg.setWindowTitle("Type synthesis")
            progdlg.resize(400, progdlg.height())
            progdlg.setModal(True)
            progdlg.show()
            for i, G in enumerate(self.answer):
                QCoreApplication.processEvents()
                if progdlg.wasCanceled():
                    return
                if self.drawAtlas(i, G):
                    progdlg.setValue(i+1)
                else:
                    break
            progdlg.setValue(progdlg.maximum())
    
    def drawAtlas(self, i: int, G: Graph) -> bool:
        """Draw atlas and return True if done."""
        item = QListWidgetItem("No. {}".format(i + 1))
        try:
            item.setIcon(graph(
                G,
                self.Topologic_result.iconSize().width(),
                self.engine,
                self.graph_link_as_node.isChecked()
            ))
        except EngineError as e:
            QMessageBox.warning(self,
                str(e),
                "Please install and make sure Graphviz is working."
            )
            return False
        else:
            item.setToolTip(str(G.edges))
            self.Topologic_result.addItem(item)
            return True
    
    def atlas_image(self, row: int =None) -> QImage:
        """Capture a result item icon to image."""
        w = self.Topologic_result
        if row is None:
            item = w.currentItem()
        else:
            item = w.item(row)
        return item.icon().pixmap(w.iconSize()).toImage()
    
    @pyqtSlot(QPoint)
    def Topologic_result_context_menu(self, point):
        """Context menu for the type synthesis results."""
        index = self.Topologic_result.currentIndex().row()
        self.add_collection.setEnabled(index>-1)
        self.copy_edges.setEnabled(index>-1)
        self.copy_image.setEnabled(index>-1)
        action = self.popMenu_topo.exec_(self.Topologic_result.mapToGlobal(point))
        if not action:
            return
        clipboard = QApplication.clipboard()
        if action==self.add_collection:
            self.addCollection(self.answer[index].edges)
        elif action==self.copy_edges:
            clipboard.setText(str(self.answer[index].edges))
        elif action==self.copy_image:
            #Turn the transparent background to white.
            image1 = self.atlas_image()
            image2 = QImage(image1.size(), image1.format())
            image2.fill(QColor(Qt.white).rgb())
            painter = QPainter(image2)
            painter.drawImage(QPointF(0, 0), image1)
            painter.end()
            pixmap = QPixmap()
            pixmap.convertFromImage(image2)
            clipboard.setPixmap(pixmap)
    
    @pyqtSlot()
    def on_Expression_copy_clicked(self):
        """Copy expression button."""
        string = self.Expression_edges.text()
        if string:
            QApplication.clipboard().setText(string)
            self.Expression_edges.selectAll()
    
    @pyqtSlot()
    def on_Expression_add_collection_clicked(self):
        """Add this expression to collections widget."""
        string = self.Expression_edges.text()
        if string:
            self.addCollection(eval(string))
    
    @pyqtSlot()
    def on_save_atlas_clicked(self):
        """Saving all the atlas to image file.
        
        We should turn transparent background to white first.
        Then using QImage class to merge into one image.
        """
        fileName = ""
        lateral = 0
        if self.save_edges_auto.isChecked():
            lateral, ok = QInputDialog.getInt(self,
                "Atlas",
                "The number of lateral:",
                5, 1, 10
            )
            if not ok:
                return
            fileName = self.outputTo("Atlas image", Qt_images)
            if fileName:
                reply = QMessageBox.question(self,
                    "Type synthesis",
                    "Do you want to Re-synthesis?",
                    (QMessageBox.Yes | QMessageBox.YesToAll | QMessageBox.Cancel),
                    QMessageBox.YesToAll
                )
                if reply == QMessageBox.Yes:
                    self.on_Combine_type_clicked()
                elif reply == QMessageBox.YesToAll:
                    self.on_Combine_type_all_clicked()
        count = self.Topologic_result.count()
        if not count:
            return
        if not lateral:
            lateral, ok = QInputDialog.getInt(self,
                "Atlas",
                "The number of lateral:",
                5, 1, 10
            )
        if not ok:
            return
        if not fileName:
            fileName = self.outputTo("Atlas image", Qt_images)
        if not fileName:
            return
        width = self.Topologic_result.iconSize().width()
        image_main = QImage(
            QSize(
                lateral * width if count>lateral else count * width,
                ((count // lateral) + bool(count % lateral)) * width
            ),
            self.atlas_image(0).format()
        )
        image_main.fill(QColor(Qt.white).rgb())
        painter = QPainter(image_main)
        for row in range(count):
            image = self.atlas_image(row)
            painter.drawImage(QPointF(
                row % lateral * width,
                row // lateral * width
            ), image)
        painter.end()
        pixmap = QPixmap()
        pixmap.convertFromImage(image_main)
        pixmap.save(fileName, format=QFileInfo(fileName).suffix())
        self.saveReplyBox("Atlas", fileName)
    
    @pyqtSlot()
    def on_save_edges_clicked(self):
        """Saving all the atlas to text file."""
        fileName = ""
        if self.save_edges_auto.isChecked():
            fileName = self.outputTo(
                "Atlas edges expression",
                ["Text file (*.txt)"]
            )
            if not fileName:
                return
            reply = QMessageBox.question(self,
                "Type synthesis",
                "Do you want to Re-synthesis?",
                (QMessageBox.Yes | QMessageBox.YesToAll | QMessageBox.Cancel),
                QMessageBox.YesToAll
            )
            if reply == QMessageBox.Yes:
                self.on_Combine_type_clicked()
            elif reply == QMessageBox.YesToAll:
                self.on_Combine_type_all_clicked()
        count = self.Topologic_result.count()
        if not count:
            return
        if not fileName:
            fileName = self.outputTo(
                "Atlas edges expression",
                ["Text file (*.txt)"]
            )
        if not fileName:
            return
        with open(fileName, 'w') as f:
            f.write('\n'.join(str(G.edges) for G in self.answer))
        self.saveReplyBox("edges expression", fileName)
    
    @pyqtSlot()
    def on_Edges_to_altas_clicked(self):
        """Turn the text files into a atlas image.
        
        This opreation will load all edges to list widget first.
        """
        fileNames = self.inputFrom(
            "Edges data",
            ["Text File (*.txt)"],
            multiple=True
        )
        if not fileNames:
            return
        read_data = []
        for fileName in fileNames:
            with open(fileName, 'r') as f:
                read_data += f.read().split('\n')
        answer = []
        for edges in read_data:
            try:
                answer.append(Graph(eval(edges)))
            except:
                QMessageBox.warning(self,
                    "Wrong format",
                    "Please check the edges text format."
                )
                return
        if not answer:
            return
        self.answer = answer
        self.on_reload_atlas_clicked()
        check_status = self.save_edges_auto.isChecked()
        self.save_edges_auto.setChecked(False)
        self.on_save_atlas_clicked()
        self.save_edges_auto.setChecked(check_status)
コード例 #2
0
class StructureSynthesis(QWidget, Ui_Form):
    """Number and type synthesis widget.

    Calculate the combinations of mechanism family and show the atlas.
    """
    def __init__(self, parent: 'mw.MainWindow'):
        """Reference names:

        + IO functions from main window.
        + Table data from PMKS expression.
        + Graph data function from main window.
        """
        super(StructureSynthesis, self).__init__(parent)
        self.setupUi(self)
        self.save_edges_auto_label.setStatusTip(
            self.save_edges_auto.statusTip())

        # Function references
        self.outputTo = parent.outputTo
        self.saveReplyBox = parent.saveReplyBox
        self.inputFrom = parent.inputFrom
        self.jointDataFunc = parent.EntitiesPoint.dataTuple
        self.linkDataFunc = parent.EntitiesLink.dataTuple
        self.getGraph = parent.getGraph

        # Splitters
        self.splitter.setStretchFactor(0, 2)
        self.splitter.setStretchFactor(1, 15)

        # Answer list.
        self.answer: List[Graph] = []

        # Signals
        self.NL_input.valueChanged.connect(self.__adjust_structure_data)
        self.NJ_input.valueChanged.connect(self.__adjust_structure_data)
        self.graph_engine.addItems(engines)
        self.structure_list.customContextMenuRequested.connect(
            self.__topologic_result_context_menu)
        """Context menu

        + Add to collections
        + Copy edges
        + Copy image
        """
        self.pop_menu_topo = QMenu(self)
        self.add_collection = QAction(
            QIcon(QPixmap(":/icons/collections.png")), "Add to collections",
            self)
        self.copy_edges = QAction("Copy edges", self)
        self.copy_image = QAction("Copy image", self)
        self.pop_menu_topo.addActions(
            [self.add_collection, self.copy_edges, self.copy_image])

        self.NL_input_old_value = 0
        self.NJ_input_old_value = 0
        self.clear()

    def clear(self):
        """Clear all sub-widgets."""
        self.answer.clear()
        self.edges_text.clear()
        self.l_a_list.clear()
        self.__clear_structure_list()
        self.NL_input.setValue(0)
        self.NJ_input.setValue(0)
        self.NL_input_old_value = 0
        self.NJ_input_old_value = 0
        self.DOF.setValue(1)

    @pyqtSlot(name='on_structure_list_clear_button_clicked')
    def __clear_structure_list(self):
        """Clear the structure list."""
        self.structure_list.clear()
        self.time_label.setText("")

    @pyqtSlot(name='on_from_mechanism_button_clicked')
    def __from_mechanism(self):
        """Reload button: Auto-combine the mechanism from the workbook."""
        joint_data = self.jointDataFunc()
        link_data = self.linkDataFunc()
        if joint_data and link_data:
            graph = Graph(self.getGraph())
            self.edges_text.setText(str(graph.edges))
        else:
            graph = Graph([])
            self.edges_text.setText("")
        keep_dof_checked = self.keep_dof.isChecked()
        self.keep_dof.setChecked(False)
        self.NL_input.setValue(
            sum(len(vlink.points) > 1 for vlink in link_data) + sum(
                len(vpoint.links) - 2 for vpoint in joint_data
                if (vpoint.type == VPoint.RP) and (len(vpoint.links) > 1)))
        self.NJ_input.setValue(
            sum((len(vpoint.links) - 1 + int(vpoint.type == VPoint.RP))
                for vpoint in joint_data if (len(vpoint.links) > 1)))
        self.keep_dof.setChecked(keep_dof_checked)

        # Auto synthesis.
        if not graph.edges:
            return
        self.l_a_list.setCurrentRow(
            compare_assortment(tuple(l_a(graph)), self.__l_a_synthesis()))
        self.c_l_a_list.setCurrentRow(
            compare_assortment(tuple(c_l_a(graph)), self.__c_l_a_synthesis()))

    def __adjust_structure_data(self):
        """Update NJ and NL values.

        If user don't want to keep the DOF:
        Change the DOF then exit.
        """
        if not self.keep_dof.isChecked():
            self.DOF.setValue(3 * (self.NL_input.value() - 1) -
                              2 * self.NJ_input.value())
            return
        """Prepare the input value.

        + N2: Get the user's adjusted value.
        + NL_func: Get the another value of parameters (N1) by
            degrees of freedom formula.
        + is_above: Is value increase or decrease?
        """
        if self.sender() == self.NJ_input:
            n2 = self.NJ_input.value()

            def nl_func() -> float:
                return ((self.DOF.value() + 2 * n2) / 3) + 1

            is_above = n2 > self.NJ_input_old_value
        else:
            n2 = self.NL_input.value()

            def nl_func() -> float:
                return (3 * (n2 - 1) - self.DOF.value()) / 2

            is_above = n2 > self.NL_input_old_value
        n1 = nl_func()
        while not n1.is_integer():
            n2 += 1 if is_above else -1
            n1 = nl_func()
            if (n1 == 0) or (n2 == 0):
                break
        """Return the result values.

        + Value of widgets.
        + Setting old value record.
        """
        if self.sender() == self.NL_input:
            self.NJ_input.setValue(n1)
            self.NL_input.setValue(n2)
            self.NJ_input_old_value = n1
            self.NL_input_old_value = n2
        else:
            self.NJ_input.setValue(n2)
            self.NL_input.setValue(n1)
            self.NJ_input_old_value = n2
            self.NL_input_old_value = n1

    @pyqtSlot(name='on_number_synthesis_button_clicked')
    def __l_a_synthesis(self) -> List[Tuple[int, ...]]:
        """Synthesis of link assortments."""
        self.l_a_list.clear()
        self.c_l_a_list.clear()
        try:
            results = number_synthesis(self.NL_input.value(),
                                       self.NJ_input.value())
        except Exception as e:
            item = QListWidgetItem(str(e))
            self.l_a_list.addItem(item)
            return []
        else:
            for result in results:
                self.l_a_list.addItem(
                    QListWidgetItem(", ".join(f"NL{i + 2} = {result[i]}"
                                              for i in range(len(result)))))
            self.l_a_list.setCurrentRow(0)
            return results

    @pyqtSlot(int, name='on_l_a_list_currentRowChanged')
    def __c_l_a_synthesis(self, index: int = 0) -> List[Tuple[int, ...]]:
        """Synthesis of contracted link assortments."""
        self.c_l_a_list.clear()
        item = self.l_a_list.item(index)
        if item is None:
            return []
        results = contracted_link(_link_assortments(item.text()))
        for c_j in results:
            self.c_l_a_list.addItem(
                QListWidgetItem(", ".join(f"Nc{i + 1} = {c_j[i]}"
                                          for i in range(len(c_j)))))
        self.c_l_a_list.setCurrentRow(0)
        return results

    def __set_time_count(self, t: float, count: int):
        """Set time and count digit to label."""
        self.time_label.setText(f"{t:.04f} s ({count})")

    @pyqtSlot(name='on_structure_synthesis_button_clicked')
    def __structure_synthesis(self):
        """Structural synthesis - find by contracted links."""
        self.__clear_structure_list()
        row = self.l_a_list.currentRow()
        if row == -1:
            self.__l_a_synthesis()
            self.__c_l_a_synthesis()
        item_l_a: QListWidgetItem = self.l_a_list.currentItem()
        item_c_l_a: QListWidgetItem = self.c_l_a_list.currentItem()
        try:
            job_l_a = _link_assortments(item_l_a.text())
            job_c_l_a = _link_assortments(item_c_l_a.text())
        except ValueError:
            return

        self.__structural_combine([(job_l_a, job_c_l_a)], 1)

    @pyqtSlot(name='on_structure_synthesis_links_button_clicked')
    def __structure_synthesis_links(self):
        """Structural synthesis - find by links."""
        self.__clear_structure_list()
        row = self.l_a_list.currentRow()
        if row == -1:
            self.__l_a_synthesis()
            self.__c_l_a_synthesis()
        item_l_a: QListWidgetItem = self.l_a_list.currentItem()
        try:
            job_l_a = _link_assortments(item_l_a.text())
        except ValueError:
            return

        jobs = contracted_link(job_l_a)

        def jobs_iterator(
            _l_a: Sequence[int], _jobs: Sequence[Sequence[int]]
        ) -> Iterator[Tuple[Sequence[int], Sequence[int]]]:
            for _c_l_a in _jobs:
                yield _l_a, _c_l_a

        self.__structural_combine(jobs_iterator(job_l_a, jobs), len(jobs))

    @pyqtSlot(name='on_structure_synthesis_all_button_clicked')
    def __structure_synthesis_all(self):
        """Structural synthesis - find all."""
        self.__clear_structure_list()
        if self.l_a_list.currentRow() == -1:
            self.__l_a_synthesis()
        item: QListWidgetItem = self.c_l_a_list.currentItem()
        try:
            _link_assortments(item.text())
        except ValueError:
            return

        job_count = 0
        jobs = []
        for row in range(self.l_a_list.count()):
            item: QListWidgetItem = self.l_a_list.item(row)
            job_l_a = _link_assortments(item.text())
            job_c_l_as = contracted_link(job_l_a)
            job_count += len(job_c_l_as)
            jobs.append((job_l_a, job_c_l_as))

        def jobs_iterator(
            _jobs: Sequence[Tuple[Sequence[int], Sequence[Sequence[int]]]]
        ) -> Iterator[Tuple[Sequence[int], Sequence[int]]]:
            for _l_a, _c_l_as in _jobs:
                for _c_l_a in _c_l_as:
                    yield _l_a, _c_l_a

        self.__structural_combine(jobs_iterator(jobs), job_count)

    def __structural_combine(self, jobs: Iterable[Tuple[Sequence[int],
                                                        Sequence[int]]],
                             job_count: int):
        """Structural combine by iterator."""
        dlg = SynthesisProgressDialog("Structural Synthesis", "", job_count,
                                      self)
        dlg.show()

        answers = []
        break_point = False
        t0 = 0.
        c0 = 0
        for job_l_a, job_c_l_a in jobs:
            answer, t1 = topo(job_l_a, job_c_l_a,
                              self.graph_degenerate.currentIndex(),
                              dlg.stop_func)
            dlg.next()
            if answer is not None:
                answers.extend(answer)
                t0 += t1
                c0 += len(answer)
            else:
                break_point = True
                break

        if not answers:
            return

        if break_point:
            reply = QMessageBox.question(self, "Type synthesis - abort",
                                         "Do you want to keep the results?")
            if reply != QMessageBox.Yes:
                return

        # Save the answer list.
        self.answer = answers
        self.__set_time_count(t0, c0)
        self.__reload_atlas()

    @pyqtSlot(name='on_graph_link_as_node_clicked')
    @pyqtSlot(name='on_reload_atlas_clicked')
    @pyqtSlot(int, name='on_graph_engine_currentIndexChanged')
    def __reload_atlas(self, *_: int):
        """Reload the atlas."""
        scroll_bar: QScrollBar = self.structure_list.verticalScrollBar()
        scroll_pos = scroll_bar.sliderPosition()
        self.structure_list.clear()

        if not self.answer:
            return

        dlg = SynthesisProgressDialog("Type synthesis", "Drawing atlas...",
                                      len(self.answer), self)
        dlg.show()
        for i, G in enumerate(self.answer):
            QCoreApplication.processEvents()
            if dlg.wasCanceled():
                return
            if self.__draw_atlas(i, G):
                dlg.setValue(i + 1)
            else:
                break
        dlg.setValue(dlg.maximum())
        scroll_bar.setSliderPosition(scroll_pos)

    def __draw_atlas(self, i: int, g: Graph) -> bool:
        """Draw atlas and return True if done."""
        item = QListWidgetItem(f"No. {i + 1}")
        item.setIcon(
            to_graph(g,
                     self.structure_list.iconSize().width(),
                     self.graph_engine.currentText(),
                     self.graph_link_as_node.isChecked()))
        item.setToolTip(f"Edge Set: {list(g.edges)}\n"
                        f"Link Assortments: {l_a(g)}\n"
                        f"Contracted Link Assortments: {c_l_a(g)}")
        self.structure_list.addItem(item)
        return True

    def __atlas_image(self, row: int = None) -> QImage:
        """Capture a result item icon to image."""
        w = self.structure_list
        if row is None:
            item = w.currentItem()
        else:
            item = w.item(row)
        return item.icon().pixmap(w.iconSize()).toImage()

    @pyqtSlot(QPoint)
    def __topologic_result_context_menu(self, point):
        """Context menu for the type synthesis results."""
        index = self.structure_list.currentIndex().row()
        self.add_collection.setEnabled(index > -1)
        self.copy_edges.setEnabled(index > -1)
        self.copy_image.setEnabled(index > -1)
        action = self.pop_menu_topo.exec_(
            self.structure_list.mapToGlobal(point))
        if not action:
            return
        clipboard = QApplication.clipboard()
        if action == self.add_collection:
            self.addCollection(self.answer[index].edges)
        elif action == self.copy_edges:
            clipboard.setText(str(self.answer[index].edges))
        elif action == self.copy_image:
            # Turn the transparent background to white.
            image1 = self.__atlas_image()
            image2 = QImage(image1.size(), image1.format())
            image2.fill(QColor(Qt.white).rgb())
            painter = QPainter(image2)
            painter.drawImage(QPointF(0, 0), image1)
            painter.end()
            pixmap = QPixmap()
            pixmap.convertFromImage(image2)
            clipboard.setPixmap(pixmap)

    @pyqtSlot(name='on_expr_copy_clicked')
    def __copy_expr(self):
        """Copy expression button."""
        string = self.edges_text.text()
        if string:
            QApplication.clipboard().setText(string)
            self.edges_text.selectAll()

    @pyqtSlot(name='on_expr_add_collection_clicked')
    def __add_collection(self):
        """Add this expression to collections widget."""
        string = self.edges_text.text()
        if string:
            self.addCollection(eval(string))

    @pyqtSlot(name='on_save_atlas_clicked')
    def __save_atlas(self):
        """Saving all the atlas to image file.

        We should turn transparent background to white first.
        Then using QImage class to merge into one image.
        """
        file_name = ""
        lateral = 0
        if self.save_edges_auto.isChecked():
            lateral, ok = QInputDialog.getInt(self, "Atlas",
                                              "The number of lateral:", 5, 1,
                                              10)
            if not ok:
                return
            file_name = self.outputTo("Atlas image", qt_image_format)
            if file_name:
                reply = QMessageBox.question(
                    self, "Type synthesis", "Do you want to Re-synthesis?",
                    (QMessageBox.Yes | QMessageBox.YesToAll
                     | QMessageBox.Cancel), QMessageBox.Yes)
                if reply == QMessageBox.Yes:
                    self.__structure_synthesis()
                elif reply == QMessageBox.YesToAll:
                    self.__structure_synthesis_all()
        count = self.structure_list.count()
        if not count:
            return
        if not lateral:
            lateral, ok = QInputDialog.getInt(self, "Atlas",
                                              "The number of lateral:", 5, 1,
                                              10)
            if not ok:
                return
        if not file_name:
            file_name = self.outputTo("Atlas image", qt_image_format)
        if not file_name:
            return
        width = self.structure_list.iconSize().width()
        image_main = QImage(
            QSize(lateral * width if count > lateral else count * width,
                  ((count // lateral) + bool(count % lateral)) * width),
            self.__atlas_image(0).format())
        image_main.fill(QColor(Qt.white).rgb())
        painter = QPainter(image_main)
        for row in range(count):
            image = self.__atlas_image(row)
            painter.drawImage(
                QPointF(row % lateral * width, row // lateral * width), image)
        painter.end()
        pixmap = QPixmap()
        pixmap.convertFromImage(image_main)
        pixmap.save(file_name, format=QFileInfo(file_name).suffix())
        self.saveReplyBox("Atlas", file_name)

    @pyqtSlot(name='on_save_edges_clicked')
    def __save_edges(self):
        """Saving all the atlas to text file."""
        file_name = ""
        if self.save_edges_auto.isChecked():
            file_name = self.outputTo("Atlas edges expression",
                                      ["Text file (*.txt)"])
            if not file_name:
                return
            reply = QMessageBox.question(
                self, "Type synthesis", "Do you want to Re-synthesis?",
                (QMessageBox.Yes | QMessageBox.YesToAll | QMessageBox.Cancel),
                QMessageBox.Yes)
            if reply == QMessageBox.Yes:
                self.__structure_synthesis()
            elif reply == QMessageBox.YesToAll:
                self.__structure_synthesis_all()
        count = self.structure_list.count()
        if not count:
            return
        if not file_name:
            file_name = self.outputTo("Atlas edges expression",
                                      ["Text file (*.txt)"])
        if not file_name:
            return
        with open(file_name, 'w') as f:
            f.write('\n'.join(str(G.edges) for G in self.answer))
        self.saveReplyBox("edges expression", file_name)

    @pyqtSlot(name='on_edges2atlas_button_clicked')
    def __edges2atlas(self):
        """Turn the text files into a atlas image.

        This operation will load all edges to list widget first.
        """
        file_names = self.inputFrom("Edges data", ["Text file (*.txt)"],
                                    multiple=True)
        if not file_names:
            return
        read_data = []

        for file_name in file_names:
            with open(file_name) as f:
                for line in f:
                    read_data.append(line)

        answer = []
        for edges in read_data:
            try:
                g = Graph(eval(edges))
            except (SyntaxError, TypeError):
                QMessageBox.warning(self, "Wrong format",
                                    "Please check text format.")
            else:
                answer.append(g)

        if not answer:
            QMessageBox.information(self, "No data",
                                    "The graph data is empty.")
            return

        self.answer = answer
        self.__reload_atlas()
        self.save_edges_auto.setChecked(False)
        self.__save_atlas()
        self.save_edges_auto.setChecked(self.save_edges_auto.isChecked())