Refactored project to remove poetry. Pure pip used instead

This commit is contained in:
2023-10-24 13:48:24 +03:00
parent eb6309582e
commit e2b9025bc5
36 changed files with 106 additions and 2077 deletions

View File

@@ -0,0 +1,3 @@
from .dialog import PlotterDialog
__all__ = ("PlotterDialog",)

View File

@@ -0,0 +1,52 @@
import sys
from PyQt5.QtWidgets import QApplication
import numpy as np
from . import PlotterDialog
def main():
app = QApplication(sys.argv)
variables = [chr(ord("a") + i) for i in range(10)]
functions = [
"abs",
"acos",
"acosh",
"acot",
"asin",
"asinh",
"atan",
"avg",
"cos",
"cosh",
"cot",
"exp",
"lg",
"ln",
"log2",
"max",
"min",
"prod",
"sgn",
"sin",
"sinh",
"sqrt",
"sum",
"tanh",
"tan",
]
dlg = PlotterDialog(
variable_full_names={key: key.upper() for key in variables},
function_full_names={key: key.upper() for key in functions},
variable_values={key: np.sort(np.random.random(10)) * 10 for key in variables},
)
dlg.show()
sys.exit(app.exec())
main()

View File

@@ -0,0 +1,44 @@
from collections.abc import Callable
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel, QPushButton
from .flow_layout import FlowLayout
class ButtonGroup(QWidget):
def __init__(
self,
category: str,
full_names: dict[str, str],
buttons_action: Callable[[str], None],
parent=None,
):
super().__init__()
self.layout = QVBoxLayout() # Создание основного лаяутв
Doplayout = FlowLayout()
label = QLabel(category)
self.layout.addWidget(label)
for button_name in full_names:
button = QPushButton(button_name, self)
button.setFixedWidth(80)
button.setToolTip(
full_names[button_name]
) # Создание подскачоки при наведении
button.clicked.connect(
lambda _, name=button_name: buttons_action( # ignore checked state with _
name
)
) # Назначение кнопочке действия
Doplayout.addWidget(button) # отрисовывание кнопок
Doplayout.setContentsMargins(0, 0, 0, 0)
self.layout.addLayout(Doplayout)
self.layout.setContentsMargins(0, 0, 0, 0)
self.setLayout(self.layout)

View File

@@ -0,0 +1,229 @@
from PyQt5.QtCore import Qt, QTimer
from PyQt5.QtWidgets import (
QDialog,
QVBoxLayout,
QScrollArea,
QWidget,
QPushButton,
QLineEdit,
QMessageBox,
QHBoxLayout,
QCheckBox,
)
import numpy as np
from .button_group import ButtonGroup
from .graph_requester import GraphRequester
from parser import Parser
from graph_widget import GraphWidget
class PlotterDialog(QDialog):
focused_line_edit = None
def __init__(
self,
variable_full_names: dict[str, str],
function_full_names: dict[str, str],
variable_values: dict[str, np.ndarray],
):
super().__init__()
self.variable_values = variable_values
self.setWindowTitle("Графопостроитель")
layout_boss = QVBoxLayout() # главный лояут
self.scroll = QScrollArea()
self.scroll.verticalScrollBar().rangeChanged.connect(self.scroll_bottom)
scrollWidget = QWidget()
# self.scroll.setFrameShape(QFrame.NoFrame)
self.scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
self.inputs_layout = QVBoxLayout() # лаяут первой трети
self.inputs_layout.addStretch(1)
self.num_of_input = 0 # инициализация первого графика
self.add_input()
QTimer.singleShot(0, self.focus_first_input)
scrollWidget.setLayout(self.inputs_layout)
self.scroll.setWidgetResizable(True)
self.scroll.setWidget(scrollWidget)
layout_boss.addWidget(self.scroll)
Button_make_fun_button = QPushButton("+")
Button_make_fun_button.clicked.connect(self.add_input)
Button_make_fun_button.setFixedWidth(80)
layout_boss.addWidget(Button_make_fun_button, alignment=Qt.AlignRight)
layout_boss.addWidget(
ButtonGroup(
"Переменные",
full_names=variable_full_names,
buttons_action=self.insert_variable,
)
)
layout_boss.addWidget(
ButtonGroup(
"Функции",
full_names=function_full_names,
buttons_action=self.insert_function,
)
)
layout_boss.addSpacing(10)
buttons_layout = QHBoxLayout()
buttons_layout.setDirection(QHBoxLayout.RightToLeft)
submit_button = QPushButton("Построить")
reset_button = QPushButton("Сброс")
submit_button.clicked.connect(self.plot)
reset_button.clicked.connect(self.reset)
submit_button.setDefault(True)
buttons_layout.addWidget(submit_button)
buttons_layout.addWidget(reset_button)
self.subplots_checkbox = QCheckBox("Рисовать графики на отдельных осях")
buttons_layout.addWidget(self.subplots_checkbox)
buttons_layout.addStretch(1)
layout_boss.addLayout(buttons_layout)
self.setLayout(layout_boss)
def add_input(self):
self.num_of_input += 1
graph_requester = GraphRequester(self.num_of_input)
for line_edit in (graph_requester.LineEditX, graph_requester.LineEditY):
line_edit.focused_in.connect(
lambda line_edit=line_edit: self.set_focused_line_edit(line_edit)
)
graph_requester.LineEditGraf.focused_in.connect(
lambda: self.set_focused_line_edit(None)
)
self.inputs_layout.insertWidget(self.inputs_layout.count() - 1, graph_requester)
graph_requester.LineEditGraf.setFocus()
def set_focused_line_edit(self, line_edit: QLineEdit | None):
self.focused_line_edit = line_edit
def scroll_bottom(self):
self.scroll.verticalScrollBar().setValue(
self.scroll.verticalScrollBar().maximum()
)
def insert_string(self, string: str, string_cursor_padding=-1):
line_edit = self.focused_line_edit
if line_edit is None:
dlg = QMessageBox(
QMessageBox.Warning,
"Ошибка",
"Выберите поле ввода выражения",
)
dlg.exec()
return
cusor_pos = line_edit.cursorPosition()
text = line_edit.text()
if string_cursor_padding < 1:
string_cursor_padding = len(string)
line_edit.setText(text[:cusor_pos] + string + text[cusor_pos:])
line_edit.setCursorPosition(cusor_pos + string_cursor_padding)
self.scroll.ensureWidgetVisible(line_edit)
line_edit.setFocus()
def insert_variable(self, name: str):
self.insert_string(f" {name} ")
def insert_function(self, name: str):
string = f" {name}()"
self.insert_string(string, len(string) - 1) # len - 1 for cursor between braces
def plot(self):
xs, ys, labels = [], [], []
for i in range(self.inputs_layout.count()):
graph_requester = self.inputs_layout.itemAt(i).widget()
if graph_requester is not None:
x_expr = graph_requester.LineEditX.text()
y_expr = graph_requester.LineEditY.text()
label = graph_requester.LineEditGraf.text()
if len(x_expr) * len(y_expr) == 0:
dlg = QMessageBox(
QMessageBox.Warning,
"Ошибка",
f'График "{label}" не задан',
)
dlg.exec()
return
x = Parser(x_expr).evaluate(self.variable_values)
y = Parser(y_expr).evaluate(self.variable_values)
xs.append(x)
ys.append(y)
labels.append(label)
mult_subplots = self.subplots_checkbox.isChecked()
self.graph = GraphWidget(xs, ys, labels, mult_plots=mult_subplots)
self.graph.show()
def reset(self):
dlg = QMessageBox(
QMessageBox.Question,
"Очистка",
"Вы уверены, что хотите очистить все введённые выражения?",
buttons=QMessageBox.Yes | QMessageBox.No,
)
res = dlg.exec()
if res != QMessageBox.Yes:
return
while self.inputs_layout.count() > 0:
widget = self.inputs_layout.takeAt(0).widget()
if widget is not None:
widget.setParent(None)
self.focused_line_edit = None
self.num_of_input = 0
self.add_input()
def focus_first_input(self):
first_graph_request = self.inputs_layout.itemAt(0).widget()
if first_graph_request is not None:
first_graph_request.LineEditGraf.setFocus()

View File

@@ -0,0 +1,147 @@
#############################################################################
##
## Copyright (C) 2013 Riverbank Computing Limited.
## Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
## All rights reserved.
##
## This file is part of the examples of PyQt.
##
## $QT_BEGIN_LICENSE:BSD$
## You may use this file under the terms of the BSD license as follows:
##
## "Redistribution and use in source and binary forms, with or without
## modification, are permitted provided that the following conditions are
## met:
## * Redistributions of source code must retain the above copyright
## notice, this list of conditions and the following disclaimer.
## * Redistributions in binary form must reproduce the above copyright
## notice, this list of conditions and the following disclaimer in
## the documentation and/or other materials provided with the
## distribution.
## * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
## the names of its contributors may be used to endorse or promote
## products derived from this software without specific prior written
## permission.
##
## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
## $QT_END_LICENSE$
##
#############################################################################
from PyQt5 import QtCore, QtWidgets
class FlowLayout(QtWidgets.QLayout):
def __init__(self, parent=None, margin=-1, hspacing=-1, vspacing=-1):
super(FlowLayout, self).__init__(parent)
self._hspacing = hspacing
self._vspacing = vspacing
self._items = []
self.setContentsMargins(margin, margin, margin, margin)
def __del__(self):
del self._items[:]
def addItem(self, item):
self._items.append(item)
def horizontalSpacing(self):
if self._hspacing >= 0:
return self._hspacing
else:
return self.smartSpacing(QtWidgets.QStyle.PM_LayoutHorizontalSpacing)
def verticalSpacing(self):
if self._vspacing >= 0:
return self._vspacing
else:
return self.smartSpacing(QtWidgets.QStyle.PM_LayoutVerticalSpacing)
def count(self):
return len(self._items)
def itemAt(self, index):
if 0 <= index < len(self._items):
return self._items[index]
def takeAt(self, index):
if 0 <= index < len(self._items):
return self._items.pop(index)
def expandingDirections(self):
return QtCore.Qt.Orientations(0)
def hasHeightForWidth(self):
return True
def heightForWidth(self, width):
return self.doLayout(QtCore.QRect(0, 0, width, 0), True)
def setGeometry(self, rect):
super(FlowLayout, self).setGeometry(rect)
self.doLayout(rect, False)
def sizeHint(self):
return self.minimumSize()
def minimumSize(self):
size = QtCore.QSize()
for item in self._items:
size = size.expandedTo(item.minimumSize())
left, top, right, bottom = self.getContentsMargins()
size += QtCore.QSize(left + right, top + bottom)
return size
def doLayout(self, rect, testonly):
left, top, right, bottom = self.getContentsMargins()
effective = rect.adjusted(+left, +top, -right, -bottom)
x = effective.x()
y = effective.y()
lineheight = 0
for item in self._items:
widget = item.widget()
hspace = self.horizontalSpacing()
if hspace == -1:
hspace = widget.style().layoutSpacing(
QtWidgets.QSizePolicy.PushButton,
QtWidgets.QSizePolicy.PushButton,
QtCore.Qt.Horizontal,
)
vspace = self.verticalSpacing()
if vspace == -1:
vspace = widget.style().layoutSpacing(
QtWidgets.QSizePolicy.PushButton,
QtWidgets.QSizePolicy.PushButton,
QtCore.Qt.Vertical,
)
nextX = x + item.sizeHint().width() + hspace
if nextX - hspace > effective.right() and lineheight > 0:
x = effective.x()
y = y + lineheight + vspace
nextX = x + item.sizeHint().width() + hspace
lineheight = 0
if not testonly:
item.setGeometry(QtCore.QRect(QtCore.QPoint(x, y), item.sizeHint()))
x = nextX
lineheight = max(lineheight, item.sizeHint().height())
return y + lineheight - rect.y() + bottom
def smartSpacing(self, pm):
parent = self.parent()
if parent is None:
return -1
elif parent.isWidgetType():
return parent.style().pixelMetric(pm, None, parent)
else:
return parent.spacing()

View File

@@ -0,0 +1,56 @@
from PyQt5.QtWidgets import (
QVBoxLayout,
QHBoxLayout,
QLabel,
QPushButton,
QLineEdit,
QWidget,
)
from PyQt5.QtCore import pyqtSignal
class FocusNotifyingLineEdit(QLineEdit):
focused_in = pyqtSignal()
def focusInEvent(self, event):
self.focused_in.emit()
super(FocusNotifyingLineEdit, self).focusInEvent(event)
class GraphRequester(QWidget):
LineEditGraf: FocusNotifyingLineEdit
LineEditX: FocusNotifyingLineEdit
LineEditY: FocusNotifyingLineEdit
def __init__(self, nomer_grafika=1):
super().__init__()
layout = QVBoxLayout(self)
layout_x = QHBoxLayout()
layout_y = QHBoxLayout()
layout_close_and_name = QHBoxLayout()
self.LineEditGraf = FocusNotifyingLineEdit(f"график {nomer_grafika}")
NameX = QLabel("X")
NameY = QLabel("Y")
Name_Close = QPushButton("x")
self.LineEditX = FocusNotifyingLineEdit()
self.LineEditY = FocusNotifyingLineEdit()
layout_x.addWidget(NameX)
layout_x.addWidget(self.LineEditX)
layout_y.addWidget(NameY)
layout_y.addWidget(self.LineEditY)
Name_Close.clicked.connect(lambda: self.setParent(None))
layout_close_and_name.addWidget(self.LineEditGraf)
layout_close_and_name.addWidget(Name_Close)
layout.addLayout(layout_close_and_name) # Вложения названия и закрыть
layout.addLayout(layout_y) # Вложение
layout.addLayout(layout_x) # Вложение
layout.setContentsMargins(0, 0, 0, 0)
layout.addStretch(1)