Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #!/usr/bin/env ipython
- # coding: utf-8
- from PyQt5 import QtCore, QtWidgets, QtGui
- import sys, math, os, time, re, sqlite3, hashlib
- class User:
- def __init__(self, db, login: str):
- self.db = db
- self.login = login
- self.password = hashlib.md5().hexdigest()
- self.upload()
- def setLogin(self, newLogin: str):
- self.login = newLogin
- def setPassword(self, newPassword: str):
- self.password = str(newPassword)
- def setAvatar(self, newAvatar: str) -> bool:
- try:
- self.avatarUrl = newAvatar
- pixAvatar = QtGui.QPixmap('Avatars/' + self.avatarUrl)
- self.avatar = QtGui.QIcon(pixAvatar)
- return True
- except:
- return False
- def registerUser(self) -> int:
- self.userId = self.db.registerUser(self)
- return self.userId
- def upload(self) -> bool:
- if self.db.userIsRegistered(self):
- try:
- self.db.cursor.execute("SELECT `user_id`, `user_password`, `user_avatar`, `user_progress` FROM `users` WHERE `user_login` = '{}'".format(self.login))
- row = self.db.cursor.fetchone()
- self.userId = row[0]
- self.password = row[1]
- self.setAvatar(row[2])
- self.progress = row[3]
- return True
- finally:
- pass
- return False
- def deleteUser(self):
- self.db.deleteUser(self)
- self.__del__()
- class Database():
- users = list()
- def __init__(self, statusBar: QtWidgets.QStatusBar, progressBar: QtWidgets.QProgressBar, enterWindow):
- self.statusBar = statusBar
- self.progressBar = progressBar
- self.enterWindow = enterWindow
- self.progressBar.setValue(0)
- self.statusBar.showMessage("Подключение к базе данных...")
- self.connection = sqlite3.connect("nn.db")
- self.cursor = self.connection.cursor()
- self.progressBar.setValue(self.progressBar.maximum() * (1 / 7))
- self.statusBar.showMessage("Сканирование имеющихся таблиц...")
- self.cursor.execute("SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%'")
- existingTables = tuple(map(lambda row: row[0], self.cursor.fetchall()))
- self.progressBar.setValue(self.progressBar.maximum() * (2 / 7))
- if "users" not in existingTables:
- self.statusBar.showMessage("Создание таблицы users...")
- self.cursor.execute("CREATE TABLE `users` ("
- "`user_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,"
- "`user_login` TEXT DEFAULT '' UNIQUE,"
- "`user_password` TEXT DEFAULT '" + hashlib.md5().hexdigest() + "',"
- "`user_avatar` TEXT DEFAULT '001-man-13.png',"
- "`user_progress` REAL DEFAULT 0.0"
- ")")
- self.progressBar.setValue(self.progressBar.maximum() * (3 / 7))
- if "saves" not in existingTables:
- self.statusBar.showMessage("Создание таблицы saves...")
- self.cursor.execute("CREATE TABLE `saves` ("
- "`user_id` INTEGER NOT NULL,"
- "`type` TEXT DEFAULT '',"
- "`data` TEXT DEFAULT '',"
- "`course_id` INTEGER NOT NULL,"
- "`exercise_id` INTEGER NULL,"
- "FOREIGN KEY (`user_id`) REFERENCES `users`(`user_id`) "
- "ON DELETE CASCADE "
- "ON UPDATE CASCADE"
- ")")
- self.progressBar.setValue(self.progressBar.maximum() * (4 / 7))
- if "courses" not in existingTables:
- self.statusBar.showMessage("Создание таблицы courses...")
- self.cursor.execute("CREATE TABLE `courses` ("
- "`course_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,"
- "`face` TEXT DEFAULT 'book.png',"
- "`author` TEXT DEFAULT 'Пользователь',"
- "`title` TEXT DEFAULT '',"
- "`description` TEXT DEFAULT ''"
- ")")
- self.progressBar.setValue(self.progressBar.maximum() * (5 / 7))
- if "pages" not in existingTables:
- self.statusBar.showMessage("Создание таблицы pages...")
- self.cursor.execute("CREATE TABLE `pages` ("
- "`course_id` INTEGER NOT NULL,"
- "`page` INTEGER DEFAULT 1,"
- "`group` INTEGER NOT NULL,"
- "`title` TEXT DEFAULT '',"
- "`data` TEXT DEFAULT '',"
- "FOREIGN KEY (`course_id`) REFERENCES `courses`(`course_id`) "
- "ON DELETE CASCADE "
- "ON UPDATE CASCADE"
- ")")
- self.progressBar.setValue(self.progressBar.maximum() * (6 / 7))
- if "exercises" not in existingTables:
- self.statusBar.showMessage("Создание таблицы exercises...")
- self.cursor.execute("CREATE TABLE `exercises` ("
- "`exercise_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,"
- "`course_id` INTEGER NOT NULL,"
- "`page` INTEGER NOT NULL,"
- "`group` INTEGER NOT NULL,"
- "`description` TEXT DEFAULT '',"
- "`type` TEXT DEFAULT '',"
- "`data` TEXT DEFAULT '',"
- "FOREIGN KEY (`course_id`) REFERENCES `courses`(`course_id`)"
- "ON DELETE CASCADE "
- "ON UPDATE CASCADE"
- ")")
- self.progressBar.setValue(self.progressBar.maximum())
- self.uploadExistsUsers()
- def __del__(self):
- try:
- self.cursor.close()
- self.connection.close()
- except:
- pass
- def offerToEnter(self):
- self.enterWindow.show()
- def uploadExistsUsers(self):
- self.progressBar.setValue(0)
- self.statusBar.showMessage("Загрузка пользователей...")
- self.cursor.execute("SELECT `user_login` FROM `users`")
- rows = self.cursor.fetchall()
- l = len(rows)
- for i in range(l):
- foundedUser = User(self, rows[i][0])
- self.users.append(foundedUser)
- self.progressBar.setValue(self.progressBar.maximum() * (i + 1) / l)
- self.progressBar.setValue(self.progressBar.maximum())
- def userIsRegistered(self, user: User) -> bool:
- self.cursor.execute("SELECT `user_login` FROM `users`")
- rows = self.cursor.fetchall()
- for row in rows:
- if row[0] == user.login:
- return True
- return False
- def registerUser(self, user: User) -> int:
- try:
- if not self.userIsRegistered(user):
- self.users.append(user)
- self.cursor.execute("INSERT INTO `users` (`user_login`, `user_password`, `user_avatar`) VALUES ('{}', '{}', '{}')".format(user.login, user.password, user.avatarUrl))
- else:
- self.cursor.execute("UPDATE `users` SET `user_password` = '{}', `user_avatar` = '{}' WHERE `user_login` = '{}'".format(user.password, user.avatarUrl, user.login))
- self.connection.commit()
- user.upload()
- return user.userId
- finally:
- return -1
- def deleteUser(self, user: User) -> bool:
- try:
- if self.userIsRegistered(user):
- self.users.remove(user)
- self.cursor.execute("DELETE FROM `users` WHERE `user_login` = '{}'".format(user.login))
- self.connection.commit()
- return True
- finally:
- return False
- loginRegExp = QtCore.QRegExp(r"[a-zA-Zа-яА-Я\_\$][a-zA-Zа-яА-Я\_\$\-0-9]*")
- currentUser = None
- mainFrame = None
- class StyledLabel(QtWidgets.QLabel):
- def __init__(self, line: str="", parent=None):
- super().__init__(line, parent)
- class RegistrationWindow(QtWidgets.QWidget):
- def __init__(self, enterWindow):
- super().__init__()
- self.setWindowTitle("Регистрация")
- self.enterWindow = enterWindow
- self.newLogin = enterWindow.loginEdit.text()
- self.vbox = QtWidgets.QVBoxLayout(self)
- self.hbox1 = QtWidgets.QHBoxLayout()
- self.hbox2 = QtWidgets.QHBoxLayout()
- self.hbox3 = QtWidgets.QHBoxLayout()
- self.buttonGo = QtWidgets.QPushButton("Зарегистрироваться")
- self.buttonGo.clicked.connect(self.register)
- self.label1 = StyledLabel("Аватар:")
- self.label1.setFixedWidth(96)
- self.label1.setAlignment(QtCore.Qt.AlignRight)
- self.label2 = StyledLabel("Логин:")
- self.label2.setFixedWidth(96)
- self.label2.setAlignment(QtCore.Qt.AlignRight)
- self.label3 = StyledLabel("Пароль:")
- self.label3.setFixedWidth(96)
- self.label3.setAlignment(QtCore.Qt.AlignRight)
- self.lineEdit = QtWidgets.QLineEdit()
- self.lineEdit.setValidator(QtGui.QRegExpValidator(loginRegExp))
- self.lineEdit.setMaxLength(50)
- self.avatarsBox = QtWidgets.QComboBox()
- self.avatarsBox.setIconSize(QtCore.QSize(48, 48))
- pixList = os.listdir("Avatars")
- pixList.sort()
- for pixName in pixList:
- self.avatarsBox.addItem(QtGui.QIcon("Avatars/" + pixName), pixName)
- self.pwdEdit = QtWidgets.QLineEdit()
- self.pwdEdit.setEchoMode(QtWidgets.QLineEdit.Password)
- self.pwdEdit.setMaxLength(32)
- self.buttonShowPassword = QtWidgets.QCheckBox()
- self.buttonShowPassword.stateChanged.connect(self.checked)
- self.hbox1.addWidget(self.label1)
- self.hbox1.addWidget(self.avatarsBox)
- self.hbox2.addWidget(self.label2)
- self.hbox2.addWidget(self.lineEdit)
- self.hbox3.addWidget(self.label3)
- self.hbox3.addWidget(self.pwdEdit)
- self.hbox3.addWidget(self.buttonShowPassword)
- self.vbox.addLayout(self.hbox1)
- self.vbox.addLayout(self.hbox2)
- self.vbox.addLayout(self.hbox3)
- self.vbox.addWidget(self.buttonGo)
- def checked(self, newState):
- if newState == QtCore.Qt.Checked:
- self.pwdEdit.setEchoMode(QtWidgets.QLineEdit.Normal)
- else:
- self.pwdEdit.setEchoMode(QtWidgets.QLineEdit.Password)
- def showEvent(self, event):
- global db
- db.statusBar.showMessage("Регистрация пользователя...")
- self.desktop = QtWidgets.QApplication.desktop()
- x = (self.desktop.width() - self.width()) // 2
- y = (self.desktop.height() - self.height()) // 2
- self.move(x, y)
- def register(self):
- global db
- newUser = User(db, self.lineEdit.text())
- newUser.setAvatar(self.avatarsBox.currentText())
- newUser.setPassword(hashlib.md5(self.pwdEdit.text().encode()).hexdigest())
- db.registerUser(newUser)
- self.hide()
- self.enterWindow.uploadUsers()
- self.enterWindow.show()
- class UsersListItem(QtWidgets.QListWidgetItem):
- def __init__(self, user: User, parent=None):
- super().__init__(parent)
- self.user = user
- self.setSizeHint(QtCore.QSize(0, 64))
- def updateItem(self):
- self.user.upload()
- self.setIcon(self.user.avatar)
- self.setText("{0}\nПрогресс: {1: >5.1f}%\n{2:\u25CB<10}".format(self.user.login, int(self.user.progress * 10) / 10.0, "\u25CF" * int(self.user.progress // 10)))
- def updateUser(self, newUser: User):
- self.user = newUser
- self.updateItem()
- class UsersList(QtWidgets.QListWidget):
- selectedItem = None
- autoSelect = False
- def __init__(self, enterWindow=None):
- super().__init__(enterWindow)
- self.enterWindow = enterWindow
- self.setSortingEnabled(True)
- self.setAlternatingRowColors(True)
- self.setIconSize(QtCore.QSize(48, 48))
- self.itemSelectionChanged.connect(self.itemIsSelected)
- self.itemClicked.connect(self.itemIsClicked)
- def updateList(self, enteredLogin: str=""):
- global db
- self.clear()
- if enteredLogin == "":
- for user in db.users:
- item = UsersListItem(user)
- item.updateItem()
- self.addItem(item)
- else:
- enteredLogin = enteredLogin.lower()
- for user in db.users:
- if user.login.lower().find(enteredLogin) != -1:
- item = UsersListItem(user)
- item.updateItem()
- self.addItem(item)
- if user is db.users[0]:
- self.autoSelect = True
- item.setSelected(True)
- self.autoSelect = False
- def itemIsSelected(self):
- if len(self.selectedItems()) > 0:
- self.selectedItem = self.selectedItems()[0]
- if not self.autoSelect:
- self.enterWindow.loginEdit.setText(self.selectedItem.user.login)
- self.enterWindow.loginEdit.setFocus(True)
- def itemIsClicked(self, clickedItem):
- if len(self.selectedItems()) > 0:
- self.selectedItem = self.selectedItems()[0]
- self.enterWindow.loginEdit.setText(self.selectedItem.user.login)
- self.enterWindow.loginEdit.setFocus(True)
- def deleteItem(self, item: UsersListItem):
- try:
- self.removeItemWidget(item)
- item.user.deleteUser()
- finally:
- return
- class EnterWindow(QtWidgets.QWidget):
- def __init__(self):
- super().__init__()
- self.setWindowTitle("Вход")
- self.vbox = QtWidgets.QVBoxLayout(self)
- self.loginEdit = QtWidgets.QLineEdit()
- self.loginEdit.setObjectName("loginEdit")
- self.loginEdit.setPlaceholderText("Введите логин")
- self.loginEdit.setValidator(QtGui.QRegExpValidator(loginRegExp))
- self.loginEdit.textChanged.connect(self.loginChanged)
- self.vbox.addWidget(self.loginEdit)
- self.usersList = UsersList(self)
- self.vbox.addWidget(self.usersList)
- self.hbox = QtWidgets.QHBoxLayout()
- self.pwdEdit = QtWidgets.QLineEdit()
- self.pwdEdit.setEchoMode(QtWidgets.QLineEdit.Password)
- self.pwdEdit.setMaxLength(32)
- self.pwdEdit.setPlaceholderText("Введите пароль")
- self.hbox.addWidget(self.pwdEdit)
- self.buttonShowPassword = QtWidgets.QCheckBox()
- self.buttonShowPassword.stateChanged.connect(self.checked)
- self.hbox.addWidget(self.buttonShowPassword)
- self.vbox.addLayout(self.hbox)
- self.buttonGo = QtWidgets.QPushButton("Войти")
- self.buttonGo.clicked.connect(self.enterUser)
- self.vbox.addWidget(self.buttonGo)
- self.buttonNew = QtWidgets.QPushButton("Новый пользователь")
- self.buttonNew.clicked.connect(self.createUser)
- self.vbox.addWidget(self.buttonNew)
- self.buttonDelete = QtWidgets.QPushButton("Удалить пользователя")
- self.buttonDelete.clicked.connect(self.deleteUser)
- self.vbox.addWidget(self.buttonDelete)
- def checked(self, newState):
- if newState == QtCore.Qt.Checked:
- self.pwdEdit.setEchoMode(QtWidgets.QLineEdit.Normal)
- else:
- self.pwdEdit.setEchoMode(QtWidgets.QLineEdit.Password)
- def showEvent(self, event):
- global db
- db.statusBar.showMessage("Выбор пользователя...")
- self.desktop = QtWidgets.QApplication.desktop()
- x = (self.desktop.width() - self.width()) // 2
- y = (self.desktop.height() - self.height()) // 2
- self.move(x, y)
- def uploadUsers(self):
- self.usersList.updateList()
- def loginChanged(self, enteredLogin):
- self.usersList.updateList(enteredLogin)
- if self.usersList.count() > 0 and len(enteredLogin) > 0:
- self.usersList.autoSelect = True
- self.usersList.item(0).setSelected(True)
- self.usersList.autoSelect = False
- def enterUser(self):
- if self.usersList.selectedItem is None:
- QtWidgets.QMessageBox.warning(self, "Ошибка", "Выберите пользователя.")
- else:
- pwd = hashlib.md5(self.pwdEdit.text().encode()).hexdigest()
- if pwd == self.usersList.selectedItem.user.password:
- global db, currentUser, mainFrame
- currentUser = self.usersList.selectedItem.user
- self.hide()
- mainFrame.menuBar.setEnabled(True)
- db.statusBar.showMessage("Добро пожаловать, {0:s}!".format(currentUser.login))
- mainFrame.reloadChoiceList()
- mainFrame.reloadUser()
- else:
- QtWidgets.QMessageBox.warning(self, "Ошибка", "Вы ввели неверный пароль пользователя.")
- def closeEvent(self, event):
- global currentUser
- if currentUser is None:
- QtWidgets.QApplication.quit()
- else:
- event.accept()
- def createUser(self):
- self.hide()
- self.registrationWindow = RegistrationWindow(self)
- self.registrationWindow.show()
- def deleteUser(self):
- try:
- global db
- user = self.usersList.selectedItem.user
- if db.userIsRegistered(user):
- self.usersList.deleteItem(self.usersList.selectedItem)
- self.usersList.updateList()
- self.loginEdit.setText("")
- finally:
- return
- class ProgressBar(QtWidgets.QProgressBar):
- progressBarBrightText = False
- def __init__(self, parent=None):
- super().__init__(parent)
- self.valueChanged.connect(self.loadingUpdate)
- def loadingUpdate(self, value):
- self.setFormat("{0:d}%".format(int(self.value() * 100 / self.maximum())))
- if value * 2 < self.maximum() + self.fontMetrics().width(self.format()) and self.progressBarBrightText:
- self.setStyleSheet("color: rgb(32, 32, 32);")
- self.progressBarBrightText = False
- elif value * 2 >= self.maximum() + self.fontMetrics().width(self.format()) and not self.progressBarBrightText:
- self.setStyleSheet("color: white;")
- self.progressBarBrightText = True
- class ProgressBarF(QtWidgets.QProgressBar):
- progressBarBrightText = False
- def __init__(self, parent=None):
- super().__init__(parent)
- self.valueChanged.connect(self.loadingUpdate)
- def loadingUpdate(self, value):
- self.setFormat("{0:.2f}%".format(self.value() * 100 / self.maximum()))
- if value * 2 < self.maximum() + self.fontMetrics().width(self.format()) and self.progressBarBrightText:
- self.setStyleSheet("color: rgb(32, 32, 32);")
- self.progressBarBrightText = False
- elif value * 2 >= self.maximum() + self.fontMetrics().width(self.format()) and not self.progressBarBrightText:
- self.setStyleSheet("color: white;")
- self.progressBarBrightText = True
- class CourseLabel(QtWidgets.QLabel):
- mouse = 0 # leaveEvent ~ 0, enterEvent ~ 1, mousePressEvent ~ 2
- def __init__(self, course_id: int):
- super().__init__()
- global db
- db.cursor.execute('SELECT face, author, title, description FROM courses WHERE course_id = {}'.format(course_id))
- row = db.cursor.fetchone()
- self.course_id, self.face, self.author, self.title, self.description = course_id, *row
- self.pixmap = QtGui.QPixmap('Images/' + self.face).scaledToHeight(240)
- self.setFixedHeight(256)
- def paintEvent(self, event):
- painter = QtGui.QPainter(self)
- painter.setBackgroundMode(QtCore.Qt.TransparentMode)
- painter.setRenderHint(QtGui.QPainter.Antialiasing, True)
- if self.mouse == 0:
- painter.setOpacity(0.125 if self.course_id & 0x01 else 0.0625)
- elif self.mouse == 1:
- painter.setOpacity(0.1875)
- else:
- painter.setOpacity(0)
- painter.fillRect(0, 0, self.width() - 1, self.height() - 1, QtGui.QBrush(QtCore.Qt.white, QtCore.Qt.SolidPattern))
- painter.setOpacity(1.0)
- painter.drawPixmap(8, 8, self.pixmap.width(), self.pixmap.height(), self.pixmap)
- painter.setFont(QtGui.QFont('Monaco', 10, QtGui.QFont.Bold))
- painter.setPen(QtGui.QPen(QtCore.Qt.white, 1))
- painter.drawText(
- QtCore.QRect(self.pixmap.width() + 16, 8, self.width() - self.pixmap.width() - 24, 240),
- QtCore.Qt.AlignLeft | QtCore.Qt.TextWordWrap,
- 'Автор: {}\nНазвание: {}'.format(self.author, self.title)
- )
- def showEvent(self, event):
- self.update()
- def resizeEvent(self, event):
- self.update()
- def enterEvent(self, event):
- self.mouse = 1
- self.update()
- self.setCursor(QtCore.Qt.PointingHandCursor)
- def leaveEvent(self, event):
- self.mouse = 0
- self.update()
- self.setCursor(QtCore.Qt.ArrowCursor)
- def mousePressEvent(self, event):
- self.mouse = 2
- self.update()
- self.setCursor(QtCore.Qt.ArrowCursor)
- """global mainFrame
- mainFrame.openCourse(self)"""
- """def CourseFrame(QtWidgets.QLabel):
- pass"""
- class MainArea(QtWidgets.QScrollArea):
- def __init__(self, mainFrame = None):
- super().__init__(mainFrame)
- def resizeEvent(self, event):
- self.mainPanel.setFixedWidth(self.width() - 21)
- if self.courses:
- for courseLabel in self.courses:
- courseLabel.setFixedWidth(self.width() - 23)
- class MainFrame(QtWidgets.QWidget):
- def __init__(self, mainWindow=None):
- super().__init__(mainWindow)
- self.mainWindow = mainWindow
- global mainFrame
- mainFrame = self
- self.vbox = QtWidgets.QVBoxLayout(self)
- self.vbox.setContentsMargins(0, 0, 0, 0)
- self.vbox.setSpacing(0)
- # Нижняя строка состояния
- self.statusBar = QtWidgets.QStatusBar(self)
- self.statusBar.setObjectName("mainStatusBar")
- self.statusBar.setFixedHeight(32)
- self.statusBar.setSizeGripEnabled(False)
- self.statusBar.setContentsMargins(0, 0, 3, 0)
- # Индикатор хода прогресса
- self.progressBar = ProgressBar(self.statusBar)
- self.progressBar.setAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter)
- self.progressBar.setFixedWidth(160)
- self.progressBar.setRange(0, 160)
- self.statusBar.addPermanentWidget(self.progressBar)
- self.userLabel = QtWidgets.QLabel()
- self.userLabel.setObjectName("userLabel")
- self.userLabel.setFixedHeight(100)
- self.userLabel.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignTop)
- self.userLabel.setContentsMargins(4, 8, 4, 4)
- self.choiceList = QtWidgets.QTreeWidget()
- self.choiceList.setObjectName("choiceList")
- self.choiceList.setHeaderHidden(True)
- self.choiceList.setAnimated(True)
- header = self.choiceList.header()
- header.setStretchLastSection(False)
- header.setSectionResizeMode(0, QtWidgets.QHeaderView.ResizeToContents)
- self.choiceList.setHeader(header)
- self.mainPanel = QtWidgets.QLabel()
- self.mainPanel.setObjectName('mainPanel')
- self.mainBox = QtWidgets.QVBoxLayout(self.mainPanel)
- self.mainBox.setContentsMargins(0, 0, 0, 0)
- self.mainBox.setSpacing(0)
- self.mainBox.setAlignment(QtCore.Qt.AlignHCenter)
- self.leftPanel = QtWidgets.QLabel()
- self.leftBox = QtWidgets.QVBoxLayout()
- self.leftBox.setContentsMargins(0, 0, 0, 0)
- self.leftBox.setSpacing(0)
- self.leftPanel.setLayout(self.leftBox)
- self.leftBox.addWidget(self.userLabel)
- self.leftBox.addWidget(self.choiceList)
- self.itemCourses = QtWidgets.QTreeWidgetItem(self.choiceList)
- self.itemCourses.setText(0, "Курсы")
- self.itemActiveCourses = QtWidgets.QTreeWidgetItem(self.itemCourses)
- self.itemActiveCourses.setText(0, "Активные курсы")
- self.itemNonactiveCourses = QtWidgets.QTreeWidgetItem(self.itemCourses)
- self.itemNonactiveCourses.setText(0, "Закрытые курсы")
- self.itemPrograms = QtWidgets.QTreeWidgetItem(self.choiceList)
- self.itemPrograms.setText(0, "Python-программы")
- # Режим пользователя
- self.splitter = QtWidgets.QSplitter()
- self.splitter.setOrientation(QtCore.Qt.Horizontal)
- self.splitter.addWidget(self.leftPanel)
- # Верхнее меню
- self.menuBar = QtWidgets.QMenuBar()
- self.menuBar.setFixedHeight(26)
- self.developerMenu = QtWidgets.QMenu("&Разработка")
- self.developerMenu.setLayoutDirection(QtCore.Qt.LeftToRight)
- self.modeGroup = QtWidgets.QActionGroup(self.developerMenu)
- self.modeGroup.setExclusive(True)
- self.menuBar.addMenu(self.developerMenu)
- self.userMode = QtWidgets.QAction("Режим пользователя", self.modeGroup)
- self.userMode.setCheckable(True)
- self.developerMode = QtWidgets.QAction("Режим разработчика", self.modeGroup)
- self.developerMode.setCheckable(True)
- self.developerMenu.addAction(self.userMode)
- self.developerMenu.addAction(self.developerMode)
- self.userMode.setChecked(True)
- self.menuBar.setDisabled(True)
- self.modeGroup.triggered.connect(self.modeToggled)
- self.vbox.addWidget(self.menuBar)
- self.vbox.addWidget(self.splitter)
- self.vbox.addWidget(self.statusBar)
- # Режим разработчика
- self.developerWidget = QtWidgets.QWidget()
- self.developerLayout = QtWidgets.QVBoxLayout(self.developerWidget)
- self.developerLayout.setSpacing(0)
- self.developerLayout.setContentsMargins(0, 0, 0, 0)
- self.buttonsArea = QtWidgets.QScrollArea()
- self.buttonsArea.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
- self.buttonsArea.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
- self.buttonsLabel = QtWidgets.QLabel()
- self.buttonsArea.setWidget(self.buttonsLabel)
- self.buttonsBox = QtWidgets.QHBoxLayout(self.buttonsLabel)
- self.buttonsBox.setContentsMargins(2, 2, 2, 2)
- self.buttonsBox.setSpacing(2)
- self.buttonExec = QtWidgets.QPushButton("Выполнить запрос (F5)")
- self.buttonExec.setShortcut(QtCore.Qt.Key_F5)
- self.buttonExec.setIcon(QtGui.QIcon("Images/exec.png"))
- self.buttonExec.setFixedWidth(210)
- self.buttonExec.clicked.connect(self.execCommand)
- self.buttonsBox.addWidget(self.buttonExec)
- self.buttonCommit = QtWidgets.QPushButton("Совершить транзакцию (F6)")
- self.buttonCommit.setShortcut(QtCore.Qt.Key_F6)
- self.buttonCommit.setIcon(QtGui.QIcon("Images/commit.png"))
- self.buttonCommit.setFixedWidth(240)
- self.buttonCommit.clicked.connect(self.commitCommand)
- self.buttonsBox.addWidget(self.buttonCommit)
- self.buttonClear = QtWidgets.QPushButton("Очистить консоль (Ctrl+R)")
- self.buttonClear.setShortcut(QtCore.Qt.ControlModifier | QtCore.Qt.Key_R)
- self.buttonClear.setIcon(QtGui.QIcon("Images/clear.png"))
- self.buttonClear.setFixedWidth(230)
- self.buttonClear.clicked.connect(self.clearCommand)
- self.buttonsBox.addWidget(self.buttonClear)
- self.buttonSave = QtWidgets.QPushButton("Сохранить SQL (Ctrl+S)")
- self.buttonSave.setShortcut(QtCore.Qt.ControlModifier | QtCore.Qt.Key_S)
- self.buttonSave.setIcon(QtGui.QIcon("Images/save.png"))
- self.buttonSave.setFixedWidth(220)
- self.buttonSave.clicked.connect(self.saveCommand)
- self.buttonsBox.addWidget(self.buttonSave)
- self.buttonsArea.setFixedHeight(30)
- self.buttonsLabel.setFixedSize(QtCore.QSize(self.buttonExec.width() + self.buttonCommit.width() + self.buttonClear.width() + self.buttonSave.width() + 10, 28))
- self.developerLayout.addWidget(self.buttonsArea)
- self.devSplitter = QtWidgets.QSplitter()
- self.devSplitter.setOrientation(QtCore.Qt.Vertical)
- self.devSplitter.setHandleWidth(4)
- self.developerLayout.addWidget(self.devSplitter)
- self.developerTextEdit = QtWidgets.QTextEdit()
- self.developerTextEdit.setObjectName("developerTextEdit")
- self.developerTextEdit.setWordWrapMode(False)
- self.devSplitter.addWidget(self.developerTextEdit)
- self.consoleTextEdit = QtWidgets.QTextEdit()
- self.consoleTextEdit.setObjectName("consoleTextEdit")
- self.consoleTextEdit.setReadOnly(True)
- self.consoleTextEdit.setWordWrapMode(False)
- self.devSplitter.addWidget(self.consoleTextEdit)
- def showEvent(self, event):
- self.update()
- def loadDatabase(self):
- global db
- db = Database(self.statusBar, self.progressBar, self.enterWindow)
- # Главная панель
- self.mainArea = MainArea()
- self.mainArea.setObjectName("mainArea")
- self.mainArea.mainBox = self.mainBox
- self.mainArea.mainPanel = self.mainPanel
- self.mainArea.courses = []
- self.mainArea.setWidget(self.mainPanel)
- db.cursor.execute('SELECT course_id FROM courses')
- self.course_ids = [row[0] for row in db.cursor.fetchall()]
- self.courses = []
- height = 0
- for course_id in self.course_ids:
- course = CourseLabel(course_id)
- self.courses.append(course)
- self.mainBox.addWidget(course)
- height += course.height()
- # Режим пользователя
- self.splitter.addWidget(self.mainArea)
- self.splitter.setSizes((320, self.mainArea.width()))
- self.mainPanel.setFixedWidth(self.mainArea.width() - 21)
- self.mainPanel.setFixedHeight(height)
- for course in self.courses:
- course.setFixedWidth(self.mainArea.width() - 23)
- self.splitter.setHandleWidth(4)
- """def openCourse(self, courseLabel: CourseLabel):
- self.currentCourseLabel = courseLabel
- self.hideListOfCourses()
- def hideListOfCourses(self):
- self.opacity1 = QtWidgets.QGraphicsOpacityEffect()
- self.mainArea.setGraphicsEffect(self.opacity1)
- self.anim1 = QtCore.QPropertyAnimation(self.opacity1, b"opacity")
- self.anim1.setDuration(250)
- self.anim1.setStartValue(1.0)
- self.anim1.setEndValue(0.0)
- self.anim1.start()
- self.anim1.finished.connect(self.showCourse)
- def showCourse(self):
- self.mainArea.hide()
- self.opacity2 = QtWidgets.QGraphicsOpacityEffect()
- # Открываем выбранный курс
- self.courseFrame =
- self.mainFrame.setGraphicsEffect(self.opacity2)
- self.mainFrame.setWindowOpacity(0.0)
- self.mainFrame.show()
- self.anim2 = QtCore.QPropertyAnimation(self.opacity2, b"opacity")
- self.anim2.setDuration(250)
- self.anim2.setStartValue(0.0)
- self.anim2.setEndValue(1.0)
- self.anim2.start()
- self.anim2.finished.connect(self.mainFrameIsVisible)"""
- def offerToEnter(self):
- global db
- db.offerToEnter()
- def modeToggled(self, action: QtWidgets.QAction):
- if self.userMode.isChecked():
- self.statusBar.showMessage("Режим пользователя.")
- self.vbox.replaceWidget(self.developerWidget, self.splitter)
- self.developerWidget.setVisible(False)
- self.reloadUser()
- self.reloadChoiceList()
- self.splitter.setVisible(True)
- if self.developerMode.isChecked():
- self.statusBar.showMessage("Режим разработчика.")
- self.vbox.replaceWidget(self.splitter, self.developerWidget)
- self.splitter.setVisible(False)
- self.developerWidget.setVisible(True)
- def reloadUser(self):
- self.userLabel.setText('<img src="Avatars/{}" width="64" height="64" align="left" /><span style="color: khaki; font-weight: bold"> {}</span><br><br> Прогресс: {:.1f}'.format(currentUser.avatarUrl, currentUser.login, currentUser.progress))
- def reloadChoiceList(self):
- for child in self.itemActiveCourses.takeChildren():
- self.itemActiveCourses.removeChild(child)
- for child in self.itemNonactiveCourses.takeChildren():
- self.itemActiveCourses.removeChild(child)
- global currentUser
- if currentUser:
- global db
- db.cursor.execute('SELECT course_id, face, title, description FROM courses')
- rows = db.cursor.fetchall()
- db.cursor.execute("SELECT course_id FROM saves WHERE user_id = {} AND type = 'isActive' AND data = 'True'".format(currentUser.userId))
- activeCourses = [r[0] for r in db.cursor.fetchall()]
- for row in rows:
- course_id, face, title, description = row
- newItem = QtWidgets.QTreeWidgetItem(self.itemActiveCourses if (course_id in activeCourses) else self.itemNonactiveCourses)
- newItem.setText(0, title)
- def execCommand(self, event):
- textCursor = QtGui.QTextCursor(self.developerTextEdit.textCursor())
- text = textCursor.selectedText()
- if text == "":
- text = self.developerTextEdit.toPlainText()
- self.statusBar.showMessage("Выполнение запроса...")
- self.progressBar.setValue(0)
- self.consoleTextEdit.moveCursor(QtGui.QTextCursor.End)
- self.consoleTextEdit.insertHtml("<span style='font-weight: bold; color: lightgray;'>{0:s}</span><br>".format(time.asctime()))
- queries = [q.replace("\\n", "<br>") for q in re.findall(r"\s*((?:(?:\'[^\']*?\')|(?:\"[^\"]*?\")|[^\"\';\\]|(?:\\(?:[^;]|\s)))+)\s*\\?;", text.replace("\\\n", "").replace("\n", " "))]
- global db
- timeSum, timeBegin, timeEnd = 0, 0, 0
- db.progressBar.setValue(0)
- try:
- qlen = len(queries)
- for i in range(qlen):
- timeBegin = time.time()
- self.consoleTextEdit.setTextColor(QtGui.QColor('gold'))
- self.consoleTextEdit.insertPlainText(queries[i] + ';\n')
- self.consoleTextEdit.setTextColor(QtGui.QColor('#336699'))
- db.cursor.execute(queries[i])
- timeEnd = time.time()
- self.consoleTextEdit.insertHtml("<span style='color: green;'>Запрос выполнен за {0:.3f} секунд.</span><br>".format(timeEnd - timeBegin))
- timeSum += timeEnd - timeBegin
- db.progressBar.setValue(db.progressBar.maximum() * (i + 1) / qlen)
- rows = list(db.cursor.fetchall())
- desc = False
- if db.cursor.description:
- rows.insert(0, [f[0] for f in db.cursor.description])
- desc = True
- if rows:
- rlen = len(rows)
- wcount = len(rows[0])
- wmaxlen = [0] * wcount
- for x in range(wcount):
- wlen = 0
- for y in range(rlen):
- wyxlen = len(str(rows[y][x]))
- if wyxlen > wlen:
- wlen = wyxlen
- wmaxlen[x] = wlen
- self.consoleTextEdit.insertPlainText("+")
- for x in range(wcount):
- self.consoleTextEdit.insertPlainText("-" * (wmaxlen[x] + 2) + ("+" if x < (wcount - 1) else "+\n"))
- if desc:
- for x in range(wcount):
- self.consoleTextEdit.insertPlainText("|" + str(rows[0][x]).center(wmaxlen[x] + 2, " "))
- self.consoleTextEdit.insertPlainText("|\n+")
- for x in range(wcount):
- self.consoleTextEdit.insertPlainText("-" * (wmaxlen[x] + 2) + ("+" if x < (wcount - 1) else "+\n"))
- for y in range(1, rlen):
- for x in range(wcount):
- self.consoleTextEdit.insertPlainText("| " + str(rows[y][x]).ljust(wmaxlen[x] + 1, " "))
- self.consoleTextEdit.insertPlainText("|\n")
- self.consoleTextEdit.insertPlainText("+")
- for x in range(wcount):
- self.consoleTextEdit.insertPlainText("-" * (wmaxlen[x] + 2) + ("+" if x < (wcount - 1) else "+\n"))
- self.progressBar.setValue(self.progressBar.maximum() / qlen * (i + 1))
- self.statusBar.showMessage("Запросы выполнены за {0:.3f} секунд.".format(timeSum))
- self.consoleTextEdit.insertHtml("<span style='color: green;'>Запросы выполнены за {0:.3f} секунд.</span><br><br>".format(timeSum))
- except (sqlite3.Error, sqlite3.Warning) as e:
- self.statusBar.showMessage("Режим разработчика.")
- self.consoleTextEdit.insertHtml("<span style='color: red;'>Ошибка: {}<br><br>".format(e.args[0].replace("<", "<").replace(">", ">")))
- db.progressBar.setValue(db.progressBar.maximum())
- self.consoleTextEdit.moveCursor(QtGui.QTextCursor.End)
- self.progressBar.setValue(self.progressBar.maximum())
- def commitCommand(self, event):
- self.statusBar.showMessage("Выполнение транзакции...")
- self.progressBar.setValue(0)
- self.consoleTextEdit.moveCursor(QtGui.QTextCursor.End)
- self.consoleTextEdit.insertHtml("<span style='font-weight: bold; color: lightgray;'>{0:s}</span><br>".format(time.asctime()))
- global db
- timeDelta, timeBegin, timeEnd = 0, 0, 0
- db.progressBar.setValue(0)
- try:
- timeBegin = time.time()
- db.connection.commit()
- timeEnd = time.time()
- timeDelta = timeEnd - timeBegin
- self.statusBar.showMessage("Транзакция выполнена за {0:.3f} секунд.".format(timeDelta))
- self.consoleTextEdit.insertHtml("<span style='color: green;'>Транзакция выполнена за {0:.3f} секунд.</span><br><br>".format(timeDelta))
- except:
- self.statusBar.showMessage("Режим разработчика.")
- self.consoleTextEdit.insertHtml("<span style='color: red;'>Ошибка: не удалось совершить транзакцию.</span><br><br>")
- db.progressBar.setValue(db.progressBar.maximum())
- self.consoleTextEdit.moveCursor(QtGui.QTextCursor.End)
- self.progressBar.setValue(self.progressBar.maximum())
- def clearCommand(self):
- self.consoleTextEdit.clear()
- self.statusBar.showMessage("Консоль очищена...")
- def saveCommand(self):
- try:
- path = QtWidgets.QFileDialog.getSaveFileName(
- self,
- caption="Сохранить SQL-код",
- directory="./",
- filter="SQL (*.sql);; TXT (*.txt);; Все файлы (*)",
- initialFilter= "SQL (*.sql)")[0]
- file = open(path, "w")
- file.write(self.developerTextEdit.toPlainText())
- file.close()
- pathConsole = re.search(r"^(.+?)(?:\.[^.]+)?$", path).group(1) + ".txt"
- fileHtml = open(pathConsole, "w")
- fileHtml.write(self.consoleTextEdit.toPlainText())
- fileHtml.close()
- self.statusBar.showMessage("SQL-код сохранён в файле \"{0:s}\", а данные консоли - в файле \"{1:s}\".".format(path, pathConsole))
- except:
- self.statusBar.showMessage("Произошла ошибка при сохранении SQL-кода и данных консоли.")
- class GoLink(QtWidgets.QLabel):
- def __init__(self, text: str="", presentationWindow=None, mainFrame=None):
- super().__init__(presentationWindow)
- self.presentationWindow = presentationWindow
- self.mainFrame = mainFrame
- self.setAlignment(QtCore.Qt.AlignCenter)
- self.setText(text)
- def enterEvent(self, event):
- self.setCursor(QtCore.Qt.PointingHandCursor)
- def mousePressEvent(self, event):
- self.opacity1 = QtWidgets.QGraphicsOpacityEffect()
- self.presentationWindow.setGraphicsEffect(self.opacity1)
- self.anim1 = QtCore.QPropertyAnimation(self.opacity1, b"opacity")
- self.anim1.setDuration(250)
- self.anim1.setStartValue(1.0)
- self.anim1.setEndValue(0.0)
- self.anim1.start()
- self.anim1.finished.connect(self.presentationWindowIsInvisible)
- def presentationWindowIsInvisible(self):
- self.presentationWindow.hide()
- self.opacity2 = QtWidgets.QGraphicsOpacityEffect()
- self.mainFrame.setGraphicsEffect(self.opacity2)
- self.mainFrame.setWindowOpacity(0.0)
- self.mainFrame.show()
- self.anim2 = QtCore.QPropertyAnimation(self.opacity2, b"opacity")
- self.anim2.setDuration(250)
- self.anim2.setStartValue(0.0)
- self.anim2.setEndValue(1.0)
- self.anim2.start()
- self.anim2.finished.connect(self.mainFrameIsVisible)
- def mainFrameIsVisible(self):
- self.mainFrame.enterWindow = EnterWindow()
- self.mainFrame.loadDatabase()
- self.mainFrame.enterWindow.uploadUsers()
- self.mainFrame.offerToEnter()
- class PresentationWindow(QtWidgets.QLabel):
- slideWidth = 1138 # Ширина одного слайда
- slideHeight = 500 # Высота одного слайда
- slidesCount = 3 # Количество слайдов
- movingTime = 750 # Скорость прокрутки слайдов в миллисекундах
- def __init__(self, mainWindow=None, mainFrame=None):
- super().__init__(mainWindow) # Экземпляр класса PresentationWindow будет дочерним относительно родителя mainWindow
- self.mainFrame = mainFrame
- self.setFixedSize(1200, 640) # Задаем фиксированный размер для презентации
- self.setAlignment(QtCore.Qt.AlignCenter) # Устанавливаем его в центр окна
- self.content = QtWidgets.QVBoxLayout(self) # Задаем вертикальный макет для содержания презентации
- self.scrollArea = QtWidgets.QScrollArea() # Область с прокруткой для перелистывания слайдов
- self.slides = [QtWidgets.QLabel() for i in range(self.slidesCount)] # Сами слайды (имеют тип QLabel)
- self.slidesPacket = QtWidgets.QLabel()
- self.slidesLayout = QtWidgets.QHBoxLayout(self.slidesPacket) # Горизонтальный макет для слайдов
- self.buttonHBox = QtWidgets.QHBoxLayout() # Горизонтальный макет для radio-кнопок
- for i in range(self.slidesCount):
- # Позволим пользователю выделять текст:
- slide = self.slides[i]
- # Пишем текст в i-ый слайд:
- if i == 0:
- slide.setTextInteractionFlags(QtCore.Qt.TextSelectableByMouse | QtCore.Qt.TextSelectableByKeyboard)
- slide.setText("<img src='Images/icon.png' /><br><br>Добро пожаловать в программу <b>NeuralNetworks</b>, которая<br>научит вас программировать искусственные нейросети на языке Python!")
- elif i == 1:
- slide.setTextInteractionFlags(QtCore.Qt.TextSelectableByMouse | QtCore.Qt.TextSelectableByKeyboard)
- slide.setText("<b>NeuralNetworks</b> содержит в себе видео-уроки, теорию, тесты<br>и интерпретатор языка программирования <b>Python</b> для написания собственных программ!<br>Результаты заданий буду сохраняться в одну локальную базу данных формата <b>SQLite</b>, которая будет<br>хранить в себе <b>весь прогресс</b> и <b>весь код ваших программ</b>.")
- else:
- self.goBox = QtWidgets.QVBoxLayout(slide)
- self.goLink = GoLink("<b>Давайте приступим...</b>", self, self.mainFrame)
- self.goBox.addStretch(1)
- self.goBox.addWidget(self.goLink)
- self.goBox.addStretch(1)
- slide.setAlignment(QtCore.Qt.AlignCenter) # Устанавливаем текст слайда по центру
- slide.setFixedWidth(self.slideWidth) # Задаем ширину для слайда
- slide.setObjectName("slide")
- self.slidesLayout.addWidget(slide) # Добавляем данный слайд в макет
- # Устанавливаем размер пакета со всеми слайдами:
- self.slidesPacket.setFixedSize(self.slideWidth * self.slidesCount, self.slideHeight)
- self.slidesPacket.setObjectName("slidesPacket")
- self.scrollArea.setObjectName("slides")
- self.scrollArea.setWidget(self.slidesPacket) # Задаем виджет (QLabel)slidesPacket для области прокрутки
- self.scrollArea.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) # Скрываем полосу прокрутки
- self.scrollArea.horizontalScrollBar().valueChanged.connect(self.scrollAreaWheelEvent)
- self.buttonHBox.addStretch(1) # Мы добавим отступы в начале и в конце для кнопок, чтобы они были по середине
- self.buttons = [QtWidgets.QRadioButton(self) for i in range(self.slidesCount)]
- self.buttons[0].setChecked(True) # Первый слайд будет виден сначала
- for button in self.buttons:
- button.clicked.connect(self.slideToggle)
- self.buttonHBox.addWidget(button)
- self.buttonHBox.addStretch(1)
- # Заполняем содержимое:
- self.content.addWidget(self.scrollArea)
- self.content.addLayout(self.buttonHBox)
- def slideToggle(self, isChecked):
- if isChecked:
- # Анимируем горизонтальную прокрутку:
- self.anim = QtCore.QPropertyAnimation(self.scrollArea.horizontalScrollBar(), b"value")
- self.anim.setDuration(self.movingTime)
- self.anim.setEasingCurve(QtCore.QEasingCurve.InOutExpo)
- i = 0
- for i in range(self.slidesCount):
- if self.buttons[i].isChecked():
- break
- self.anim.setEndValue(self.scrollArea.horizontalScrollBar().pageStep() * i)
- self.anim.start()
- def scrollAreaWheelEvent(self, value):
- k = value / (self.scrollArea.horizontalScrollBar().pageStep() * self.slidesCount)
- i = 0
- for i in range(self.slidesCount):
- if self.buttons[i].isChecked():
- break
- j = int(math.ceil(k * self.slidesCount - 0.5))
- if j != i:
- self.buttons[j].setChecked(True)
- class MainWindow(QtWidgets.QWidget):
- sourceSize = QtCore.QSize(1280, 768)
- def __init__(self):
- super().__init__()
- # Создаем произвольное окно с задним фоном:
- self.setWindowTitle("NeuralNetworks")
- self.icon = QtGui.QIcon("Images/icon.png")
- self.setWindowIcon(self.icon)
- self.resize(self.sourceSize)
- # Центруем окно по экрану:
- self.desktop = QtWidgets.QApplication.desktop()
- x = (self.desktop.width() - self.width()) // 2
- y = (self.desktop.height() - self.height()) // 2
- self.move(x, y)
- self.hbox = QtWidgets.QHBoxLayout(self)
- self.hbox.setContentsMargins(0, 0, 0, 0)
- self.mainFrame = MainFrame(self)
- self.mainFrame.resize(self.sourceSize)
- self.mainFrame.hide()
- self.hbox.addWidget(self.mainFrame)
- self.presentationWindow = PresentationWindow(self, self.mainFrame)
- self.hbox.addWidget(self.presentationWindow)
- self.keyEsc = QtWidgets.QShortcut(QtCore.Qt.Key_Escape, self)
- self.keyEsc.activated.connect(self.pressedKeyEsc)
- self.keyF11 = QtWidgets.QShortcut(QtCore.Qt.Key_F11, self)
- self.keyF11.activated.connect(self.pressedKeyF11)
- def resizeEvent(self, event):
- self.mainFrame.resize(self.size())
- def paintEvent(self, event):
- painter = QtGui.QPainter(self)
- painter.drawPixmap(0, 0, self.width(), self.height(), QtGui.QPixmap("Images/background.jpg"))
- def closeEvent(self, event):
- if currentUser is not None:
- msgBox = QtWidgets.QMessageBox(QtWidgets.QMessageBox.Question,
- "Закрыть программу",
- "Вы действительно хотите выйти из программы? Все данные будут сохранены.",
- QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No)
- msgBox.setDefaultButton(QtWidgets.QMessageBox.No)
- if msgBox.exec() == QtWidgets.QMessageBox.Yes:
- QtWidgets.QApplication.closeAllWindows()
- event.accept()
- else:
- event.ignore()
- else:
- QtWidgets.QApplication.quit()
- def pressedKeyEsc(self):
- self.close()
- def pressedKeyF11(self):
- if self.isFullScreen():
- self.showNormal()
- else:
- self.showFullScreen()
- app = QtWidgets.QApplication(sys.argv)
- qss = open("stylesheet.css")
- app.setStyleSheet(qss.read())
- qss.close()
- mainWindow = MainWindow()
- mainWindow.show()
- code = app.exec_()
- sys.exit(code)
Add Comment
Please, Sign In to add comment