Ejemplo n.º 1
0
class LeftFrame(QFrame):
    def __init__(self, father, top):
        super().__init__(father)
        self.setObjectName('api_frame')
        self.father, self.top = father, top
        self.setGeometry(0, 0, 150, 300)

        self.all_api = self.top.all_api[1:]
        self.now_api = self.all_api[0]

        self.inside_frame = QFrame(self)
        self.inside_frame.resize(self.width(), len(self.all_api)*55+5)
        self.inside_frame.move(0, 0)
        self.inside_frame.setObjectName('inside')

        for idx in range(len(self.all_api)):
            label = ApiLabel(self.all_api[idx], self, self.top)
            label.move(7, idx*55+5)

    def wheelEvent(self, e):
        if self.inside_frame.height() > self.height():
            if e.angleDelta().y() > 0:
                self.inside_frame.move(0, self.inside_frame.y() + 60)
                if self.inside_frame.y() > 0:
                    self.inside_frame.move(0, 0)
            else:
                self.inside_frame.move(0, self.inside_frame.y() - 60)
                if self.inside_frame.y() < self.height()-self.inside_frame.height():
                    self.inside_frame.move(0, self.height()-self.inside_frame.height())
            self.father.list_scroll.bar.setValue(abs(self.inside_frame.y()))
Ejemplo n.º 2
0
class Demo(QWidget):
    def __init__(self):
        super(Demo, self).__init__()
        self.resize(500, 400)
        self.label = QLineEdit(self)
        self.label.setGeometry(0, 10, 450, 38)
        self.frame = QFrame(self)
        self.frame.setGeometry(120, 120, 100, 100)
        self.frame.setStyleSheet('background-color : #B9F9C5')
        print(self.frame.width())
        print(self.frame.height())
        print(self.frame.pos())
        self.setAcceptDrops(True)

    def dragEnterEvent(self, a0: QtGui.QDragEnterEvent) -> None:
        self.setWindowTitle('mouse in')
        print(a0.mimeData().text())
        a0.accept()

    def dragMoveEvent(self, a0: QtGui.QDragMoveEvent) -> None:
        print(a0.pos())
        # pass

    def dropEvent(self, a0: QtGui.QDropEvent) -> None:
        self.setWindowTitle('mouse drop')
        if self.frame.pos().x() <= a0.pos().x() <= self.frame.pos().x() + self.frame.width() \
                and self.frame.pos().y() <= a0.pos().y() <= self.frame.pos().y() + self.frame.height():
            self.label.setText(a0.mimeData().text().replace('file:///', ''))
        print(a0.mimeData().text())
Ejemplo n.º 3
0
class RightFrame(QFrame):
    def __init__(self, father, top):
        super().__init__(father)
        self.setObjectName('tag_frame')
        self.father, self.top = father, top
        self.resize(322, 300)

        self.inside_frame = QFrame(self)
        self.inside_frame.setObjectName('right_inside')
        self.inside_frame.move(0, 0)

        self.now_api = None
        self.init()

    def init(self):
        self.now_api = eval('self.top.%s' % self.father.list.now_api)
        for c in self.inside_frame.children():
            delete(c)
        self.inside_frame.resize(
            self.width(),
            ceil(len(self.now_api.cate[1:]) / ((self.width()) // 95)) * 45)
        self.inside_frame.move(0, 0)
        for idx in range(len(self.now_api.cate[1:])):
            label = TagLabel(self.now_api.cate[1:][idx], self, self.top)
            label.move(95 * (idx % (self.width() // 95)),
                       (idx // (self.width() // 95)) * 45)

    def wheelEvent(self, e):
        if self.inside_frame.height() > self.height():
            if e.angleDelta().y() > 0:
                self.inside_frame.move(0, self.inside_frame.y() + 60)
                if self.inside_frame.y() > 0:
                    self.inside_frame.move(0, 0)
            else:
                self.inside_frame.move(0, self.inside_frame.y() - 60)
                if self.inside_frame.y(
                ) < self.height() - self.inside_frame.height():
                    self.inside_frame.move(
                        0,
                        self.height() - self.inside_frame.height())
            self.father.tags_scroll.bar.setValue(abs(self.inside_frame.y()))

    def resizeEvent(self, e):
        self.inside_frame.move(0, 0)
        self.inside_frame.resize(
            self.width(),
            ceil(len(self.now_api.cate[1:]) / ((self.width()) // 95)) * 45)
        self.father.tags_scroll.mid_frame.setGeometry(
            0, 0, 0, self.inside_frame.height())
        a_line = self.width() // 95
        for c in range(len(self.inside_frame.children())):
            self.inside_frame.children()[c].move(
                95 * (c % a_line) + (self.width() - a_line * 95) *
                (c % a_line) / a_line, 45 * (c // a_line) + 5)
Ejemplo n.º 4
0
 def __init__(self, items: t_.Sequence[str], parent=None):
     super().__init__(parent=parent)
     # self.setFrameStyle(QFrame.Panel)
     self._bGroup = QButtonGroup(self)
     l = QHBoxLayout(self)
     for i, itemName in enumerate(items):
         b = QPushButton(itemName, parent=self)
         b.setCheckable(True)  # Toggleable
         self._bGroup.addButton(b, id=i)
         l.addWidget(b)
     self._selectedButtonId = self._bGroup.id(self._bGroup.buttons()[0])
     self._bGroup.buttons()[0].click(
     )  # Make sure at least one button is selected.
     self._bGroup.buttonClicked.connect(self._buttonSelected)
     l.setSpacing(1)  # Move buttons close together
     l.setContentsMargins(0, 0, 0, 0)
     w = QFrame(self)
     w.setFrameStyle(QFrame.Box)
     w.setLayout(l)
     scrollArea = QScrollArea(parent=self)
     scrollArea.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
     scrollArea.setStyleSheet("""QScrollBar:horizontal {
          height:10px;     
      }""")
     scrollArea.setWidget(w)
     scrollArea.setFixedHeight(10 + w.height())
     ll = QHBoxLayout()
     ll.setContentsMargins(0, 0, 0, 0)
     ll.addWidget(scrollArea)
     self.setLayout(ll)
Ejemplo n.º 5
0
class QShadowFrame(QFrame):

	def __init__(self, master=None, only_pressed=True):
		super().__init__(master)
		self.only_pressed = only_pressed
		self._be_pressing = False
		self.build_inter()

	def build_inter(self):
		self.init_images()

		self.main_frame = QFrame()
		self.main_frame.setAlignment(Qt.AlignCenter)
		self.main_frame.setSizePolicy(self.sizePolicy())
		self.main_frame.setContentsMargins(0, 0, 0, 0)

		self.left_lb = QLabel(self)

		self.setContentsMargins(0, 0, 0, 0)

	def init_images(self):
		self._left = Image.open(os.path.join(paths.IMAGE, "left_shadow.png"))
		self._right = left.rotate(180)
		self._top = Image.open(os.path.join(paths.IMAGE, "top_shadow.png"))
		self._bottom = top.rotate(180)
		self.left = self._left.toqpixmap()
		self.left.fill()
		self.right = self._right.toqpixmap()
		self.right.fill()
		self.top = self._top.toqpixmap()
		self.top.fill()
		self.bottom = self._bottom.toqpixmap()
		self.bottom.fill()
		self.adjust_size()

	def adjust_size(self):
		self.left = self.left.scaled(self.left.width(), self.main_frame.height() + 2 * self.top.height())
		self.right = self.right.scaled(self.left.width(), self.left.height())
		self.top = self.top.scaled(self.main_frame.width(), self.top.height())
		self.bottom = self.bottom.scaled(self.top.width(), self.top.height())

	def update_lb(self):
		self.left_lb.setPixmap(self.left)
		self.right_lb.setPixmap(self.right)
		self.top_lb.setPixmap(self.top)
		self.bottom.setPixmap(self.bottom)

	def mousePressEvent(self, *args, **kwargs):
		pass

	def resizeEvent(self):
		if self.only_pressed and not self._be_pressing:
			return
		self.adjust_size()
		self.update_lb()
class ProgramWindow(QtWidgets.QMainWindow):
    """
        How to increase QFrame.HLine line separator width and distance with the other buttons?
        https://stackoverflow.com/questions/50825126/how-to-increase-qframe-hline-line-separator-width-and-distance-with-the-other-bu
    """

    def __init__(self):
        QtWidgets.QMainWindow.__init__( self )
        self.setup_main_window()
        self.create_input_text()
        self.set_window_layout()

    def setup_main_window(self):
        self.resize( 400, 300  )
        self.centralwidget = QWidget()
        self.setCentralWidget( self.centralwidget )

    def create_input_text(self):
        self.separatorLine = QFrame()
        self.separatorLine.setFrameShape( QFrame.HLine )
        self.separatorLine.setFrameShadow( QFrame.Raised )

        self.separatorLine.setLineWidth( 150 )
        self.separatorLine.setMidLineWidth( 150 )

        rect = self.separatorLine.frameRect()
        print( "frameShape: %s" % rect )
        print( "width: %s" % self.separatorLine.width() )
        print( "height: %s" % self.separatorLine.height() )

        self.redoButton = QPushButton( "Redo Operations" )
        self.calculate  = QPushButton( "Compute and Follow" )
        self.open       = QPushButton( "Open File" )
        self.save       = QPushButton( "Save File" )

        self.verticalGridLayout = QGridLayout()
        self.verticalGridLayout.addWidget( self.redoButton    , 1 , 0)
        self.verticalGridLayout.addWidget( self.calculate     , 2 , 0)
        self.verticalGridLayout.addWidget( self.separatorLine , 3 , 0)
        self.verticalGridLayout.addWidget( self.open          , 4 , 0)
        self.verticalGridLayout.addWidget( self.save          , 5 , 0)
        self.verticalGridLayout.setSpacing( 0 )
        self.verticalGridLayout.setRowMinimumHeight(3, 20)
        self.verticalGridLayout.setAlignment(Qt.AlignTop)

        self.innerLayout = QHBoxLayout()
        self.innerLayout.addLayout( self.verticalGridLayout )

    def set_window_layout(self):
        main_vertical_layout = QVBoxLayout( self.centralwidget )
        main_vertical_layout.addLayout( self.innerLayout )
Ejemplo n.º 7
0
class TreeVisualizer(QWidget):
    def __init__(self):
        """Initial configuration."""
        super().__init__()

        # GLOBAL VARIABLES
        # graph variables
        self.graph: Graph = Graph()
        self.selected_node: Node = None

        self.selected_vertex: Tuple[Node, Node] = None

        # offset of the mouse from the position of the currently dragged node
        self.mouse_drag_offset: Vector = None

        # position of the mouse; is updated when the mouse moves
        self.mouse_position: Vector = Vector(-1, -1)

        # variables for visualizing the graph
        self.node_radius: float = 20
        self.weight_rectangle_size: float = self.node_radius / 3

        self.arrowhead_size: float = 8
        self.arrow_separation: float = pi / 7

        self.selected_color = Qt.red
        self.regular_node_color = Qt.white
        self.regular_vertex_weight_color = Qt.black

        # limit the displayed length of labels for each node
        self.node_label_limit: int = 10

        # UI variables
        self.font_family: str = "Times New Roman"
        self.font_size: int = 18

        self.layout_margins: float = 8
        self.layout_item_spacing: float = 2 * self.layout_margins

        # canvas positioning (scale and translation)
        self.scale: float = 1
        self.scale_coefficient: float = 2  # by how much the scale changes on scroll
        self.translation: float = Vector(0, 0)

        # by how much the rotation of the nodes changes
        self.node_rotation_coefficient: float = 0.7

        # TIMERS
        # timer that runs the simulation (60 times a second... once every ~= 16ms)
        self.simulation_timer = QTimer(
            interval=16, timeout=self.perform_simulation_iteration)

        # WIDGETS
        self.canvas = QFrame(self, minimumSize=QSize(0, 400))
        self.canvas_size: Vector = None
        self.canvas.resizeEvent = self.adjust_canvas_translation

        # toggles between directed/undirected graphs
        self.directed_toggle_button = QPushButton(
            text="undirected", clicked=self.toggle_directed_graph)

        # for showing the labels of the nodes
        self.labels_checkbox = QCheckBox(text="labels")

        # sets, whether the graph is weighted or not
        self.weighted_checkbox = QCheckBox(text="weighted",
                                           clicked=self.set_weighted_graph)

        # enables/disables forces (True by default - they're fun!)
        # self.forces_checkbox = QCheckBox(text="forces", checked=False)

        # input of the labels and vertex weights
        self.input_line_edit = QLineEdit(
            enabled=self.labels_checkbox.isChecked(),
            textChanged=self.input_line_edit_changed,
        )

        # displays information about the app
        self.about_button = QPushButton(
            text="?",
            clicked=self.show_help,
            sizePolicy=QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed),
        )

        # for creating complements of the graph
        self.complement_button = QPushButton(
            text="complement",
            clicked=self.graph.complement,
        )

        # imports/exports the current graph
        self.import_graph_button = QPushButton(text="import",
                                               clicked=self.import_graph)
        self.export_graph_button = QPushButton(text="export",
                                               clicked=self.export_graph)

        # WIDGET LAYOUT
        self.main_v_layout = QVBoxLayout(self, margin=0)
        self.main_v_layout.addWidget(self.canvas)

        self.option_h_layout = QHBoxLayout(self, margin=self.layout_margins)
        self.option_h_layout.addWidget(self.directed_toggle_button)
        self.option_h_layout.addSpacing(self.layout_item_spacing)
        self.option_h_layout.addWidget(self.weighted_checkbox)
        self.option_h_layout.addSpacing(self.layout_item_spacing)
        self.option_h_layout.addWidget(self.labels_checkbox)
        self.option_h_layout.addSpacing(self.layout_item_spacing)
        # self.option_h_layout.addWidget(self.forces_checkbox)
        self.option_h_layout.addSpacing(self.layout_item_spacing)
        self.option_h_layout.addWidget(self.input_line_edit)

        self.bottom_h_layout = QHBoxLayout(self, margin=self.layout_margins)
        self.bottom_h_layout.addWidget(self.complement_button)
        self.bottom_h_layout.addSpacing(self.layout_item_spacing)
        self.bottom_h_layout.addWidget(self.import_graph_button)
        self.bottom_h_layout.addSpacing(self.layout_item_spacing)
        self.bottom_h_layout.addWidget(self.export_graph_button)
        self.bottom_h_layout.addSpacing(self.layout_item_spacing)
        self.bottom_h_layout.addWidget(self.about_button)

        self.main_v_layout.addLayout(self.option_h_layout)
        self.main_v_layout.addSpacing(-self.layout_margins)
        self.main_v_layout.addLayout(self.bottom_h_layout)

        self.setLayout(self.main_v_layout)

        # WINDOW SETTINGS
        self.setWindowTitle("Graph Visualizer")
        self.setFont(QFont(self.font_family, self.font_size))
        self.setWindowIcon(QIcon("icon.ico"))
        self.show()

        # start the simulation
        self.simulation_timer.start()

    def set_weighted_graph(self):
        """Is called when the weighted checkbox is pressed; sets, whether the graph is 
        weighted or not."""
        self.graph.set_weighted(self.weighted_checkbox.isChecked())

    def adjust_canvas_translation(self, event):
        """Is called when the canvas widget is resized; changes translation so the 
        center stays in the center."""
        size = Vector(event.size().width(), event.size().height())

        if self.canvas_size is not None:
            self.translation += self.scale * (size - self.canvas_size) / 2

        self.canvas_size = size

    def repulsion_force(self, distance: float) -> float:
        """Calculates the strength of the repulsion force at the specified distance."""
        # return 1 / distance * 10 if self.forces_checkbox.isChecked() else 0
        return 0

    def attraction_force(self, distance: float, leash_length=80) -> float:
        """Calculates the strength of the attraction force at the specified distance 
        and leash length."""
        # return (
        #     -(distance - leash_length) / 10 if self.forces_checkbox.isChecked() else 0
        # )
        return 0

    def import_graph(self):
        """Is called when the import button is clicked; imports a graph from a file."""
        path = QFileDialog.getOpenFileName()[0]

        if path != "":
            try:
                with open(path, "r") as file:
                    # a list of vertices of the graph
                    data = [line.strip() for line in file.read().splitlines()]

                    # set the properties of the graph by its first vertex
                    sample = data[0].split(" ")

                    directed = True if sample[1] in ["->", "<-", "<>"
                                                     ] else False
                    weighted = (False if len(sample) == 2
                                or directed and len(sample) == 3 else True)

                    graph = Graph(directed=directed, weighted=weighted)

                    node_dictionary = {}

                    # add each of the nodes of the vertex to the graph
                    for vertex in data:
                        vertex_components = vertex.split(" ")

                        # the formats are either 'A B' or 'A <something> B'
                        nodes = [
                            vertex_components[0],
                            vertex_components[2]
                            if directed else vertex_components[1],
                        ]

                        # if weights are present, the formats are:
                        # - 'A B num' for undirected graphs
                        # - 'A <something> B num (num)' for directed graphs
                        weights_strings = (None if not weighted else [
                            vertex_components[2]
                            if not directed else vertex_components[3],
                            None
                            if not directed or vertex_components[1] != "<>"
                            else vertex_components[4],
                        ])

                        for node in nodes:
                            if node not in node_dictionary:
                                # slightly randomize the coordinates, so the graph
                                # doesn't stay in one place
                                x = self.canvas.width() / 2 + (random() - 0.5)
                                y = self.canvas.height() / 2 + (random() - 0.5)

                                # add it to graph with default values
                                node_dictionary[node] = graph.add_node(
                                    Vector(x, y), self.node_radius, node)

                        # get the node objects from the names
                        n1, n2 = node_dictionary[nodes[0]], node_dictionary[
                            nodes[1]]

                        graph.add_vertex(
                            n2 if vertex_components[1] == "<-" else n1,
                            n1 if vertex_components[1] == "<-" else n2,
                            0 if not weighted else ast.literal_eval(
                                weights_strings[0]),
                        )

                        # possibly add the other way
                        if vertex_components[1] == "<>":
                            graph.add_vertex(
                                n2,
                                n1,
                                0 if not weighted else ast.literal_eval(
                                    weights_strings[1]),
                            )

                # if everything was successful, override the current graph
                self.graph = graph

            except UnicodeDecodeError:
                QMessageBox.critical(self, "Error!",
                                     "Can't read binary files!")
            except ValueError:
                QMessageBox.critical(
                    self, "Error!",
                    "The weights of the graph are not numbers!")
            except Exception:
                QMessageBox.critical(
                    self,
                    "Error!",
                    "An error occurred when importing the graph. Make sure that the "
                    +
                    "file is in the correct format and that it isn't currently being "
                    + "used!",
                )

            # make sure that the UI is in order
            self.deselect_node()
            self.deselect_vertex()
            self.set_checkbox_values()

    def set_checkbox_values(self):
        """Sets the values of the checkboxes from the graph."""
        self.weighted_checkbox.setChecked(self.graph.is_weighted())
        self.update_directed_toggle_button_text()

    def export_graph(self):
        """Is called when the export button is clicked; exports a graph to a file."""
        path = QFileDialog.getSaveFileName()[0]

        if path != "":
            try:
                with open(path, "w") as file:
                    # look at every pair of nodes and examine the vertices
                    for i, n1 in enumerate(self.graph.get_nodes()):
                        for j, n2 in enumerate(self.graph.get_nodes()[i + 1:]):
                            # information about vertices and weights
                            v1_exists = self.graph.does_vertex_exist(n1, n2)
                            v2_exists = self.graph.does_vertex_exist(n2, n1)

                            if not v1_exists and v2_exists:
                                continue

                            w1_value = self.graph.get_weight(n1, n2)
                            w1 = ("" if not self.graph.is_weighted()
                                  or w1_value is None else str(w1_value))

                            # undirected graphs
                            if not self.graph.is_directed() and v1_exists:
                                file.write(
                                    f"{n1.get_label()} {n2.get_label()} {w1}\n"
                                )
                            else:
                                w2_value = self.graph.get_weight(n2, n1)
                                w2 = ("" if not self.graph.is_weighted()
                                      or w2_value is None else str(w2_value))

                                symbol = ("<>" if v1_exists and v2_exists else
                                          "->" if v1_exists else "<-")

                                vertex = f"{n1.get_label()} {symbol} {n2.get_label()}"

                                if w1 != "":
                                    vertex += f" {w1}"
                                if w2 != "":
                                    vertex += f" {w2}"

                                file.write(vertex + "\n")
            except Exception:
                QMessageBox.critical(
                    self,
                    "Error!",
                    "An error occurred when exporting the graph. Make sure that you "
                    "have permission to write to the specified file and try again!",
                )

    def show_help(self):
        """Is called when the help button is clicked; displays basic information about 
        the application."""
        message = """
            <p>Welcome to <strong>Graph Visualizer</strong>.</p>
            <p>The app aims to help with creating, visualizing and exporting graphs. 
            It is powered by PyQt5 &ndash; a set of Python bindings for the C++ library Qt.</p>
            <hr />
            <p>The controls are as follows:</p>
            <ul>
            <li><em>Left Mouse Button</em> &ndash; selects nodes and moves them</li>
            <li><em>Right Mouse Button</em> &ndash; creates/removes nodes and vertices</li>
            <li><em>Mouse Wheel</em> &ndash; zooms in/out</li>
            <li><em>Shift + Left Mouse Button</em> &ndash; moves connected nodes</li>
            <li><em>Shift + Mouse Wheel</em> &ndash; rotates nodes around the selected node</li>
            </ul>
            <hr />
            <p>If you spot an issue, or would like to check out the source code, see the app's 
            <a href="https://github.com/xiaoxiae/GraphVisualizer">GitHub repository</a>.</p>
        """

        QMessageBox.information(self, "About", message)

    def toggle_directed_graph(self):
        """Is called when the directed checkbox changes; toggles between directed and 
        undirected graphs."""
        self.graph.set_directed(not self.graph.is_directed())
        self.update_directed_toggle_button_text()

    def update_directed_toggle_button_text(self):
        """Changes the text of the directed toggle button, according to whether the 
        graph is directer or not."""
        self.directed_toggle_button.setText(
            "directed" if self.graph.is_directed() else "undirected")

    def input_line_edit_changed(self, text: str):
        """Is called when the input line edit changes; changes either the label of the 
        node selected node, or the value of the selected vertex."""
        palette = self.input_line_edit.palette()
        text = text.strip()

        if self.selected_node is not None:
            # text is restricted for rendering and graph export purposes
            if 0 < len(text) < self.node_label_limit and " " not in text:
                self.selected_node.set_label(text)
                palette.setColor(self.input_line_edit.backgroundRole(),
                                 Qt.white)
            else:
                palette.setColor(self.input_line_edit.backgroundRole(), Qt.red)
        elif self.selected_vertex is not None:
            # try to parse the input text either as an integer, or as a float
            weight = None
            try:
                weight = int(text)
            except ValueError:
                try:
                    weight = float(text)
                except ValueError:
                    pass

            # if the parsing was unsuccessful, set the input line edit background to
            # red to indicate this
            if weight is None:
                palette.setColor(self.input_line_edit.backgroundRole(), Qt.red)
            else:
                self.graph.add_vertex(self.selected_vertex[0],
                                      self.selected_vertex[1], weight)
                palette.setColor(self.input_line_edit.backgroundRole(),
                                 Qt.white)

        self.input_line_edit.setPalette(palette)

    def select_node(self, node: Node):
        """Sets the selected node to the specified node, sets the input line edit to 
        its label and enables it."""
        self.selected_node = node

        self.input_line_edit.setText(node.get_label())
        self.input_line_edit.setEnabled(True)
        self.input_line_edit.setFocus()

    def deselect_node(self):
        """Sets the selected node to None and disables the input line edit."""
        self.selected_node = None
        self.input_line_edit.setEnabled(False)

    def select_vertex(self, vertex):
        """Sets the selected vertex to the specified vertex, sets the input line edit to
        its weight and enables it."""
        self.selected_vertex = vertex

        self.input_line_edit.setText(str(self.graph.get_weight(*vertex)))
        self.input_line_edit.setEnabled(True)
        self.input_line_edit.setFocus()

    def deselect_vertex(self):
        """Sets the selected vertex to None and disables the input line edit."""
        self.selected_vertex = None
        self.input_line_edit.setEnabled(False)

    def mousePressEvent(self, event):
        """Is called when a mouse button is pressed; creates and moves 
        nodes/vertices."""
        pos = self.get_mouse_position(event)

        # if we are not on canvas, don't do anything
        if pos is None:
            return

        # sets the focus to the window (for the keypresses to register)
        self.setFocus()

        # (potentially) find a node that has been pressed
        pressed_node = None
        for node in self.graph.get_nodes():
            if distance(pos, node.get_position()) <= node.get_radius():
                pressed_node = node

        # (potentially) find a vertex that has been pressed
        pressed_vertex = None
        if self.graph.is_weighted():
            for n1 in self.graph.get_nodes():
                for n2, weight in n1.get_neighbours().items():

                    if self.graph.is_directed() or id(n1) < id(n2):
                        weight = self.graph.get_weight(n1, n2)

                        # the bounding box of this weight
                        weight_rect = self.get_vertex_weight_rect(
                            n1, n2, weight)
                        if weight_rect.contains(QPointF(*pos)):
                            pressed_vertex = (n1, n2)

        if event.button() == Qt.LeftButton:
            # nodes have the priority in selection over vertices
            if pressed_node is not None:
                self.deselect_vertex()
                self.select_node(pressed_node)

                self.mouse_drag_offset = pos - self.selected_node.get_position(
                )
                self.mouse_position = pos

            elif pressed_vertex is not None:
                self.deselect_node()
                self.select_vertex(pressed_vertex)

            else:
                self.deselect_node()
                self.deselect_vertex()

        elif event.button() == Qt.RightButton:
            if pressed_node is not None:
                if self.selected_node is not None:
                    self.graph.toggle_vertex(self.selected_node, pressed_node)
                else:
                    self.graph.remove_node(pressed_node)
                    self.deselect_node()

            elif pressed_vertex is not None:
                self.graph.remove_vertex(*pressed_vertex)
                self.deselect_vertex()

            else:
                node = self.graph.add_node(pos, self.node_radius)

                # if a selected node exists, connect it to the newly created node
                if self.selected_node is not None:
                    self.graph.add_vertex(self.selected_node, node)

                self.deselect_vertex()
                self.select_node(node)

    def mouseReleaseEvent(self, event):
        """Is called when a mouse button is released; stops node drag."""
        self.mouse_drag_offset = None

    def mouseMoveEvent(self, event):
        """Is called when the mouse is moved across the window; updates mouse 
        coordinates."""
        self.mouse_position = self.get_mouse_position(event, scale_down=True)

    def wheelEvent(self, event):
        """Is called when the mouse wheel is moved; node rotation and zoom."""
        # positive/negative for scrolling away from/towards the user
        scroll_distance = radians(event.angleDelta().y() / 8)

        if QApplication.keyboardModifiers() == Qt.ShiftModifier:
            if self.selected_node is not None:
                self.rotate_nodes_around(
                    self.selected_node.get_position(),
                    scroll_distance * self.node_rotation_coefficient,
                )
        else:
            mouse_coordinates = self.get_mouse_position(event)

            # only do something, if we're working on canvas
            if mouse_coordinates is None:
                return

            prev_scale = self.scale
            self.scale *= 2**(scroll_distance)

            # adjust translation so the x and y of the mouse stay in the same spot
            self.translation -= mouse_coordinates * (self.scale - prev_scale)

    def rotate_nodes_around(self, point: Vector, angle: float):
        """Rotates coordinates of all of the nodes in the same component as the selected 
        node by a certain angle (in radians) around it."""
        for node in self.graph.get_nodes():
            if self.graph.share_component(node, self.selected_node):
                node.set_position((node.position - point).rotated(angle) +
                                  point)

    def get_mouse_position(self, event, scale_down=False) -> Vector:
        """Returns mouse coordinates if they are within the canvas and None if they are 
        not. If scale_down is True, the function will scale down the coordinates to be 
        within the canvas (useful for dragging) and return them instead."""
        x = event.pos().x()
        y = event.pos().y()

        x_on_canvas = 0 <= x <= self.canvas.width()
        y_on_canvas = 0 <= y <= self.canvas.height()

        # scale down the coordinates if scale_down is True, or return None if we are
        # not on canvas
        if scale_down:
            x = x if x_on_canvas else 0 if x <= 0 else self.canvas.width()
            y = y if y_on_canvas else 0 if y <= 0 else self.canvas.height()
        elif not x_on_canvas or not y_on_canvas:
            return None

        # return the mouse coordinates, accounting for canvas translation and scale
        return (Vector(x, y) - self.translation) / self.scale

    def perform_simulation_iteration(self):
        """Performs one iteration of the simulation."""
        # evaluate forces that act upon each pair of nodes
        for i, n1 in enumerate(self.graph.get_nodes()):
            for j, n2 in enumerate(self.graph.get_nodes()[i + 1:]):
                # if they are not in the same component, no forces act on them
                if not self.graph.share_component(n1, n2):
                    continue

                # if the nodes are right on top of each other, no forces act on them
                d = distance(n1.get_position(), n2.get_position())
                if n1.get_position() == n2.get_position():
                    continue

                uv = (n2.get_position() - n1.get_position()).unit()

                # the size of the repel force between the two nodes
                #fr = self.repulsion_force(d)

                # add a repel force to each of the nodes, in the opposite directions
            # n1.add_force(-uv * fr)
            # n2.add_force(uv * fr)

            # if they are also connected, add the attraction force
            #if self.graph.does_vertex_exist(n1, n2, ignore_direction=True):
            #    fa = self.attraction_force(d)

            #    n1.add_force(-uv * fa)
            #    n2.add_force(uv * fa)

            # since this node will not be visited again, we can evaluate the forces
            # n1.evaluate_forces()

        # drag the selected node
        if self.selected_node is not None and self.mouse_drag_offset is not None:
            prev_node_position = self.selected_node.get_position()

            self.selected_node.set_position(self.mouse_position -
                                            self.mouse_drag_offset)

            # move the rest of the nodes that are connected to the selected node if
            # shift is pressed
            if QApplication.keyboardModifiers() == Qt.ShiftModifier:
                pos_delta = self.selected_node.get_position(
                ) - prev_node_position

                for node in self.graph.get_nodes():
                    if node is not self.selected_node and self.graph.share_component(
                            node, self.selected_node):
                        node.set_position(node.get_position() + pos_delta)

        self.update()

    def paintEvent(self, event):
        """Paints the board."""
        painter = QPainter(self)

        painter.setRenderHint(QPainter.Antialiasing, True)

        painter.setPen(QPen(Qt.black, Qt.SolidLine))
        painter.setBrush(QBrush(Qt.white, Qt.SolidPattern))

        painter.setClipRect(0, 0, self.canvas.width(), self.canvas.height())

        # background
        painter.drawRect(0, 0, self.canvas.width(), self.canvas.height())

        painter.translate(*self.translation)
        painter.scale(self.scale, self.scale)

        # draw vertexes
        for n1 in self.graph.get_nodes():
            for n2, weight in n1.get_neighbours().items():
                self.draw_vertex(n1, n2, weight, painter)

        # draw nodes
        for node in self.graph.get_nodes():
            self.draw_node(node, painter)

    def draw_node(self, node: Node, painter):
        """Draw the specified node."""
        painter.setBrush(
            QBrush(
                self.selected_color
                if node is self.selected_node else self.regular_node_color,
                Qt.SolidPattern,
            ))

        node_position = node.get_position()
        node_radius = Vector(node.get_radius()).repeat(2)

        painter.drawEllipse(QPointF(*node_position), *node_radius)

        if self.labels_checkbox.isChecked():
            label = node.get_label()

            # scale font down, depending on the length of the label of the node
            painter.setFont(
                QFont(self.font_family, self.font_size / len(label)))

            # draw the node label within the node dimensions
            painter.drawText(
                QRectF(*(node_position - node_radius), *(2 * node_radius)),
                Qt.AlignCenter,
                label,
            )

    def draw_vertex(self, n1: Node, n2: Node, weight: float, painter):
        """Draw the specified vertex."""
        # special case for a node pointing to itself
        if n1 is n2:
            r = n1.get_radius()
            x, y = n1.get_position()

            painter.setPen(QPen(Qt.black, Qt.SolidLine))
            painter.setBrush(QBrush(Qt.black, Qt.NoBrush))

            painter.drawEllipse(QPointF(x - r / 2, y - r), r / 2, r / 2)

            head = Vector(x, y) - Vector(0, r)
            uv = Vector(0, 1)

            painter.setBrush(QBrush(Qt.black, Qt.SolidPattern))
            painter.drawPolygon(
                QPointF(*head),
                QPointF(*(head +
                          (-uv).rotated(radians(10)) * self.arrowhead_size)),
                QPointF(*(head +
                          (-uv).rotated(radians(-50)) * self.arrowhead_size)),
            )
        else:
            start, end = self.get_vertex_position(n1, n2)

            # draw the head of a directed arrow, which is an equilateral triangle
            if self.graph.is_directed():
                uv = (end - start).unit()

                painter.setBrush(QBrush(Qt.black, Qt.SolidPattern))
                painter.drawPolygon(
                    QPointF(*end),
                    QPointF(
                        *(end +
                          (-uv).rotated(radians(30)) * self.arrowhead_size)),
                    QPointF(
                        *(end +
                          (-uv).rotated(radians(-30)) * self.arrowhead_size)),
                )

            painter.setPen(QPen(Qt.black, Qt.SolidLine))
            painter.drawLine(QPointF(*start), QPointF(*end))

        if self.graph.is_weighted():
            # set color according to whether the vertex is selected or not
            painter.setBrush(
                QBrush(
                    self.selected_color if self.selected_vertex is not None and
                    ((n1 is self.selected_vertex[0]
                      and n2 is self.selected_vertex[1]) or
                     (not self.graph.is_directed()
                      and n2 is self.selected_vertex[0]
                      and n1 is self.selected_vertex[1])) else
                    self.regular_vertex_weight_color,
                    Qt.SolidPattern,
                ))

            weight_rectangle = self.get_vertex_weight_rect(n1, n2, weight)
            painter.drawRect(weight_rectangle)

            painter.setFont(QFont(self.font_family, int(self.font_size / 4)))

            painter.setPen(QPen(Qt.white, Qt.SolidLine))
            painter.drawText(weight_rectangle, Qt.AlignCenter, str(weight))
            painter.setPen(QPen(Qt.black, Qt.SolidLine))

    def get_vertex_position(self, n1: Node, n2: Node) -> Tuple[Vector, Vector]:
        """Return the position of the vertex on the screen."""
        # positions of the nodes
        n1_p = Vector(*n1.get_position())
        n2_p = Vector(*n2.get_position())

        # unit vector from n1 to n2
        uv = (n2_p - n1_p).unit()

        # start and end of the vertex to be drawn
        start = n1_p + uv * n1.get_radius()
        end = n2_p - uv * n2.get_radius()

        if self.graph.is_directed():
            # if the graph is directed and a vertex exists that goes the other way, we
            # have to move the start end end so the vertexes don't overlap
            if self.graph.does_vertex_exist(n2, n1):
                start = start.rotated(self.arrow_separation, n1_p)
                end = end.rotated(-self.arrow_separation, n2_p)

        return start, end

    def get_vertex_weight_rect(self, n1: Node, n2: Node, weight: float):
        """Get a RectF surrounding the weight of the node."""
        r = self.weight_rectangle_size

        # width adjusted to number of chars in weight label
        adjusted_width = len(str(weight)) / 3 * r + r / 3
        weight_vector = Vector(r if adjusted_width <= r else adjusted_width, r)

        if n1 is n2:
            # special case for a vertex pointing to itself
            mid = n1.get_position() - Vector(r * 3, r * 4)
        else:
            start, end = self.get_vertex_position(n1, n2)
            mid = (start + end) / 2

        return QRectF(*(mid - weight_vector), *(2 * weight_vector))
Ejemplo n.º 8
0
class DataStatistics(QDialog):
    def __init__(self, parent=None):
        super(DataStatistics, self).__init__(parent)
        self.initData()
        self.initModule()
        self.setModule()
        self.funcLink()
        self.drawGraph()
        self.analyzeSingleMemo()

    def initModule(self):
        self.setWindowTitle(u'数据统计')
        self.setWindowIcon(QIcon(css.dataBtnPath))
        self.resize(500, 500)
        pg.setConfigOptions(foreground=QColor(113, 148, 116), antialias=True)

        self.btnframe = QFrame(self)
        self.mainframe = QFrame(self)
        self.btngroup = QButtonGroup(self.btnframe)
        self.stacklayout = QStackedLayout(self.mainframe)

        self.btn1 = QToolButton(self.btnframe)
        self.btn2 = QToolButton(self.btnframe)
        self.btngroup.addButton(self.btn1, 1)
        self.btngroup.addButton(self.btn2, 2)

        self.frame1 = QMainWindow()
        self.frame1_bar = QStatusBar()
        self.frame1.setStatusBar(self.frame1_bar)
        self.frame1_bar.showMessage(self.date + ':  ' +
                                    str(readFinishRate(self.date)[2] * 100) +
                                    '%')

        self.frame2 = QMainWindow()
        self.frame2_bar = QStatusBar()
        self.frame2.setStatusBar(self.frame2_bar)
        self.frame2_bar.showMessage("坚持,就是每一天很难,可一年一年越来越容易。")

        self.stacklayout.addWidget(self.frame1)
        self.stacklayout.addWidget(self.frame2)

    def setModule(self):
        self.btnframe.setGeometry(0, 0, self.width(), 35)
        self.btnframe.setStyleSheet("border-color: rgb(0, 0, 0);")
        self.btnframe.setFrameShape(QFrame.Panel)
        self.btnframe.setFrameShadow(QFrame.Raised)
        self.mainframe.setGeometry(0, 35, self.width(),
                                   self.height() - self.btnframe.height())

        self.btn1.setCheckable(True)
        self.btn1.setText("整体完成率")
        self.btn1.resize(100, 35)
        self.btn2.setCheckable(True)
        self.btn2.setText("单条完成情况")
        self.btn2.resize(100, 35)
        self.btn2.move(self.btn1.width(), 0)

    def funcLink(self):
        self.btn1.clicked.connect(self.showFrame1)
        self.btn2.clicked.connect(self.showFrame2)

    def initData(self):
        date = QDate.currentDate()
        self.date = date.toString(Qt.ISODate)
        self.statistics = readStatistics(css.statistics, self.date)

    def drawGraph(self):
        self.myplot = pg.PlotWidget(self.frame1, title='每日任务完成率')
        self.frame1.setCentralWidget(self.myplot)

        x = []
        for key in self.statistics.keys():
            x.append(key)
        points = []
        for key in x:
            points.append(readFinishRate(key)[2])

        tick_b = [list(zip(range(len(x)), x))]
        bottom = self.myplot.getAxis('bottom')
        bottom.setTicks(tick_b)

        self.myplot.setBackground((210, 240, 240))  # 背景色
        self.myplot.showGrid(y=True)

        pen = pg.mkPen({'color': (155, 200, 160), 'width': 4})  # 画笔设置
        self.myplot.plot(points[0:],
                         clear=True,
                         pen=pen,
                         symbol='o',
                         symbolBrush=QColor(113, 148, 116))

    def analyzeSingleMemo(self):
        #data extract
        data = read(css.userdata)
        self.content = []
        self.set_date = []
        self.if_done = []
        if data['memo_data']:
            for memo in data['memo_data']:
                self.content.append(memo['content'])
                self.set_date.append(memo['set_date'])
                self.if_done.append(memo['if_done'])
        #UI init
        self.ui = QWidget(self.frame2)
        self.option = QComboBox(self.ui)
        self.label0 = QLabel(self.ui)
        self.label1 = QLabel(self.ui)
        self.label2 = QLabel(self.ui)
        self.label3 = QLabel(self.ui)
        self.frame2.setCentralWidget(self.ui)
        #UI set
        self.option.setGeometry(10, 95, self.width() - 100, 40)
        self.option.setStyleSheet(css.combobox_style)
        self.option.addItems(self.content)
        self.option.currentIndexChanged.connect(self.getMemoMessage)

        self.label0.setGeometry(10, 5, self.width() - 100, 80)
        self.label1.setGeometry(10, 145, self.width() - 100, 80)
        self.label2.setGeometry(10, 235, self.width() - 100, 80)
        self.label3.setGeometry(10, 325, self.width() - 100, 80)
        self.label0.setStyleSheet(css.label_style)
        self.label1.setStyleSheet(css.label_style)
        self.label2.setStyleSheet(css.label_style)
        self.label3.setStyleSheet(css.label_style)
        self.label0.setText('选择需要查看的memo:')
        self.label1.setText('设立已 ' +
                            str(getDateDiffer(self.set_date[0], self.date)) +
                            ' 天')
        self.label2.setText('已完成 ' + str(len(self.if_done[0])) + ' 天')
        self.label3.setText('最大连续完成 ' + str(len(getLongest(self.if_done[0]))) +
                            ' 天')

    def getMemoMessage(self):
        self.label1.setText('设立已 ' + str(
            getDateDiffer(self.set_date[self.option.currentIndex()],
                          self.date)) + ' 天')
        self.label2.setText(
            '已完成 ' + str(len(self.if_done[self.option.currentIndex()])) + ' 天')
        self.label3.setText(
            '最大连续完成 ' +
            str(len(getLongest(self.if_done[self.option.currentIndex()]))) +
            ' 天')

    def showFrame1(self):
        if self.stacklayout.currentIndex() != 0:
            self.stacklayout.setCurrentIndex(0)

    def showFrame2(self):
        if self.stacklayout.currentIndex() != 1:
            self.stacklayout.setCurrentIndex(1)
Ejemplo n.º 9
0
class P2(QWidget):
    def __init__(self):
        """Initial game configuration."""
        super().__init__()

        # GAME VARIABLES
        # valid moves for the king
        self.valid_moves = [(1, 0), (1, 1), (0, 1), (-1, 1), (-1, 0), (-1, -1), (0, -1), (1, -1)]

        # starting and ending tiles
        self.start = None
        self.end = None

        # a boolean array, where True values are obstacles/pieces
        self.obstacles = [[False] * 8 for _x in range(8)]

        # WIDGET LAYOUT
        self.canvas = QFrame(self, minimumSize=QSize(600, 600))

        self.main_v_layout = QVBoxLayout(self, margin=0)
        self.main_v_layout.addWidget(self.canvas)
        self.setLayout(self.main_v_layout)

        self.setWindowTitle('Graph Visualizer')
        self.show()

        self.setFixedSize(self.size())

    def are_coordinates_valid(self, x, y):
        """Returns True, if the coordinates are valid board coordinates and False if they are not."""
        return 0 <= x < len(self.obstacles) and 0 <= y < len(self.obstacles[0])

    def calculate_shortest_path(self):
        """Returns either the shortest possible path connecting (and including) the start and the end , or None if such
        path doesn't exist."""
        # don't calculate the path if either start or end are unknown
        if self.start is None or self.end is None:
            return None

        # squares to be explored (a linked list
        unexplored_squares = [self.start]

        # for tracking where we came from
        board = [[None] * len(self.obstacles[0]) for _x in range(len(self.obstacles))]
        board[self.start[0]][self.start[1]] = -1  # special value, so it isn't explored

        # explore, until there are tiles to explore
        while len(unexplored_squares) != 0:
            x, y = unexplored_squares.pop(0)

            # if we reached the end, back-track to start; if not, add unexplored tiles and repeat
            if x == self.end[0] and y == self.end[1]:
                path = [(x, y)]
                coordinate = (x, y)

                # add coordinates to the path list, until we reach the start
                while coordinate != self.start:
                    coordinate = board[coordinate[0]][coordinate[1]]
                    path.append(coordinate)

                return path
            else:
                for move in self.valid_moves:
                    new_x = x + move[0]
                    new_y = y + move[1]

                    # add the coordinate, only if it's valid, unexplored, and not an obstacle
                    if self.are_coordinates_valid(new_x, new_y):
                        unexplored = board[new_x][new_y] is None
                        no_obstacle = not self.obstacles[new_x][new_y]

                        if unexplored and no_obstacle:
                            unexplored_squares.append((new_x, new_y))
                            board[new_x][new_y] = (x, y)

    def mouseMoveEvent(self, event):
        """Is called when a mouse button is pressed; creates start, end and obstacles."""
        mouse_x, mouse_y = event.pos().x(), event.pos().y()

        x = int((mouse_x / self.canvas.width()) * len(self.obstacles))
        y = int((mouse_y / self.canvas.height()) * len(self.obstacles[0]))

        if self.are_coordinates_valid(x, y):
            if event.buttons() == Qt.LeftButton and not self.obstacles[x][y]:
                self.start = (x, y)
            if event.buttons() == Qt.RightButton and not self.obstacles[x][y]:
                self.end = (x, y)
            if event.buttons() == Qt.MiddleButton and (x, y) != self.start and (x, y) != self.end:
                self.obstacles[x][y] = True

        self.update()

    # make mouse press the same thing as mouse move
    mousePressEvent = mouseMoveEvent

    def paintEvent(self, event):
        """Paints the board."""
        painter = QPainter(self)

        painter.setRenderHint(QPainter.Antialiasing, True)

        painter.setPen(QPen(Qt.black, Qt.SolidLine))
        painter.setBrush(QBrush(Qt.white, Qt.SolidPattern))

        painter.drawRect(0, 0, self.canvas.width(), self.canvas.height())

        tile_width = self.canvas.width() / len(self.obstacles)
        tile_height = self.canvas.height() / len(self.obstacles[0])

        # draw obstacles
        for x in range(len(self.obstacles)):
            for y in range(len(self.obstacles[0])):
                if self.obstacles[x][y]:
                    painter.setBrush(QBrush(Qt.black, Qt.SolidPattern))
                else:
                    painter.setBrush(QBrush(Qt.white, Qt.SolidPattern))

                painter.drawRect(x * tile_width, y * tile_height, tile_width, tile_height)

        # draw path if it exists
        path = self.calculate_shortest_path()
        if path is not None:
            painter.setBrush(QBrush(Qt.green, Qt.SolidPattern))
            for p in path:
                painter.drawRect(p[0] * tile_width, p[1] * tile_height, tile_width, tile_height)

        # draw start if it exists
        if self.start is not None:
            painter.setBrush(QBrush(Qt.blue, Qt.SolidPattern))
            painter.drawRect(self.start[0] * tile_width, self.start[1] * tile_height, tile_width, tile_height)

        # draw end if it exists
        if self.end is not None:
            painter.setBrush(QBrush(Qt.red, Qt.SolidPattern))
            painter.drawRect(self.end[0] * tile_width, self.end[1] * tile_height, tile_width, tile_height)
Ejemplo n.º 10
0
class RunFrame(QFrame):
    def __init__(self, q_main_window):
        super(RunFrame, self).__init__()

        # Split the frame
        self.split_frame = QVBoxLayout(self)
        self.output_frame = QFrame()
        self.button_frame = QFrame()
        self.split_frame.addWidget(self.output_frame, 2)
        self.split_frame.addWidget(self.button_frame)
        self.output = QTextEdit(self.output_frame)
        self.output.setReadOnly(True)
        # print(self.output.isReadOnly())
        # self.output_cursor = QTextCursor(self.output)

        self.button_layout = QHBoxLayout(self.button_frame)

        # Store the parent
        self.q_main_window = q_main_window

        # Define Layout
        self.define_settings()
        self.define_text_output()

        # Running
        # self.stop_running = False
        self.m2m = Makr2Maker(self.q_main_window)

    def update_text_listener(self, is_same, new_string):
        if is_same == '0':
            self.new_text(new_string)

    def new_text(self, text):
        self.output.setText(text)
        QApplication.processEvents()

    def define_settings(self):
        self.define_controls()

    def define_controls(self):
        def add_button(self, text, callback):
            button = QPushButton(self.button_frame)
            button.clicked.connect(callback)
            button.setText(text)
            self.button_layout.addWidget(button)
            return button

        self.bt_validate = add_button(self, 'Validate', self.validate)
        self.bt_run = add_button(self, 'Run', self.run)
        self.bt_interrupt = add_button(self, 'Interrupt', self.interrupt)
        self.bt_clear = add_button(self, 'Clear Output', self.clear_output)
        self.bt_remove_results = add_button(self, 'Remove Results',
                                            self.remove_results)

    def interrupt(self):
        self.q_main_window.is_interrupting = True
        self.q_main_window.statusBar.showMessage('Interrupting')
        self.q_main_window.match_maker.stopSearch()
        self.q_main_window.match_maker.is_running = False
        self.q_main_window.statusBar.showMessage('Matchmaking Interrupted')
        self.q_main_window.is_interrupting = False

    def validate(self):
        self.q_main_window.statusBar.showMessage('Validating...')
        self.m2m.apply_settings()
        t = threading.Thread(target=self.q_main_window.match_maker.validate)
        t.start()

    def run(self):
        self.m2m.apply_settings()
        t = threading.Thread(target=self.q_main_window.match_maker.main)
        t.start()
        # self.q_main_window.is_running = False

    def clear_output(self):
        self.output.setText('')

    def remove_results(self):

        working_dir = self.q_main_window.settings_frame.tb_path.text()
        results_dir = self.q_main_window.settings_frame.tb_results_dir.text()
        self.dir_to_remove = path.join(working_dir, results_dir)

        def callback(button_pressed):
            if button_pressed.text() == '&OK':
                try:
                    shutil.rmtree(self.dir_to_remove)
                    print('Directory Removed: ' + self.dir_to_remove)
                except:
                    print('Directory not found: ' + self.dir_to_remove)
            else:
                return

        dialog = QMessageBox()
        dialog.setIcon(QMessageBox.Critical)
        dialog.setText(
            'You are about to delete the results of the optimization.')
        dialog.setInformativeText(self.dir_to_remove)
        dialog.setWindowTitle('Delete Optimization Results?')
        dialog.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel)
        dialog.buttonClicked.connect(callback)
        dialog.exec_()

    def define_text_output(self):
        self.output.setReadOnly(True)
        self.resize_text_output()

    def resize_text_output(self):
        frame_width = self.output_frame.width()
        frame_height = self.output_frame.height()
        self.output.resize(frame_width, frame_height)
Ejemplo n.º 11
0
class Demo(QWidget):
    def __init__(self):
        super().__init__()

        self.__setup_ui__()

    def __setup_ui__(self):
        self.setWindowTitle("测试")
        #窗口大小
        self.resize(1400, 800)
        # 工具栏
        self.frame_tool = QFrame(self)
        self.frame_tool.setObjectName("frame_tool")
        self.frame_tool.setGeometry(0, 0, self.width(), 25)
        self.frame_tool.setStyleSheet("border-color: rgb(0, 0, 0);")
        self.frame_tool.setFrameShape(QFrame.Panel)
        self.frame_tool.setFrameShadow(QFrame.Raised)

        # 1.1 界面1按钮
        self.window1_btn = QToolButton(self.frame_tool)
        self.window1_btn.setCheckable(True)
        self.window1_btn.setText("window1")
        self.window1_btn.setObjectName("menu_btn")
        self.window1_btn.resize(100, 25)
        self.window1_btn.clicked.connect(self.click_window1)
        self.window1_btn.setAutoRaise(True)

        # 1.2 界面2按钮
        self.window2_btn = QToolButton(self.frame_tool)
        self.window2_btn.setCheckable(True)
        self.window2_btn.setText("window2")
        self.window2_btn.setObjectName("menu_btn")
        self.window2_btn.resize(100, 25)
        self.window2_btn.move(self.window1_btn.width(), 0)
        self.window2_btn.clicked.connect(self.click_window2)
        self.window2_btn.setAutoRaise(True)

        self.btn_group = QButtonGroup(self.frame_tool)
        self.btn_group.addButton(self.window1_btn, 1)
        self.btn_group.addButton(self.window2_btn, 2)

        # 2. 工作区域
        self.main_frame = QFrame(self)
        self.main_frame.setGeometry(0, 25, self.width(),
                                    self.height() - self.frame_tool.height())
        # self.main_frame.setStyleSheet("background-color: rgb(65, 95, 255)")

        # 创建堆叠布局
        self.stacked_layout = QStackedLayout(self.main_frame)

        # 第一个布局界面
        self.main_frame1 = QMainWindow()
        self.frame1_bar = QStatusBar()
        self.frame1_bar.setObjectName("frame1_bar")
        self.main_frame1.setStatusBar(self.frame1_bar)
        self.frame1_bar.showMessage("欢迎进入frame1")

        rom_frame = QFrame(self.main_frame1)
        rom_frame.setGeometry(0, 0, self.width(),
                              self.main_frame.height() - 25)
        rom_frame.setFrameShape(QFrame.Panel)
        rom_frame.setFrameShadow(QFrame.Raised)

        frame1_bar_frame = QFrame(self.main_frame1)
        frame1_bar_frame.setGeometry(0, self.main_frame.height(), self.width(),
                                     25)

        # 第二个布局界面
        self.main_frame2 = QMainWindow()
        self.frame2_bar = QStatusBar()
        self.frame2_bar.setObjectName("frame2_bar")
        self.main_frame2.setStatusBar(self.frame2_bar)
        self.frame2_bar.showMessage("欢迎进入frame2")

        custom_frame = QFrame(self.main_frame2)
        custom_frame.setGeometry(0, 0, self.width(),
                                 self.main_frame.height() - 25)
        custom_frame.setFrameShape(QFrame.Panel)
        custom_frame.setFrameShadow(QFrame.Raised)

        frame2_bar_frame = QFrame(self.main_frame2)
        frame2_bar_frame.setGeometry(0, self.main_frame.height(), self.width(),
                                     25)

        # 把两个布局界面放进去
        self.stacked_layout.addWidget(self.main_frame1)
        self.stacked_layout.addWidget(self.main_frame2)

    def click_window1(self):
        if self.stacked_layout.currentIndex() != 0:
            self.stacked_layout.setCurrentIndex(0)
            self.frame1_bar.showMessage("欢迎进入frame1")

    def click_window2(self):
        if self.stacked_layout.currentIndex() != 1:
            self.stacked_layout.setCurrentIndex(1)
            self.frame2_bar.showMessage("欢迎进入frame2")
Ejemplo n.º 12
0
class Demo(QWidget):
    def __init__(self):
        super().__init__()

        self.setWindowTitle("测试")
        # 窗口大小
        self.resize(1400, 800)
        # self.setFixedSize(1500, 600)  # 设置窗口为固定尺寸, 此时窗口不可调整大小
        # self.setMinimumSize(1800, 1000)  # 设置窗口最大尺寸
        # self.setMaximumSize(900, 300)  # 设置窗口最小尺寸
        # self.setWindowFlag(Qt.WindowStaysOnTopHint)   # 设置窗口顶层显示
        # self.setWindowFlags(Qt.FramelessWindowHint)  # 设置无边框窗口样式,不显示最上面的标题栏

        self.content_font = QFont("微软雅黑", 12, QFont.Medium)  # 定义字体样式

        self.center()

        self.__setup_ui__()

    # 控制窗口显示在屏幕中心的方法

    def center(self):
        # 获得窗口
        qr = self.frameGeometry()
        # 获得屏幕中心点
        cp = QDesktopWidget().availableGeometry().center()
        # 显示到屏幕中心
        qr.moveCenter(cp)
        self.move(qr.topLeft())

    # 关闭窗口的时候,触发了QCloseEvent,需要重写closeEvent()事件处理程序,这样就可以弹出是否退出的确认窗口
    def closeEvent(self, event):
        reply = QMessageBox.question(
            self,
            "退出程序",  # 提示框标题
            "确定退出xxxx程序吗?",  # 消息对话框中显示的文本
            QMessageBox.Yes | QMessageBox.No,  # 指定按钮的组合 Yes和No
            QMessageBox.No  # 默认的按钮焦点,这里默认是No按钮
        )
        # 判断按钮的选择
        if reply == QMessageBox.Yes:
            event.accept()
        else:
            event.ignore()

    def __setup_ui__(self):
        # 工具栏
        self.frame_tool = QFrame(self)
        self.frame_tool.setObjectName("frame_tool")
        self.frame_tool.setGeometry(0, 0, self.width(), 25)
        self.frame_tool.setStyleSheet("border-color: rgb(0, 0, 0);")
        self.frame_tool.setFrameShape(QFrame.Panel)
        self.frame_tool.setFrameShadow(QFrame.Raised)

        # 1.1 界面1按钮
        self.window1_btn = QToolButton(self.frame_tool)
        self.window1_btn.setCheckable(True)
        self.window1_btn.setText("window1")
        self.window1_btn.setObjectName("menu_btn")
        self.window1_btn.resize(100, 25)
        self.window1_btn.clicked.connect(self.click_window1)
        self.window1_btn.setAutoRaise(
            True)  # 去掉工具按钮的边框线如果是QPushButton按钮的话,就是用setFlat(True)这个方法,用法相同

        # 1.2 界面2按钮
        self.window2_btn = QToolButton(self.frame_tool)
        self.window2_btn.setCheckable(True)
        self.window2_btn.setText("window2")
        self.window2_btn.setObjectName("menu_btn")
        self.window2_btn.resize(100, 25)
        self.window2_btn.move(self.window1_btn.width(), 0)
        self.window2_btn.clicked.connect(self.click_window2)
        self.window2_btn.setAutoRaise(True)

        self.btn_group = QButtonGroup(self.frame_tool)
        self.btn_group.addButton(self.window1_btn, 1)
        self.btn_group.addButton(self.window2_btn, 2)

        # 1.3 帮助下拉菜单栏
        # 创建帮助工具按钮
        help_btn = QToolButton(self.frame_tool)
        help_btn.setText("帮助")
        help_btn.setObjectName("menu_btn")
        help_btn.resize(100, 25)
        help_btn.move(self.window2_btn.x() + self.window2_btn.width(), 0)
        help_btn.setAutoRaise(True)
        help_btn.setPopupMode(QToolButton.InstantPopup)
        # 创建关于菜单
        help_menu = QMenu("帮助", self.frame_tool)
        feedback_action = QAction(QIcon("xxx.png"), "反馈", help_menu)
        feedback_action.triggered.connect(self.click_feedback)
        about_action = QAction(QIcon("xxx.png"), "关于", help_menu)
        about_action.triggered.connect(self.click_about)
        # 把两个QAction放入help_menu
        help_menu.addAction(feedback_action)
        help_menu.addAction(about_action)
        # 把help_menu放入help_btn
        help_btn.setMenu(help_menu)

        # 2. 工作区域
        self.main_frame = QFrame(self)
        self.main_frame.setGeometry(0, 25, self.width(),
                                    self.height() - self.frame_tool.height())
        # self.main_frame.setStyleSheet("background-color: rgb(65, 95, 255)")

        # 创建堆叠布局
        self.stacked_layout = QStackedLayout(self.main_frame)

        # 第一个布局
        self.main_frame1 = QMainWindow()
        self.frame1_bar = QStatusBar()
        self.frame1_bar.setObjectName("frame1_bar")
        self.main_frame1.setStatusBar(self.frame1_bar)
        self.frame1_bar.showMessage("欢迎进入frame1")

        rom_frame = QFrame(self.main_frame1)
        rom_frame.setGeometry(0, 0, self.width(),
                              self.main_frame.height() - 25)
        rom_frame.setFrameShape(QFrame.Panel)
        rom_frame.setFrameShadow(QFrame.Raised)

        # 超链接
        self.super_link = QLabel(rom_frame)
        self.super_link.setText("""
            超链接: <a href="https://blog.csdn.net/s_daqing">点击打开查看</a>
            """)
        self.super_link.setGeometry(20, 30, 300, 25)
        self.super_link.setFont(self.content_font)  # 使用字体样式
        self.super_link.setOpenExternalLinks(True)  # 使其成为超链接
        self.super_link.setTextInteractionFlags(
            Qt.TextBrowserInteraction)  # 双击可以复制文本

        self.start_btn = QPushButton("开 始", rom_frame)
        self.start_btn.setGeometry(self.width() * 0.7,
                                   self.height() * 0.8, 100, 40)
        # self.start_btn.clicked.connect(self.start_btn_click)
        self.quit_btn = QPushButton("退 出", rom_frame)
        self.quit_btn.setGeometry(self.width() * 0.85,
                                  self.height() * 0.8, 100, 40)
        self.quit_btn.setStatusTip("点击关闭程序")
        # self.quit_btn.clicked.connect(QCoreApplication.instance().quit)  # 点击退出可以直接退出
        self.quit_btn.clicked.connect(self.close)  # 点击退出按钮的退出槽函数

        #rom_frame1 = QFrame()
        #rom_frame1.setFrameShape(QFrame.Panel)
        #rom_frame1.setFrameShadow(QFrame.Raised)

        #rom_frame2 = QFrame()
        #rom_frame2.setFrameShape(QFrame.Panel)
        #rom_frame2.setFrameShadow(QFrame.Raised)

        # 创建布局管理器
        self.layout1 = QBoxLayout(QBoxLayout.TopToBottom)

        # 给管理器对象设置父控件
        rom_frame.setLayout(self.layout1)
        self.main_frame1.setCentralWidget(rom_frame)

        # 把子控件添加到布局管理器中
        #self.layout1.addWidget(rom_frame1, 1)
        #self.layout1.addWidget(rom_frame2, 1)

        self.layout1.setContentsMargins(0, 0, 0, 0)  # 设置布局的左上右下外边距
        self.layout1.setSpacing(0)  # 设置子控件的内边距

        frame1_bar_frame = QFrame(self.main_frame1)
        frame1_bar_frame.setGeometry(0, self.main_frame.height(), self.width(),
                                     25)

        # 第二个布局
        self.main_frame2 = QMainWindow()
        self.frame2_bar = QStatusBar()
        self.frame2_bar.setObjectName("frame2_bar")
        self.main_frame2.setStatusBar(self.frame2_bar)
        self.frame2_bar.showMessage("欢迎进入frame2")

        custom_frame = QFrame(self.main_frame2)
        custom_frame.setGeometry(0, 0, self.width(),
                                 self.main_frame.height() - 25)
        custom_frame.setFrameShape(QFrame.Panel)
        custom_frame.setFrameShadow(QFrame.Raised)

        custom_frame1 = QFrame()
        custom_frame1.setFrameShape(QFrame.Panel)
        custom_frame1.setFrameShadow(QFrame.Raised)

        custom_frame2 = QFrame()
        custom_frame2.setFrameShape(QFrame.Panel)
        custom_frame2.setFrameShadow(QFrame.Raised)

        custom_frame3 = QFrame()
        custom_frame3.setFrameShape(QFrame.Panel)
        custom_frame3.setFrameShadow(QFrame.Raised)

        # 创建布局管理器
        self.layout2 = QBoxLayout(QBoxLayout.TopToBottom)

        # 给管理器对象设置父控件
        custom_frame.setLayout(self.layout2)
        """
        使用了父类为QMainWindow的话,在里面使用布局类,QGridLayout, QHBoxLayout ,QVBoxLayout 等等时,发现不好用,
        加上下面这句代码就可以了,QMainWindow对象.setCentralWidget(这里填布局管理器的父控件对象)
        """
        self.main_frame2.setCentralWidget(custom_frame)

        # 把子控件添加到布局管理器中
        self.layout2.addWidget(custom_frame1, 1)
        self.layout2.addWidget(custom_frame2, 1)
        self.layout2.addWidget(custom_frame3, 1)

        self.layout2.setContentsMargins(0, 0, 0, 0)  # 设置布局的左上右下外边距
        self.layout2.setSpacing(0)  # 设置子控件的内边距

        frame2_bar_frame = QFrame(self.main_frame2)
        frame2_bar_frame.setGeometry(0, self.main_frame.height(), self.width(),
                                     25)

        # 把两个布局放进去
        self.stacked_layout.addWidget(self.main_frame1)
        self.stacked_layout.addWidget(self.main_frame2)

    def click_window1(self):
        if self.stacked_layout.currentIndex() != 0:
            self.stacked_layout.setCurrentIndex(0)
            self.frame1_bar.showMessage("欢迎进入frame1")

    def click_window2(self):
        if self.stacked_layout.currentIndex() != 1:
            self.stacked_layout.setCurrentIndex(1)
            self.frame2_bar.showMessage("欢迎进入frame2")
            QDesktopServices.openUrl(QUrl("https://www.csdn.net/")
                                     )  # 点击window2按钮后,执行这个槽函数的时候,会在浏览器自动打开这个网址

    def click_feedback(self, event):
        QMessageBox.about(self, "反馈",
                          "使用过程中如有疑问,请联系:xxxx.163.com\r\n\r\n版本:V1.0.1")

    def click_about(self, event):
        QMessageBox.about(self, "关于", "使用文档,请参考:xxxxxx")
Ejemplo n.º 13
0
class MainWindow(QMainWindow):
	def __init__(self):
		super().__init__()
		self.setWindowTitle("Tree Viewer v2")
		self.setGeometry(0,0,800,600)
		self.setObjectName("Layer1")

		self.offsets = [0, 35, 0, 2]
		self.removeThread = None
		self.searchThread = None
		self.addThread = None
		self.selected = None
		self.knotsSize = 30
		self.fontSize = 10

		self.windowError = QMessageBox()
		self.windowError.setIcon(QMessageBox.Information)
		self.windowError.setObjectName("Error")

		self.setUI()

	def setUI(self):
		s = QGraphicsDropShadowEffect()
		s.setColor(QColor("#000000"))
		s.setBlurRadius(5)
		s.setOffset(1,1)
		self.frameTree = QFrame(self)
		self.frameTree.setGeometry(0, 0, 600, 600)

		self.defaultTree()

		self.frameInfo = QFrame(self)
		self.frameInfo.setGeometry(605, 5, 190, 590)
		self.frameInfo.setObjectName("Layer2")
		self.frameInfo.setGraphicsEffect(s)

		self.labelAddKnot = QLabel("Add Knot:", self.frameInfo)
		self.labelAddKnot.setGeometry(10, 150, 60, 30)
		self.labelAddKnot.setAlignment(Qt.AlignCenter)
		self.labelAddKnot.setObjectName("Layer2NoBG")
		self.labelAddKnot.setFont(QFont("Arial", 15))
		self.entryAddKnot = QLineEdit(self.frameInfo)
		self.entryAddKnot.setGeometry(120, 150, 60, 30)
		self.entryAddKnot.setAlignment(Qt.AlignCenter)
		self.entryAddKnot.setObjectName("Layer2")
		self.entryAddKnot.setFont(QFont("Arial", 15))
		self.entryAddKnot.setValidator(QRegExpValidator(QRegExp("[0-9]+"), self.entryAddKnot))
		self.buttonAddKnot = QPushButton(QApplication.style().standardIcon(QStyle.SP_DialogApplyButton), '', self.frameInfo)
		self.buttonAddKnot.setGeometry(180, 150, 60, 30)
		self.buttonAddKnot.setObjectName("Layer2")
		self.buttonAddKnot.clicked.connect(self.addKnot)

		self.labelVisualizeKnot = QLabel("Search for:", self.frameInfo)
		self.labelVisualizeKnot.setGeometry(10, 300, 60, 30)
		self.labelVisualizeKnot.setAlignment(Qt.AlignCenter)
		self.labelVisualizeKnot.setObjectName("Layer2NoBG")
		self.labelVisualizeKnot.setFont(QFont("Arial", 15))
		self.entryVisualizeKnot = QLineEdit(self.frameInfo)
		self.entryVisualizeKnot.setGeometry(70, 300, 60, 30)
		self.entryVisualizeKnot.setAlignment(Qt.AlignCenter)
		self.entryVisualizeKnot.setObjectName("Layer2")
		self.entryVisualizeKnot.setFont(QFont("Arial", 15))
		self.entryVisualizeKnot.setValidator(QRegExpValidator(QRegExp("[0-9]+"), self.entryVisualizeKnot))
		self.buttonVisualizeStartPause = QPushButton(QApplication.style().standardIcon(QStyle.SP_MediaPlay), '', self.frameInfo)
		self.buttonVisualizeStartPause.setGeometry(130, 300, 60, 30)
		self.buttonVisualizeStartPause.setObjectName("Layer2")
		self.buttonVisualizeStartPause.clicked.connect(self.startSearch)

		self.labelRemoveKnot = QLabel("Remove\nKnot:", self.frameInfo)
		self.labelRemoveKnot.setGeometry(10, 450, 60, 40)
		self.labelRemoveKnot.setAlignment(Qt.AlignCenter)
		self.labelRemoveKnot.setObjectName("Layer2NoBG")
		self.labelRemoveKnot.setFont(QFont("Arial", 15))
		self.entryRemoveKnot = QLineEdit(self.frameInfo)
		self.entryRemoveKnot.setGeometry(70, 450, 60, 30)
		self.entryRemoveKnot.setAlignment(Qt.AlignCenter)
		self.entryRemoveKnot.setObjectName("Layer2")
		self.entryRemoveKnot.setFont(QFont("Arial", 15))
		self.entryRemoveKnot.setValidator(QRegExpValidator(QRegExp("[0-9]+"), self.entryRemoveKnot))
		self.buttonRemoveKnot = QPushButton(QApplication.style().standardIcon(QStyle.SP_TrashIcon), '', self.frameInfo)
		self.buttonRemoveKnot.setGeometry(130, 450, 60, 30)
		self.buttonRemoveKnot.setObjectName("Layer2")
		self.buttonRemoveKnot.clicked.connect(self.deleteKnot)

		self.labelSize = QLabel("Change Knots Size:", self.frameInfo)
		self.labelSize.setGeometry(10, 540, 180, 30)
		self.labelSize.setAlignment(Qt.AlignCenter)
		self.labelSize.setObjectName("Layer2NoBG")
		self.labelSize.setFont(QFont("Arial", 15))
		self.buttonMinusSize = QPushButton("-", self.frameInfo)
		self.buttonMinusSize.setGeometry(10, 560, 90, 30)
		self.buttonMinusSize.setObjectName("Layer2")
		self.buttonMinusSize.setFont(QFont("Arial", 20))
		self.buttonMinusSize.clicked.connect(lambda: self.changeSize('-'))
		self.buttonMinusSize.setAutoRepeat(True)
		self.buttonPlusSize = QPushButton("+", self.frameInfo)
		self.buttonPlusSize.setGeometry(170, 560, 90, 30)
		self.buttonPlusSize.setObjectName("Layer2")
		self.buttonPlusSize.setFont(QFont("Arial", 20))
		self.buttonPlusSize.clicked.connect(lambda: self.changeSize('+'))
		self.buttonPlusSize.setAutoRepeat(True)

	def defaultTree(self):
		self.rootTree = sortedKnot(20, parent=self)
		self.rootTree.addSorted(5)
		self.rootTree.addSorted(3)
		self.rootTree.addSorted(12)
		self.rootTree.addSorted(8)
		self.rootTree.addSorted(6)
		self.rootTree.addSorted(13)
		self.rootTree.addSorted(25)
		self.rootTree.addSorted(21)
		self.rootTree.addSorted(28)
		self.rootTree.addSorted(29)
		self.rootTree.addSorted(24)
		self.rootTree.update()

	def deleteKnot(self):
		if self.entryRemoveKnot.text():
			if int(self.entryRemoveKnot.text()) != self.rootTree.value:
				if not self.removeThread:
					self.buttonRemoveKnot.setIcon(QApplication.style().standardIcon(QStyle.SP_DialogDiscardButton))
					self.removeThread = visualRemoveThread(self, int(self.entryRemoveKnot.text()))
					self.removeThread.finished.connect(self.deleteRemoveThread)
					self.removeThread.start()
				else:
					self.deleteRemoveThread()
			else:
				self.showError("You can't remove the root", "Deletion Error")
		else:
			self.showError("You have to enter a valid value to remove", "Deletion Error")


	def startSearch(self):
		if self.entryVisualizeKnot.text():
			if not self.searchThread:
				self.buttonVisualizeStartPause.setIcon(QApplication.style().standardIcon(QStyle.SP_MediaStop))
				self.searchThread = visualSearchThread(self, int(self.entryVisualizeKnot.text()))
				self.searchThread.finished.connect(self.deleteSearchThread)
				self.searchThread.start()
			else:
				self.deleteSearchThread()
		else:
			self.showError("You have to enter a valid value to search for", "Search Error")

	def addKnot(self):
		if self.entryAddKnot.text():
			if not self.addThread:
				self.buttonAddKnot.setIcon(QApplication.style().standardIcon(QStyle.SP_DialogCancelButton))
				self.addThread = visualAddThread(self, int(self.entryAddKnot.text()))
				self.addThread.finished.connect(self.deleteAddThread)
				self.addThread.start()
			else:
				self.deleteAddThread()
		else:
			self.showError("You have to enter a valid value to insert", "Insertion Error")

	def deleteRemoveThread(self):
		if self.removeThread:
			self.buttonRemoveKnot.setIcon(QApplication.style().standardIcon(QStyle.SP_TrashIcon))
			for i in self.removeThread.labels: i.setParent(None)
			self.showError("The knot you wanted to remove has been succesfully annihilated", "Knot Deleted") if self.removeThread.deleted else self.showError("The knot you tried to remove encountered an error", "Deletion Error")
			if self.removeThread.isRunning(): self.removeThread.terminate()
			self.removeThread = None
			self.selected = None
			self.rootTree.update()
			self.update()

	def deleteAddThread(self):
		if self.addThread:
			self.buttonAddKnot.setIcon(QApplication.style().standardIcon(QStyle.SP_DialogApplyButton))
			if self.addThread.added:
				self.addThread.added[0].addLeft(self.addThread.toAdd) if self.addThread.added[1] == 'left' else self.addThread.added[0].addRight(self.addThread.toAdd)
				self.addThread.added[0].left.label.show() if self.addThread.added[1] == 'left' else self.addThread.added[0].right.label.show()
				self.showError("Your knot has been added with success", "Added Knot")
			else:
				self.showError("The value you tried to insert is already taken", "Value Error")
			if self.addThread.isRunning(): self.addThread.terminate()
			self.addThread = None
			self.selected = None
			self.rootTree.update()
			self.update()

	def deleteSearchThread(self):
		if self.searchThread:
			self.buttonVisualizeStartPause.setIcon(QApplication.style().standardIcon(QStyle.SP_MediaPlay))
			self.showError("The knot you were searching for has been found", "Knot Found") if self.searchThread.found else self.showError("The knot you were searching for was not found", "Knot Not Found")
			if self.searchThread.isRunning(): self.searchThread.terminate()
			self.searchThread = None
			self.selected = None
			self.update()

	def changeSize(self, mode):
		if mode == '-':
			if self.fontSize-2 >= 10:
				self.fontSize -= 2
				self.knotsSize -= 4
				for i in self.frameTree.children():
					i.setGeometry(i.x()+2, i.y()+2, self.knotsSize, self.knotsSize)
					i.setFont(QFont("Arial", self.fontSize))
					self.offsets[0] -= 0
					self.offsets[1] -= .18
					self.offsets[2] += 0
					self.offsets[3] += .18
		elif mode == '+':
			if self.fontSize+2 <= 42:
				self.fontSize += 2
				self.knotsSize += 4
				for i in self.frameTree.children():
					i.setGeometry(i.x()-2, i.y()-2, i.width()+4, i.height()+4)
					i.setFont(QFont("Arial", self.fontSize))
					self.offsets[0] += 0
					self.offsets[1] += .18
					self.offsets[2] -= 0
					self.offsets[3] -= .18
		self.update()

	def paintEvent(self, event):
		painter = QPainter()
		painter.begin(self)
		painter.setPen(QPen(QColor(3,218,197), 3, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin))
		for cos in self.knotCoordinates:
			painter.drawLine(int(cos[0]+self.offsets[0]), int(cos[1]+self.offsets[1]), int(cos[2]+self.offsets[2]), int(cos[3]+self.offsets[3]))
		if self.selected:
			painter.setPen(QPen(QColor("#D8DEE9"), 3, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin))
			painter.drawLine(int(self.selected[0]+self.offsets[0]), int(self.selected[1]+self.offsets[1]), int(self.selected[2]+self.offsets[2]), int(self.selected[3]+self.offsets[3]))
		painter.end()

	def resizeEvent(self, event):
		self.frameTree.setGeometry(0, 0, int(6*(self.width()/8)), self.height())
		self.frameInfo.setGeometry(int(6*(self.width()/8))+5, 10, int(2*(self.width()/8))-10, self.height()-20)

		self.labelAddKnot.setGeometry(10, int(self.frameInfo.height()/4)-30, int((self.frameInfo.width()-20)/3), 30)
		self.entryAddKnot.setGeometry(10 + int((self.frameInfo.width()-20)/3), int(self.frameInfo.height()/4)-30, int((self.frameInfo.width()-20)/3), 30)
		self.buttonAddKnot.setGeometry(10 + 2*int((self.frameInfo.width()-20)/3), int(self.frameInfo.height()/4)-30, int((self.frameInfo.width()-20)/3), 30)

		self.labelVisualizeKnot.setGeometry(10, int(self.frameInfo.height()/2), int((self.frameInfo.width()-20)/3), 30)
		self.entryVisualizeKnot.setGeometry(10 + int((self.frameInfo.width()-20)/3), int(self.frameInfo.height()/2), int((self.frameInfo.width()-20)/3), 30)
		self.buttonVisualizeStartPause.setGeometry(10 + 2*int((self.frameInfo.width()-20)/3), int(self.frameInfo.height()/2), int((self.frameInfo.width()-20)/3), 30)

		self.labelRemoveKnot.setGeometry(10, 3*int(self.frameInfo.height()/4)-10, int((self.frameInfo.width()-20)/3), 50)
		self.entryRemoveKnot.setGeometry(10 + int((self.frameInfo.width()-20)/3), 3*int(self.frameInfo.height()/4), int((self.frameInfo.width()-20)/3), 30)
		self.buttonRemoveKnot.setGeometry(10 + 2*int((self.frameInfo.width()-20)/3), 3*int(self.frameInfo.height()/4), int((self.frameInfo.width()-20)/3), 30)

		self.labelSize.setGeometry(10, self.frameInfo.height()-70, self.frameInfo.width()-30, 30)
		self.buttonPlusSize.setGeometry(int((self.frameInfo.width()-20)/2+10), self.frameInfo.height()-40, int((self.frameInfo.width()-30)/2), 30)
		self.buttonMinusSize.setGeometry(10, self.frameInfo.height()-40, int((self.frameInfo.width()-30)/2), 30)

		self.rootTree.update()

	def showError(self, text, title):
		self.windowError.setText(text)
		self.windowError.setWindowTitle(title)
		self.windowError.show()
Ejemplo n.º 14
0
class TreeVisualizer(QWidget):
    def __init__(self):
        """Initial configuration."""
        super().__init__()

        # GLOBAL VARIABLES
        # graph variables
        self.graph = Graph()
        self.selected_node = None

        self.vertex_positions = []
        self.selected_vertex = None

        # offset of the mouse from the position of the currently dragged node
        self.mouse_drag_offset = None

        # position of the mouse; is updated when the mouse moves
        self.mouse_x = -1
        self.mouse_y = -1

        # variables for visualizing the graph
        self.node_radius = 20
        self.weight_rectangle_size = self.node_radius / 3

        self.arrowhead_size = 4
        self.arrow_separation = pi / 7

        self.selected_color = Qt.red
        self.regular_node_color = Qt.white
        self.regular_vertex_weight_color = Qt.black

        # limit the displayed length of labels for each node
        self.node_label_limit = 10

        # UI variables
        self.font_family = "Times New Roman"
        self.font_size = 18

        self.layout_margins = 8
        self.layout_item_spacing = 2 * self.layout_margins

        # canvas positioning - scale and translation
        self.scale = 1
        self.scale_coefficient = 1.1  # by how much the scale changes on scroll
        self.translation = [0, 0]

        # angle (in degrees) by which all of the nodes rotate
        self.node_rotation_angle = 15

        # TIMERS
        # runs the simulation 60 times a second (1000/60 ~= 16ms)
        self.simulation_timer = QTimer(
            interval=16, timeout=self.perform_simulation_iteration)

        # WIDGETS
        self.canvas = QFrame(self, minimumSize=QSize(0, 400))
        self.canvas_size = None
        self.canvas.resizeEvent = self.adjust_canvas_translation

        # toggles between directed/undirected graphs
        self.directed_toggle_button = QPushButton(
            text="undirected", clicked=self.toggle_directed_graph)

        # for showing the labels of the nodes
        self.labels_checkbox = QCheckBox(text="labels")

        # sets, whether the graph is weighted or not
        self.weighted_checkbox = QCheckBox(text="weighted",
                                           clicked=self.set_weighted_graph)

        # enables/disables forces (True by default - they're fun!)
        self.forces_checkbox = QCheckBox(text="forces", checked=True)

        # input of the labels and vertex weights
        self.input_line_edit = QLineEdit(
            enabled=self.labels_checkbox.isChecked(),
            textChanged=self.input_line_edit_changed)

        # displays information about the app
        self.about_button = QPushButton(text="?",
                                        clicked=self.show_help,
                                        sizePolicy=QSizePolicy(
                                            QSizePolicy.Fixed,
                                            QSizePolicy.Fixed))

        # imports/exports the current graph
        self.import_graph_button = QPushButton(text="import",
                                               clicked=self.import_graph)
        self.export_graph_button = QPushButton(text="export",
                                               clicked=self.export_graph)

        # WIDGET LAYOUT
        self.main_v_layout = QVBoxLayout(self, margin=0)
        self.main_v_layout.addWidget(self.canvas)

        self.option_h_layout = QHBoxLayout(self, margin=self.layout_margins)
        self.option_h_layout.addWidget(self.directed_toggle_button)
        self.option_h_layout.addSpacing(self.layout_item_spacing)
        self.option_h_layout.addWidget(self.weighted_checkbox)
        self.option_h_layout.addSpacing(self.layout_item_spacing)
        self.option_h_layout.addWidget(self.labels_checkbox)
        self.option_h_layout.addSpacing(self.layout_item_spacing)
        self.option_h_layout.addWidget(self.forces_checkbox)
        self.option_h_layout.addSpacing(self.layout_item_spacing)
        self.option_h_layout.addWidget(self.input_line_edit)

        self.io_h_layout = QHBoxLayout(self, margin=self.layout_margins)
        self.io_h_layout.addWidget(self.import_graph_button)
        self.io_h_layout.addSpacing(self.layout_item_spacing)
        self.io_h_layout.addWidget(self.export_graph_button)
        self.io_h_layout.addSpacing(self.layout_item_spacing)
        self.io_h_layout.addWidget(self.about_button)

        self.main_v_layout.addLayout(self.option_h_layout)
        self.main_v_layout.addSpacing(-self.layout_margins)
        self.main_v_layout.addLayout(self.io_h_layout)

        self.setLayout(self.main_v_layout)

        # WINDOW SETTINGS
        self.setWindowTitle('Graph Visualizer')
        self.setFont(QFont(self.font_family, self.font_size))
        self.setWindowIcon(QIcon("icon.ico"))
        self.show()

        # start the simulation
        self.simulation_timer.start()

    def set_weighted_graph(self):
        """Is called when the weighted checkbox is pressed; sets, whether the graph is weighted or not."""
        self.graph.set_weighted(self.weighted_checkbox.isChecked())

    def adjust_canvas_translation(self, event):
        """Is called when the canvas widget is resized; changes translation so the center stays in the center."""
        size = (event.size().width(), event.size().height())

        # don't translate on the initial resize
        if self.canvas_size is not None:
            self.translation[0] += self.scale * (size[0] -
                                                 self.canvas_size[0]) / 2
            self.translation[1] += self.scale * (size[1] -
                                                 self.canvas_size[1]) / 2

        self.canvas_size = size

    def repulsion_force(self, distance):
        """Calculates the strength of the repulsion force at the specified distance."""
        return 1 / distance * 10 if self.forces_checkbox.isChecked() else 0

    def attraction_force(self, distance, leash_length=80):
        """Calculates the strength of the attraction force at the specified distance and leash length."""
        return -(distance -
                 leash_length) / 10 if self.forces_checkbox.isChecked() else 0

    def import_graph(self):
        """Is called when the import button is clicked; imports a graph from a file."""
        path = QFileDialog.getOpenFileName()[0]

        if path != "":
            try:
                with open(path, "r") as file:
                    # a list of vertices of the graph
                    data = [line.strip() for line in file.read().splitlines()]

                    sample = data[0].split(" ")
                    vertex_types = ["->", "<", "<>"]

                    # whether the graph is directed and weighted; done by looking at a sample input vertex
                    directed = True if sample[1] in vertex_types else False
                    weighted = False if len(
                        sample) == 2 or directed and len(sample) == 3 else True

                    graph = Graph(directed=directed, weighted=weighted)

                    # to remember the created nodes and to connect them later
                    node_dictionary = {}

                    # add each of the nodes of the vertex to the graph
                    for vertex in data:
                        vertex_components = vertex.split(" ")

                        # the formats are either 'A B' or 'A ... B', where ... is one of the vertex types
                        nodes = [
                            vertex_components[0], vertex_components[2]
                            if directed else vertex_components[1]
                        ]

                        # if weights are present, the formats are either 'A B num' or 'A ... B num (num)'
                        weights_strings = None if not weighted else [
                            vertex_components[2] if not directed else
                            vertex_components[3], None if not directed
                            or vertex_components[1] != vertex_types[2] else
                            vertex_components[4]
                        ]

                        for node in nodes:
                            if node not in node_dictionary:
                                # slightly randomize the coordinates so the graph doesn't stay in one place
                                x = self.canvas.width() / 2 + (random() - 0.5)
                                y = self.canvas.height() / 2 + (random() - 0.5)

                                # add it to graph with default values
                                node_dictionary[node] = graph.add_node(
                                    x, y, self.node_radius, node)

                        # get the node objects from the names
                        n1, n2 = node_dictionary[nodes[0]], node_dictionary[
                            nodes[1]]

                        # export the graph, according to the direction of the vertex, and whether it's weighted or not
                        if not directed or vertex_components[1] == "->":
                            if weighted:
                                graph.add_vertex(
                                    n1, n2,
                                    self._convert_string_to_number(
                                        weights_strings[0]))
                            else:
                                graph.add_vertex(n1, n2)
                        elif vertex_components[1] == "<-":
                            if weighted:
                                graph.add_vertex(
                                    n2, n1,
                                    self._convert_string_to_number(
                                        weights_strings[0]))
                            else:
                                graph.add_vertex(n2, n1)
                        else:
                            if weighted:
                                graph.add_vertex(
                                    n1, n2,
                                    self._convert_string_to_number(
                                        weights_strings[0]))
                                graph.add_vertex(
                                    n2, n1,
                                    self._convert_string_to_number(
                                        weights_strings[1]))
                            else:
                                graph.add_vertex(n1, n2)
                                graph.add_vertex(n2, n1)

                self.graph = graph

            except UnicodeDecodeError:
                QMessageBox.critical(self, "Error!",
                                     "Can't read binary files!")
            except ValueError:
                QMessageBox.critical(
                    self, "Error!",
                    "The weights of the graph are not numbers!")
            except Exception:
                QMessageBox.critical(
                    self, "Error!",
                    "An error occurred when importing the graph. Make sure that the "
                    "file is in the correct format!")

            # make sure that the UI is in order
            self.deselect_node()
            self.deselect_vertex()
            self.set_checkbox_values()

    def _convert_string_to_number(self, str):
        """Attempts to convert the specified string to a number. Throws error if it fails to do so!"""
        try:
            return int(str)
        except ValueError:
            return float(str)

    def set_checkbox_values(self):
        """Sets the values of the checkboxes from the graph."""
        self.weighted_checkbox.setChecked(self.graph.is_weighted())
        self.update_directed_toggle_button_text()

    def export_graph(self):
        """Is called when the export button is clicked; exports a graph to a file."""
        path = QFileDialog.getSaveFileName()[0]

        if path != "":
            try:
                with open(path, "w") as file:
                    # look at every pair of nodes and examine the vertices
                    for i in range(len(self.graph.get_nodes())):
                        n1 = self.graph.get_nodes()[i]
                        for j in range(i + 1, len(self.graph.get_nodes())):
                            n2 = self.graph.get_nodes()[j]

                            v1_exists = self.graph.does_vertex_exist(n1, n2)

                            w1_value = self.graph.get_weight(n1, n2)
                            w1 = "" if not self.graph.is_weighted(
                            ) or w1_value is None else str(w1_value)

                            if not self.graph.is_directed() and v1_exists:
                                # for undirected graphs, no direction symbols are necessary
                                file.write(n1.get_label() + " " +
                                           n2.get_label() + " " + w1 + "\n")
                            else:
                                v2_exists = self.graph.does_vertex_exist(
                                    n2, n1)

                                w2_value = self.graph.get_weight(n2, n1)
                                w2 = "" if not self.graph.is_weighted(
                                ) or w2_value is None else str(w2_value)

                                # node that w1 and w2 might be empty strings, so we can combine
                                # directed weighted and unweighted graphs in one command
                                if v1_exists and v2_exists:
                                    file.write("%s <> %s %s %s\n" %
                                               (n1.get_label(), n2.get_label(),
                                                str(w1), str(w2)))
                                elif v1_exists:
                                    file.write("%s -> %s %s\n" %
                                               (n1.get_label(), n2.get_label(),
                                                str(w1)))
                                elif v2_exists:
                                    file.write("%s <- %s %s\n" %
                                               (n1.get_label(), n2.get_label(),
                                                str(w2)))
            except Exception:
                QMessageBox.critical(
                    self, "Error!",
                    "An error occurred when exporting the graph. Make sure that you "
                    "have permission to write to the specified file and try again!"
                )

    def show_help(self):
        """Is called when the help button is clicked; displays basic information about the application."""
        message = """
            <p>Welcome to <strong>Graph Visualizer</strong>.</p>
            <p>The app aims to help with creating, visualizing and exporting graphs. 
            It is powered by PyQt5 &ndash; a set of Python bindings for the C++ library Qt.</p>
            <hr />
            <p>The controls are as follows:</p>
            <ul>
            <li><em>Left Mouse Button</em> &ndash; selects nodes and moves them</li>
            <li><em>Right Mouse Button</em> &ndash; creates/removes nodes and vertices</li>
            <li><em>Mouse Wheel</em> &ndash; zooms in/out</li>
            <li><em>Shift + Left Mouse Button</em> &ndash; moves connected nodes</li>
            <li><em>Shift + Mouse Wheel</em> &ndash; rotates nodes around the selected node</li>
            </ul>
            <hr />
            <p>If you spot an issue, or would like to check out the source code, see the app's 
            <a href="https://github.com/xiaoxiae/GraphVisualizer">GitHub repository</a>.</p>
        """

        QMessageBox.information(self, "About", message)

    def toggle_directed_graph(self):
        """Is called when the directed checkbox changes; toggles between directed and undirected graphs."""
        self.graph.set_directed(not self.graph.is_directed())
        self.update_directed_toggle_button_text()

    def update_directed_toggle_button_text(self):
        """Changes the text of the directed toggle button, according to whether the graph is directer or not."""
        self.directed_toggle_button.setText(
            "directed" if self.graph.is_directed() else "undirected")

    def input_line_edit_changed(self, text):
        """Is called when the input line edit changes; changes either the label of the node selected node, or the value
        of the selected vertex."""
        palette = self.input_line_edit.palette()

        if self.selected_node is not None:
            # the text has to be non-zero and not contain spaces, for the import/export language to work properly
            # the text length is also restricted, for rendering purposes
            if 0 < len(text) < self.node_label_limit and " " not in text:
                self.selected_node.set_label(text)
                palette.setColor(self.input_line_edit.backgroundRole(),
                                 Qt.white)
            else:
                palette.setColor(self.input_line_edit.backgroundRole(), Qt.red)
        elif self.selected_vertex is not None:
            # try to parse the input text either as an integer, or as a float
            weight = None
            try:
                weight = int(text)
            except ValueError:
                try:
                    weight = float(text)
                except ValueError:
                    pass

            # if the parsing was unsuccessful, set the input line edit background to red to indicate this fact
            # if it worked, set the weight by adding a new vertex with the input weight
            if weight is None:
                palette.setColor(self.input_line_edit.backgroundRole(), Qt.red)
            else:
                self.graph.add_vertex(self.selected_vertex[0],
                                      self.selected_vertex[1], weight)
                palette.setColor(self.input_line_edit.backgroundRole(),
                                 Qt.white)

        self.input_line_edit.setPalette(palette)

    def select_node(self, node):
        """Sets the selected node to the specified node, sets the input line edit to its label and enables it."""
        self.selected_node = node

        self.input_line_edit.setText(node.get_label())
        self.input_line_edit.setEnabled(True)
        self.input_line_edit.setFocus()

    def deselect_node(self):
        """Sets the selected node to None and disables the input line edit."""
        self.selected_node = None
        self.input_line_edit.setEnabled(False)

    def select_vertex(self, vertex):
        """Sets the selected vertex to the specified vertex, sets the input line edit to its weight and enables it."""
        self.selected_vertex = vertex

        self.input_line_edit.setText(str(self.graph.get_weight(*vertex)))
        self.input_line_edit.setEnabled(True)
        self.input_line_edit.setFocus()

    def deselect_vertex(self):
        """Sets the selected vertex to None and disables the input line edit."""
        self.selected_vertex = None
        self.input_line_edit.setEnabled(False)

    def mousePressEvent(self, event):
        """Is called when a mouse button is pressed; creates and moves nodes/vertices."""
        mouse_coordinates = self.get_mouse_coordinates(event)

        # if we are not on canvas, don't do anything
        if mouse_coordinates is None:
            return

        # sets the focus to the entire window, for the keypresses to register
        self.setFocus()

        x = mouse_coordinates[0]
        y = mouse_coordinates[1]

        # (potentially) find a node that has been pressed
        pressed_node = None
        for node in self.graph.get_nodes():
            if self.distance(x, y, node.get_x(),
                             node.get_y()) <= node.get_radius():
                pressed_node = node

        # (potentially) find a vertex that has been pressed
        pressed_vertex = None
        for vertex in self.vertex_positions:
            # vertex position items have the structure [x, y, (n1, n2)]
            if abs(vertex[0] - x) < self.weight_rectangle_size and abs(
                    vertex[1] - y) < self.weight_rectangle_size:
                pressed_vertex = vertex[2]

        # select on left click
        # create/connect on right click
        if event.button() == Qt.LeftButton:
            # nodes have the priority in selection before vertices
            if pressed_node is not None:
                self.deselect_vertex()
                self.select_node(pressed_node)

                self.mouse_drag_offset = (x - self.selected_node.get_x(),
                                          y - self.selected_node.get_y())
                self.mouse_x = x
                self.mouse_y = y
            elif pressed_vertex is not None:
                self.deselect_node()
                self.select_vertex(pressed_vertex)
            else:
                self.deselect_node()
                self.deselect_vertex()
        elif event.button() == Qt.RightButton:
            # either make/remove a connection if we right clicked a node, or create a new node if we haven't
            if pressed_node is not None:
                if self.selected_node is not None and pressed_node is not self.selected_node:
                    # if a connection does not exist between the nodes, create it; otherwise remove it
                    if self.graph.does_vertex_exist(self.selected_node,
                                                    pressed_node):
                        self.graph.remove_vertex(self.selected_node,
                                                 pressed_node)
                    else:
                        self.graph.add_vertex(self.selected_node, pressed_node)
                else:
                    self.graph.remove_node(pressed_node)
                    self.deselect_node()
            elif pressed_vertex is not None:
                self.graph.remove_vertex(*pressed_vertex)
                self.deselect_vertex()
            else:
                node = self.graph.add_node(x, y, self.node_radius)

                # if a selected node exists, connect it to the newly created node
                if self.selected_node is not None:
                    self.graph.add_vertex(self.selected_node, node)

                self.select_node(node)
                self.deselect_vertex()

    def mouseReleaseEvent(self, event):
        """Is called when a mouse button is released; stops node drag."""
        self.mouse_drag_offset = None

    def mouseMoveEvent(self, event):
        """Is called when the mouse is moved across the window; updates mouse coordinates."""
        mouse_coordinates = self.get_mouse_coordinates(event, scale_down=True)

        self.mouse_x = mouse_coordinates[0]
        self.mouse_y = mouse_coordinates[1]

    def wheelEvent(self, event):
        """Is called when the mouse wheel is moved; node rotation and zoom."""
        if QApplication.keyboardModifiers() == Qt.ShiftModifier:
            if self.selected_node is not None:
                # positive/negative for scrolling away from/towards the user
                angle = self.node_rotation_angle if event.angleDelta().y(
                ) > 0 else -self.node_rotation_angle

                self.rotate_nodes_around(self.selected_node.get_x(),
                                         self.selected_node.get_y(), angle)
        else:
            mouse_coordinates = self.get_mouse_coordinates(event)

            # only do something, if we're working on canvas
            if mouse_coordinates is None:
                return

            x, y = mouse_coordinates[0], mouse_coordinates[1]
            prev_scale = self.scale

            # adjust the canvas scale, depending on the scroll direction
            # if angleDelta.y() is positive, scroll away (zoom out) from the user (and vice versa)
            if event.angleDelta().y() > 0:
                self.scale *= self.scale_coefficient
            else:
                self.scale /= self.scale_coefficient

            # adjust translation so the x and y of the mouse stay in the same spot
            scale_delta = self.scale - prev_scale
            self.translation[0] += -(x * scale_delta)
            self.translation[1] += -(y * scale_delta)

    def rotate_nodes_around(self, x, y, angle):
        """Rotates coordinates of all of the points by a certain angle (in degrees) around the specified point."""
        angle = radians(angle)

        for node in self.graph.get_nodes():
            # only rotate points that are in the same continuity set
            if self.graph.share_continuity_set(node, self.selected_node):
                # translate the coordinates to origin for the rotation to work
                node_x, node_y = node.get_x() - x, node.get_y() - y

                # rotate and translate the coordinates of the node
                node.set_x(node_x * cos(angle) - node_y * sin(angle) + x)
                node.set_y(node_x * sin(angle) + node_y * cos(angle) + y)

    def get_mouse_coordinates(self, event, scale_down=False):
        """Returns mouse coordinates if they are within the canvas and None if they are not.
        If scale_down is True, the function will scale down the coordinates to be within the canvas (useful for
        dragging) and return them instead."""
        x = event.pos().x()
        y = event.pos().y()

        # booleans for whether the coordinate components are on canvas
        x_on_canvas = 0 <= x <= self.canvas.width()
        y_on_canvas = 0 <= y <= self.canvas.height()

        # scale down the coordinates if scale_down is True, or return none if we are not on canvas
        if scale_down:
            x = x if x_on_canvas else 0 if x <= 0 else self.canvas.width()
            y = y if y_on_canvas else 0 if y <= 0 else self.canvas.height()
        elif not x_on_canvas or not y_on_canvas:
            return None

        # return the mouse coordinates, accounting for the translation and scale of the canvas
        return ((x - self.translation[0]) / self.scale,
                (y - self.translation[1]) / self.scale)

    def perform_simulation_iteration(self):
        """Performs one iteration of the simulation."""
        # evaluate forces that act upon each pair of nodes
        for i in range(len(self.graph.get_nodes())):
            n1 = self.graph.get_nodes()[i]
            for j in range(i + 1, len(self.graph.get_nodes())):
                n2 = self.graph.get_nodes()[j]

                # if they are not in the same continuity set, no forces act on them
                if not self.graph.share_continuity_set(n1, n2):
                    continue

                # calculate the distance of the nodes and a unit vector from the first to the second
                d = self.distance(n1.get_x(), n1.get_y(), n2.get_x(),
                                  n2.get_y())

                # if the nodes are right on top of each other, the force can't be calculated
                if d == 0:
                    continue

                ux, uy = (n2.get_x() - n1.get_x()) / d, (n2.get_y() -
                                                         n1.get_y()) / d

                # the size of the repel force between the two nodes
                fr = self.repulsion_force(d)

                # add a repel force to each of the nodes, in the opposite directions
                n1.add_force((-ux * fr, -uy * fr))
                n2.add_force((ux * fr, uy * fr))

                # if they are connected, add the leash force, regardless of whether the graph is directed or not
                if self.graph.does_vertex_exist(n1, n2, ignore_direction=True):
                    # the size of the attraction force between the two nodes
                    fa = self.attraction_force(d)

                    # add the repel force to each of the nodes, in the opposite directions
                    n1.add_force((-ux * fa, -uy * fa))
                    n2.add_force((ux * fa, uy * fa))

            # since this node will not be visited again, evaluate the forces
            n1.evaluate_forces()

        # drag the selected node, after all of the forces have been applied, so it doesn't move anymore
        if self.selected_node is not None and self.mouse_drag_offset is not None:
            prev_x = self.selected_node.get_x()
            prev_y = self.selected_node.get_y()

            self.selected_node.set_x(self.mouse_x - self.mouse_drag_offset[0])
            self.selected_node.set_y(self.mouse_y - self.mouse_drag_offset[1])

            # move the rest of the nodes that are connected to the selected node, if shift is pressed
            if QApplication.keyboardModifiers() == Qt.ShiftModifier:
                x_delta = self.selected_node.get_x() - prev_x
                y_delta = self.selected_node.get_y() - prev_y

                for node in self.graph.get_nodes():
                    if node is not self.selected_node and self.graph.share_continuity_set(
                            node, self.selected_node):
                        node.set_x(node.get_x() + x_delta)
                        node.set_y(node.get_y() + y_delta)

        self.update()

    def paintEvent(self, event):
        """Paints the board."""
        painter = QPainter(self)

        painter.setRenderHint(QPainter.Antialiasing, True)

        painter.setPen(QPen(Qt.black, Qt.SolidLine))
        painter.setBrush(QBrush(Qt.white, Qt.SolidPattern))

        # bound the canvas area to not draw outside of it
        painter.setClipRect(0, 0, self.canvas.width(), self.canvas.height())

        # draw the background
        painter.drawRect(0, 0, self.canvas.width(), self.canvas.height())

        # transform and scale the painter accordingly
        painter.translate(self.translation[0], self.translation[1])
        painter.scale(self.scale, self.scale)

        # if the graph is weighted, reset the positions, since they will be re-drawn later on
        if self.graph.is_weighted():
            self.vertex_positions = []

        # draw vertices; has to be drawn before nodes, so they aren't drawn on top of them
        for node in self.graph.get_nodes():
            for neighbour, weight in node.get_neighbours().items():
                x1, y1, x2, y2 = node.get_x(), node.get_y(), neighbour.get_x(
                ), neighbour.get_y()

                # create a unit vector from the first to the second graph
                d = self.distance(x1, y1, x2, y2)
                ux, uy = (x2 - x1) / d, (y2 - y1) / d
                r = neighbour.get_radius()

                # if it's directed, draw the head of the arrow
                if self.graph.is_directed():
                    # in case there is a vertex going the other way, we will move the line up the circles by an angle,
                    # so there is separation between the vertices
                    if self.graph.does_vertex_exist(neighbour, node):
                        nx = -uy * r * sin(self.arrow_separation) + ux * r * (
                            1 - cos(self.arrow_separation))
                        ny = ux * r * sin(self.arrow_separation) + uy * r * (
                            1 - cos(self.arrow_separation))

                        x1, x2, y1, y2 = x1 + nx, x2 + nx, y1 + ny, y2 + ny

                    # the position of the head of the arrow
                    xa, ya = x1 + ux * (d - r), y1 + uy * (d - r)

                    # calculate the two remaining points of the arrow
                    # this is done the same way as the previous calculation (shift by vector)
                    d = self.distance(x1, y1, xa, ya)
                    ux_arrow, uy_arrow = (xa - x1) / d, (ya - y1) / d

                    # position of the base of the arrow
                    x, y = x1 + ux_arrow * (
                        d - self.arrowhead_size * 2), y1 + uy_arrow * (
                            d - self.arrowhead_size * 2)

                    # the normal vectors to the unit vector of the arrow head
                    nx_arrow, ny_arrow = -uy_arrow, ux_arrow

                    # draw the tip of the arrow, as the triangle
                    painter.setBrush(QBrush(Qt.black, Qt.SolidPattern))
                    painter.drawPolygon(
                        QPointF(xa, ya),
                        QPointF(x + nx_arrow * self.arrowhead_size,
                                y + ny_arrow * self.arrowhead_size),
                        QPointF(x - nx_arrow * self.arrowhead_size,
                                y - ny_arrow * self.arrowhead_size))

                # draw only one of the two vertices, if the graph is undirected
                if self.graph.is_directed() or id(node) < id(neighbour):
                    painter.drawLine(QPointF(x1, y1), QPointF(x2, y2))

                    if self.graph.is_weighted():
                        x_middle, y_middle = (x2 + x1) / 2, (y2 + y1) / 2

                        # if the graph is directed, the vertices are offset (so they aren't draw on top of each other),
                        # so we need to shift them back to be at the midpoint between the nodes
                        if self.graph.is_directed():
                            x_middle -= ux * r * (1 -
                                                  cos(self.arrow_separation))
                            y_middle -= uy * r * (1 -
                                                  cos(self.arrow_separation))

                        r = self.weight_rectangle_size

                        self.vertex_positions.append(
                            (x_middle, y_middle, (node, neighbour)))

                        # make the selected vertex rectangle background different, if it's selected (for aesthetics)
                        if self.selected_vertex is not None and node is self.selected_vertex[0] and neighbour is \
                                self.selected_vertex[1]:
                            painter.setBrush(
                                QBrush(self.selected_color, Qt.SolidPattern))
                        else:
                            painter.setBrush(
                                QBrush(self.regular_vertex_weight_color,
                                       Qt.SolidPattern))

                        # draw the square
                        square_rectangle = QRectF(x_middle - r, y_middle - r,
                                                  2 * r, 2 * r)
                        painter.drawRect(square_rectangle)

                        # adjust the length of the weight string, so the minus sign doesn't make the number smaller
                        length = len(str(weight)) - (1 if weight < 0 else 0)

                        painter.setFont(
                            QFont(self.font_family,
                                  self.font_size / (length * 3)))

                        # draw the value of the vertex (in white, so it's visible against the background)
                        painter.setPen(QPen(Qt.white, Qt.SolidLine))
                        painter.drawText(square_rectangle, Qt.AlignCenter,
                                         str(weight))
                        painter.setPen(QPen(Qt.black, Qt.SolidLine))

        # draw nodes
        for node in self.graph.get_nodes():
            # selected nodes are red to make them distinct; others are white
            if node is self.selected_node:
                painter.setBrush(QBrush(self.selected_color, Qt.SolidPattern))
            else:
                painter.setBrush(
                    QBrush(self.regular_node_color, Qt.SolidPattern))

            x, y, r = node.get_x(), node.get_y(), node.get_radius()

            painter.drawEllipse(QPointF(x, y), r, r)

            # only draw labels if the label checkbox is checked
            if self.labels_checkbox.isChecked():
                label = node.get_label()

                # scale font down, depending on the length of the label of the node
                painter.setFont(
                    QFont(self.font_family, self.font_size / len(label)))

                # draw the node label within the node dimensions
                painter.drawText(QRectF(x - r, y - r, 2 * r, 2 * r),
                                 Qt.AlignCenter, label)

    def distance(self, x1, y1, x2, y2):
        """Returns the distance of two points in space."""
        return sqrt((x1 - x2)**2 + (y1 - y2)**2)
Ejemplo n.º 15
0
class explore(QWidget):
    files = None
    icon_size = 120
    hidden = False

    maxWidth = 1200

    scroll_pos_old = 0
    scroll_pos = 0

    btns = []
    #targetType = None

    mouse = [0, 0]
    collized = []

    #Print with hotkey
    keyPrint = None
    selectBtn = None

    anitimer = None
    newSelect = 0

    select_old = [0]

    def __init__(self, parent=None):
        super(explore, self).__init__(parent)
        self.parent = parent
        self.setAcceptDrops(True)

        self.init()

    def init(self):
        sys.modules['explore'] = self
        self.core = core()
        self.keyPrint = lambda: (print(
            self.btns), print(len(self.btns), print('f:', self.files)))

        self.history = self.parent.parent().history
        self.scroll = QScrollArea(self)

        self.window = QWidget(self)
        self.window.setAcceptDrops(True)

        self.scroll.setWidget(self.window)

        self.scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)

        #self.scroll.verticalScrollBar().valueChanged.connect(self.wh_change)
        #self.scroll.verticalScrollBar().sliderMoved.connect(self.scrollMoved)
        #self.scroll.mousePressEvent = lambda e: print(e, 'de')

        self.scrBar = QScrollBar()
        self.scroll.setVerticalScrollBar(self.scrBar)

        self.scrBar.valueChanged.connect(self.wh_change)
        self.scrBar.sliderMoved.connect(self.scrollMoved)
        self.scrBar.wheelEvent = self.wh
        self.scrBar.mousePressEvent = self.scrollBarPress
        self.scrBar.mouseMoveEvent = self.scrollBarMove

        self.scroll.wheelEvent = self.wh

        self.anitimer = QTimer()
        self.anitimer.setInterval(1000 / 60)
        self.anitimer.timeout.connect(self.ani_func)
        self.anitimer.start()

        self.qrun = qrun(self)

        self.app = QApplication([])

        hk = sys.modules['scr.hotkey'].hotkey

        def setkey(name, func):
            key = initKey()
            hk.setInitName(hk, name)
            key.func = func
            hk.add(hk, key)

        setkey('redo', lambda: self.redo())
        setkey('undo', lambda: self.undo())
        setkey('selectall', lambda: self.selectAll())
        setkey('deselectall', lambda: self.deselect())

        setkey('keyright', lambda: self.keyArrow('right'))
        setkey('keyleft', lambda: self.keyArrow('left'))
        setkey('keyup', lambda: self.keyArrow('up'))
        setkey('keydown', lambda: self.keyArrow('down'))

        setkey('ctrlkeyright', lambda: self.keyArrow('right', 1))
        setkey('ctrlkeyleft', lambda: self.keyArrow('left', 1))
        setkey('ctrlkeyup', lambda: self.keyArrow('up', 1))
        setkey('ctrlkeydown', lambda: self.keyArrow('down', 1))

        setkey('keyenter', lambda: self.keyEnter())
        setkey('keyreturn', lambda: self.keyEnter())

        setkey('copy', lambda: self.copyCB())
        setkey('paste', lambda: self.pasteCB())

        setkey('toggleqrun', lambda: self.qrun.toggle())

        setkey('escape', lambda: print('esc explore'))

        sys.modules['m'].callbackResize.append(self.res)
        self.res()

    def copyCB(self):  #CLIPBOARD
        self.collized = [x for x in self.btns if x.select == True]

        self.sourceDir = os.getcwd()

        if self.collized:

            if self.current_dir[-1] == "/":
                self.current_dir = self.current_dir[:-1]
            self.mimeData = QMimeData()

            self.urls = []

            for e in self.collized:
                self.urls.append(
                    QUrl.fromLocalFile(self.current_dir + '/' + e.value))

            self.mimeData.setUrls(self.urls)

            #--------------------------------
            cb = self.app.clipboard()
            cb.clear(mode=cb.Clipboard)
            self.app.clipboard().setMimeData(self.mimeData, mode=cb.Clipboard)

    def pasteCB(self):  #CLIPBOARD
        print("paste")
        print(self.app.clipboard().mimeData().urls())

        links = []
        mimeurls = self.app.clipboard().mimeData().urls()
        for url in mimeurls:
            url = QUrl(url)
            links.append(url.toLocalFile())

        cp = sys.modules['scr.core'].core.copy
        cp(sys.modules['scr.core'].core, self.sourceDir, links, os.getcwd())
        self.refresh()

        pass

    def keyEnter(self):
        try:
            self.selectBtn = self.btns[self.newSelect]
        except:
            self.selectBtn = self.btns[0]

        self.selectFile = os.path.normpath(self.current_dir + '/' +
                                           self.selectBtn.value)

        if os.path.isdir(self.selectFile):
            self.setDir(self.current_dir + '/' + self.selectBtn.value)
            self.history.set(self.current_dir, self.scroll_pos)

        else:
            self.setDir(self.current_dir + '/' + self.selectBtn.value)
            self.lm_menu()

    def selectFiles(self):
        self.collized = [x.value for x in self.btns if x.select == True]
        return self.collized

    def keyArrow(self, e, mod=0):
        if self.btns:

            iconPerRow = int(self.scroll.width() / self.icon_size)
            iconPerCol = int(self.scroll.height() / self.icon_size)
            selectOn = [x for x in self.btns if x.select == True]

            frameTop = self.scroll_pos
            frameBottom = self.scroll_pos + iconPerCol * 120

            if selectOn:
                #select = selectOn[:1][0].value
                slo = self.select_old[-1]

                #select = self.btns[self.select_old[-1]][0].value
                select = self.btns[slo].value
            else:
                select = self.btns[0].value

            for i in range(len(self.btns)):

                if select == self.btns[i].value:

                    if e == 'left':
                        if i - 1 >= -1:
                            self.deselect()
                            if i - 1 == -1:
                                self.newSelect = 0
                            else:
                                self.newSelect = i - 1
                            b = self.btns[self.newSelect]

                            b.select = True
                            b.leaveEvent(QMouseEvent)
                    elif e == 'right':
                        if i + 1 < len(self.btns):
                            self.deselect()
                            self.newSelect = i + 1
                            b = self.btns[self.newSelect]

                            b.select = True
                            b.leaveEvent(QMouseEvent)
                    elif e == 'up':
                        if i - iconPerRow >= 0:
                            self.deselect()
                            self.newSelect = i - iconPerRow
                            b = self.btns[self.newSelect]
                            b.select = True
                            b.leaveEvent(QMouseEvent)
                    elif e == 'down':
                        if i + iconPerRow < len(self.btns):
                            self.deselect()
                            self.newSelect = i + iconPerRow
                            b = self.btns[self.newSelect]
                            b.select = True
                            b.leaveEvent(QMouseEvent)

                    if mod == 1:
                        for e in self.select_old:

                            b = self.btns[e]
                            b.select = True
                            b.leaveEvent(QMouseEvent)

                        self.select_old += [self.newSelect]
                    else:
                        self.select_old = [self.newSelect]
                    print(self.select_old)
                    ######################################################

                    selectOnLine = int(self.newSelect / iconPerRow)
                    PosOnSelect = selectOnLine * self.icon_size

                    if PosOnSelect < frameTop:
                        self.scroll_pos = PosOnSelect

                    elif PosOnSelect > frameBottom - 120:
                        self.scroll_pos = PosOnSelect - 120 * 3

            if e == 'enter':
                pass

    def scrollMoved(self):
        #self.targetType = None
        pass

    def mousePressed(self, e):  #НЕ РАБОТАЕТ
        self.globalPos = e.globalPos()

    def scrollBarPress(self, e):
        self.vbar = self.scroll.verticalScrollBar()
        frameHeight = self.scroll.height()
        current = self.vbar.value()
        currentClick = int(self.window.height() * (e.y() / frameHeight))

        min = current
        max = min + (self.window.height() - self.vbar.maximum())
        if not (currentClick > min and currentClick < max):

            if currentClick > max:
                self.scroll_pos += self.scroll.height()
            elif currentClick < min:
                self.scroll_pos -= self.scroll.height()

        else:  #Scroll Btn
            self.dragStartScroll = currentClick - min  # нужно, чтобы убрать дерганье
        super(explore, self).mousePressEvent(e)

    def scrollBarMove(self, e):
        scrH = self.scroll.height()
        wH = self.window.height()  #self.vbar.maximum()
        pos = e.y() / scrH
        try:
            self.hardScroll((wH * pos) - self.dragStartScroll)
        except:
            pass

    def wh_change(self, e):

        #print(e, self.scroll_pos, self.targetType)
        #if self.targetType == None:

        #EEEE
        #self.scroll_pos = self.scroll_pos_old = e

        pass

    def wh(self, e):
        max = self.scroll.verticalScrollBar().maximum()
        #self.targetType = 'wheel'

        self.scroll_pos = self.scroll_pos - e.angleDelta().y()

        if self.scroll_pos > max:
            self.hardScroll(max - 1)
        elif self.scroll_pos < 0:
            self.hardScroll(0)

    def hardScroll(self, e):
        self.scroll_pos = self.scroll_pos_old = e

    def ani_func(self):

        self.scroll_pos_old = self.scroll_pos_old - (
            (self.scroll_pos_old - self.scroll_pos) / 4)
        self.scroll.verticalScrollBar().setValue(self.scroll_pos_old)

    def setSize(self, *size):
        h, v = size
        print(self.size())
        self.window.setFixedWidth(h)

        self.setFixedSize(*size)
        self.scroll.setFixedSize(h, v - 20)

        self.maxWidth = int(self.width() / self.icon_size) * self.icon_size
        self.reposition()

    def run_c(self, command):
        """Executes a system command."""

    def lm_cmd(self):
        cmd = self.lb.text() + ' ' + "'" + self.selectFile + "'"
        self.cmd_run(cmd)

    def rm_cmd(self):
        cmd = self.lb.text()
        self.cmd_run(cmd)

    def cmd_run(self, command):
        print(command)

        #subprocess.Popen(command, shell=True, stdout=subprocess.PIPE)
        pros = QProcess(self)
        pros.finished.connect(self.proc_finish)

        str = "/bin/sh -c \"" + command + "\""

        pros.start(str)
        try:
            self.mousemenu.close()
        except:
            pass
        #self.scroll.verticalScrollBar().setValue = self.scroll_pos
        #update

        pass

    def proc_finish(self):

        self.refresh()

    def lm_menu(self):
        print(self.selectFile)
        self.cmd_run("xdg-open " + self.selectFile)

        return
        #НУЖНО ЛИ МЕНЮ НА ЛЕВЫЙ КЛИК?
        #НАВЕРНОЕ ЛУЧШЕ ОРГАНИЗОВАТЬ ЧЕРЕЗ ПРОБЕЛ
        self.mousemenu = QMenu(self)
        act = QWidgetAction(self)

        self.menuMain = QWidget(self)
        self.menuMain.setStyleSheet('background: #111')

        #XDG-OPEN
        self.btn_open = QPushButton(self.menuMain)

        self.btn_open.setStyleSheet(
            "background: gray; text-align: left; padding-left: 3px")
        self.btn_open.setText("Open")
        self.btn_open.setFixedSize(194, 20)
        self.btn_open.pressed.connect(
            lambda: self.cmd_run("xdg-open " + self.selectFile))
        self.btn_open.move(3, 3)

        #INPUT CMD
        self.lb = QLineEdit(self.menuMain)
        self.lb.move(3, 23)
        self.lb.setFixedSize(194, 20)
        self.lb.setPlaceholderText('cmd')
        self.lb.setStyleSheet('background: white')

        self.lb.returnPressed.connect(self.lm_cmd)

        self.menuMain.setFixedSize(200, 100)
        act.setDefaultWidget(self.menuMain)
        self.mousemenu.addAction(act)
        try:
            self.mousemenu.exec_(self.globalPos)
        except:
            self.mousemenu.exec_([0, 0])

    def contextMenuEvent(self, event):
        self.mousemenu = QMenu(self)
        act = QWidgetAction(self)

        self.menuSecond = QWidget(self)
        self.menuSecond.setStyleSheet('background: #511')

        self.lb = QLineEdit(self.menuSecond)
        self.lb.move(3, 3)
        self.lb.setFixedSize(194, 20)
        self.lb.setPlaceholderText('cmd')
        self.lb.setStyleSheet('background: white')

        self.lb.returnPressed.connect(self.rm_cmd)

        self.menuSecond.setFixedSize(200, 100)
        act.setDefaultWidget(self.menuSecond)
        self.mousemenu.addAction(act)
        self.mousemenu.exec_(self.globalPos)

    def btnsClear(self):
        if len(self.btns):
            for i in self.btns:
                i.deleteLater()
                del i
            del self.btns[:]  # че за хрень

    def setDir(self, dir):
        dir = dir.replace("//", "/")

        #dir_enc = self.dFilter.enc(dir)
        #dir_dec = self.dFilter.dec(dir_enc)

        if os.path.isdir(dir) != True:

            if self.selectBtn.select != True:
                self.selectBtn.select = True
            return

        self.btnsClear()

        self.current_dir = dir.replace("//", "/")

        self.core.sort = 'abs123'

        self.files = self.core.read_dir(self.current_dir)

        #FILTER DIR
        self.parent.parent().address.setText(dir)

        os.chdir(dir)

        #self.history.set(self.current_dir)

        self.cycle = self.tmpL = 0
        self.asyncRender()

        self.refreshtime = QTimer()
        self.refreshtime.timeout.connect(self.refresh)
        self.refreshtime.start(1000)
        """
        self.ftime = QTimer()
        self.ftime.timeout.connect(self.dir_final)
        self.ftime.start(1000/60)
        """
        sys.modules['m'].setProgramName(sys.modules['appName'] + ' - ' +
                                        os.path.basename(os.getcwd()))

        #def dir_final(self):
        #    self.ftime.deleteLater()
        """
        try:
            self.btns[0].select = True
            self.btns[0].leaveEvent(QMouseEvent)
        except:
            print("first select btns error")
        """

    def redo(self):
        link = self.history.get(1)
        if link != None:
            #self.hardScroll()
            self.hardScroll(link[2])
            self.setDir(link[1])

    def undo(self):
        link = self.history.get(-1)
        if link != None:

            self.hardScroll(link[2])
            self.setDir(link[1])

    def selectAll(self):
        #selectOn = next((x for x in self.btns if x.select == True), None)
        selectOn = [x for x in self.btns if x.select == True]

        if not len(selectOn) == len(self.btns):
            for b in self.btns:
                b.selected()
                b.leaveEvent(QMouseEvent)
        else:
            for b in self.btns:
                b.unselected()
                b.leaveEvent(QMouseEvent)

    def deselect(self):
        for b in self.btns:
            b.unselected()
            b.leaveEvent(QMouseEvent)

    def asyncRender(self):

        self.renderTime = QTimer()
        self.renderTime.timeout.connect(self.render)
        self.renderTime.start(1000 / 60)

    def getTextSplit(self, text):
        def width(t):
            return label.fontMetrics().boundingRect(t).width()

        label = QLabel()
        mw = self.icon_size - 15
        spl = text.split(' ')

        group = ''
        for item in spl:
            if width(item) > mw:
                i = 0
                w = 0
                letter = ''
                while True:
                    w = w + width(item[i])
                    if w > mw:
                        w = 0
                        letter += ' ' + item[i]
                    elif item[i] == '-':
                        w = 0
                        letter += item[i] + ' '
                    else:
                        letter += item[i]

                    i = i + 1
                    if i == len(item):
                        break

                group += letter + ' '
            else:
                group += item + ' '
        return group

    def rebuild(self):
        btns_rebuild = []
        for f in self.btns:
            try:
                f.move(1, 1)
                btns_rebuild.append(f)
            except:
                pass
        self.btns = btns_rebuild

    def res(self):
        print('a', self.window.size(), self.size())

    def reposition(self):
        if self.btns == None:
            self.btns = []
        self.rebuild()
        #if f.value not in self.files:
        #    print(f.value)
        #self.create_button(f.value)
        #123
        i = 0
        l = 0

        for f in self.btns:

            count_hoz = int(self.maxWidth / self.icon_size)
            x = (self.icon_size * i) % self.maxWidth
            y = self.icon_size * l
            try:
                f.move(x, y)
            except:
                print('ERROR BTN', f)

            if i % count_hoz == count_hoz - 1:
                l = l + 1
            i = i + 1

        #WINDOW SCROLL
        m = sys.modules['m']

        if self.window.height() <= self.height() - m.addrborder.height():
            self.window.setFixedHeight(self.height() - m.addrborder.height())
        else:
            self.window.setFixedHeight((l + 1) * self.icon_size)

    def dir_mod(self):
        self.refresh()

    def create_button(self, f):
        btn = object_file(self.window)
        realfile = self.current_dir + '/' + f

        #btn.realfile = realfile
        if core.type_file(self, realfile) == 'folder':
            btn.setIconFromTheme('folder')
        else:
            ic = iconProvider()
            ic = ic.icon(QFileInfo(realfile))

            btn.setIcon(ic)

        btn.setFileSize(120, 120)
        btn.setText(f)
        btn.init()

        btn.value = f
        btn.clicked.connect(self.btn_press)

        btn.setTrigger('123')
        self.btns.append(btn)

    def refresh(self):

        dir = self.current_dir

        #dir_dec = d.dec(dir)

        self.core.sort = 'abs123'

        #GET NEW LS`
        self.newfiles = self.core.read_dir(dir)

        l1 = set(self.files)
        l2 = set(self.newfiles)

        toDel = l1 - l2

        if len(toDel) > 0:

            for d in toDel:
                for btn in self.btns:
                    if d == btn.value:
                        btn.deleteLater()

        toAdd = l2 - l1

        unique = list(dict.fromkeys(toAdd))
        for f in unique:

            self.create_button(f)

        def btn_text(elem):
            return core.nat_keys(self, elem.value)[0]

        if self.btns != None:
            self.btns.sort(key=btn_text)

        self.btns = self.splitFolders(self.btns)
        self.files = self.newfiles

        self.reposition()

    def splitFolders(self, source_files):
        if source_files == None:
            return
        if len(source_files) == 0:
            return

        folders = []
        files = []

        if type(source_files[0]) is object_file:
            for file in source_files:
                realfile = self.current_dir + '/' + file.value

                if file.value[0] != '.' or self.hidden != True:
                    if core.type_file(self, realfile) == 'folder':
                        folders.append(file)
                    else:
                        files.append(file)

        else:
            for file in source_files:
                realfile = self.current_dir + '/' + file

                if file[0] != '.' or self.hidden != True:
                    if core.type_file(self, realfile) == 'folder':
                        folders.append(file)
                    else:
                        files.append(file)
        return folders + files

    def render(self):
        if len(self.files) == 0:
            return

        i = self.cycle
        l = self.tmpL

        combine = self.splitFolders(self.files)
        cycleAll = int(len(combine) / 50) * 50
        try:
            float_perc = self.cycle / cycleAll
        except ZeroDivisionError:
            float_perc = 0

        self.parent.parent().setLoadPercent(float_perc * 100)
        if int(float_perc) == 1:
            self.parent.parent().hideLoader()

        for file in range(len(combine)):
            file = file + self.cycle
            btn = object_file(self.window)

            try:
                realfile = self.current_dir + '/' + combine[file]
            except:
                print('733 explore', 'no found file error')
                return

            btn.realfile = realfile

            if core.type_file(self, realfile) == 'folder':
                btn.setIconFromTheme('folder')
            else:
                ic = iconProvider()
                ic = ic.icon(QFileInfo(realfile))

                btn.setIcon(ic)

            btn.setFileSize(120, 120)
            btn.setText(combine[file])
            btn.init()

            x = (self.icon_size * i) % self.maxWidth
            y = self.icon_size * l

            btn.move(x, y)

            count_hoz = int(self.maxWidth / self.icon_size)

            if file % count_hoz == count_hoz - 1:
                l = l + 1
            i = i + 1

            btn.value = combine[file]
            try:
                btn.date = os.path.getctime(realfile)
            except:
                btn.date = 0

            btn.clicked.connect(self.btn_press)
            btn.setTrigger(lambda: self.button_trigger)
            self.btns.append(btn)

            if i % 50 == 0:
                self.cycle = i
                self.tmpL = l
                break
            elif file == len(combine) - 1:
                self.renderTime = None
                self.cycle = -1
                break
        self.window.setFixedHeight((l + 1) * self.icon_size)

        if self.cycle == -1:
            return
        self.asyncRender()

    def collision(self):
        iscoll = []

        for b in self.btns:
            e = {
                'x': b.pos().x() + 120,
                'y': b.pos().y() + 120,
                'w': (b.pos().x() + b.size.width()) - b.size.width(),
                'h': (b.pos().y() + b.size.height()) - b.size.height()
            }

            if (e['x'] > self.select_rect_coll[0]  and e['w'] < self.select_rect_coll[2]) \
                and (e['y'] > self.select_rect_coll[1] and e['h'] < self.select_rect_coll[3]):
                iscoll.append(b)
        return iscoll

    def mousePressEvent(self, QMouseEvent):
        #self.unSelectAll() #Здесь сбивает работу мыши
        hover = self.isHoverButton()
        if not hover:
            self.deselect()

        self.press = True
        self.globalPos = QMouseEvent.globalPos()
        self.windowMouseCoord = self.window.mapFromGlobal(
            QMouseEvent.globalPos())

        self.selection = QFrame(self.window)

        self.selection.setStyleSheet("""
            background: rgba(140,200,255,.0);
            border-radius: 10px;
            border-width: 1px;
            border-style: solid;
            border-color: rgba(0,0,0,.2);

        """)
        self.startCoord = QPoint(self.windowMouseCoord)

        #if abs(self.startCoord.x()) > 1 and abs(self.startCoord.y()) > 1:
        self.selection.setFixedSize(0, 0)
        self.selection.move(5, 5)
        self.selection.move(self.windowMouseCoord)
        self.selection.show()

        if self.collized:
            self.itemsSelection = True
        else:
            self.itemsSelection = None

    def isHoverButton(self):
        for e in self.btns:
            try:
                if e.hover:
                    return True
            except:
                pass

    def isHoverButtonSelection(self):
        for e in self.collized:
            try:
                if e.hover:
                    return True
            except:
                pass

    def mouseMoveEvent(self, QMouseEvent):
        hover = self.isHoverButtonSelection()

        if self.itemsSelection:
            if hover:
                self.mimeData = QMimeData()

                urls = []
                for e in self.collized:
                    urls.append(
                        QUrl('file://' + self.current_dir + '/' + e.value))

                self.mimeData.setUrls(urls)
                self.drag = QDrag(self)
                self.drag.setMimeData(self.mimeData)

                ####################
                # ВОТ ЭТО ВАЖНО
                #self.drag.exec(Qt.LinkAction)
                self.drag.exec(Qt.CopyAction)

                if self.press:
                    pass
            else:
                for b in self.btns:
                    b.unselected()
                self.select_rect_coll = []
                self.collized = []

        else:  #if self.itemsSelection
            if self.btns == None:
                return

            self.select_rect_coll = [
                self.selection.x(),
                self.selection.y(),
                self.selection.x() + self.selection.width(),
                self.selection.y() + self.selection.height()
            ]
            for b in self.btns:
                b.unselected()
                b.leaveEvent(QMouseEvent)

            self.collized = self.collision()

            for b in self.collized:
                b.selected()
                b.leaveEvent(QMouseEvent)
                # b.clicked.emit()

            self.windowMouseCoord = self.window.mapFromGlobal(
                QMouseEvent.globalPos())
            self.mouse = [self.windowMouseCoord.x(), self.windowMouseCoord.y()]
            self.windowMouseCoord = self.window.mapFromGlobal(
                QMouseEvent.globalPos())
            movePoint = self.startCoord - self.windowMouseCoord

            invertmove = self.startCoord - movePoint

            #Drawing selection
            if movePoint.x() > 0 and movePoint.y() > 0:
                self.selection.setFixedSize(abs(movePoint.x()),
                                            abs(movePoint.y()))
                self.selection.move(invertmove)
            elif movePoint.x() > 0:
                self.selection.setFixedSize(abs(movePoint.x()),
                                            abs(movePoint.y()))
                self.selection.move(invertmove.x(), self.startCoord.y())
            elif movePoint.y() > 0:
                self.selection.setFixedSize(abs(movePoint.x()),
                                            abs(movePoint.y()))
                self.selection.move(self.startCoord.x(), invertmove.y())
            else:
                self.selection.setFixedSize(abs(movePoint.x()),
                                            abs(movePoint.y()))
            #############################################################################

    def mouseReleaseEvent(self, QMouseEvent):
        self.press = False
        self.globalPos = QMouseEvent.globalPos()
        self.windowMouseCoord = self.window.mapFromGlobal(
            QMouseEvent.globalPos())
        if self.startCoord == QPoint(self.windowMouseCoord):

            self.select_rect_coll = []
        self.selection.deleteLater()
        pass

    def mimeTypes(self):
        mimetypes = super().mimeTypes()
        mimetypes.append('text/plain')
        return mimetypes

    def dropEvent(self, event):

        #print(event.dropAction() == Qt.CopyAction)

        #url = QUrl()

        links = []
        for url in event.mimeData().urls():
            url = QUrl(url)
            links.append(url.toLocalFile())
        sys.modules['scr.core'].core.copy(sys.modules['scr.core'].core, links,
                                          os.getcwd())
        self.refresh()
        #path = url.toLocalFile().toLocal8Bit().data()
        #if os.path.isfile(path):
        #    print(path)
        #self.refresh()
    def dragEnterEvent(self, event):

        if event.mimeData().hasUrls():
            event.accept()
        else:
            event.ignore()

    def dragMoveEvent(self, Event):
        print('drag', Event.pos())

    def button_trigger(self, e):
        print('WIN', e.value)

    def btn_press(self):

        self.globalPos = self.sender().globalPos

        if self.sender().target == 'None':
            try:
                self.selectBtn.select = None
            except:
                print('none target')
                pass

        if not self.sender().select and self.sender().target == 'icon':
            try:
                self.selectBtn.select = None
                self.selectBtn.btn_update = True
                self.selectBtn.update()
            except:
                pass

            self.sender().select = True
            self.selectBtn = self.sender()
            return

        if self.sender() == self.selectBtn and self.sender().target == 'icon':

            try:
                self.selectBtn.select = None
                self.selectBtn.btn_update = True
                self.selectBtn.update()
            except:
                pass

            self.selectBtn = self.sender()
            self.selectFile = os.path.normpath(self.current_dir + '/' +
                                               self.sender().value)

            if os.path.isdir(self.selectFile):
                self.setDir(self.current_dir + '/' + self.sender().value)
                self.history.set(self.current_dir, self.scroll_pos)

            else:
                self.setDir(self.current_dir + '/' + self.sender().value)
                self.lm_menu()
Ejemplo n.º 16
0
class Snake(QWidget):

    def __init__(self):
        """Initial game configuration."""
        super().__init__()

        # GLOBAL VARIABLES
        self.zoom = 1 / 50
        self.zoom_coefficient = 1.3

        self.origin_position = [300, 300]

        self.move_start_position = [0, 0]  # start of the move
        self.move_origin_position = [0, 0]  # origin position at the beginning of the move

        # WIDGET CREATION
        self.canvas = QFrame(self, minimumSize=QSize(600, 600))

        # WIDGET LAYOUT
        self.main_v_layout = QVBoxLayout(self, margin=0)

        self.equation_inputs_layout = QVBoxLayout(self, margin=10)

        # add 3 equation inputs
        for i in range(3):
            self.equation_inputs_layout.addWidget(QLineEdit(self, textChanged=self.update))

        self.main_v_layout.addWidget(self.canvas)
        self.main_v_layout.addLayout(self.equation_inputs_layout)

        self.setLayout(self.main_v_layout)

        # WINDOW SETTINGS
        self.setWindowTitle('Window Title')
        self.setFont(QFont("Fira Code", 16))
        self.show()

    def calculate_y_values(self, function: str, x_values: list):
        """Calculates function values from a list of x values."""
        return evaluate_function_from_list(function, x_values)

    def paintEvent(self, event):
        """Paints on the canvas."""
        painter = QPainter(self)

        painter.setPen(QPen(Qt.black, Qt.SolidLine))
        painter.setBrush(QBrush(Qt.white, Qt.SolidPattern))

        # draw background
        painter.drawRect(0, 0, self.canvas.width(), self.canvas.height())

        # draw axes
        painter.drawLine(0,
                         self.canvas.height() - self.origin_position[1],
                         self.canvas.width(),
                         self.canvas.height() - self.origin_position[1])

        painter.drawLine(self.origin_position[0],
                         0,
                         self.origin_position[0],
                         self.canvas.height())

        # colors of the graphs
        function_colors = [Qt.red, Qt.blue, Qt.green]

        for i in range(self.equation_inputs_layout.count()):
            # get the text of the function
            function = self.equation_inputs_layout.itemAt(i).widget().text()

            # set color
            painter.setPen(QPen(function_colors[i], Qt.SolidLine))

            x_values = []
            for j in range(self.canvas.width()):
                # calculate the position from canvas to the zoomed and translated coordinates
                x_values.append((j - self.origin_position[0]) * self.zoom)

            # calculate the y values of the function
            y_values = self.calculate_y_values(function, x_values)

            # adjust the y values for the zoom
            for j in range(len(y_values)):
                if y_values[j] is not None:
                    y_values[j] = (j, self.canvas.height() - (y_values[j] / self.zoom + self.origin_position[1]))

            # form the graph by connecting the points
            for j in range(len(y_values) - 1):
                # if both exist, simply connect them
                if y_values[j] is not None and y_values[j + 1] is not None:
                    # if they visually don't connect on the canvas, don't connect them
                    if abs(y_values[j][1] - y_values[j + 1][1]) < self.canvas.height():
                        painter.drawLine(y_values[j][0], y_values[j][1], y_values[j + 1][0], y_values[j + 1][1])

    def mouseMoveEvent(self, event):
        """Is called when the mouse is moved and has a button pressed. Moves the axes and updates the canvas."""
        self.origin_position = [
            self.move_origin_position[0] + (event.pos().x() - self.move_start_position[0]),
            self.move_origin_position[1] - (event.pos().y() - self.move_start_position[1])
        ]
        self.update()

    def mousePressEvent(self, event):
        """Is called when the mouse is pressed. Starts to move the axes."""
        self.move_start_position = [event.pos().x(),
                                    event.pos().y()]
        self.move_origin_position = list(self.origin_position)

    def wheelEvent(self, event):
        """Is called when the mouse wheel is moved. Adjusts zoom and updates the canvas."""
        if event.angleDelta().y() > 0:
            self.zoom /= self.zoom_coefficient
        else:
            self.zoom *= self.zoom_coefficient

        self.update()
Ejemplo n.º 17
0
class StreamScope( QWidget ):
    resize_signal  = pyqtSignal(int)
    resolutions    = [
        '320x200',  # CGA
        '320x240',  # QVGA
        '480x320',  # HVGA
        '640x480',  # VGA
        '720x480',  # 480p
        '800x480',  # WVGA
        '854x480',  # WVGA (NTSC+)
        '1024x576', # PAL+
        '1024x768', # XGA
        '1280x720', # HD
        '1280x768', # WXGA
        'Elastic'
    ]
    def __init__( self ):
        super().__init__()
        self.title_bar_h   = self.style().pixelMetric( QStyle.PM_TitleBarHeight )
        self.devices       = get_video_devices()
        self.device        = '/dev/{0}'.format( self.devices[0] )
        res                = self.resolutions[0].split( 'x' )
        self.res_w         = int( res[0])
        self.res_h         = int( res[1])
        self.stream_thread = QThread(parent=self)
        self.streamer      = DeskStreamer()
        self.streamer.moveToThread( self.stream_thread )
        self.streamer.finished.connect( self.stream_thread.quit )
        self.streamer.finished.connect( self.streamer.deleteLater )
        self.stream_thread.finished.connect( self.stream_thread.deleteLater )
        self.stream_thread.start()
        self.stream_thread.started.connect( self.streamer.long_running )

        self.setWindowTitle( self.__class__.__name__ )
        self.setGeometry( 0, 0, self.res_w, self.res_h )

        self.init_layout()

        self.setWindowFlags( self.windowFlags() # Keep existing flags
                             | Qt.WindowStaysOnTopHint 
        )

        
        # Show the window
        self.show()


    def init_layout( self ):
        layout1 = QVBoxLayout()
        layout2 = QHBoxLayout()

        # Get video devices
        combo      = QComboBox( self )
        for i, device in enumerate( self.devices ):
            combo.addItem( device )
            if device == 'video20':
               combo.setCurrentIndex( i )
               self.device = '/dev/{0}'.format( device )
        combo.activated[str].connect( self.comboChanged )

        resolution = QComboBox( self )
        for res in self.resolutions:
            resolution.addItem( res )
        resolution.activated[str].connect( self.resolutionChanged )


        # Buttons
        self.stream_btn = QPushButton( self, objectName='stream_btn' )
        self.stream_btn.setText( 'Stream' )
        self.stream_btn.clicked.connect( self.stream )

        self.stop_btn = QPushButton( self, objectName='stop_btn' )
        self.stop_btn.setText( 'Stop' )
        self.stop_btn.clicked.connect( self.stop )

        self.frame = QFrame(self )
        style='''
        QPushButton{
            text-align:center;
        }
        QPushButton#stream_btn{
        background-color: orange;
        }
        QPushButton#stop_btn{
        background-color: white;
        }
        QFrame{
        background-color: #3E3E3E;
        border-bottom-right-radius: 10px;
        border-bottom-left-radius:  10px;
        }
        '''

        self.frame.setStyleSheet( style )

        layout2.addWidget( self.stream_btn )
        layout2.addWidget( self.stop_btn )
        layout2.addWidget( resolution )
        layout2.addWidget( combo )
        self.frame.setLayout( layout2 )
        self.frame.setFixedHeight( self.frame.height()*1.5 )

        self.viewfinder = QLabel(self, objectName='view_finder')
        style='''
        QLabel{
        background-color: cyan;
        }
        QLabel#view_finder{
        border:5px solid orange;
        background:transparent;
        padding:0px;
        }
        '''
        self.viewfinder.setStyleSheet( style )
        self.viewfinder.setMinimumWidth( 0 )
        self.viewfinder.setMinimumHeight( 0 )
        self.viewfinder.setGeometry( QRect( self.frame.pos().x(), 0, self.res_w, self.res_h ) )
        layout1.addWidget( self.viewfinder )
        layout1.addWidget( self.frame )
        layout1.setSpacing(0)
        layout1.setContentsMargins( 0 ,0 ,0, 0 )
        self.setAttribute( Qt.WA_TranslucentBackground )
        self.setLayout( layout1 )
        self.update_frustum()

    def stream( self ):
        if not self.streamer.isStreaming():
            self.stream_btn.setStyleSheet( 'background-color: grey;' )
            self.stop_btn.setStyleSheet( 'background-color: red;' )
            self.viewfinder.setStyleSheet( 'background-color: cyan; border:1px hidden red; background:transparent; ' )
            self.update_frustum()
            self.streamer.stream()

    def stop( self ):
        if self.streamer.isStreaming():
            self.stream_btn.setStyleSheet( 'background-color: orange;')
            self.stop_btn.setStyleSheet( 'background-color: white;')
            self.viewfinder.setStyleSheet( 'background-color: cyan; border:5px solid orange; background:transparent; padding:0;' )
            self.streamer.stop()

    def comboChanged( self, text ):
        self.stop()
        self.device = '/dev/{0}'.format( text )

    def resolutionChanged( self, text ):
        self.stop()
        
        if text == 'Elastic':
            min_w = 0
            min_h = 0
            win_w = self.viewfinder.width()
            win_h = self.viewfinder.height()
            
        else:
            res = text.split( 'x' )
            self.res_w = int( res[0] )
            self.res_h = int( res[1] )
            min_w = self.res_w
            min_h = self.res_h
            win_w = self.res_w
            win_h = self.res_h
            
        self.viewfinder.setMinimumWidth( min_w )
        self.viewfinder.setMinimumHeight( min_h )
        self.viewfinder.setGeometry( QRect( self.frame.pos().x(), 0, win_w, win_h ) )
        self.setGeometry( QRect( self.pos().x(), self.pos().y(), win_w, win_h ) )
        self.adjustSize()

    def debug_frustum( self ):
        print( 'Window: {0}, {1}x{2}'.format( self.pos(), self.width(), self.height() ) )
        print( 'Status: {0}'.format( self.title_bar_h ) )
        print( 'Scope:  {0}, {1}x{2}'.format( self.viewfinder.pos(),self.viewfinder.width(), self.viewfinder.height() ) )
        print( 'Device: {0}'.format( self.device ) )

    def update_frustum( self ):
        #self.debug_frustum()
        self.streamer.x = self.pos().x() + self.viewfinder.pos().x() + 0
        self.streamer.y = self.pos().y() + self.viewfinder.pos().y() + self.title_bar_h + 5
        self.streamer.width  = self.viewfinder.width()
        self.streamer.height = self.viewfinder.height()
        self.streamer.device = self.device

    def moveEvent( self, event ):
        self.stop()
        self.update_frustum()
        super().moveEvent(event)

    def resizeEvent(self, event = None):
        self.stop()
        self.update_frustum()
        self.resize_signal.emit( 1 )

    def closeEvent( self, event ):
        self.thread_clean_up()
        super().closeEvent( event )

    def thread_clean_up( self ):
        print( 'Cleaning up thread' )
        self.streamer.exit()
        self.stream_thread.quit()
        self.stream_thread.wait()