class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("Assignment") self.scene = QGraphicsScene() self.button = QPushButton("Draw Text Boxes") # self.image = QImage(self.size(), QImage.Format_RGB32) # self.image.fill(Qt.black) self.scene.addWidget(self.button) self.view = QGraphicsView(self.scene) # self.view.resize(800, 600) self.setCentralWidget(self.view) self.button.clicked.connect(self.buttonClicked) self.view.viewport().installEventFilter(self) self.drawing = False self.brushSize = 2 self.brushColor = Qt.black self.lastPoint = QPoint() def mousePressEvent(self, event): # super(MainWindow, self).mousePressEvent(event) if event.button() == Qt.LeftButton: self.drawing = True self.startPoint = event.pos() print(self.drawing) def mouseReleaseEvent(self, event): if (Qt.LeftButton & self.drawing): self.lastPoint = event.pos() print(self.startPoint) painter = QPainter() painter.setPen( QPen(self.brushColor, self.brushSize, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)) painter.drawRect(QtCore.QRect(self.startPoint, self.lastPoint)) self.update() # def paintEvent(self, event): # canvasPainter = QPainter(self) # canvasPainter.drawImage(self.rect()) def buttonClicked(self): self.button.hide() def eventFilter(self, obj, event): if obj is self.view.viewport(): if event.type() == QEvent.MouseButtonPress: print('mouse press event = ', event.pos()) elif event.type() == QEvent.MouseButtonRelease: self.mouseReleaseEvent(event) print('mouse release event = ', event.pos()) return QWidget.eventFilter(self, obj, event)
def createEditor(self, parent, option, index): if index.column() == 0: pushButton = QPushButton(parent) pushButton.hide() #pushButton.setIcon(QtGui.QPixmap(":/spotify/resources/icons/play_lgrey.png"), QtGui.QIcon.Normal, # QtGui.QIcon.Off) return pushButton else: return QStyledItemDelegate.createEditor(self, parent, option, index)
class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("Assignment") self.scene = QGraphicsScene() self.resize(800, 600) self.button = QPushButton("Draw Text Boxes") self.scene.addWidget(self.button) self.view = QGraphicsView() self.view.setScene(self.scene) self.setCentralWidget(self.view) self.button.clicked.connect(self.buttonClicked) self.view.viewport().installEventFilter(self) self.drawing = False self.lastPoint = QPoint() self.startPoint = QPoint() def mousePressEvent(self, event): if event.button() == Qt.LeftButton: self.startPoint = self.view.mapToScene(event.pos()) self.drawing = True def mouseReleaseEvent(self, event): if Qt.LeftButton and self.drawing: self.lastPoint = self.view.mapToScene(event.pos()) self.update() def paintEvent(self, event): if self.drawing : self.view.le = QPlainTextEdit() width = QtCore.QRectF(self.startPoint, self.lastPoint).size().width() height = QtCore.QRectF(self.startPoint, self.lastPoint).size().height() x = self.startPoint.x() y = self.startPoint.y() if width > 1 and height > 1: self.view.le.setGeometry(x, y, width, height) self.qsizegrip = QSizeGrip(self.view.le) self.scene.addWidget(self.view.le) def buttonClicked(self): self.button.hide() def eventFilter(self, obj, event): if obj is self.view.viewport(): if event.type() == QEvent.MouseButtonPress: pass # self.mousePressEvent(event) elif event.type() == QEvent.MouseButtonRelease: self.mouseReleaseEvent(event) return QWidget.eventFilter(self, obj, event)
class QLabelDescription(QWidget): click_on=Signal(str) def __init__(self,label="",description="",parent=None): super (QLabelDescription,self).__init__(parent) widget=QWidget() HBox=QHBoxLayout() self.label=QLabel() self.labelText=label self.label.setText('<span style="font-size:14pt"><b>{}</b></span>'.format(label)) self.label.setStyleSheet("border:0px;margin:0px;") HBox.addWidget(self.label,1) self.btn_edit=QPushButton() self.btn_edit.setToolTip(_("Edit {} file".format(label))) icn=QtGui.QIcon().fromTheme('document-edit') self.btn_edit.setIcon(icn) self.btn_edit.clicked.connect(self.editRepo) self.btn_edit.hide() HBox.addWidget(self.btn_edit) widget.setLayout(HBox) self.description=QLabel() self.description.setStyleSheet("border:3px solid silver;border-top:0px;border-right:0px;border-left:0px;margin-top:0px;") self.descriptionText=description self.description.setText('<span style="font-size:10pt; color:grey">{}</span>'.format(description)) QBox=QVBoxLayout() QBox.addWidget(widget,1,Qt.AlignBottom) QBox.addWidget(self.description,1,Qt.AlignTop) self.setLayout(QBox) self.show() def setText(self,label,description=""): self.labelText=label self.label.setText('<span style="font-size:14pt"><b>{}</b></span>'.format(label)) self.descriptionText=description self.description.setText('<span style="font-size:10pt; color:grey">{}</span>'.format(description)) def text(self): return([self.labelText,self.descriptionText]) def showEdit(self): self.btn_edit.show() def stateEdit(self,state): self.btn_edit.setEnabled(state) if state: self.btn_edit.setToolTip(_("Edit {} file".format(self.labelText))) else: self.btn_edit.setToolTip(_("Enable {} and apply to edit".format(self.labelText))) def editRepo(self): self.click_on.emit(self.labelText)
class ContractKeiUI(QSplitter): """ 合约K线界面 """ def __init__(self, *args, **kwargs): super(ContractKeiUI, self).__init__(*args, **kwargs) main_layout = QHBoxLayout() # 使用主layout,让控件自适应窗口改变大小 main_layout.setSpacing(0) self.variety_tree = VarietyTree(self) main_layout.addWidget(self.variety_tree) self.right_widget = QWidget(self) right_layout = QVBoxLayout() right_layout.setContentsMargins(QMargins(1, 1, 1, 1)) opts_layout = QHBoxLayout() opts_layout.addWidget(QLabel("选择合约:", self)) self.contract_combobox = QComboBox(self) opts_layout.addWidget(self.contract_combobox) self.confirm_button = QPushButton("确定", self) opts_layout.addWidget(self.confirm_button) self.tip_button = QPushButton("正在查询数据 ", self) self.tip_button.hide() opts_layout.addWidget(self.tip_button) opts_layout.addStretch() right_layout.addLayout(opts_layout) self.web_container = QWebEngineView(self) right_layout.addWidget(self.web_container) self.right_widget.setLayout(right_layout) main_layout.addWidget(self.right_widget) self.setStretchFactor(1, 2) self.setStretchFactor(2, 8) self.setHandleWidth(1) self.contract_combobox.setMinimumWidth(80) self.setLayout(main_layout) self.tip_button.setObjectName("tipButton") self.setStyleSheet("#tipButton{border:none;color:rgb(230,50,50);font-weight:bold}")
def add_row(self, criterion, deleteable=True): # The last row for this criterion index = self.rows_for_each_criteria[criterion] value_spin_box = QSpinBox() value_spin_box.setRange(0, 100) self.value_spin_boxes[criterion].append(value_spin_box) score_spin_box = QSpinBox() score_spin_box.setRange(0, 100) self.score_spin_boxes[criterion].append(score_spin_box) delete_button = QPushButton('&Delete') cb = partial(self.delete, criterion, index) delete_button.clicked.connect(cb) size_policy = QSizePolicy() size_policy.setRetainSizeWhenHidden(True) delete_button.setSizePolicy(size_policy) cb = partial(self.value_changed, criterion, index) value_spin_box.valueChanged.connect(cb) cb = partial(self.score_changed, criterion, index) score_spin_box.valueChanged.connect(cb) inner_grid = QGridLayout() inner_grid.addWidget(value_spin_box, index, 0) inner_grid.addWidget(QLabel('then score should be '), index, 1) inner_grid.addWidget(score_spin_box, index, 2) inner_grid.addWidget(delete_button, index, 3) if not deleteable: delete_button.hide() form = QFormLayout() form.addRow(QLabel('If ' + str(criterion) + ' is '), inner_grid) pos = self.vertical_layouts[criterion].count() - 1 self.vertical_layouts[criterion].insertLayout(pos, form) # Increment the row number self.rows_for_each_criteria[criterion] += 1
class UserBarUI(QWidget): def __init__(self, *args, **kwargs): super(UserBarUI, self).__init__(*args, **kwargs) layout = QHBoxLayout() layout.setContentsMargins(QMargins(0, 0, 5, 0)) self.login_button = QPushButton("点击登录", self) self.login_button.setCursor(Qt.PointingHandCursor) self.login_button.setIcon(QIcon("icons/login.png")) self.login_button.setFixedWidth(88) setattr(self.login_button, "username", "") layout.addWidget(self.login_button) self.logout_button = QPushButton("退出", self) self.logout_button.setCursor(Qt.PointingHandCursor) layout.addWidget(self.logout_button) self.setLayout(layout) self.logout_button.hide() self.login_button.setObjectName("loginButton") self.logout_button.setObjectName("logoutButton") # loginButton::menu-indicator{image:none;} self.setStyleSheet( "#loginButton,#logoutButton{border:none;height:22px}" "#loginButton:hover{color:rgb(100,160,210)}" )
class shotgun_ui(QWidget): e_local_export = Signal() e_shotgun_export = Signal(str) export_path = None def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding) # Setup UI view v_main_box = QVBoxLayout() # Setup list for production handoff export option self.handoff_type_list = QComboBox() self.handoff_type_list.addItems(['Local Export', 'Shotgun Export']) self.handoff_type_label = QLabel('Handoff Type') self.handoff_type_label.setBuddy(self.handoff_type_list) self.handoff_type_list.currentTextChanged.connect( self.__on_handoff_type_changed) # Setup Local Export option self.export_layout = QHBoxLayout() self.export_path_button = QPushButton('Browse') self.export_path_button.clicked.connect(self.__browse_export_path) self.export_path, self.export_path_label = self.__create_line_label( '', 'Export Path', 200) self.__add_widget_to_layout(self.export_layout, self.export_path, self.export_path_button) # Setup Shotgun export option self.sg_hostname, self.sg_hostname_label = self.__create_line_label( 'https://thomaslacroix.shotgunstudio.com', 'Shotgun URL', 350) self.sg_login, self.sg_login_label = self.__create_line_label( '*****@*****.**', 'Username', 200) pull = QPushButton('Export Latest') pull.clicked.connect(self.__pull_latest) self.__add_widget_to_layout(v_main_box, self.handoff_type_label, self.handoff_type_list, self.export_path_label) v_main_box.addLayout(self.export_layout) self.__add_widget_to_layout(v_main_box, self.sg_hostname_label, self.sg_hostname, self.sg_login_label, self.sg_login, pull) self.__update_ui_handoff_type('Local Export') self.setLayout(v_main_box) def get_shotgun_api(self) -> shotgun_api.shotgun: """get_shotgun_api will return the shotgun_api Returns: shotgun_api.shotgun -- Shotgun api """ return self.shotgun def create_folders(self, show_tc: str, seq_tc: str, seq_rev_nbr: int, episode_tc: str = None) -> str: """create_folders will create the structure of folders from shows to sequence revision Arguments: show_tc {str} -- Show tracking code seq_tc {str} -- Sequence tracking code seq_rev_nbr {int} -- Sequence revision number episode_tc {str} -- Episode tracking code (default: {None}) Returns: str -- Sequence revision path """ show_path = os.path.join(self.export_path.text(), show_tc) self.__create_folder(show_path) sequence_path = os.path.join(show_path, seq_tc) if episode_tc is not None: episode_path = os.path.join(show_path, episode_tc) self.__create_folder(episode_path) sequence_path = os.path.join(episode_path, seq_tc) self.__create_folder(sequence_path) sequence_revision_path = os.path.join(sequence_path, 'v{0}'.format(seq_rev_nbr)) self.__create_folder(sequence_revision_path) return sequence_revision_path def get_shot_download_paths(self, export_path: str, shot: str) -> Tuple[str, str, str]: """get_shot_download_paths will create folders for show, artwork and thumbnails and return those paths Arguments: export_path {str} -- Base export path shot {str} -- Shot name Returns: Tuple[str, str, str] -- show_path, artwork_path, thumb_path """ show_folder_path = os.path.join(export_path, shot) self.__create_folder(show_folder_path) artwork_folder_path = os.path.join(show_folder_path, 'artwork') self.__create_folder(artwork_folder_path) thumb_folder_path = os.path.join(show_folder_path, 'thumbnail') self.__create_folder(thumb_folder_path) return show_folder_path, artwork_folder_path, thumb_folder_path def export_to_version(self, shots: List, sg_password: str, show_tc: str, seq_rev_nbr: int, seq_tc: str, fn_progress: Callable[[str], None]) -> Dict: """export_to_version will export to shotgun a project, a sequence, a shot and version Arguments: shots {List} -- List of shots sg_password {str} -- Shotgun password show_tc {str} -- Show tracking code seq_rev_nbr {int} -- Sequence revision number seq_tc {str} -- Sequence tracking code Callable[[str], None]) -- fn_progress is a progress function Returns: Dict -- Mapping of shot to quicktime info with his corresponding shotgun version """ fn_progress('get or create shotgun project') sg_show = self.shotgun.get_project(show_tc) if sg_show is None: sg_show = self.shotgun.create_project(show_tc) fn_progress('get or create shotgun sequence') sg_seq = self.shotgun.get_sequence(sg_show, seq_tc) if sg_seq is None: sg_seq = self.shotgun.create_seq(sg_show, seq_tc) shot_to_file = {} for shot_name in shots: fn_progress( 'get or create shotgun shot for shot {0}'.format(shot_name)) sg_shot = self.shotgun.get_shot(sg_show, sg_seq, shot_name) if sg_shot is None: sg_shot = self.shotgun.create_shot(sg_show, sg_seq, shot_name) fn_progress( 'get or create shotgun version for shot {0}'.format(shot_name)) version = self.shotgun.get_version(sg_show, sg_shot) if version is None: new_version = 1 else: ver = re.search('(.*)v([0-9]+)', version['code']) new_version = int(ver.group(2)) + 1 version = self.shotgun.create_version(sg_show, sg_shot, new_version) mov_name = '{0}_v{1}_{2}.mov'.format(seq_tc, seq_rev_nbr, shot_name) shot_to_file[shot_name] = { 'mov_name': mov_name, 'version': version } return shot_to_file def init_local_export(self) -> bool: """init_local_export will initialise the export Returns: bool -- If the export path is valid or not """ if len(self.export_path.text()) <= 0: self.__info('You need to select an export path') return False if os.path.exists(self.export_path.text()) is False: self.__info('Invalid export path') return False return True def init_shotgun_export(self) -> bool: """init_shotgun_export will init the shotgun export Returns: bool -- Can login to shotgun """ if self.sg_login.text() == '' or self.sg_hostname.text() == '': self.__info('You need to enter your shotgun info') return '', False sg_password, ok = QInputDialog().getText(self, 'Shotgun password', 'Shotgun password:'******'', False self.shotgun = shotgun_api.shotgun(self.sg_hostname.text(), self.sg_login.text(), sg_password) return sg_password, True def __pull_latest(self): """__pull_latest will export the latest sequence revision """ if self.selected_handoff_type == 'Local Export': if self.init_local_export(): self.e_local_export.emit() else: sg_password, ok = self.init_shotgun_export() if ok: self.e_shotgun_export.emit(sg_password) def __create_line_label(self, name: str, label: str, min_width: int = 200) -> Tuple[Dict, Dict]: """__create_line_label will create a line edit button and his label Arguments: name {str} -- Default value label {str} -- Label name min_width {int} -- Minium width (default: {200}) Returns: Tuple[Dict, Dict] -- Line Edit, Label """ line_edit = QLineEdit(name) line_edit.setMinimumWidth(min_width) label = QLabel(label) label.setBuddy(line_edit) return line_edit, label def __add_widget_to_layout(self, layout: Dict, *widgets: Dict): """__add_widget_to_layout will add all the widget to a layout __add_widget_to_layout(layout, widget1, widget2, widget3) Arguments: layout {Dict} -- Layout to add widget to widgets {*Dict} -- All the widgets to add """ for w in widgets: layout.addWidget(w) def __error(self, message: str): """__error will show a error message with a given message Arguments: message {str} -- Message to show """ err = QErrorMessage(self.parent()) err.setWindowTitle('Flix') err.showMessage(message) err.exec_() def __info(self, message: str): """__info will show a message with a given message Arguments: message {str} -- Message to show """ msgbox = QMessageBox(self.parent()) msgbox.setWindowTitle('Flix') msgbox.setText(message) msgbox.exec_() def __on_handoff_type_changed(self, handoff_type: str): """__on_handoff_type_changed triggered when the handoff type changed Arguments: handoff_type {str} -- Handoff type from the event """ self.__update_ui_handoff_type(handoff_type) def __browse_export_path(self): """__browse_export_path will create a dialog window to browse and set an export path """ dialog = QFileDialog() export_p = None if self.export_path.text() is not '': if os.path.exists(self.export_path.text()): export_p = self.export_path.text() export_p = dialog.getExistingDirectory(dir=export_p) if len(export_p) < 1: return self.export_path.setText(export_p) def __update_ui_handoff_type(self, handoff_type: str): """__update_ui_handoff_type will update the UI depending of the handoff type Arguments: handoff_type {str} -- Handoff type """ if handoff_type == 'Local Export': self.sg_hostname.hide() self.sg_hostname_label.hide() self.sg_login.hide() self.sg_login_label.hide() self.export_path_label.show() self.export_path.show() self.export_path_button.show() else: self.sg_hostname.show() self.sg_hostname_label.show() self.sg_login.show() self.sg_login_label.show() self.export_path_label.hide() self.export_path.hide() self.export_path_button.hide() self.selected_handoff_type = handoff_type def __create_folder(self, path: str): """__create_folder will create a folder if it does not exist Arguments: path {str} -- Path to create the folder """ if not os.path.exists(path): os.makedirs(path)
class UserSelect(QWidget): def __init__(self, kernel, parent=None): super(UserSelect, self).__init__(parent) self.kernel = kernel self.parent = parent self.initUI() def initUI(self): self.createElements() self.createLayout() self.createActions() self.hideNewUserForm() self.hideLoginForm() def createElements(self): self.userButtons = [ UserTile(u, self) for u in self.kernel.getAllUsers() ] for b, u in zip(self.userButtons, self.kernel.getAllUsers()): b.setProfilePicture(self.kernel.getUsersDir() + u + '/profile/profile_pic.png') b.createLayout() self.newUserButton = QPushButton('New User') self.nevermindButton = QPushButton("Nevermind") self.newUserNameField = QLineEdit("Username") self.newUserPasswordField = QLineEdit("Password") self.newUserPasswordField.setEchoMode(QLineEdit.Password) self.newUserConfirmPasswordField = QLineEdit("Confirm Password") self.newUserConfirmPasswordField.setEchoMode(QLineEdit.Password) self.submitNewUserButton = QPushButton('submit') self.newUserNameErrorLabel = QLabel('') self.newPasswordErrorLabel = QLabel('') self.existingUserLoginField = QLineEdit('Password') self.existingUserLoginField.setEchoMode(QLineEdit.Password) self.existingUserLoginButton = QPushButton('Login') self.existingUserLoginErrorLabel = QLabel('') self.selectedUser = None def createLayout(self): self.existingUsersLayout = QHBoxLayout() self.existingUsersLayout.addStretch() for ub in self.userButtons: ub.resize(100, 120) self.existingUsersLayout.addWidget(ub) self.existingUsersLayout.addStretch() self.formLayout = QVBoxLayout() self.formLayout.addStretch() self.formLayout.addWidget(self.newUserButton) self.formLayout.addWidget(self.nevermindButton) self.formLayout.addWidget(self.newUserNameField) self.formLayout.addWidget(self.newUserNameErrorLabel) self.formLayout.addWidget(self.newUserPasswordField) self.formLayout.addWidget(self.newUserConfirmPasswordField) self.formLayout.addWidget(self.newPasswordErrorLabel) self.formLayout.addWidget(self.submitNewUserButton) self.formLayout.addWidget(self.existingUserLoginField) self.formLayout.addWidget(self.existingUserLoginErrorLabel) self.formLayout.addWidget(self.existingUserLoginButton) self.formLayout.addStretch() self.lowLayout = QHBoxLayout() self.lowLayout.addStretch() self.lowLayout.addLayout(self.formLayout) self.lowLayout.addStretch() self.layout = QVBoxLayout(self) self.layout.addStretch() self.layout.addLayout(self.existingUsersLayout) self.layout.addLayout(self.lowLayout) self.layout.addStretch() def createActions(self): self.newUserButton.clicked.connect(self.revealNewUserForm) self.nevermindButton.clicked.connect(self.hideNewUserForm) self.submitNewUserButton.clicked.connect(self.submitNewUserRequest) self.existingUserLoginButton.clicked.connect(self.login) for btn in self.userButtons: btn.nameButton.clicked.connect( lambda: self.revealLoginForm(btn.nameButton.text())) def revealNewUserForm(self): self.hideLoginForm() self.newUserButton.hide() self.newUserNameField.show() self.newUserPasswordField.show() self.newUserConfirmPasswordField.show() self.submitNewUserButton.show() self.nevermindButton.show() def hideNewUserForm(self): self.newUserButton.show() self.newUserNameErrorLabel.hide() self.newPasswordErrorLabel.hide() self.newUserNameField.hide() self.newUserPasswordField.hide() self.newUserConfirmPasswordField.hide() self.submitNewUserButton.hide() self.nevermindButton.hide() def revealLoginForm(self, user): self.hideNewUserForm() self.selectedUser = user self.existingUserLoginButton.show() self.existingUserLoginErrorLabel.show() self.existingUserLoginField.show() def hideLoginForm(self): self.selectedUser = None self.existingUserLoginButton.hide() self.existingUserLoginErrorLabel.hide() self.existingUserLoginField.hide() def submitNewUserRequest(self): userName = self.newUserNameField.text() pwd = self.newUserPasswordField.text() conf_pwd = self.newUserConfirmPasswordField.text() err_msg = '' if pwd != conf_pwd: err_msg = 'Error: passwords do not match' self.newPasswordErrorLabel.setText(err_msg) self.newPasswordErrorLabel.show() else: self.newPasswordErrorLabel.hide() if self.kernel.userExists(userName): err_msg = 'Error: Username is already taken. Choose something else' self.newUserNameErrorLabel.setText(err_msg) self.newUserNameErrorLabel.show() else: self.newUserNameErrorLabel.hide() if err_msg != '': return if self.kernel.addUser(userName, pwd): self.parent.loadApplication() else: err = "Error: User Creation failed.\nPlease use your Robinhood Credentials" self.newPasswordErrorLabel.setText(err) self.newPasswordErrorLabel.show() def login(self): assert (self.selectedUser != None) pwd = self.existingUserLoginField.text() if not self.kernel.switchUser(self.selectedUser, pwd): self.existingUserLoginErrorLabel.setText(err) self.existingUserLoginErrorLabel.show() else: self.parent.loadApplication()
class QDataViewer(QWidget): def __init__(self): QWidget.__init__(self) # Layout Init. self.language = 'ud' if len(sys.argv)>1: self.language = sys.argv[1] self.setGeometry(650, 300, 600, 600) self.setWindowTitle('Data Viewer') self.uploadButton = QPushButton('Load Conll File', self) self.sentence_id = 0 self.column_number = 10 self.columns = ["ID", "FORM", "LEMMA", "UPOS", "XPOS", "FEATS", "HEAD", "DEPREL", "DEPS", "MISC"] self.current_dict = {} self.load_finished = True self.first_time = True self.session_start = True self.map_col = {0:"ID", 1:"FORM", 2:"LEMMA", 3:"UPOS", 4:"XPOS", 5:"FEATS", 6:"HEAD", 7:"DEPREL", 8:"DEPS", 9:"MISC", 10:"Abbr", 11:"Animacy", 12:"Aspect", 13:"Case", 14:"Clusivity", 15:"Definite", 16:"Degree", 17:"Echo", 18:"Evident", 19:"Foreign", 20:"Gender", 21:"Mood", 22:"NounClass", 23:"Number", 24:"Number[psor]", 25:"NumType", 26:"Person", 27:"Person[psor]", 28:"Polarity", 29:"Polite", 30:"Poss", 31:"PronType", 32:"Reflex", 33:"Register", 34:"Tense", 35:"VerbForm", 36:"Voice"} self.doc = None self.vBoxLayout = QVBoxLayout() self.vBoxLayout.addWidget(self.uploadButton) self.setLayout(self.vBoxLayout) # Signal Init. self.connect(self.uploadButton, QtCore.SIGNAL('clicked()'), self.open) def open(self): filename = QFileDialog.getOpenFileName(self, 'Open File', '.')[0] self.notename = "notes-"+filename.split("/")[-1].split(".")[0]+".txt" self.uploadButton.hide() print(filename) self.doc = Doc(filename) if not os.path.exists(self.notename): open(self.notename, "w").close() self.construct() def writeNotes(self): if self.qTextEdit2.toPlainText() != "Write your note here...": if self.qTextEdit2.toPlainText() == "": if str(self.sentence_id) in self.noteDictionary: del self.noteDictionary[str(self.sentence_id)] else: self.noteDictionary[str(self.sentence_id)] = self.qTextEdit2.toPlainText().rstrip().replace("\r\n", " ").replace("\n", " ").replace("\r", " ") noteTxt = open(self.notename, "w") for noteKey in sorted(self.noteDictionary.keys()): noteTxt.write(noteKey+" --- "+self.noteDictionary[noteKey]+"\n") noteTxt.close() def go_prev(self): self.first_time = True self.writeNotes() if self.sentence_id>0: self.sentence_id-=1 self.update_table() self.session_start = True self.update_html() self.check_errors() self.qTextEdit.setText(str(self.sentence_id)) self.first_time = False def go_next(self): self.first_time = True self.writeNotes() if self.sentence_id<len(self.doc.sentences)-1: self.sentence_id+=1 self.update_table() self.session_start = True self.update_html() self.check_errors() self.qTextEdit.setText(str(self.sentence_id)) self.first_time = False def go(self): self.doc.write() self.first_time = True self.writeNotes() try: self.sentence_id = int(self.qTextEdit.toPlainText()) self.update_table() self.session_start = True self.update_html() self.check_errors() except Exception as e: print(e) self.qTextEdit.setText(str(self.sentence_id)) self.first_time = False def reset(self): if not self.first_time: self.first_time = True self.sentence = copy.deepcopy(self.sentence_backup) self.doc.sentences[self.sentence_id] = copy.deepcopy(self.sentence_backup) self.session_start = True self.doc.write() self.update_table() self.update_html() self.check_errors() self.first_time = False def construct(self): self.hBoxLayout = QHBoxLayout() self.prevButton = QPushButton("Prev", self) self.prevButton.setShortcut("Alt+O") self.resetButton = QPushButton("Reset", self) self.resetButton.setShortcut("Alt+R") self.qTextEditAddRow = QTextEdit() self.qTextEditAddRow.setFixedHeight(20) self.qTextEditAddRow.setFixedWidth(60) self.qTextEditDeleteRow = QTextEdit() self.qTextEditDeleteRow.setFixedHeight(20) self.qTextEditDeleteRow.setFixedWidth(60) self.qTextEdit = QTextEdit() self.qTextEdit.setFixedHeight(20) self.qTextEdit.setFixedWidth(60) self.qTextEdit2 = QTextEdit() self.qTextEdit2.setFixedHeight(20) self.qTextEdit2.setFixedWidth(500) self.shortcutText=QShortcut(QtGui.QKeySequence("Alt+M"), self) self.shortcutText.activated.connect(self.qTextEdit2.setFocus) self.goButton = QPushButton("Go", self) self.goButton.setShortcut("Alt+G") self.nextButton = QPushButton("Next", self) self.nextButton.setShortcut("Alt+P") self.addRowButton = QPushButton("Add Row", self) self.deleteRowButton = QPushButton("Delete Row", self) self.hBoxLayout.addWidget(self.prevButton) self.hBoxLayout.addStretch() self.hBoxLayout.addWidget(self.resetButton) self.hBoxLayout.addStretch() self.hBoxLayout.addWidget(self.qTextEditAddRow) self.hBoxLayout.addWidget(self.addRowButton) self.hBoxLayout.addStretch() self.hBoxLayout.addWidget(self.qTextEditDeleteRow) self.hBoxLayout.addWidget(self.deleteRowButton) self.hBoxLayout.addStretch() self.hBoxLayout.addWidget(self.qTextEdit) self.hBoxLayout.addWidget(self.goButton) self.hBoxLayout.addStretch() self.hBoxLayout.addWidget(self.qTextEdit2) self.hBoxLayout.addStretch() self.hBoxLayout.addWidget(self.nextButton) self.vBoxLayout.addLayout(self.hBoxLayout) self.chBoxLayout = QHBoxLayout() self.chBoxLayout.addStretch() cb_ids = ["ID", "FORM", "LEMMA", "UPOS", "XPOS", "FEATS", "HEAD", "DEPREL", "DEPS", "MISC"] cb_ids2 = ["Abbr", "Animacy", "Aspect", "Case", "Clusivity", "Definite", "Degree", "Echo", "Evident", "Foreign", "Gender", "Mood", "NounClass", "Number"] cb_ids3 = ["Number[psor]", "NumType", "Person", "Person[psor]", "Polarity", "Polite", "Poss", "PronType", "Reflex", "Register", "Tense", "VerbForm", "Voice"] for cb_id in cb_ids: cb = QCheckBox(cb_id) cb.setChecked(True) cb.stateChanged.connect(self.cb_change) self.chBoxLayout.addWidget(cb) self.chBoxLayout.addStretch() self.vBoxLayout.addLayout(self.chBoxLayout) self.chBoxLayout_2 = QHBoxLayout() self.chBoxLayout_2.addStretch() for cb_id in cb_ids2: cb = QCheckBox(cb_id) cb.setChecked(False) cb.stateChanged.connect(self.cb_change) self.chBoxLayout_2.addWidget(cb) self.chBoxLayout_2.addStretch() self.vBoxLayout.addLayout(self.chBoxLayout_2) self.chBoxLayout_3 = QHBoxLayout() self.chBoxLayout_3.addStretch() for cb_id in cb_ids3: cb = QCheckBox(cb_id) cb.setChecked(False) cb.stateChanged.connect(self.cb_change) self.chBoxLayout_3.addWidget(cb) self.chBoxLayout_3.addStretch() self.vBoxLayout.addLayout(self.chBoxLayout_3) self.qTextEdit.setText(str(self.sentence_id)) self.noteDictionary = {} noteFile = open(self.notename, "r") for note in noteFile: noteSplitted = note.split(" --- ") noteID = noteSplitted[0] noteContent = noteSplitted[1].rstrip() self.noteDictionary[noteID] = noteContent noteFile.close() self.connect(self.prevButton, QtCore.SIGNAL('clicked()'), self.go_prev) self.connect(self.resetButton, QtCore.SIGNAL('clicked()'), self.reset) self.connect(self.goButton, QtCore.SIGNAL('clicked()'), self.go) self.connect(self.nextButton, QtCore.SIGNAL('clicked()'), self.go_next) self.connect(self.addRowButton, QtCore.SIGNAL('clicked()'), self.add_row) self.connect(self.deleteRowButton, QtCore.SIGNAL('clicked()'), self.delete_row) # create table here self.tableWidget = QTableWidget(self) self.tableWidget.itemChanged.connect(self.handle_change) self.connect(self.tableWidget.verticalHeader(), QtCore.SIGNAL("sectionClicked(int)"), self.agg) self.qTextEditError = QTextEdit() self.qTextEditError.setReadOnly(True) self.splitter = QSplitter(Qt.Vertical) self.splitter.addWidget(self.tableWidget) self.splitter.addWidget(self.qTextEditError) self.vBoxLayout.addWidget(self.splitter) self.webView = QWebEngineView() self.update_table() self.update_html() self.check_errors() self.splitter2 = QSplitter(Qt.Vertical) self.splitter2.addWidget(self.splitter) self.splitter2.addWidget(self.webView) self.vBoxLayout.addWidget(self.splitter2) self.webView.loadFinished.connect(self.finito) self.first_time = False def finito(self): self.load_finished = True def add_row(self): if "-" not in self.qTextEditAddRow.toPlainText(): word_id = int(self.qTextEditAddRow.toPlainText()) possible_move = True new_sentence_words = [] for word in self.sentence.words: if word.unitword: x1 = int(word.id.split("-")[0]) x2 = int(word.id.split("-")[1]) if word_id == x1 or word_id == x2: possible_move = False if possible_move: for word in self.sentence.words: new_word = copy.deepcopy(word) if new_word.head != "_" and int(new_word.head) >= word_id: new_word.head = str(int(new_word.head) + 1) if new_word.unitword: new_word_id = int(new_word.id.split("-")[0]) else: new_word_id = int(new_word.id) if new_word_id < word_id: new_sentence_words.append(new_word) elif new_word_id == word_id: if new_word.unitword: x1 = int(new_word.id.split("-")[0]) x2 = int(new_word.id.split("-")[1]) w = Word("\t".join( [str(x1), new_word.form, "_", "_", "_", "_", new_word.head, "_", "_", "_"]), self.sentence.sent_address) new_word.id = str(x1 + 1) + "-" + str(x2 + 1) else: w = Word("\t".join( [new_word.id, new_word.form, "_", "_", "_", "_", new_word.head, "_", "_", "_"]), self.sentence.sent_address) new_word.id = str(int(new_word.id) + 1) new_sentence_words.append(w) new_sentence_words.append(new_word) elif new_word_id > word_id: if new_word.unitword: x1 = int(new_word.id.split("-")[0]) x2 = int(new_word.id.split("-")[1]) new_word.id = str(x1 + 1) + "-" + str(x2 + 1) else: new_word.id = str(int(new_word.id) + 1) new_sentence_words.append(new_word) self.sentence.words = copy.deepcopy(new_sentence_words) self.first_time = True self.update_table() self.update_html() self.first_time = False def delete_row(self): if "-" not in self.qTextEditDeleteRow.toPlainText(): word_id = int(self.qTextEditDeleteRow.toPlainText()) possible_move = True new_sentence_words = [] for word in self.sentence.words: if word.unitword: x1 = int(word.id.split("-")[0]) x2 = int(word.id.split("-")[1]) if word_id == x1 or word_id == x2: possible_move = False if not word.head == "_": if int(word.head) == word_id: possible_move = False if possible_move: for word in self.sentence.words: new_word = copy.deepcopy(word) if new_word.head != "_" and int(new_word.head) >= word_id: new_word.head = str(int(new_word.head) - 1) if new_word.unitword: new_word_id = int(new_word.id.split("-")[0]) else: new_word_id = int(new_word.id) if new_word_id < word_id: new_sentence_words.append(new_word) elif new_word_id > word_id: if new_word.unitword: x1 = int(new_word.id.split("-")[0]) x2 = int(new_word.id.split("-")[1]) new_word.id = str(x1 - 1) + "-" + str(x2 - 1) else: new_word.id = str(int(new_word.id) - 1) new_sentence_words.append(new_word) self.sentence.words = copy.deepcopy(new_sentence_words) self.first_time = True self.update_table() self.update_html() self.first_time = False def agg(self, x): if self.sentence.words[x].unitword:#remove two-words thing into one limit = int(self.sentence.words[x].id.split("-")[0]) self.sentence.words[x].head = self.sentence.words[x+1].head self.sentence.words[x].lemma = self.sentence.words[x+1].lemma self.sentence.words[x].upos = self.sentence.words[x+1].upos self.sentence.words[x].xpos = self.sentence.words[x+1].xpos self.sentence.words[x].feats = self.sentence.words[x+1].feats self.sentence.words[x].deprel = self.sentence.words[x+1].deprel self.sentence.words[x].deps = self.sentence.words[x+1].deps self.sentence.words[x].misc = self.sentence.words[x+1].misc self.sentence.words[x].id = str(limit) self.sentence.words[x].unitword = False del self.sentence.words[x+1] del self.sentence.words[x+1] for word in self.sentence.words: if word.unitword: first_word_id = int(word.id.split("-")[0]) if first_word_id>limit: word.id = str(first_word_id-1)+"-"+str(first_word_id) else: if int(word.id) > limit: word.id = str(int(word.id)-1) if word.head != "_" and int(word.head) > limit: word.head = str(int(word.head)-1) self.first_time = True self.update_table() self.update_html() self.first_time = False else:#add two-elements below base_word = self.sentence.words[x] limit = int(base_word.id) for word in self.sentence.words: if word.unitword: first_word_id = int(word.id.split("-")[0]) if first_word_id>limit: word.id = str(first_word_id+1)+"-"+str(first_word_id+2) else: if int(word.id) > limit: word.id = str(int(word.id)+1) if word.head != "_" and int(word.head) > limit: word.head = str(int(word.head)+1) w1 = Word("\t".join([str(limit), base_word.form, base_word.lemma, base_word.upos, base_word.xpos, base_word.feats, base_word.head, base_word.deprel, base_word.deps, "_"]), self.sentence.sent_address) w2 = Word("\t".join([str(limit+1), base_word.form, base_word.lemma, base_word.upos, base_word.xpos, base_word.feats, str(limit), base_word.deprel, base_word.deps, "_"]), self.sentence.sent_address) self.sentence.words = self.sentence.words[:x+1]+[w1, w2]+self.sentence.words[x+1:] base_word.id = str(limit)+"-"+str(limit+1) base_word.lemma = "_" base_word.upos = "_" base_word.xpos = "_" base_word.feats = "_" base_word.head = "_" base_word.deprel = "_" base_word.deps = "_" base_word.unitword = True self.first_time = True self.update_table() self.update_html() self.first_time = False def update_table(self): if str(self.sentence_id) in self.noteDictionary: self.qTextEdit2.setText(self.noteDictionary[str(self.sentence_id)]) else: self.qTextEdit2.setText("Write your note here...") self.sentence = self.doc.sentences[self.sentence_id] self.tableWidget.setRowCount(len(self.sentence.words)) self.tableWidget.setColumnCount(self.column_number) self.tableWidget.setHorizontalHeaderLabels(self.columns) for enum, word in enumerate(self.sentence.words): if word.unitword: self.tableWidget.setVerticalHeaderItem(enum, QTableWidgetItem("-")) else: self.tableWidget.setVerticalHeaderItem(enum, QTableWidgetItem("+")) dict_feat = {} uni_feats = re.split('\|', word.feats) if uni_feats[0] != "_": for uni_feat in uni_feats: uf = re.split('\=', uni_feat) dict_feat[uf[0]]=uf[1] for i in range(self.column_number): if self.columns[i]=="ID": self.tableWidget.setItem(enum, i, QTableWidgetItem(word.id)) elif self.columns[i]=="FORM": self.tableWidget.setItem(enum, i, QTableWidgetItem(word.form)) elif self.columns[i]=="LEMMA": self.tableWidget.setItem(enum, i, QTableWidgetItem(word.lemma)) elif self.columns[i]=="UPOS": self.tableWidget.setItem(enum, i, QTableWidgetItem(word.upos)) elif self.columns[i]=="XPOS": self.tableWidget.setItem(enum, i, QTableWidgetItem(word.xpos)) elif self.columns[i]=="FEATS": self.tableWidget.setItem(enum, i, QTableWidgetItem(word.feats)) elif self.columns[i]=="HEAD": self.tableWidget.setItem(enum, i, QTableWidgetItem(word.head)) elif self.columns[i]=="DEPREL": self.tableWidget.setItem(enum, i, QTableWidgetItem(word.deprel)) elif self.columns[i]=="DEPS": self.tableWidget.setItem(enum, i, QTableWidgetItem(word.deps)) elif self.columns[i]=="MISC": self.tableWidget.setItem(enum, i, QTableWidgetItem(word.misc)) else: if self.columns[i] in dict_feat: self.tableWidget.setItem(enum, i, QTableWidgetItem(dict_feat[self.columns[i]])) else: self.tableWidget.setItem(enum, i, QTableWidgetItem("_")) self.tableWidget.resizeColumnsToContents() def check_errors(self): index = "" for w in self.sentence.words: index += w.form + "(" + w.id + ") " index += "\n" error_list = get_errors(self.sentence.get_raw(), self.sentence.sent_id, self.language) if len(error_list)>0: error_raw_string = 'ERRORS:\n' for error in error_list: error_raw_string+=error+'\n' self.qTextEditError.setText(index + error_raw_string) else: self.qTextEditError.setText(index) def update_html(self): if not self.load_finished: #If the js function not loaded an image onto app it removes browser print("Load error!") self.splitter2.deleteLater() self.webView = QWebEngineView() self.splitter2 = QSplitter(Qt.Vertical) self.splitter2.addWidget(self.splitter) self.splitter2.addWidget(self.webView) self.vBoxLayout.addWidget(self.splitter2) self.webView.loadFinished.connect(self.finito) self.sentence = self.doc.sentences[self.sentence_id] if self.session_start: self.sentence_backup = copy.deepcopy(self.doc.sentences[self.sentence_id]) self.session_start = False html = process_document(self.sentence) self.webView.setHtml(html) self.load_finished = False def cb_change(self): self.column_number = 0 self.columns = [] self.map_col = {} x = 0 for i in range(self.chBoxLayout.count()): if isinstance(self.chBoxLayout.itemAt(i), QWidgetItem): wid = self.chBoxLayout.itemAt(i).widget() if wid.isChecked(): self.columns.append(wid.text()) self.column_number += 1 self.map_col[x] = wid.text() x+=1 for i in range(self.chBoxLayout_2.count()): if isinstance(self.chBoxLayout_2.itemAt(i), QWidgetItem): wid = self.chBoxLayout_2.itemAt(i).widget() if wid.isChecked(): self.columns.append(wid.text()) self.column_number += 1 self.map_col[x] = wid.text() x+=1 for i in range(self.chBoxLayout_3.count()): if isinstance(self.chBoxLayout_3.itemAt(i), QWidgetItem): wid = self.chBoxLayout_3.itemAt(i).widget() if wid.isChecked(): self.columns.append(wid.text()) self.column_number += 1 self.map_col[x] = wid.text() x+=1 self.first_time = True self.update_table() self.first_time = False def handle_change(self, item): col = self.map_col[item.column()] text = item.text() #print(text) isSpace = False if text == "": if col!="ID" and col!="FORM" and col!="LEMMA" and col!="UPOS" and col!="XPOS" and col!="HEAD" and col!="DEPREL" and col!="DEPS" and col!="MISC": isSpace = True text = "_" row = item.row() self.sentence = self.doc.sentences[self.sentence_id] if col=="ID": self.sentence.words[row].id = text elif col=="FORM": self.sentence.words[row].form = text elif col=="LEMMA": self.sentence.words[row].lemma = text elif col=="UPOS": self.sentence.words[row].upos = text.upper() elif col=="XPOS": self.sentence.words[row].xpos = text elif col=="FEATS": self.sentence.words[row].feats = text elif col=="HEAD": self.sentence.words[row].head = text elif col=="DEPREL": self.sentence.words[row].deprel = text elif col=="DEPS": self.sentence.words[row].deps = text elif col=="MISC": self.sentence.words[row].misc = text else: cur_col = col if col=="Number[psor]": cur_col = "Number\[psor\]" if col=="Person[psor]": cur_col = "Person\[psor\]" if re.search(cur_col+'=\w*', self.sentence.words[row].feats) is None: if text!="_": if self.sentence.words[row].feats=="_": self.sentence.words[row].feats = col+"="+text else: sorted_feats = re.split('\|', self.sentence.words[row].feats) match_col="" match_val="" for sorted_feat in sorted_feats: sf = re.split('\=', sorted_feat) if sf[0].lower()<col.lower(): match_col = sf[0] match_val = sf[1] if match_col=="": self.sentence.words[row].feats = col+"="+text+"|"+self.sentence.words[row].feats else: cur_match_col=match_col if match_col == "Number[psor]": cur_match_col = "Number\[psor\]" if match_col == "Person[psor]": cur_match_col = "Person\[psor\]" self.sentence.words[row].feats = re.sub(cur_match_col+'='+match_val, match_col+'='+match_val+"|"+col+"="+text, self.sentence.words[row].feats) elif isSpace: old_feats = re.split('\|', self.sentence.words[row].feats) new_feats = [] for old_feat in old_feats: if old_feat.split("=")[0]!=cur_col: new_feats.append(old_feat) self.sentence.words[row].feats = "|".join(new_feats) else: self.sentence.words[row].feats = re.sub(cur_col+'=\w*', col+"="+text, self.sentence.words[row].feats) if not self.first_time: self.doc.write() self.first_time = True self.writeNotes() self.update_table() self.update_html() self.check_errors() self.first_time = False
class Log(QWidget): def __init__(self, sender, title=''): super(Log, self).__init__() self.main_layout = QVBoxLayout() self.header_layout = QHBoxLayout() title_label = QLabel(title) title_label.setFont(QFont('Poppins', 13)) self.header_layout.addWidget(title_label) self.remove_button = QPushButton('x') self.remove_button.clicked.connect(self.remove_clicked) self.header_layout.addWidget(self.remove_button) self.remove_button.hide() holder_label = QLabel(shorten(str(sender), 76)) holder_label.setWordWrap(True) self.log_view = QPlainTextEdit() self.log_view.setReadOnly(True) self.main_layout.addLayout(self.header_layout) self.main_layout.addWidget(holder_label) self.main_layout.addWidget(self.log_view) self.setLayout(self.main_layout) self.enabled_style_sheet = ''' QLabel { border: None; } QWidget { color: #e9f4fb; } ''' self.disabled_style_sheet = ''' QLabel { border: None; } QWidget { color: #e9f4fb; } QPlainTextEdit { background: black; color: grey; } ''' self.setStyleSheet(self.enabled_style_sheet) def log(self, *args): s = '' for arg in args: s += ' ' + str(arg) self.log_view.appendPlainText('> ' + s) def clear(self): self.log_view.clear() def disable(self): self.remove_button.show() self.setStyleSheet(self.disabled_style_sheet) def enable(self): self.remove_button.hide() self.setStyleSheet(self.enabled_style_sheet) self.show() def remove_clicked(self): self.hide()
class MainConsole(QWidget): """Complete console interpreter. One instance will be created at the end of this file, when being imported in Ryven.py.""" def __init__( self, context=locals(), # context for interpreter history: int = 100, # max lines in history buffer blockcount: int = 5000 # max lines in output buffer ): super(MainConsole, self).__init__() # CREATE UI self.content_layout = QGridLayout(self) self.content_layout.setContentsMargins(0, 0, 0, 0) self.content_layout.setSpacing(0) # reset scope button self.reset_scope_button = QPushButton('reset console scope') self.reset_scope_button.clicked.connect(self.reset_scope_clicked) self.content_layout.addWidget(self.reset_scope_button, 0, 0, 1, 2) self.reset_scope_button.hide() # display for output self.out_display = ConsoleDisplay(blockcount, self) self.content_layout.addWidget(self.out_display, 1, 0, 1, 2) # colors to differentiate input, output and stderr self.inpfmt = self.out_display.currentCharFormat() self.inpfmt.setForeground(QBrush(QColor('white'))) self.outfmt = QTextCharFormat(self.inpfmt) self.outfmt.setForeground(QBrush(QColor('#A9D5EF'))) self.errfmt = QTextCharFormat(self.inpfmt) self.errfmt.setForeground(QBrush(QColor('#B55730'))) # display input prompt left besides input edit self.prompt_label = QLabel('> ', self) self.prompt_label.setFixedWidth(15) self.content_layout.addWidget(self.prompt_label, 2, 0) # command line self.inpedit = LineEdit(max_history=history) self.inpedit.returned.connect(self.push) self.content_layout.addWidget(self.inpedit, 2, 1) self.interp = None self.reset_interpreter() self.buffer = [] self.num_added_object_contexts = 0 def setprompt(self, text: str): self.prompt_label.setText(text) def reset_scope_clicked(self): self.reset_interpreter() def add_obj_context(self, context_obj): """adds the new context to the current context by initializing a new interpreter with both""" old_context = {} if self.interp is None else self.interp.locals name = 'obj' + (str(self.num_added_object_contexts+1) if self.num_added_object_contexts > 0 else '') new_context = {name: context_obj} context = {**old_context, **new_context} # merge dicts self.interp = code.InteractiveConsole(context) print('added as ' + name) self.num_added_object_contexts += 1 self.reset_scope_button.show() def reset_interpreter(self): """Initializes a new plain interpreter""" context = locals() self.num_added_object_contexts = 0 self.reset_scope_button.hide() self.interp = code.InteractiveConsole(context) def push(self, commands: str) -> None: """execute entered command which may span multiple lines when code was pasted""" if commands == 'clear': self.out_display.clear() else: lines = commands.split('\n') # usually just one entry # clean and print commands for line in lines: # remove '> '-and '. ' prefixes which may remain from copy&paste if re.match('^[\>\.] ', line): line = line[2:] # print input self.writeoutput(self.prompt_label.text() + line, self.inpfmt) # prepare for multi-line input self.setprompt('. ') self.buffer.append(line) # merge commands source = '\n'.join(self.buffer) more = self.interp.runsource(source, '<console>') if not more: # no more input required self.setprompt('> ') self.buffer = [] # reset buffer def write(self, line: str) -> None: """capture stdout and print to outdisplay""" if len(line) != 1 or ord(line[0]) != 10: self.writeoutput(line.rstrip(), self.outfmt) def errorwrite(self, line: str) -> None: """capture stderr and print to outdisplay""" self.writeoutput(line, self.errfmt) def writeoutput(self, line: str, fmt: QTextCharFormat = None) -> None: """prints to outdisplay""" if fmt is not None: self.out_display.setCurrentCharFormat(fmt) self.out_display.appendPlainText(line.rstrip())
class Resultat(QDialog): """ This class is used to create the window to search new products """ def __init__(self, mydb, parent=None): super(Resultat, self).__init__(parent) self.setWindowTitle("Project 5 : Openfoodfacts") self.mydb = mydb self.text_cat = QtWidgets.QLabel( "Select a category below") self.mycombo_cat = QtWidgets.QComboBox() self.text_prod = QtWidgets.QLabel("Then select a product") self.mycombo_prod = QtWidgets.QComboBox() self.text_select_subs = QtWidgets.QLabel("Choose one of the \ substitution products") self.mytable = QtWidgets.QTableWidget(1, 4) self.mytable.setHorizontalHeaderLabels( ("Selected product;Nutriscore;Stores;Link to website").split(";")) header = self.mytable.horizontalHeader() header.setSectionResizeMode(0, QtWidgets.QHeaderView.Stretch) header.setSectionResizeMode(1, QtWidgets.QHeaderView.Stretch) header.setSectionResizeMode(2, QtWidgets.QHeaderView.Stretch) header.setSectionResizeMode(3, QtWidgets.QHeaderView.Stretch) self.subs_table = QtWidgets.QTableWidget(1, 4) self.subs_table.setHorizontalHeaderLabels( ("Substitution product;Nutriscore;\ Stores;Link to website").split(";")) header2 = self.mytable.horizontalHeader() header2.setSectionResizeMode(0, QtWidgets.QHeaderView.Stretch) header2.setSectionResizeMode(1, QtWidgets.QHeaderView.Stretch) header2.setSectionResizeMode(2, QtWidgets.QHeaderView.Stretch) header2.setSectionResizeMode(3, QtWidgets.QHeaderView.Stretch) header2.setSectionResizeMode(0, QtWidgets.QHeaderView.ResizeToContents) header2.setSectionResizeMode(1, QtWidgets.QHeaderView.ResizeToContents) header2.setSectionResizeMode(2, QtWidgets.QHeaderView.ResizeToContents) header2.setSectionResizeMode(3, QtWidgets.QHeaderView.ResizeToContents) self.search_button = QPushButton("Search a substitution product") self.save_button = QPushButton("Save result") self.save_button.hide() self.mycursor = self.mydb.cursor() # Getting all product categories sql_query_cat = "SELECT * FROM Category" self.mycursor.execute(sql_query_cat) result = self.mycursor.fetchall() for category in result: category_id = category[0] category_name = category[1] self.mycombo_cat.addItem("{} - {}".format(category_id, (category_name))) self.first_cat = result[0][0] self.layout = QVBoxLayout() self.layout.addWidget(self.text_cat) self.layout.addWidget(self.mycombo_cat) self.layout.addWidget(self.text_prod) self.layout.addWidget(self.mycombo_prod) self.update_combo_prod() # adding products to combo box self.update_table() self.layout.addWidget(self.mytable) self.layout.addWidget(self.search_button) self.layout.addWidget(self.text_select_subs) self.setLayout(self.layout) self.search_button.clicked.connect(self.update_subs_table) self.mycombo_cat.currentIndexChanged.connect(self.update_combo_prod) self.mycombo_prod.currentIndexChanged.connect(self.update_table) self.save_button.clicked.connect(self.save_results) self.layout.addWidget(self.subs_table) self.subs_table.hide() self.layout.addWidget(self.save_button) def update_combo_prod(self): """ This function is used to update the list of products for the category selected. """ self.mycombo_prod.clear() category_selected = (self.mycombo_cat.currentIndex()) + self.first_cat sql_query_test = """SELECT product_name FROM Product inner join Product_category WHERE Product.id = Product_category.product_id and Product_category.category_id = %s""" self.mycursor.execute(sql_query_test, (category_selected,)) result = self.mycursor.fetchall() for x in result: self.mycombo_prod.addItem(x[0]) def update_table(self): """ This function is used to update product table that the user can select. """ self.mytable.setHorizontalHeaderLabels(("Selected product;\ Nutriscore;Stores;Link to website").split(";")) header = self.mytable.horizontalHeader() product_selected_name = self.mycombo_prod.currentText() sql_query = "SELECT id FROM Product WHERE product_name = %s" self.mycursor.execute(sql_query, (product_selected_name,)) result = self.mycursor.fetchall() for product in result: self.product_selected_id = product[0] sql_query2 = """SELECT product_name, nutriscore, stores, url FROM Product WHERE id = %s""" self.mycursor.execute(sql_query2, (self.product_selected_id,)) result2 = self.mycursor.fetchall() for product in result2: res_prod_name = product[0] self.res_nutri = product[1] res_stores = product[2] res_url = product[3] url = QtWidgets.QTableWidgetItem(res_url) product_name = QtWidgets.QTableWidgetItem(res_prod_name) nutriscore = QtWidgets.QTableWidgetItem(self.res_nutri) stores = QtWidgets.QTableWidgetItem(res_stores) self.mytable.setItem(0, 3, url) self.mytable.setItem(0, 0, product_name) self.mytable.setItem(0, 1, nutriscore) self.mytable.setItem(0, 2, stores) header.setSectionResizeMode(0, QtWidgets.QHeaderView.Stretch) header.setSectionResizeMode(1, QtWidgets.QHeaderView.Stretch) header.setSectionResizeMode(2, QtWidgets.QHeaderView.Stretch) header.setSectionResizeMode(3, QtWidgets.QHeaderView.Stretch) header.setSectionResizeMode(0, QtWidgets.QHeaderView.ResizeToContents) header.setSectionResizeMode(1, QtWidgets.QHeaderView.ResizeToContents) header.setSectionResizeMode(2, QtWidgets.QHeaderView.ResizeToContents) header.setSectionResizeMode(3, QtWidgets.QHeaderView.ResizeToContents) def update_subs_table(self): """ This function is used to update the substitution products table """ self.delete_rows_subs_table() self.subs_table.setHorizontalHeaderLabels(("Substitution product;\ Nutriscore;Stores;Link to website").split(";")) header2 = self.subs_table.horizontalHeader() header2.setSectionResizeMode(0, QtWidgets.QHeaderView.Stretch) header2.setSectionResizeMode(1, QtWidgets.QHeaderView.Stretch) header2.setSectionResizeMode(2, QtWidgets.QHeaderView.Stretch) header2.setSectionResizeMode(0, QtWidgets.QHeaderView.ResizeToContents) header2.setSectionResizeMode(1, QtWidgets.QHeaderView.ResizeToContents) header2.setSectionResizeMode(2, QtWidgets.QHeaderView.ResizeToContents) category_selected = (self.mycombo_cat.currentIndex()) + self.first_cat sql_query_subs = """SELECT id, product_name, nutriscore, stores, url FROM Product inner join Product_category WHERE Product.id = Product_category.product_id and Product_category.category_id = %s""" self.mycursor.execute(sql_query_subs, (category_selected,)) result = self.mycursor.fetchall() row_nbr = 0 self.possible_substitution = [] for product in result: if product[2] < self.res_nutri: self.subs_table.insertRow(row_nbr) self.possible_substitution.append(product[0]) res_prod_name = product[1] res_nutri = product[2] res_stores = product[3] res_url = product[4] url = QtWidgets.QTableWidgetItem(res_url) product_name = QtWidgets.QTableWidgetItem(res_prod_name) nutriscore = QtWidgets.QTableWidgetItem(res_nutri) stores = QtWidgets.QTableWidgetItem(res_stores) self.subs_table.setItem(row_nbr, 3, url) self.subs_table.setItem(row_nbr, 0, product_name) self.subs_table.setItem(row_nbr, 1, nutriscore) self.subs_table.setItem(row_nbr, 2, stores) row_nbr += 1 if row_nbr == 0: message_box = QtWidgets.QMessageBox() message_box.setWindowTitle("Project 5 : Openfoodfacts") message_box.setText("There is no better product \ than the one selected ! :=)") message_box.exec() self.subs_table.show() self.save_button.show() def delete_rows_subs_table(self): """ Clear the substitution products table before a new search. """ self.subs_table.clear() self.subs_table.setRowCount(0) def save_results(self): """ This function is used to save results by inserting the product selected and the substitution product chosen into the table 'Product_saved' in our database. """ line_selected = self.subs_table.currentRow() substitution_chosen = self.possible_substitution[line_selected] sql1 = """INSERT INTO Product_saved (product_selected_id, substitution_product_id) VALUES (%s, %s)""" self.mycursor.execute(sql1, (self.product_selected_id, substitution_chosen)) self.mydb.commit()
class WMatSelectV(QGroupBox): """ Material related widget including a Label, a Combobox to select a material and a Button to edit a material libary. WMatSelect is instantiated to empty material data, so it has to be referenced to actual material data with the update method prior to its first usage. """ # Signal to W_MachineSetup to know that the save popup is needed saveNeeded = Signal() def __init__(self, parent=None): """ Set a reference to a material libray and material data path, updates the Combobox by the material names of the libary and set a referenced material by name. Parameters ---------- self : A WMatSelect object parent : A reference to the widgets parent Returns ------- """ # Build the interface according to the .ui file QGroupBox.__init__(self, parent) self.verticalLayout = QVBoxLayout(self) self.c_mat_type = QComboBox(self) self.c_mat_type.setObjectName(u"c_mat_type") self.verticalLayout.addWidget(self.c_mat_type) self.b_matlib = QPushButton(self) self.b_matlib.setObjectName(u"b_matlib") self.b_matlib.setText("Edit Materials") self.verticalLayout.addWidget(self.b_matlib) # Create the property of the widget self.mat_win = None # DMatLib widget self.obj = None # object that has a material attribute self.mat_attr_name = "" # material attribute name self.matlib = list() # Matlib self.matlib_path = "" # Path to save the matlib self.def_mat = "M400-50A" # Default material self.is_hide_button = False # To hide the "Edit material" button # Connect the signals self.c_mat_type.currentIndexChanged.connect(self.set_mat_type) self.b_matlib.clicked.connect(self.s_open_matlib) def update(self, obj, mat_attr_name, matlib, matlib_path=""): """ Set a reference to a material libray and material data path, updates the Combobox by the material names of the libary and set a referenced material by name. Parameters ---------- self : A WMatSelect object obj : A pyleecan object that has a material attribute mat_attr_name : A string of the material attribute name matlib : A material libary, i.e. a list of Material objects matlib_path : A string containing the path of material data Returns ------- """ self.c_mat_type.blockSignals(True) # Set material combobox according to matlib names self.obj = obj self.mat_attr_name = mat_attr_name self.matlib = matlib self.matlib_path = matlib_path if self.is_hide_button: self.b_matlib.hide() else: self.b_matlib.show() # Update the list of materials self.c_mat_type.clear() items_to_add = [] # Add RefMatLib materials items_to_add.extend([mat.name for mat in matlib.dict_mat["RefMatLib"]]) # Add machine-specific materials items_to_add.extend( [mat.name for mat in matlib.dict_mat["MachineMatLib"]]) self.c_mat_type.addItems(items_to_add) mat = getattr(self.obj, mat_attr_name, None) if mat is None or mat.name is None: # Default lamination material: M400-50A index = self.c_mat_type.findText(self.def_mat) if index != -1: # self.mat.__init__(init_dict=self.matlib[index].as_dict()) setattr( self.obj, self.mat_attr_name, self.matlib.dict_mat["RefMatLib"][index], ) else: index = self.c_mat_type.findText(mat.name) self.c_mat_type.setCurrentIndex(index) self.c_mat_type.blockSignals(False) def setText(self, txt): """ Set the Label's text Parameters ---------- self : A WMatSelect object txt : A text string Returns ------- """ self.setTitle(txt) def set_mat_type(self, index): """ Signal to set the referenced material from the material libary by the selected Combobox index Parameters ---------- self : A WMatSelect object index : Current index of the combobox Returns ------- """ if index >= len(self.matlib.dict_mat["RefMatLib"]): index -= len(self.matlib.dict_mat["RefMatLib"]) dict_key = "MachineMatLib" else: dict_key = "RefMatLib" setattr(self.obj, self.mat_attr_name, self.matlib.dict_mat[dict_key][index]) # Notify the machine GUI that the machine has changed self.saveNeeded.emit() def s_open_matlib(self): """ Open the GUI (DMatLib widget) to Edit the Material library Parameters ---------- self : A WMatSelect object Returns ------- """ if self.c_mat_type.currentIndex() >= len( self.matlib.dict_mat["RefMatLib"]): index = self.c_mat_type.currentIndex() - len( self.matlib.dict_mat["RefMatLib"]) key = "MachineMatLib" else: index = self.c_mat_type.currentIndex() key = "RefMatLib" self.mat_win = DMatLib(self.matlib, key, index) self.mat_win.accepted.connect(self.set_matlib) self.mat_win.saveNeeded.connect(self.emit_save) self.mat_win.show() def emit_save(self): """ Emit saveNeeded if a material has been edited """ self.saveNeeded.emit() def set_matlib(self): """Update the matlib with the new value Parameters ---------- self : A WMatSelect object Returns ------- """ # Empty and fill the list to keep the same object (to change it everywhere) # del self.matlib[:] # self.matlib.extend(self.mat_win.matlib) # Update the material # index = int(self.mat_win.nav_mat.currentItem().text()[:3]) - 1 # not needed if machine materials are "connected" properly # mat_dict = (self.mat_win.matlib[index]).as_dict() # self.mat.__init__(init_dict=mat_dict) # Do not clear for now to keep editor (DMatLib) open # # Clear the window # self.mat_win.deleteLater() # self.mat_win = None # Update the widget # Avoid trigger signal currentIndexChanged self.c_mat_type.blockSignals(True) self.c_mat_type.clear() items_to_add = [] # Add RefMatLib materials items_to_add.extend( [mat.name for mat in self.matlib.dict_mat["RefMatLib"]]) # Add machine-specific materials items_to_add.extend( [mat.name for mat in self.matlib.dict_mat["MachineMatLib"]]) self.c_mat_type.addItems(items_to_add) index = self.c_mat_type.findText( getattr(self.obj, self.mat_attr_name).name) self.c_mat_type.setCurrentIndex(index) self.c_mat_type.blockSignals(False)
class appConfigStack(QWidget): message = Signal("QObject", "QObject") def __init__(self, stack): super().__init__() self.dbg = True self.default_icon = 'shell' self.menu_description = (_("Configure stack")) self.description = (_("Configure custom stack")) self.icon = ('org.kde.plasma.quicklaunch') self.tooltip = (_("From here you can configure something")) self.index = 1 self.enabled = True self.sw_changes = False self.level = 'user' self.appConfig = None self.config = {} self.changes = False self.add_events = False self.refresh = False self.stack = stack self.textdomain = '' self.btn_ok = QPushButton(_("Apply")) self.btn_cancel = QPushButton(_("Undo")) self.__init_stack__() self.writeConfig = self.writeDecorator(self.writeConfig) #def __init__ def __init_stack__(self): raise NotImplementedError() #def __init_stack__ def _debug(self, msg): if self.dbg: logging.warning("Stack {0}: {1}".format(self.description, msg)) #def _debug def initScreen(self): self._debug("No init values") def setAppConfig(self, appconfig): self.appConfig = appconfig #def setAppConfig def translate(self, msg=""): return (gettext.dgettext(self.textdomain, msg)) def setTextDomain(self, textDomain): #gettext.textdomain(textDomain) gettext.textdomain('{}'.format(textDomain)) _ = gettext.gettext #def set_textDomain(self,textDomain): def applyParms(self, app): self._debug("Set parm %s" % app) self.app = app #def apply_parms(self,app): def getConfig(self, level=None, exclude=[]): self._debug("Getting config for level {}".format(level)) self._debug("Exclude keys: {}".format(exclude)) cursor = QtGui.QCursor(Qt.WaitCursor) self.setCursor(cursor) data = {'system': {}, 'user': {}, 'n4d': {}} self._debug("Refresh: {}".format(self.refresh)) self._debug("Changes: {}".format(self.changes)) if self.refresh or self.changes: if level: data = self.appConfig.getConfig(level, exclude) else: data = self.appConfig.getConfig('system', exclude) # self._debug("Data: %s"%data) self.level = data['system'].get('config', 'user') if self.level != 'system': data = self.appConfig.getConfig(self.level, exclude) level = data[self.level].get('config', 'n4d') if level != self.level: self.level = level data = self.appConfig.getConfig(level, exclude) data[self.level]['config'] = self.level else: if self.config[self.level]: data[self.level] = self.config[self.level].copy() self._debug("Read level from config: {}".format(self.level)) self.refresh = False cursor = QtGui.QCursor(Qt.PointingHandCursor) self.setCursor(cursor) return (data) #def get_default_config def setConfig(self, config): if self.config and self.config == config: self.refresh = False else: if self.config: self.refresh = True self.config = config.copy() #def setConfig def setLevel(self, level): self.level = level #def setLevel def _reset_screen(self): self.updateScreen() self.setChanged(False) #def _reset_screen def updateScreen(self): print("updateScreen method not implemented in this stack") raise NotImplementedError() #def updateScreen def saveChanges(self, key, data, level=None): cursor = QtGui.QCursor(Qt.WaitCursor) self.setCursor(cursor) retval = False if not level: self.getConfig() level = self.level self._debug("Saving to level {}".format(level)) retval = True if not self.appConfig.write_config(data, level=level, key=key): self.btn_ok.setEnabled(True) self.btn_cancel.setEnabled(True) self.refresh = False self.changes = True retval = False self.showMsg("Failed to write config") cursor = QtGui.QCursor(Qt.PointingHandCursor) self.setCursor(cursor) return retval #def saveChanges def writeDecorator(self, func): def states(): cursor = QtGui.QCursor(Qt.WaitCursor) self.setCursor(cursor) func() self.btn_ok.setEnabled(False) self.btn_cancel.setEnabled(False) self.refresh = True self.changes = False cursor = QtGui.QCursor(Qt.PointingHandCursor) self.setCursor(cursor) return states #def writeDecorator def writeConfig(self): print("writeConfig method not implemented in this stack") raise NotImplementedError() #def writeConfig def showEvent(self, event): def recursive_add_events(layout): def recursive_explore_widgets(widget): if widget == None: return if isinstance(widget, QCheckBox): widget.stateChanged.connect(self.setChanged) if isinstance(widget, QRadioButton): widget.toggled.connect(self.setChanged) elif isinstance(widget, QComboBox): widget.currentTextChanged.connect(self.setChanged) elif isinstance(widget, QLineEdit): widget.textChanged.connect(self.setChanged) elif isinstance(widget, QSlider): widget.valueChanged.connect(self.setChanged) elif isinstance(widget, QPushButton): if widget.menu(): widget.menu().triggered.connect(self.setChanged) else: widget.clicked.connect(self.setChanged) elif 'dropButton' in str(widget): widget.drop.connect(self.setChanged) elif isinstance(widget, QTableWidget): for x in range(0, widget.rowCount()): for y in range(0, widget.columnCount()): tableWidget = widget.cellWidget(x, y) recursive_explore_widgets(tableWidget) elif widget.layout(): recursive_add_events(widget.layout()) for idx in range(0, layout.count()): widget = layout.itemAt(idx).widget() if widget: recursive_explore_widgets(widget) elif layout.itemAt(idx).layout(): recursive_add_events(layout.itemAt(idx).layout()) if self.add_events == False: self.add_events = True layout = self.layout() if layout: recursive_add_events(layout) box_btns = QHBoxLayout() self.btn_ok.clicked.connect(self.writeConfig) self.btn_ok.setFixedWidth(self.btn_ok.sizeHint().width()) self.btn_cancel.clicked.connect(self._reset_screen) self.btn_cancel.setFixedWidth(self.btn_ok.sizeHint().width()) box_btns.addWidget(self.btn_ok, 1, Qt.AlignRight) box_btns.addWidget(self.btn_cancel, Qt.AlignRight) try: layout.addLayout(box_btns, Qt.AlignRight) except: layout.addLayout(box_btns, layout.rowCount(), 0, 1, layout.columnCount()) self.btn_ok.setEnabled(False) self.btn_cancel.setEnabled(False) try: self.updateScreen() self.setChanged(False) except: print("updateScreen method is not implemented in this stack") #def showEvent def hideControlButtons(self): self.btn_ok.hide() self.btn_cancel.hide() #def hideControlButtons(self): def setChanged(self, state=True): self._debug("State: {}".format(state)) if isinstance(state, bool) == False: if state: state = True else: state = False if self.btn_ok.isHidden() == False: self.btn_ok.setEnabled(state) self.btn_cancel.setEnabled(state) else: state = False self.changes = state self._debug("New State: {}".format(state)) #def setChanged def getChanges(self): self._debug("Read state: {}".format(self.changes)) return self.changes #def getChanges def setParms(self, parms): return #def setParms def showMsg(self, msg, title='', state=None): self._debug("Sending {}".format(msg)) if title == '': title = self.description notify2.init(title) notice = notify2.Notification(msg) notice.show() return #self.message.emit(msg,state) #def showMsg def n4dGetVar(self, client=None, var=''): ret = self.appConfig.n4dGetVar(client, var) return (ret) #def n4dQuery def n4dSetVar(self, client=None, var='', val={}): ret = self.appConfig.n4dSetVar(client, var, val) return (ret) #def n4dQuery def n4dDelVar(self, client=None, var=''): ret = self.appConfig.n4dDelVar(client, var) return (ret) #def n4dQuery def n4dQuery(self, n4dclass, n4dmethod, *args, **kwargs): ret = self.appConfig.n4dQuery(n4dclass, n4dmethod, *args, **kwargs) return (ret)
class MainWindow(QMainWindow): def __init__(self): super().__init__(flags=Qt.WindowContextHelpButtonHint | Qt.WindowCloseButtonHint | Qt.CustomizeWindowHint) self.ui = Ui_MainWindow() self.ui.setupUi(self) self.setUpMainWindow() def setUpMainWindow(self): self.setWindowTitle( "Lab Tools 1.0 (2021 Edition) - Applications of ML in Mechatronics" ) # set icon self.setUpIcon() #set up the info and start processing button self.inputLineEdit = QLineEdit() self.outputFolderLineEdit = QLineEdit() self.modelLineEdit = QLineEdit() self.browseOutputButton = QPushButton() self.browseInputButton = QPushButton() self.browseModelButton = QPushButton() self.startStopButton = QPushButton() self.useDefaultModelCheckbox = QCheckBox() self.inputLineEdit = self.findChild(QLineEdit, "inputLineEdit") self.modelLineEdit = self.findChild(QLineEdit, "modelLineEdit") self.outputFolderLineEdit = self.findChild(QLineEdit, "outputFolderLineEdit") self.browseOutputButton = self.findChild(QPushButton, "browseOutputButton") self.browseInputButton = self.findChild(QPushButton, "browseInputButton") self.browseModelButton = self.findChild(QPushButton, "browseModelButton") self.startStopButton = self.findChild(QPushButton, "startStopButton") self.useDefaultModelCheckbox = self.findChild( QCheckBox, "useDefaultModelCheckbox") self.chooseModelLabel = self.findChild(QLabel, "chooseModelLabel") #disabling model transfer capability if needed self._modelRPiPath = False if DISABLE_MODEL_TRASFER: self.useDefaultModelCheckbox.hide() self.modelLineEdit.setEnabled(1) self.browseModelButton.hide() self.chooseModelLabel.setText(self.chooseModelLabel.text() + " Name") # self.gridLayout = self.findChild(QGridLayout, "gridLayout") # self.gridLayout.removeWidget(self.browseModelButton) # self.gridLayout.removeWidget(self.useDefaultModelCheckbox) self._modelRPiPath = True self.startStopButton.clicked.connect(self.handleStartStopButtonClicked) self.browseOutputButton.clicked.connect(self.handleBrowseOutputButton) self.browseInputButton.clicked.connect(self.handleBrowseInputButton) self.browseModelButton.clicked.connect(self.handleBrowseModelButton) self.useDefaultModelCheckbox.stateChanged.connect( self.handleUseDefaultModelCheckboxStateChanged) self.useDefaultModelCheckbox.setChecked(True) #set up the log and progress bar self.logTextBrowser = QTextBrowser() self.lastLogTextLabel = QLabel() self.logTextBrowser = self.findChild(QTextBrowser, "logTextBrowser") self.progressBar = self.findChild(QProgressBar, "progressBar") self.clearLogButton = self.findChild(QPushButton, "clearLogButton") self.saveLogButton = self.findChild(QPushButton, "saveLogButton") self.lastLogTextLabel = self.findChild(QLabel, "lastLogTextLabel") self.clearLogButton.clicked.connect(self.handleClearLogButton) self.saveLogButton.clicked.connect(self.handleSaveLogButton) #set up menu bar self.actionHelp = self.findChild(QAction, "actionHelp") self.actionAbout = self.findChild(QAction, "actionAbout") self.actionHelp.triggered.connect(self.handleActionHelpClicked) self.actionAbout.triggered.connect(self.handleActionAboutClicked) # Add additional menu actions self.utilitiesMenu = self.menuBar().addMenu("Utilities") self.actionGetRPiIP = QAction("Get RPi IP") self.utilitiesMenu.addAction(self.actionGetRPiIP) self.actionGetRPiIP.triggered.connect(self.handleActionGetRPiIPClicked) self.actionUpdateRPiScript = QAction("Updare RPi Script") self.utilitiesMenu.addAction(self.actionUpdateRPiScript) self.actionUpdateRPiScript.triggered.connect( self.handleActionUpdateRPiScript) #create objects from the other classes self.logger = Logger(self.logTextBrowser, self.lastLogTextLabel) #initialize member variables self._b_processRunning = False # set up lab names self.setUpLabNames() #set up serial comms self.setupSerial() self.refreshSerialPorts() self.logger.log("The application is ready!", type="INFO") def setUpIcon(self): self.appIcon = QIcon("images/favicon.png") self.setWindowIcon(self.appIcon) def setUpLabNames(self): self.labNameComboBox = QComboBox() self.labNameComboBox = self.findChild(QComboBox, "labNameComboBox") self.labNameComboBox.currentIndexChanged.connect( self.handleLabNameComboboxCurrentIndexChanged) for code, name in utils.lab_names.items(): self.labNameComboBox.addItem(code + ": " + name) self.labNameComboBox.setCurrentIndex(1) def setupSerial(self): self.refreshSerialPortsButton = QPushButton() self.connectDisconnectSerialButton = QPushButton() self.serialPortComboBox = QComboBox() self.refreshSerialPortsButton = self.findChild( QPushButton, "refreshSerialPortsButton") self.connectDisconnectSerialButton = self.findChild( QPushButton, "connectDisconnectSerialButton") self.serialPortComboBox = self.findChild(QComboBox, "serialPortComboBox") self.refreshSerialPortsButton.clicked.connect(self.refreshSerialPorts) self.connectDisconnectSerialButton.clicked.connect( self.handleSerialConnectDisconnect) self._b_serialConnected = False def refreshSerialPorts(self): availablePorts = utils.find_serial_ports() self.serialPortComboBox.clear() for portName in availablePorts: self.serialPortComboBox.addItem(portName) def handleSerialConnectDisconnect(self): if not self.b_serialConnected: try: currentPortName = self.serialPortComboBox.currentText() self.port = serial.Serial(currentPortName, 115200, timeout=1, write_timeout=240, bytesize=8, parity='N', stopbits=1) self.port.set_buffer_size(rx_size=10**3, tx_size=10**8) self.serialPortComboBox.setItemText( self.serialPortComboBox.currentIndex(), currentPortName + " (CONNECTED)") self.connectDisconnectSerialButton.setText("Disconnect") self.b_serialConnected = True self.refreshSerialPortsButton.setDisabled(1) except (OSError, serial.SerialException): print("Problem with Serial Connection!") self.logger.log( "Problem with Serial Connection, Make sure you chose the right port", type="ERROR") else: try: self.port.close() self.refreshSerialPorts() self.connectDisconnectSerialButton.setText("Connect") self.b_serialConnected = False self.refreshSerialPortsButton.setEnabled(1) except (OSError, serial.SerialException): print("Problem with Serial Connection!") self.logger.log("Problem with Serial Connection", type="ERROR") def _startButtonClicked(self): self.logger.log("Attempting to start the processing", type="INFO") if not self.b_serialConnected: self.logger.log( "Serial is not connected, Please connect serial first", type="ERROR") return if self.inputLineEdit.text()[-4:].lower() != ".csv": self.logger.log("Please select a valid input csv file", type="ERROR") return if self.outputFolderLineEdit.text() == "": self.logger.log("Please select an output directory", type="ERROR") return self.executer = Executer(serialObj=self.port, loggerObj=self.logger) if self.modelLineEdit.text() != "": modelPath = self.modelLineEdit.text() else: modelPath = None if self._modelRPiPath: self.logger.log( "Please select a valid model that is already available in the folder saved_models on the RPi", type="ERROR") return #Read the Input File try: inputDataFrame = pd.read_csv(self.inputLineEdit.text()) except: self.logger.log("CSV File Reading Failed, select a valid csv file", type="ERROR") possibleInputs = list(inputDataFrame.columns) #Display a dialog to ask the user to choose what inputs they want dialog = QDialog(self) dialog.setWindowTitle("Select the Input Fields") dialogButtons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) dialogButtons.button(QDialogButtonBox.Ok).setDisabled(0) dialogButtons.accepted.connect(dialog.accept) dialogButtons.rejected.connect(dialog.reject) mainLayout = QVBoxLayout(dialog) scroll = QScrollArea(dialog) scroll.setWidgetResizable(True) layoutWidget = QWidget() layout = QVBoxLayout(layoutWidget) scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) scroll.setWidget(layoutWidget) chosenInputs = [] checkboxes = [] def handleCheckboxClicked(): dialogButtons.button(QDialogButtonBox.Ok).setDisabled(1) for checkbox in checkboxes: if checkbox.isChecked(): dialogButtons.button(QDialogButtonBox.Ok).setDisabled(0) for input in possibleInputs: checkbox = QCheckBox(text=input) checkbox.clicked.connect(handleCheckboxClicked) checkbox.setChecked(True) checkboxes.append(checkbox) layout.addWidget(checkbox) mainLayout.addWidget( QLabel(text="Please select the input fields from the following:")) mainLayout.addWidget(scroll) mainLayout.addWidget(dialogButtons) dialog.setLayout(mainLayout) # dialog.setFixedHeight(400) if dialog.exec_() == QDialog.Accepted: for checkbox in checkboxes: if checkbox.isChecked(): chosenInputs.append(checkbox.text()) self.logger.log("The chosen input fields are: " + ', '.join(chosenInputs), type="INFO") else: return self.startStopButton.setText("Stop Processing") self.b_processRunning = True executionResult = self.executer.execute(self.labNameComboBox.currentText().split(":")[0], inputDataFrame, \ self.outputFolderLineEdit.text(), inputFields=chosenInputs, progressBar=self.progressBar, \ model=modelPath if not self._modelRPiPath else "RPI:"+modelPath) if executionResult == ExecutionResult.COMPLETED: self._stopButtonClicked(finishedProcessing=True) elif executionResult == ExecutionResult.INTERRUPTED or executionResult == ExecutionResult.FAILED: self._stopButtonClicked() if self.executer.reset() == ExecutionResult.FAILED: self.logger.log( "Resetting the serial state of RPi Failed, please power cycle the RPi", type="ERROR") else: self.logger.log("The serial state of RPi has been reset", type="INFO") def _stopButtonClicked(self, finishedProcessing=False): self.startStopButton.setText("Start Processing") if finishedProcessing: self.logger.log("", special="ProcessingCompleted") else: self.logger.log("", special="ProcessingStopped") self.b_processRunning = False #TODO: Complete Implementing this def handleStartStopButtonClicked(self): if (self.b_processRunning): self.executer.requestStop() else: self._startButtonClicked() def handleActionHelpClicked(self): helpBox = QMessageBox() helpBox.setIcon(QMessageBox.Information) helpBox.setStandardButtons(QMessageBox.Ok) helpBox.setWindowTitle("Need Help?") helpBox.setText( "For Help, please reach out to your Instructor or TA or read the lab manual" ) helpBox.setTextFormat(Qt.RichText) helpBox.setInformativeText( f"You can access the project <a href=\"{utils.docs_link}\">Manual</a> and source in the <a href=\"{utils.repo_link}\">Github Repo!</a>" ) helpBox.setWindowIcon(self.appIcon) helpBox.exec_() def handleActionAboutClicked(self): aboutBox = QMessageBox() aboutBox.setIcon(QMessageBox.Information) aboutBox.setStandardButtons(QMessageBox.Ok) aboutBox.setWindowTitle("About the Software") aboutBox.setText(utils.license_text) aboutBox.setWindowIcon(self.appIcon) aboutBox.exec_() def handleBrowseInputButton(self): dialog = QFileDialog(self) dialog.setFileMode(QFileDialog.ExistingFile) dialog.setNameFilter("*.csv") if dialog.exec_(): filePath = dialog.selectedFiles() if len(filePath) == 1: self.inputLineEdit.setText(filePath[0]) def handleBrowseModelButton(self): dialog = QFileDialog(self) dialog.setFileMode(QFileDialog.ExistingFile) dialog.setNameFilter(utils.lab_model_extensions[ self.labNameComboBox.currentText().split(":")[0]]) if dialog.exec_(): filePath = dialog.selectedFiles() if len(filePath) == 1: self.modelLineEdit.setText(filePath[0]) def handleBrowseOutputButton(self): dialog = QFileDialog(self) dialog.setFileMode(QFileDialog.DirectoryOnly) dialog.setOption(QFileDialog.ShowDirsOnly) if dialog.exec_(): folderPath = dialog.selectedFiles() if len(folderPath) == 1: self.outputFolderLineEdit.setText(folderPath[0]) def handleSaveLogButton(self): dialog = QFileDialog(self) dialog.setFileMode(QFileDialog.AnyFile) dialog.AcceptMode(QFileDialog.AcceptSave) dialog.setNameFilter("*.txt") if dialog.exec_(): filePath = dialog.selectedFiles() if len(filePath) == 1: if self.logger.saveLog(filePath[0]) != -1: self.logger.log( "The log has been saved, feel free to clear the log now", type="SUCCESS") return self.logger.log("Failed to save the log, please select a valid file", type="ERROR") def handleClearLogButton(self): self.logger.clearLog() def handleUseDefaultModelCheckboxStateChanged(self): if self._modelRPiPath: return if not self.useDefaultModelCheckbox.checkState(): self.modelLineEdit.setDisabled(0) self.browseModelButton.setDisabled(0) else: self.modelLineEdit.clear() self.modelLineEdit.setDisabled(1) self.browseModelButton.setDisabled(1) def handleLabNameComboboxCurrentIndexChanged(self): if self._modelRPiPath: self.modelLineEdit.setText(utils.lab_default_models[ self.labNameComboBox.currentText().split(":")[0]]) # Disable the model options when the lab selected is Communication Test if self.labNameComboBox.currentIndex() == 0: self.modelLineEdit.setDisabled(1) self.browseModelButton.setDisabled(1) self.modelLineEdit.setText("Model is not required") else: self.modelLineEdit.setDisabled(0) self.browseModelButton.setDisabled(0) def handleActionGetRPiIPClicked(self): self.logger.log("Attempting to Get Raspberry Pi IP Address", type="INFO") if not self.b_serialConnected: replyMessage = "Serial is not connected, Please connect serial first" else: self.executer = Executer(serialObj=self.port, loggerObj=self.logger) ipaddr = self.executer.executeOther("GET_IP") replyMessage = ( "Raspberry Pi IP is " + ipaddr ) if ipaddr != ExecutionResult.FAILED else "Failed to obtain the IP Address" self.logger.log(replyMessage) ipAddrMessage = QMessageBox() ipAddrMessage.setIcon(QMessageBox.Information) ipAddrMessage.setStandardButtons(QMessageBox.Ok) ipAddrMessage.setWindowTitle("Raspberry Pi IP Address") ipAddrMessage.setText(replyMessage) ipAddrMessage.setTextFormat(Qt.RichText) ipAddrMessage.setWindowIcon(self.appIcon) ipAddrMessage.exec_() def handleActionUpdateRPiScript(self): self.logger.log( 'Attempting to "git pull" for the Raspberry Pi Python Script', type="INFO") if not self.b_serialConnected: replyMessage = "Serial is not connected, Please connect serial first" else: self.executer = Executer(serialObj=self.port, loggerObj=self.logger) result = self.executer.executeOther("UPDATE_SCRIPT") if result != ExecutionResult.FAILED and "FAIL" not in result: replyMessage = "Raspberry Pi Script has been updated. You still need to reboot or power cycle the Raspberry Pi for the updated script to run" \ "\nReceived: " + result else: replyMessage = "Failed to update the RPi Script" if "FAIL" in result: replyMessage = replyMessage + "\nReceived: " + result self.logger.log(replyMessage) ipAddrMessage = QMessageBox() ipAddrMessage.setIcon(QMessageBox.Information) ipAddrMessage.setStandardButtons(QMessageBox.Ok) ipAddrMessage.setWindowTitle("Raspberry Pi Script Updating Status") ipAddrMessage.setText(replyMessage) ipAddrMessage.setTextFormat(Qt.RichText) ipAddrMessage.setWindowIcon(self.appIcon) ipAddrMessage.exec_() @property def b_processRunning(self): return self._b_processRunning @property def b_serialConnected(self): return self._b_serialConnected @b_serialConnected.setter def b_serialConnected(self, newValue): self._b_serialConnected = newValue if newValue is True: self.logger.log("", special="SerialConnected") else: self.logger.log("", special="SerialDisconnected") @b_processRunning.setter def b_processRunning(self, newValue): self._b_processRunning = newValue if newValue is True: self.logger.log("", special="ProcessingStarted") def __del__(self): if self.b_serialConnected: self.port.close()
class Window(QWidget): def __init__(self, app, parent=None): print("Window init") super().__init__(parent) # self.win_event_filter = WinEventFilter("window") # self.installNativeEventFilter(self.win_event_filter) self.app = app self.window_size = QtCore.QSize(400, 250) self.window_size_offset = QtCore.QSize(0, 150) self.window_position = QtCore.QPoint(0, 0) self.window_position_offset = QtCore.QPoint(0, 0) # self.setWindowFlags( # QtCore.Qt.Window | # QtCore.Qt.CustomizeWindowHint | # QtCore.Qt.WindowTitleHint | # QtCore.Qt.WindowCloseButtonHint | # QtCore.Qt.WindowStaysOnTopHint # ) self.setWindowFlags(self.windowFlags() | QtCore.Qt.FramelessWindowHint) self.setWindowFlags(self.windowFlags() | QtCore.Qt.WindowStaysOnTopHint) self.setWindowFlags( QtCore.Qt.FramelessWindowHint | QtCore.Qt.WindowStaysOnTopHint | QtCore.Qt.Tool) # hlayout = QHBoxLayout() # hlayout.setMargin(0) # hlayout.setContentsMargins(0, 0, 0, 0) # hlayout.setSpacing(0) # buttonslayout = QVBoxLayout() self.labels = [] self.menuButton = QPushButton(u"\U00002261") self.menuLabel = QLabel("Menu") myFontBold = self.menuLabel.font() myFontBold.setBold(True) # buttons myFont = self.menuButton.font() myFont2 = self.menuButton.font() if (myFont.pointSize() > 0): myFont.setPointSizeF(1.25 * myFont.pointSizeF()) myFont2.setPointSizeF(1.4 * myFont.pointSizeF()) else: myFont.setPixelSize(1.25 * myFont.pixelSize()) myFont2.setPixelSize(1.4 * myFont.pixelSize()) self.menuLabel.setFont(myFontBold) width = self.menuButton.fontMetrics().boundingRect("OO").width() + 7 height = width # okButton.height() self.menuButton.setFont(myFont2) self.menuButton.setMaximumWidth(width) self.menuButton.setMinimumWidth(width) self.menuButton.setFlat(True) self.menuButton.clicked.connect(self.menuPressed) mainButton = QPushButton(u"\U0000239A") mainLabel = QLabel("Main") width = mainButton.fontMetrics().boundingRect("OO").width() + 7 height = width # okButton.height() mainButton.setFont(myFont2) mainButton.setMaximumWidth(width) mainButton.setMinimumWidth(width) mainButton.clicked.connect(self.main) mainButton.setFlat(True) setupButton = QPushButton(u"\U0001F527") setupLabel = QLabel("Setup") setupButton.setFont(myFont) setupButton.setFlat(True) setupButton.setMaximumWidth(width) setupButton.setMinimumWidth(width) setupButton.clicked.connect(self.setup) identifyButton = QPushButton(u"\U00002755") identifyLabel = QLabel("Identify") identifyButton.setFont(myFont) identifyButton.setFlat(True) identifyButton.setMaximumWidth(width) identifyButton.setMinimumWidth(width) identifyButton.clicked.connect(self.identify) self.refreshButton = QPushButton(u"\U000021BB") self.refreshLabel = QLabel("Detect") self.refreshButton.setFont(myFont) self.refreshButton.setFlat(True) self.refreshButton.setMaximumWidth(width) self.refreshButton.setMinimumWidth(width) self.refreshButton.clicked.connect(self.refreshPressed) aboutButton = QPushButton(u"\U00002754") aboutLabel = QLabel("About") aboutButton.setFont(myFont) aboutButton.setFlat(True) aboutButton.setMaximumWidth(width) aboutButton.setMinimumWidth(width) aboutButton.clicked.connect(self.about) # closeButton = QPushButton(u"\U00002573") closeButton = QPushButton(u"\U000026CC") closeLabel = QLabel("Close") closeButton.setFont(myFont) closeButton.setFlat(True) closeButton.setMaximumWidth(width) closeButton.setMinimumWidth(width) closeButton.clicked.connect(self.close_) buttongrid = QGridLayout() buttongrid.addWidget(self.menuButton, 0, 0) buttongrid.addWidget(mainButton, 1, 0) buttongrid.addWidget(setupButton, 2, 0) buttongrid.addWidget(self.refreshButton, 3, 0) buttongrid.addWidget(identifyButton, 4, 0) buttongrid.addWidget(aboutButton, 6, 0) buttongrid.addWidget(closeButton, 7, 0) buttongrid.addWidget(self.menuLabel, 0, 1) buttongrid.addWidget(mainLabel, 1, 1) buttongrid.addWidget(setupLabel, 2, 1) buttongrid.addWidget(self.refreshLabel, 3, 1) buttongrid.addWidget(identifyLabel, 4, 1) buttongrid.addWidget(aboutLabel, 6, 1) buttongrid.addWidget(closeLabel, 7, 1) self.labels.append(self.menuLabel) self.labels.append(mainLabel) self.labels.append(setupLabel) self.labels.append(self.refreshLabel) self.labels.append(identifyLabel) self.labels.append(aboutLabel) self.labels.append(closeLabel) self.menuLabel .mousePressEvent = self.menuLabelPressed mainLabel .mousePressEvent = self.mainLabel setupLabel.mousePressEvent = self.setupLabel self.refreshLabel.mousePressEvent = self.refreshLabelPressed identifyLabel.mousePressEvent = self.identifyLabel aboutLabel.mousePressEvent = self.aboutLabel closeLabel.mousePressEvent = self.closeLabel buttongrid.setRowStretch(0, 0) buttongrid.setRowStretch(1, 0) buttongrid.setRowStretch(2, 0) buttongrid.setRowStretch(3, 0) buttongrid.setRowStretch(4, 0) buttongrid.setRowStretch(5, 1) buttongrid.setRowStretch(6, 0) buttongrid.setRowStretch(7, 0) self.labels_set_visible(False) self.layout = QHBoxLayout() # buttonslayout.addWidget(mainButton) # buttonslayout.addWidget(setupButton) # buttonslayout.addStretch(1) # buttonslayout.addWidget(aboutButton) # hlayout.addLayout(buttonslayout) # hlayout.addLayout(buttongrid) # grid.addLayout(hlayout, 1, 1) buttongrid.setSpacing(0) self.layout.addLayout(buttongrid) self.body_layout = QVBoxLayout() self.body_layout.setContentsMargins(0, 0, 0, 1) self.body_layout.setSpacing(0) self.title_layout = QHBoxLayout() self.title_layout.setContentsMargins(0, 0, 0, 0) self.title_layout.setSpacing(0) self.titleLabel = QLabel("Monitor Control") self.titleLabel.setWordWrap(True) self.titleLabel.setSizeIncrement(10, 10) myFont = self.titleLabel.font() myFont.setBold(True) self.titleLabel.setFont(myFont) width = self.titleLabel.fontMetrics().boundingRect("OO").width() + 7 height = width # okButton.height() self.titleLabel.mousePressEvent = self.mainLabel self.backButton = QPushButton(u"\U00002190", self) myFont = self.backButton.font() myFont.setBold(True) self.backButton.setFont(myFont) self.backButton.setMaximumWidth(width) self.backButton.setMinimumWidth(width) self.backButton.setFlat(True) self.backButton.clicked.connect(self.main) self.titleLabel.setMinimumHeight(self.backButton.height()) self.title_layout.addWidget(self.backButton, 0, QtCore.Qt.AlignVCenter) self.title_layout.addSpacing(20) self.title_layout.addWidget(self.titleLabel, 1, QtCore.Qt.AlignVCenter) # self.backButton.setAlignment(Qt.AlignTop) self.title_layout.setAlignment(QtCore.Qt.AlignTop) self.body_layout.addLayout(self.title_layout) self.main_frame = QtWidgets.QFrame(self) self.main_layout = QVBoxLayout() self.feature_brightness = FeatureWidget( self.main_frame, "Brightness", self.app.brightness) self.feature_contrast = FeatureWidget( self.main_frame, "Contrast", self.app.contrast) self.main_layout.addWidget(self.feature_brightness) self.main_layout.addWidget(self.feature_contrast) self.main_layout.addStretch(1) self.main_frame.setLayout(self.main_layout) self.main_frame.hide() self.body_layout.addWidget(self.main_frame, 1) self.setup_frame = QtWidgets.QFrame(self) leftButton = QPushButton("<", self.setup_frame) width = leftButton.fontMetrics().boundingRect("<").width() + 7 leftButton.setFlat(True) leftButton.setMaximumWidth(width) leftButton.setMinimumWidth(width) leftButton.setSizePolicy(QtWidgets.QSizePolicy( QSizePolicy.Fixed, QSizePolicy.Expanding)) self.setup_layout = QHBoxLayout() self.setup_layout.addWidget(leftButton) self.feature_setup_widget = FeatureSetupWidget( self.app, self.setup_frame) # hlayout.addWidget(self.feature_setup_widget, 1) self.feature_setup_widget.setSizePolicy( QSizePolicy.Expanding, QSizePolicy.Expanding) rightButton = QPushButton(">", self.setup_frame) rightButton.setFlat(True) rightButton.setMaximumWidth(width) rightButton.setMinimumWidth(width) rightButton.setSizePolicy(QtWidgets.QSizePolicy( QSizePolicy.Fixed, QSizePolicy.Expanding)) self.setup_layout.addWidget(self.feature_setup_widget, 1) self.setup_layout.addWidget(rightButton) self.setup_layout.setContentsMargins(0, 0, 0, 0) self.setup_layout.setSpacing(0) leftButton.clicked.connect(self.feature_setup_widget.previous) rightButton.clicked.connect(self.feature_setup_widget.next) self.setup_frame.setLayout(self.setup_layout) # self.layout.setContentsMargins(0, 0, 0, 0) self.layout.setSpacing(10) self.body_layout.addWidget(self.setup_frame, 1) self.layout.addLayout(self.body_layout, 1) self.about_frame = QtWidgets.QFrame(self) self.about_layout = QVBoxLayout() self.aboutLabel1 = QLabel("About", self.about_frame) self.aboutLabel1.setWordWrap(True) myFont = self.aboutLabel1.font() myFont.setBold(True) self.aboutLabel1.setFont(myFont) about = "©️ ™️ Juno\n\nMonitor Control synchronizes your monitor hardware properties like brightness and contrast.\nThese properties can be changed by the software sliders, or monitor buttons. These changes are monitored and read, and subsequently set to the other monitors using a calibration. This will ensure an input change has the same result on all monitors.\n" self.aboutLabel2 = QLabel("{}".format(about), self.about_frame) self.aboutLabel2.setAlignment( QtCore.Qt.AlignLeft | QtCore.Qt.AlignTop) self.aboutLabel2.setWordWrap(True) self.about_layout.addWidget(self.aboutLabel1) self.about_layout.addWidget(self.aboutLabel2, 1) self.about_frame.setLayout(self.about_layout) self.about_frame.hide() self.body_layout.addWidget(self.about_frame, 1) # self.layout.setSizeConstraint(QtGui.QLayout.setFixedSize) self.setLayout(self.layout) self.setWindowIcon(QtGui.QIcon('artifacts/icon.png')) # set the title self.setWindowTitle("Monitors Control") self.main() self.setFixedSize(400, 250) def labels_set_visible(self, visible): for label in self.labels: if (self.refreshLabel == label) and visible: self.refreshLabel.setVisible(self.refreshButton.isVisible()) else: label.setVisible(visible) def refresh_visible(self, visible): if (visible): self.refreshButton.setVisible(visible) self.refreshLabel.setVisible(self.menuLabel.isVisible()) else: self.refreshLabel.setVisible(visible) self.refreshButton.setVisible(visible) def focusOutEvent(self, event): print('Lost focus') def menuLabelPressed(self, event): self.menuPressed() def menuPressed(self): print("Menu") self.labels_set_visible(not self.labels[0].isVisible()) def aboutLabel(self, event): self.about() def about(self): print("About") self.setupUpdate() self.setMinimumSize(200, 130) # self.feature_setup_widget.hide() self.setup_frame.hide() self.main_frame.hide() self.refresh_visible(False) self.backButton.show() self.about_frame.show() self.move(self.window_position) self.setFixedSize(self.window_size) def closeLabel(self, event): self.close_() def close_(self): print("Close {}".format(len(self.app.identifyWindows))) self.setupUpdate() if (len(self.app.identifyWindows) == 0): self.hide() def setupLabel(self, event): self.setup() def setup(self): print("Setup") self.move(self.window_position + self.window_position_offset) self.setFixedSize(self.window_size + self.window_size_offset) self.app.monitors._calibrations.loadYaml() self.feature_setup_widget.init() self.backButton.show() self.main_frame.hide() self.about_frame.hide() self.refresh_visible(True) self.setup_frame.show() self.setMinimumSize(200, 130) def setupUpdate(self): if (self.setup_frame.isVisible()): self.app.monitors._calibrations.saveYaml() def mainLabel(self, event): self.main() def main(self): print("Main") self.setMinimumSize(200, 130) self.setupUpdate() self.refresh_visible(False) self.backButton.hide() # self.feature_setup_widget.hide() self.setup_frame.hide() self.about_frame.hide() self.main_frame.hide() self.move(self.window_position) self.setFixedSize(self.window_size) self.main_frame.show() def identifyLabel(self, event): self.identify() def identify(self): print("Identify") self.app.identify() def refreshLabelPressed(self, event): self.refreshPressed() def refreshPressed(self): QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) print("detect") self.feature_setup_widget.clear() self.app.detect() self.setup() self.feature_setup_widget.set_infos(self.app.monitors) self.feature_setup_widget.init() self.app.list_monitors() QApplication.restoreOverrideCursor() def position_show(self): print("position_show") self.app.position_next_to_tray() self.main() self.show() # self.requestActivate() # QtCore.Qt.ActiveWindowFocusReason self.activateWindow() self.setFocus(QtCore.Qt.PopupFocusReason) def contrast(self, value): # from gui self.app.contrast(value) def brightness(self, value): # from gui self.app.brightness(value) def set_contrast(self, value): # to gui self.feature_contrast.set_value(value) self.feature_setup_widget.set_contrast(value) def set_brightness(self, value): # to gui self.feature_brightness.set_value(value) self.feature_setup_widget.set_brightness(value) def show(self): # to gui value = self.app.monitors.get_contrast() if (value is not None): self.set_contrast(value) value = self.app.monitors.get_brightness() if (value is not None): self.set_brightness(value) self.feature_setup_widget.set_infos(self.app.monitors) super().show()
class TrainingWindow(QWidget): # Dataset __dataset = None # Netwok model __modelList = [] # Start training @Slot() def startTraining(self): # Get split value split = 1 - float( (self.__datasetSplitComboBox.currentIndex() + 1) / 10.0) # Get split method if self.__datasetSplitRandom.isChecked(): ((x_train, y_train), (x_test, y_test)) = self.network.random_split(self.__dataset, split) elif self.__datasetSplitRegular.isChecked(): ((x_train, y_train), (x_test, y_test)) = self.network.regular_split(self.__dataset, split) # Get epochs number if not self.__epochsLineEdit.text(): ret = QMessageBox.warning(self, "Epochs number", "Please enter the number of epochs", QMessageBox.Ok) return else: epochs = int(self.__epochsLineEdit.text()) self.__xAxis.setRange(0, epochs) # Get learning rate value if not self.__learningRateLineEdit.text(): ret = QMessageBox.warning(self, "Learning rate", "Please select a learning rate", QMessageBox.Ok) return else: learning_rate = float(self.__learningRateLineEdit.text().replace( ",", ".")) if not learning_rate: ret = QMessageBox.warning( self, "Learning rate", "The learning rate cannot be equal to zero", QMessageBox.Ok) return # Get learning rate mode if self.__learningRateCheckBox.isChecked(): mode = 2 else: mode = 1 # Save before training ret = QMessageBox.question( self, "Network save", "Would you like to save the network before the training starts?", QMessageBox.Yes | QMessageBox.No) if ret == QMessageBox.Yes: save_matrix_neural_network(self.network, self.networkName) manual_save_model_neural_network(self.network, self.networkName) QMessageBox.information(self, "Network save", "Network successfully saved !", QMessageBox.Ok) # Clearing the graph self.__series.clear() # Starting training length = len(x_train) for i in range(epochs): err = 0 training_accuracy = 0 l_rate = self.network.Learning_rate_schedule( mode, epochs, epochs - i + 1, learning_rate) for j in range(length): outputs = x_train[j] for layer in self.network.layers: outputs = layer.forward_propagation(outputs) err += self.network.loss(y_train[j], outputs) training_accuracy = training_accuracy + self.network.verification_of_prediction( x_train, y_train, j) error = self.network.loss_prime(y_train[j], outputs) for layer in reversed(self.network.layers): error = layer.backward_propagation(error, l_rate) err = err / length training_accuracy = training_accuracy / float(length) self.__epochNumberLabel.setText("Epoch : " + str(i + 1) + "/" + str(epochs)) self.__trainingAccuracyLabel.setText("Taux de precision : " + str(training_accuracy * 100) + "%") # Appending values to the chart self.__series.append(i, training_accuracy * 100) self.__chartView.repaint() # Auto saving network save_matrix_neural_network(self.network, self.networkName + "_auto") manual_save_model_neural_network(self.network, self.networkName + "_auto") # Saving trained network ret = QMessageBox.question( self, "Network save", "Would you like to save the trained network? ", QMessageBox.Yes | QMessageBox.No) if ret == QMessageBox.Yes: save_matrix_neural_network(self.network, self.networkName) manual_save_model_neural_network(self.network, self.networkName) QMessageBox.information(self, "Network save", "Network successfully saved !", QMessageBox.Ok) # Evaluate network and show confusion matrix (self.test_accuracy, self.matrix) = self.network.evaluate(x_test, y_test) self.__confusionMatrixButton.show() # Showing the confusion matrix @Slot() def showStats(self): # Creating matrix window self.matrixWindow = QMainWindow() self.matrixWindow.setFixedSize(640, 480) key_list = list(self.__classes.keys()) val_list = list(self.__classes.values()) # Creating matrix table self.matrixTable = QTableWidget( len(self.matrix) + 1, len(self.matrix[0]) + 1) for i in range(len(self.matrix)): self.matrixTable.setItem( i + 1, 0, QTableWidgetItem(str(key_list[val_list.index(i)]))) self.matrixTable.setItem( 0, i + 1, QTableWidgetItem(str(key_list[val_list.index(i)]))) for i in range(len(self.matrix)): for j in range(len(self.matrix[0])): self.matrixTable.setItem( i + 1, j + 1, QTableWidgetItem(str(self.matrix[i][j]))) # Printing test accuracy self.matrixLabel = QLabel( "Test accuracy : " + str(self.test_accuracy * 100) + "%", self) self.matrixLabel.setFont(QFont("BebasNeue", 20, QFont.Bold)) # Matrix window layout self.matrixLayout = QVBoxLayout() self.matrixLayout.addWidget(self.matrixTable) self.matrixLayout.addWidget(self.matrixLabel) # Matrix window groupbox self.matrixGroupBox = QGroupBox(self.matrixWindow) self.matrixGroupBox.setLayout(self.matrixLayout) # Showing the matrix window self.matrixWindow.setCentralWidget(self.matrixGroupBox) self.matrixWindow.show() def __init__(self, ds, classes, model, created, *args, **kwargs): super(TrainingWindow, self).__init__(*args, **kwargs) if created: # Initialising network self.network = Network() self.network.use(mean_squared_error, mean_squared_error_prime) else: self.network = model[0] # Getting inputs and outputs self.__dataset = ds self.__classes = classes #fill_missing_values(self.__dataset) #min_max_normalize_dataset(self.__dataset) ((x_train, y_train), (x_test, y_test)) = self.network.regular_split(self.__dataset, 0.5) # Getting inputs if len(x_train.shape) == 2: inputs = x_train.shape[1] else: inputs = x_train.shape[1:] first = inputs[0] second = inputs[1] third = inputs[2] # Getting expected outputs expected_output = y_train.shape[1] # Getting network name self.networkName = model[1] if created: # Getting model list self.__modelList = model[0] self.__modelList[0].setOutput(inputs) for i in range(1, len(self.__modelList)): # Getting the layer name name = self.__modelList[i].text( )[:len(self.__modelList[i].text()) - 6] activation = None activ_prime = None # Getting the activation function if self.__modelList[i].activation() == 0: activation = sigmoid activ_prime = sigmoid_prime elif self.__modelList[i].activation() == 1: activation = tanh activ_prime = tanh_prime elif self.__modelList[i].activation() == 2: activation = rectified_linear_unit activ_prime = rectified_linear_unit_prime elif self.__modelList[i].activation() == 3: activation = softmax activ_prime = softmax_prime # Adding layer to the network if name == "Dense": if self.__modelList[i - 1].text()[:2] == "Fl": self.network.add( FullyConnectedLayer(first * second * third, self.__modelList[i].output())) self.network.add( ActivationLayer(activation, activ_prime)) else: self.network.add( FullyConnectedLayer( self.__modelList[i - 1].output(), self.__modelList[i].output())) self.network.add( ActivationLayer(activation, activ_prime)) elif name == "Flatten": self.network.add(FlattenLayer()) elif name == "Convolutional": self.network.add( ConvLayer((first, second, third), (self.__modelList[i].kernelRows, self.__modelList[i].kernelColumns), 1)) self.network.add(ActivationLayer(activation, activ_prime)) first = first - self.__modelList[i].kernelRows + 1 second = second - self.__modelList[i].kernelColumns + 1 self.network.add( FullyConnectedLayer( self.__modelList[len(self.__modelList) - 1].output(), expected_output)) self.network.add(ActivationLayer(sigmoid, sigmoid_prime)) # Loading Fonts QFontDatabase.addApplicationFont("fonts/BebasNeue-Light.ttf") # Window Settings self.setFixedSize(1280, 720) self.setWindowTitle("Training window") #background = QPixmap("images/menu") #palette = QPalette() #palette.setBrush(QPalette.Background, background) #self.setAttribute(Qt.WA_StyledBackground, True) #self.setPalette(palette) self.setAutoFillBackground(True) # Stylesheet Settings styleFile = QFile("stylesheets/training.qss") styleFile.open(QFile.ReadOnly) style = str(styleFile.readAll()) self.setStyleSheet(style) # Title Settings self.title = QLabel("Training", self) self.title.setFont(QFont("BebasNeue", 30, QFont.Bold)) self.title.setAlignment(Qt.AlignCenter) self.title.setGeometry(600, 10, 300, 120) # Epochs line edit settings self.__epochsLineEdit = QLineEdit(self) self.__epochsLineEdit.setValidator(QIntValidator(0, 100000, self)) # Epochs label settings self.__epochsLabel = QLabel("Epoch number", self) self.__epochsLabel.setFont(QFont("BebasNeue", 20, QFont.Bold)) # Learning rate line edit settings self.__learningRateLineEdit = QLineEdit(self) self.__learningRateLineEdit.setValidator( QDoubleValidator(0.0, 1.0, 3, self)) # Learning rate label settings self.__learningRateLabel = QLabel("Learning rate", self) self.__learningRateLabel.setFont(QFont("BebasNeue", 20, QFont.Bold)) # Learning rate checkboxsettings (auto or not) self.__learningRateCheckBox = QCheckBox("Auto adjustment", self) self.__learningRateCheckBox.setFont(QFont("BebasNeue", 15, QFont.Bold)) # Dataset split settings label self.__datasetSplitLabel = QLabel("Dataset split percentage", self) self.__datasetSplitLabel.setFont((QFont("BebasNeue", 20, QFont.Bold))) # Dataset split mode buttons self.__datasetSplitRegular = QRadioButton("Regular split") self.__datasetSplitRandom = QRadioButton("Random split") # Dataset split mode buttons groupbox self.__datasetSplitModeButtonsLayout = QHBoxLayout(self) self.__datasetSplitModeButtonsGroupBox = QGroupBox(self) self.__datasetSplitModeButtonsGroupBox.setObjectName("setting") self.__datasetSplitModeButtonsLayout.addWidget( self.__datasetSplitRegular) self.__datasetSplitModeButtonsLayout.addWidget( self.__datasetSplitRandom) self.__datasetSplitModeButtonsGroupBox.setLayout( self.__datasetSplitModeButtonsLayout) self.__datasetSplitRegular.setChecked(True) # Dataset split combo box settings self.__datasetSplitComboBox = QComboBox(self) self.__datasetSplitComboBox.addItems( ['90% - 10%', '80% - 20%', '70% - 30%', '60% - 40%']) # Dataset split form layout settings self.__datasetSplitLayout = QFormLayout(self) self.__datasetSplitGroupBox = QGroupBox(self) self.__datasetSplitGroupBox.setObjectName("setting") self.__datasetSplitLayout.addWidget(self.__datasetSplitLabel) self.__datasetSplitLayout.addWidget(self.__datasetSplitComboBox) self.__datasetSplitGroupBox.setLayout(self.__datasetSplitLayout) # Epochs form layout settings self.__epochsFormLayout = QFormLayout(self) self.__epochsGroupBox = QGroupBox(self) self.__epochsGroupBox.setObjectName("setting") self.__epochsFormLayout.addWidget(self.__epochsLabel) self.__epochsFormLayout.addWidget(self.__epochsLineEdit) self.__epochsGroupBox.setLayout(self.__epochsFormLayout) # Learning rate form layout settings self.__learningRateFormLayout = QFormLayout(self) self.__learningRateGroupBox = QGroupBox(self) self.__learningRateGroupBox.setObjectName("setting") self.__learningRateFormLayout.addWidget(self.__learningRateLabel) self.__learningRateFormLayout.addWidget(self.__learningRateCheckBox) self.__learningRateFormLayout.addWidget(self.__learningRateLineEdit) self.__learningRateGroupBox.setLayout(self.__learningRateFormLayout) # Epochs number label self.__epochNumberLabel = QLabel("Epoch : ", self) self.__epochNumberLabel.setFont((QFont("BebasNeue", 15, QFont.Bold))) # Training accuracy label self.__trainingAccuracyLabel = QLabel("Accuracy : ", self) self.__trainingAccuracyLabel.setFont((QFont("BebasNeue", 15, QFont.Bold))) # Training stats layout self.__trainingStatsLayout = QVBoxLayout(self) self.__trainingStatsGroupBox = QGroupBox(self) self.__trainingStatsLayout.addWidget(self.__epochNumberLabel) self.__trainingStatsLayout.addWidget(self.__trainingAccuracyLabel) self.__trainingStatsGroupBox.setLayout(self.__trainingStatsLayout) self.__trainingStatsGroupBox.setGeometry(1000, -30, 300, 150) # Training button settings self.__trainingButton = QPushButton("Start", self) self.__trainingButton.setCursor(Qt.PointingHandCursor) self.__trainingButton.setFont((QFont("BebasNeue", 30, QFont.Bold))) self.__trainingButton.clicked.connect(self.startTraining) # Go back button self.goBackButton = QPushButton("Back", self) self.goBackButton.setObjectName("retour") # Customising go back button self.goBackButton.setCursor(Qt.PointingHandCursor) self.goBackButton.setIcon(QIcon("images/goback_icon")) self.goBackButton.setIconSize(QSize(30, 30)) self.goBackButton.setFont(QFont("BebasNeue", 20, QFont.Bold)) # Confusion matrix button self.__confusionMatrixButton = QPushButton("Show confusion matrix", self) self.__confusionMatrixButton.setCursor(Qt.PointingHandCursor) self.__confusionMatrixButton.setFont((QFont("BebasNeue", 17, QFont.Bold))) self.__confusionMatrixButton.clicked.connect(self.showStats) self.__confusionMatrixButton.setGeometry(420, 20, 250, 80) self.__confusionMatrixButton.hide() # Parameters group box settings self.__parametersGroupBox = QGroupBox("Training parameters", self) self.__parametersGroupBox.setObjectName("parameters") self.__parametersLayout = QVBoxLayout(self) self.__parametersLayout.addWidget(self.__epochsGroupBox) self.__parametersLayout.addWidget(self.__datasetSplitGroupBox) self.__parametersLayout.addWidget( self.__datasetSplitModeButtonsGroupBox) self.__parametersLayout.addWidget(self.__learningRateGroupBox) self.__parametersLayout.addWidget(self.__trainingButton) self.__parametersLayout.addWidget(self.goBackButton) self.__parametersGroupBox.setLayout(self.__parametersLayout) self.__parametersGroupBox.setGeometry(0, 0, 400, 720) # Chart axis settings self.__xAxis = QtCharts.QValueAxis() self.__xAxis.setRange(0, 5) self.__yAxis = QtCharts.QValueAxis() self.__yAxis.setRange(0, 100) # Chart settings self.__series = QtCharts.QLineSeries() self.__chart = QtCharts.QChart() self.__chart.addAxis(self.__xAxis, Qt.AlignBottom) self.__chart.addAxis(self.__yAxis, Qt.AlignLeft) self.__chart.addSeries(self.__series) self.__series.attachAxis(self.__xAxis) self.__series.attachAxis(self.__yAxis) self.__chart.setTitle("Accuracy") self.__chartView = QtCharts.QChartView(self.__chart) self.__chartView.setRenderHint(QPainter.Antialiasing) # Chart layout settings self.__chartLayout = QVBoxLayout(self) self.__chartGroupBox = QGroupBox(self) self.__chartGroupBox.setObjectName("chart") self.__chartLayout.addWidget(self.__chartView) self.__chartGroupBox.setLayout(self.__chartLayout) self.__chartGroupBox.setGeometry(390, 100, 900, 600) # Update timer settings #self.__timer = QTimer(self) #self.__timer.timeout.connect(self.autoSave) #self.__timer.start(1000) #app = QApplication(sys.argv) #window = TrainingWindow() #window.show() #app.exec_()
class VideoPlayer(QWidget): def __init__(self, aPath, parent=None): super(VideoPlayer, self).__init__(parent) self.setAttribute(Qt.WA_NoSystemBackground, True) self.setAcceptDrops(True) self.mediaPlayer = QMediaPlayer(None, QMediaPlayer.StreamPlayback) self.mediaPlayer.mediaStatusChanged.connect(self.printMediaData) self.mediaPlayer.setVolume(80) self.videoWidget = QVideoWidget(self) self.lbl = QLineEdit('00:00:00') self.lbl.setReadOnly(True) self.lbl.setFixedWidth(70) self.lbl.setUpdatesEnabled(True) self.lbl.setStyleSheet(stylesheet(self)) self.lbl.selectionChanged.connect(lambda: self.lbl.setSelection(0, 0)) self.elbl = QLineEdit('00:00:00') self.elbl.setReadOnly(True) self.elbl.setFixedWidth(70) self.elbl.setUpdatesEnabled(True) self.elbl.setStyleSheet(stylesheet(self)) self.elbl.selectionChanged.connect( lambda: self.elbl.setSelection(0, 0)) self.playButton = QPushButton() self.playButton.setEnabled(False) self.playButton.setFixedWidth(32) self.playButton.setStyleSheet("background-color: black") self.playButton.setIcon(self.style().standardIcon(QStyle.SP_MediaPlay)) self.playButton.clicked.connect(self.play) self.positionSlider = QSlider(Qt.Horizontal, self) self.positionSlider.setStyleSheet(stylesheet(self)) self.positionSlider.setRange(0, 100) self.positionSlider.sliderMoved.connect(self.setPosition) self.positionSlider.setSingleStep(2) self.positionSlider.setPageStep(20) self.positionSlider.setAttribute(Qt.WA_TranslucentBackground, True) self.clip = QApplication.clipboard() self.process = QProcess(self) self.process.readyRead.connect(self.dataReady) self.process.finished.connect(self.playFromURL) self.myurl = "" controlLayout = QHBoxLayout() controlLayout.setContentsMargins(5, 0, 5, 0) controlLayout.addWidget(self.playButton) controlLayout.addWidget(self.lbl) controlLayout.addWidget(self.positionSlider) controlLayout.addWidget(self.elbl) layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(self.videoWidget) layout.addLayout(controlLayout) self.setLayout(layout) self.myinfo = "©2016\nAxel Schneider\n\nMouse Wheel = Zoom\nUP = Volume Up\nDOWN = Volume Down\n" + \ "LEFT = < 1 Minute\nRIGHT = > 1 Minute\n" + \ "SHIFT+LEFT = < 10 Minutes\nSHIFT+RIGHT = > 10 Minutes" self.widescreen = True #### shortcuts #### self.shortcut = QShortcut(QKeySequence("q"), self) self.shortcut.activated.connect(self.handleQuit) self.shortcut = QShortcut(QKeySequence("u"), self) self.shortcut.activated.connect(self.playFromURL) self.shortcut = QShortcut(QKeySequence("y"), self) self.shortcut.activated.connect(self.getYTUrl) self.shortcut = QShortcut(QKeySequence("o"), self) self.shortcut.activated.connect(self.openFile) self.shortcut = QShortcut(QKeySequence(" "), self) self.shortcut.activated.connect(self.play) self.shortcut = QShortcut(QKeySequence("f"), self) self.shortcut.activated.connect(self.handleFullscreen) self.shortcut = QShortcut(QKeySequence("i"), self) self.shortcut.activated.connect(self.handleInfo) self.shortcut = QShortcut(QKeySequence("s"), self) self.shortcut.activated.connect(self.toggleSlider) self.shortcut = QShortcut(QKeySequence(Qt.Key_Right), self) self.shortcut.activated.connect(self.forwardSlider) self.shortcut = QShortcut(QKeySequence(Qt.Key_Left), self) self.shortcut.activated.connect(self.backSlider) self.shortcut = QShortcut(QKeySequence(Qt.Key_Up), self) self.shortcut.activated.connect(self.volumeUp) self.shortcut = QShortcut(QKeySequence(Qt.Key_Down), self) self.shortcut.activated.connect(self.volumeDown) self.shortcut = QShortcut( QKeySequence(Qt.ShiftModifier + Qt.Key_Right), self) self.shortcut.activated.connect(self.forwardSlider10) self.shortcut = QShortcut(QKeySequence(Qt.ShiftModifier + Qt.Key_Left), self) self.shortcut.activated.connect(self.backSlider10) self.mediaPlayer.setVideoOutput(self.videoWidget) self.mediaPlayer.stateChanged.connect(self.mediaStateChanged) self.mediaPlayer.positionChanged.connect(self.positionChanged) self.mediaPlayer.durationChanged.connect(self.durationChanged) self.mediaPlayer.error.connect(self.handleError) print("QT5 Player started") print("press 'o' to open file (see context menu for more)") self.suspend_screensaver() def mouseDoubleClickEvent(self, event): self.handleFullscreen() def playFromURL(self): self.mediaPlayer.pause() self.myurl = self.clip.text() self.mediaPlayer.setMedia(QMediaContent(QUrl(self.myurl))) self.playButton.setEnabled(True) self.mediaPlayer.play() self.hideSlider() print(self.myurl) def getYTUrl(self): cmd = "youtube-dl -g -f best " + self.clip.text() print("grabbing YouTube URL") self.process.start(cmd) def dataReady(self): self.myurl = str(self.process.readAll(), encoding='utf8').rstrip() ### self.myurl = self.myurl.partition("\n")[0] print(self.myurl) self.clip.setText(self.myurl) self.playFromURL() def suspend_screensaver(self): 'suspend linux screensaver' proc = subprocess.Popen( 'gsettings set org.gnome.desktop.screensaver idle-activation-enabled false', shell=True) proc.wait() def resume_screensaver(self): 'resume linux screensaver' proc = subprocess.Popen( 'gsettings set org.gnome.desktop.screensaver idle-activation-enabled true', shell=True) proc.wait() def openFile(self): fileName, _ = QFileDialog.getOpenFileName( self, "Open Movie", QDir.homePath() + "/Videos", "Media (*.webm *.mp4 *.ts *.avi *.mpeg *.mpg *.mkv *.VOB *.m4v *.3gp *.mp3 *.m4a *.wav *.ogg *.flac *.m3u *.m3u8)" ) if fileName != '': self.loadFilm(fileName) print("File loaded") def play(self): if self.mediaPlayer.state() == QMediaPlayer.PlayingState: self.mediaPlayer.pause() else: self.mediaPlayer.play() def mediaStateChanged(self, state): if self.mediaPlayer.state() == QMediaPlayer.PlayingState: self.playButton.setIcon(self.style().standardIcon( QStyle.SP_MediaPause)) else: self.playButton.setIcon(self.style().standardIcon( QStyle.SP_MediaPlay)) def positionChanged(self, position): self.positionSlider.setValue(position) mtime = QTime(0, 0, 0, 0) mtime = mtime.addMSecs(self.mediaPlayer.position()) self.lbl.setText(mtime.toString()) def durationChanged(self, duration): self.positionSlider.setRange(0, duration) mtime = QTime(0, 0, 0, 0) mtime = mtime.addMSecs(self.mediaPlayer.duration()) self.elbl.setText(mtime.toString()) def setPosition(self, position): self.mediaPlayer.setPosition(position) def handleError(self): self.playButton.setEnabled(False) print("Error: ", self.mediaPlayer.errorString()) def handleQuit(self): self.mediaPlayer.stop() self.resume_screensaver() print("Goodbye ...") app.quit() def contextMenuRequested(self, point): menu = QMenu() actionFile = menu.addAction(QIcon.fromTheme("video-x-generic"), "open File (o)") actionclipboard = menu.addSeparator() actionURL = menu.addAction(QIcon.fromTheme("browser"), "URL from Clipboard (u)") actionclipboard = menu.addSeparator() actionYTurl = menu.addAction(QIcon.fromTheme("youtube"), "URL from YouTube (y)") actionclipboard = menu.addSeparator() actionToggle = menu.addAction(QIcon.fromTheme("next"), "show / hide Slider (s)") actionFull = menu.addAction(QIcon.fromTheme("view-fullscreen"), "Fullscreen (f)") action169 = menu.addAction(QIcon.fromTheme("tv-symbolic"), "16 : 9") action43 = menu.addAction(QIcon.fromTheme("tv-symbolic"), "4 : 3") actionSep = menu.addSeparator() actionInfo = menu.addAction(QIcon.fromTheme("help-about"), "Info (i)") action5 = menu.addSeparator() actionQuit = menu.addAction(QIcon.fromTheme("application-exit"), "Exit (q)") actionFile.triggered.connect(self.openFile) actionQuit.triggered.connect(self.handleQuit) actionFull.triggered.connect(self.handleFullscreen) actionInfo.triggered.connect(self.handleInfo) actionToggle.triggered.connect(self.toggleSlider) actionURL.triggered.connect(self.playFromURL) actionYTurl.triggered.connect(self.getYTUrl) action169.triggered.connect(self.screen169) action43.triggered.connect(self.screen43) menu.exec_(self.mapToGlobal(point)) def wheelEvent(self, event): mwidth = self.frameGeometry().width() mheight = self.frameGeometry().height() mleft = self.frameGeometry().left() mtop = self.frameGeometry().top() mscale = event.angleDelta().y() / 5 if self.widescreen == True: self.setGeometry(mleft, mtop, mwidth + mscale, round((mwidth + mscale) / 1.778)) else: self.setGeometry(mleft, mtop, mwidth + mscale, round((mwidth + mscale) / 1.33)) #elif self.positionSlider.hasFocus(): # self.positionSlider.value = self.positionSlider.value + 5 def screen169(self): self.widescreen = True mwidth = self.frameGeometry().width() mheight = self.frameGeometry().height() mleft = self.frameGeometry().left() mtop = self.frameGeometry().top() mratio = 1.778 self.setGeometry(mleft, mtop, mwidth, round(mwidth / mratio)) def screen43(self): self.widescreen = False mwidth = self.frameGeometry().width() mheight = self.frameGeometry().height() mleft = self.frameGeometry().left() mtop = self.frameGeometry().top() mratio = 1.33 self.setGeometry(mleft, mtop, mwidth, round(mwidth / mratio)) def handleFullscreen(self): if self.windowState() & Qt.WindowFullScreen: QApplication.setOverrideCursor(Qt.ArrowCursor) self.showNormal() print("no Fullscreen") else: self.showFullScreen() QApplication.setOverrideCursor(Qt.BlankCursor) print("Fullscreen entered") def handleInfo(self): msg = QMessageBox.about(self, "QT5 Player", self.myinfo) def toggleSlider(self): if self.positionSlider.isVisible(): self.hideSlider() else: self.showSlider() def hideSlider(self): self.playButton.hide() self.lbl.hide() self.positionSlider.hide() self.elbl.hide() mwidth = self.frameGeometry().width() mheight = self.frameGeometry().height() mleft = self.frameGeometry().left() mtop = self.frameGeometry().top() if self.widescreen == True: self.setGeometry(mleft, mtop, mwidth, round(mwidth / 1.778)) else: self.setGeometry(mleft, mtop, mwidth, round(mwidth / 1.33)) def showSlider(self): self.playButton.show() self.lbl.show() self.positionSlider.show() self.elbl.show() mwidth = self.frameGeometry().width() mheight = self.frameGeometry().height() mleft = self.frameGeometry().left() mtop = self.frameGeometry().top() if self.widescreen == True: self.setGeometry(mleft, mtop, mwidth, round(mwidth / 1.55)) else: self.setGeometry(mleft, mtop, mwidth, round(mwidth / 1.33)) def forwardSlider(self): self.mediaPlayer.setPosition(self.mediaPlayer.position() + 1000 * 60) def forwardSlider10(self): self.mediaPlayer.setPosition(self.mediaPlayer.position() + 10000 * 60) def backSlider(self): self.mediaPlayer.setPosition(self.mediaPlayer.position() - 1000 * 60) def backSlider10(self): self.mediaPlayer.setPosition(self.mediaPlayer.position() - 10000 * 60) def volumeUp(self): self.mediaPlayer.setVolume(self.mediaPlayer.volume() + 10) print("Volume: " + str(self.mediaPlayer.volume())) def volumeDown(self): self.mediaPlayer.setVolume(self.mediaPlayer.volume() - 10) print("Volume: " + str(self.mediaPlayer.volume())) def mousePressEvent(self, evt): self.oldPos = evt.globalPos() def mouseMoveEvent(self, evt): delta = QPoint(evt.globalPos() - self.oldPos) self.move(self.x() + delta.x(), self.y() + delta.y()) self.oldPos = evt.globalPos() def dragEnterEvent(self, event): if event.mimeData().hasUrls(): event.accept() elif event.mimeData().hasText(): event.accept() else: event.ignore() def dropEvent(self, event): print("drop") if event.mimeData().hasUrls(): url = event.mimeData().urls()[0].toString() print("url = ", url) self.mediaPlayer.stop() self.mediaPlayer.setMedia(QMediaContent(QUrl(url))) self.playButton.setEnabled(True) self.mediaPlayer.play() elif event.mimeData().hasText(): mydrop = event.mimeData().text() ### YouTube url if "youtube" in mydrop: print("is YouTube", mydrop) self.clip.setText(mydrop) self.getYTUrl() else: ### normal url print("generic url = ", mydrop) self.mediaPlayer.setMedia(QMediaContent(QUrl(mydrop))) self.playButton.setEnabled(True) self.mediaPlayer.play() self.hideSlider() def loadFilm(self, f): self.mediaPlayer.setMedia(QMediaContent(QUrl.fromLocalFile(f))) self.playButton.setEnabled(True) self.mediaPlayer.play() def printMediaData(self): if self.mediaPlayer.mediaStatus() == 6: if self.mediaPlayer.isMetaDataAvailable(): res = str(self.mediaPlayer.metaData("Resolution")).partition( "PyQt5.QtCore.QSize(")[2].replace(", ", "x").replace(")", "") print("%s%s" % ("Video Resolution = ", res)) if int(res.partition("x")[0]) / int( res.partition("x")[2]) < 1.5: self.screen43() else: self.screen169() else: print("no metaData available") def openFileAtStart(self, filelist): matching = [s for s in filelist if ".myformat" in s] if len(matching) > 0: self.loadFilm(matching)
class App(QWidget): def __init__(self, bk, prefs): super().__init__() self.bk = bk self.prefs = prefs self.update = False # Install translator for the DOCXImport plugin dialog. # Use the Sigil language setting unless manually overridden. plugin_translator = QTranslator() if prefs['language_override'] is not None: print('Plugin preferences language override in effect') qmf = '{}_{}'.format(bk._w.plugin_name.lower(), prefs['language_override']) else: qmf = '{}_{}'.format(bk._w.plugin_name.lower(), bk.sigil_ui_lang) print( qmf, os.path.join(bk._w.plugin_dir, bk._w.plugin_name, 'translations')) plugin_translator.load( qmf, os.path.join(bk._w.plugin_dir, bk._w.plugin_name, 'translations')) print(QCoreApplication.instance().installTranslator(plugin_translator)) self._ok_to_close = False self.FTYPE_MAP = { 'smap': { 'title': _translate('App', 'Select custom style-map file'), 'defaultextension': '.txt', 'filetypes': 'Text Files (*.txt);;All files (*.*)', }, 'css': { 'title': _translate('App', 'Select custom CSS file'), 'defaultextension': '.css', 'filetypes': 'CSS Files (*.css)', }, 'docx': { 'title': _translate('App', 'Select DOCX file'), 'defaultextension': '.docx', 'filetypes': 'DOCX Files (*.docx)', }, } # Check online github files for newer version if self.prefs['check_for_updates']: self.update, self.newversion = self.check_for_update() self.initUI() def initUI(self): main_layout = QVBoxLayout(self) self.setWindowTitle('DOCXImport') self.upd_layout = QVBoxLayout() self.update_label = QLabel() self.update_label.setAlignment(Qt.AlignCenter) self.upd_layout.addWidget(self.update_label) self.get_update_button = QPushButton() self.get_update_button.clicked.connect(self.get_update) self.upd_layout.addWidget(self.get_update_button) main_layout.addLayout(self.upd_layout) if not self.update: self.update_label.hide() self.get_update_button.hide() self.details_grid = QGridLayout() self.epub2_select = QRadioButton() self.epub2_select.setText('EPUB2') self.epubType = QButtonGroup() self.epubType.addButton(self.epub2_select) self.details_grid.addWidget(self.epub2_select, 0, 0, 1, 1) self.checkbox_get_updates = QCheckBox() self.details_grid.addWidget(self.checkbox_get_updates, 0, 1, 1, 1) self.epub3_select = QRadioButton() self.epub3_select.setText('EPUB3') self.epubType.addButton(self.epub3_select) self.details_grid.addWidget(self.epub3_select, 1, 0, 1, 1) main_layout.addLayout(self.details_grid) self.checkbox_get_updates.setChecked(self.prefs['check_for_updates']) if self.prefs['epub_version'] == '2.0': self.epub2_select.setChecked(True) elif self.prefs['epub_version'] == '3.0': self.epub3_select.setChecked(True) else: self.epub2_select.setChecked(True) self.groupBox = QGroupBox() self.groupBox.setTitle('') self.verticalLayout_2 = QVBoxLayout(self.groupBox) self.docx_grid = QGridLayout() self.docx_label = QLabel() self.docx_grid.addWidget(self.docx_label, 0, 0, 1, 1) self.docx_path = QLineEdit() self.docx_grid.addWidget(self.docx_path, 1, 0, 1, 1) self.choose_docx_button = QPushButton() self.choose_docx_button.setText('...') self.docx_grid.addWidget(self.choose_docx_button, 1, 1, 1, 1) self.verticalLayout_2.addLayout(self.docx_grid) self.choose_docx_button.clicked.connect( lambda: self.fileChooser('docx', self.docx_path)) if len(self.prefs['lastDocxPath']): self.docx_path.setText(self.prefs['lastDocxPath']) self.docx_path.setEnabled(False) self.smap_grid = QGridLayout() self.checkbox_smap = QCheckBox(self.groupBox) self.smap_grid.addWidget(self.checkbox_smap, 0, 0, 1, 1) self.cust_smap_path = QLineEdit(self.groupBox) self.smap_grid.addWidget(self.cust_smap_path, 1, 0, 1, 1) self.choose_smap_button = QPushButton(self.groupBox) self.choose_smap_button.setText('...') self.smap_grid.addWidget(self.choose_smap_button, 1, 1, 1, 1) self.verticalLayout_2.addLayout(self.smap_grid) self.checkbox_smap.setChecked(self.prefs['useSmap']) self.checkbox_smap.stateChanged.connect(lambda: self.chkBoxActions( self.checkbox_smap, self.choose_smap_button)) self.choose_smap_button.clicked.connect( lambda: self.fileChooser('smap', self.cust_smap_path, self. checkbox_smap, self.choose_smap_button)) if len(self.prefs['useSmapPath']): self.cust_smap_path.setText(self.prefs['useSmapPath']) self.cust_smap_path.setEnabled(False) self.chkBoxActions(self.checkbox_smap, self.choose_smap_button) self.css_grid = QGridLayout() self.checkbox_css = QCheckBox(self.groupBox) self.css_grid.addWidget(self.checkbox_css, 0, 0, 1, 1) self.cust_css_path = QLineEdit(self.groupBox) self.css_grid.addWidget(self.cust_css_path, 1, 0, 1, 1) self.choose_css_button = QPushButton(self.groupBox) self.choose_css_button.setText('...') self.css_grid.addWidget(self.choose_css_button, 1, 1, 1, 1) self.verticalLayout_2.addLayout(self.css_grid) self.checkbox_css.setChecked(self.prefs['useCss']) self.checkbox_css.stateChanged.connect(lambda: self.chkBoxActions( self.checkbox_css, self.choose_css_button)) self.choose_css_button.clicked.connect( lambda: self.fileChooser('css', self.cust_css_path, self. checkbox_css, self.choose_css_button)) if len(self.prefs['useCssPath']): self.cust_css_path.setText(self.prefs['useCssPath']) self.cust_css_path.setEnabled(False) self.chkBoxActions(self.checkbox_css, self.choose_css_button) main_layout.addWidget(self.groupBox) self.checkbox_debug = QCheckBox() main_layout.addWidget(self.checkbox_debug) self.checkbox_debug.setChecked(self.prefs['debug']) spacerItem = QSpacerItem(20, 15, QSizePolicy.Minimum, QSizePolicy.Expanding) main_layout.addItem(spacerItem) button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) button_box.accepted.connect(self._ok_clicked) button_box.rejected.connect(self._cancel_clicked) main_layout.addWidget(button_box) self.retranslateUi(self) if self.prefs['qt_geometry'] is not None: try: self.restoreGeometry( QByteArray.fromHex( self.prefs['qt_geometry'].encode('ascii'))) except: pass self.show() def retranslateUi(self, App): self.update_label.setText(_translate('App', 'Plugin Update Available')) self.get_update_button.setText(_translate('App', 'Go to download page')) self.checkbox_get_updates.setText( _translate('App', 'Check for plugin updates')) self.docx_label.setText(_translate('App', 'DOCX File to import')) self.checkbox_smap.setText(_translate('App', 'Use Custom Style Map')) self.checkbox_css.setText(_translate('App', 'Use Custom CSS')) self.checkbox_debug.setText( _translate('App', 'Debug Mode (change takes effect next plugin run)')) def fileChooser(self, ftype, qlineedit, qcheck=None, qbutton=None): options = QFileDialog.Options() options |= QFileDialog.DontUseNativeDialog title = self.FTYPE_MAP[ftype]['title'] startfolder = self.prefs['lastDir'][ftype] ffilter = self.FTYPE_MAP[ftype]['filetypes'] inpath, _ = QFileDialog.getOpenFileName(self, title, startfolder, ffilter, options=options) if len(inpath): qlineedit.setEnabled(True) qlineedit.setText(os.path.normpath(inpath)) self.prefs['lastDir'][ftype] = os.path.dirname(inpath) qlineedit.setEnabled(False) else: if qcheck is not None: qcheck.setChecked(False) if qbutton is not None: qbutton.setEnabled(False) def chkBoxActions(self, chk, btn): btn.setEnabled(chk.isChecked()) def cmdDo(self): global _DETAILS self.prefs['qt_geometry'] = self.saveGeometry().toHex().data().decode( 'ascii') self.prefs['check_for_updates'] = self.checkbox_get_updates.isChecked() self.prefs['epub_version'] = self.epubType.checkedButton().text( )[-1] + '.0' self.prefs['debug'] = self.checkbox_debug.isChecked() _DETAILS['vers'] = self.epubType.checkedButton().text()[-1] + '.0' self.prefs['useSmap'] = self.checkbox_smap.isChecked() if self.checkbox_smap.isChecked(): if len(self.cust_smap_path.text()): self.prefs['useSmapPath'] = self.cust_smap_path.text() _DETAILS['smap'] = (self.checkbox_smap.isChecked(), self.cust_smap_path.text()) else: # Message box that no file is selected return self.prefs['useCss'] = self.checkbox_css.isChecked() if self.checkbox_css.isChecked(): if len(self.cust_css_path.text()): self.prefs['useCssPath'] = self.cust_css_path.text() _DETAILS['css'] = (self.checkbox_css.isChecked(), self.cust_css_path.text()) else: # Message box that no file is selected return if len(self.docx_path.text()): self.prefs['lastDocxPath'] = self.docx_path.text() _DETAILS['docx'] = self.docx_path.text() else: # Message box that no file is selected return def check_for_update(self): '''Use updatecheck.py to check for newer versions of the plugin''' chk = UpdateChecker(self.prefs['last_time_checked'], self.bk._w) update_available, online_version, time = chk.update_info() # update preferences with latest date/time/version self.prefs['last_time_checked'] = time if online_version is not None: self.prefs['last_online_version'] = online_version if update_available: return (True, online_version) return (False, online_version) def get_update(self): url = DOWNLOAD_PAGE if self.update: latest = '/tag/v{}'.format(self.newversion) url = url + latest webbrowser.open_new_tab(url) def _ok_clicked(self): self._ok_to_close = True self.cmdDo() self.bk.savePrefs(self.prefs) QCoreApplication.instance().quit() def _cancel_clicked(self): self._ok_to_close = True '''Close aborting any changes''' self.prefs['qt_geometry'] = self.saveGeometry().toHex().data().decode( 'ascii') self.prefs['check_for_updates'] = self.checkbox_get_updates.isChecked() self.prefs['debug'] = self.checkbox_debug.isChecked() self.bk.savePrefs(self.prefs) QCoreApplication.instance().quit() def closeEvent(self, event): if self._ok_to_close: event.accept() # let the window close else: self._cancel_clicked()
class CameraPanel(QWidget): __obj = None def __init__(self, n_camera, app: QApplication, *args, **kwargs): if CameraPanel.__obj is not None: raise type( 'InstanceExists', (Exception, ), {})('A CameraPanel instance has already been constructed.') CameraPanel.__obj = self super().__init__(*args, **kwargs) self.cameras = [] self.box = QVBoxLayout() self.setLayout(self.box) Configuration(n_camera, self) TrafficMonitor(n_camera) FrameRateMonitor(n_camera) FrameDropMonitor(n_camera) self.connectButton = QPushButton("Connect") self.connectButton.clicked.connect(self.connectRemote) self.reconnecting = QLabel("Disconnected. Trying to reconnect") self.reconnecting.setWordWrap(True) self.total_traffic = QLabel("0.000 KB/s") self.restart_remote = QPushButton("Restart Remote") self.restart_remote.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Expanding) self.restart_remote.clicked.connect(self.restartRemote) self.scan = QPushButton("Scan") self.scan.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Expanding) self.scan.clicked.connect(Configuration().scan) self.top_frame = QFrame() self.top_frame.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Maximum) self.top_grid = QGridLayout() self.top_grid.setContentsMargins(0, 0, 0, 0) self.top_frame.setLayout(self.top_grid) self.top_grid.addWidget(self.connectButton, 0, 1) self.top_grid.addWidget(self.reconnecting, 0, 1) self.top_grid.addWidget(self.total_traffic, 0, 1) self.top_grid.addWidget(self.restart_remote, 0, 2) self.top_grid.addWidget(self.scan, 0, 0) self.total_traffic.hide() self.reconnecting.hide() self.box.addWidget(self.top_frame) self.timer = QTimer() self.timer.timeout.connect(self.updateTraffic) self.timer.start(100) for i in range(n_camera): self.cameras.append(Camera(i, app)) main_splitter = QSplitter(Qt.Horizontal) main_splitter.addWidget(self.cameras[0]) if n_camera == 2: main_splitter.addWidget(self.cameras[1]) elif n_camera == 3: sub_splitter = QSplitter(Qt.Horizontal) sub_splitter.addWidget(self.cameras[1]) sub_splitter.addWidget(self.cameras[2]) main_splitter.addWidget(sub_splitter) elif n_camera == 4: sub_splitter = QSplitter(Qt.Horizontal) sub_sub_splitter = QSplitter(Qt.Horizontal) sub_splitter.addWidget(sub_sub_splitter) sub_sub_splitter.addWidget(self.cameras[1]) sub_sub_splitter.addWidget(self.cameras[2]) sub_splitter.addWidget(self.cameras[3]) main_splitter.addWidget(sub_splitter) self.box.addWidget(main_splitter) def connectRemote(self): """ This function blocks until TCP connection succeeds. Call with caution """ if Configuration().connect(): self.connectButton.hide() self.total_traffic.show() for cam in self.cameras: cam.startReceiving() cam.mode_selection.setEnabled(True) cam.updateMode(cam.mode_selection.currentIndex()) def restartRemote(self): os.system( f'ssh root@{REMOTE_IP_ADDR} systemctl restart vision-server.service' ) if self.connectButton.isVisible(): self.connectRemote() else: Configuration().reconnect() def updateTraffic(self): self.total_traffic.setText('{:<4} KB/s'.format( round(TrafficMonitor().total / 1024, 2)))
class TrainWindow(QWidget): ''' The TrainWindow class is a PySide2 Graphical User Interface (GUI) window that is called by MainWindow class in order to configure a custom training session and initiates training for a selected Precision-Level. ''' def __init__(self, debug=False): ''' The constructor. Sets the size of the window and configurations for a training session. Calls setButtons function to populate window with button. ''' super().__init__() self.debug = debug self._TRAIN_WIN_H = 500 self._TRAIN_WIN_W = 500 self.model_name = '' self._model_list = [] self._label_list = [] self._precision_level = 1 self._path_to_dataset = '' self._path_to_label_list = '' self._is_valid_dataset = False self.buttonConnected = False self.label_process = None self._is_model_ready = False self._is_dataset_linked = False self._is_dataset_labelled = False self._is_labellist_linked = False self.label_train_process = None self.label_val_process = None self.setWindowTitle('Train') self.setGeometry(self._TRAIN_WIN_W * 2, 0, self._TRAIN_WIN_W, self._TRAIN_WIN_H) self.setFixedSize(self._TRAIN_WIN_W, self._TRAIN_WIN_H) self.setButtons() def setButtons(self): '''A Mutator function that defines all buttons in TrainWindow.''' self.p1_button = QPushButton('P1', self) self.p1_button.setGeometry(0, 0, 50, 100) self.p1_button.setStyleSheet( 'background-color: rgba(180,180,180,255);') self.p2_button = QPushButton('P2', self) self.p2_button.setGeometry(50, 0, 50, 100) self.p3_button = QPushButton('P3', self) self.p3_button.setGeometry(100, 0, 50, 100) # Model dropdown menu to select Precision Level specific model self.model_selector = QComboBox(self) self.model_selector.setGeometry(self._TRAIN_WIN_W - 150, 0, 150, 100) self.model_selector.setStyleSheet('background-color: red;') self.populateModelSelector() self.model_selector_label = QLabel(self) self.model_selector_label.setText('Choose Model =') self.model_selector_label.move(220, 40) # Labeller button to initiate labelme self.label_button = QPushButton('Label Dataset', self) self.label_button.setIcon(QIcon('img/label.png')) self.label_button.setIconSize(QSize(50, 50)) self.label_button.setGeometry(0, 200, self._TRAIN_WIN_W / 2, 100) self.label_button.setStyleSheet( 'background-color: rgba(0,200,10,255);') if self._precision_level == 1: self.label_button.hide() self.generate_button = QPushButton('Generate Dataset', self) self.generate_button.setIcon(QIcon('img/label.png')) self.generate_button.setIconSize(QSize(50, 50)) self.generate_button.setGeometry(self._TRAIN_WIN_W / 2, 200, self._TRAIN_WIN_W / 2, 100) self.generate_button.setStyleSheet( 'background-color: rgba(0,200,10,255);') if self._precision_level == 1: self.generate_button.hide() # Labeller button to initiate labelme self.validate_button = QPushButton('Validate Dataset', self) self.validate_button.setIcon(QIcon('img/validate.png')) self.validate_button.setIconSize(QSize(50, 50)) self.validate_button.setGeometry(self._TRAIN_WIN_W / 2, 300, self._TRAIN_WIN_W / 2, 100) # Dataset button to prompt input via FileDialogue self.dataset_button = QPushButton('Choose Dataset', self) self.dataset_button.setIcon(QIcon('img/dataset.png')) self.dataset_button.setIconSize(QSize(50, 50)) self.dataset_button.setGeometry(0, 300, self._TRAIN_WIN_W / 2, 100) self.dataset_button.setStyleSheet('background-color: red;') # Start Training button to start and display training process self.train_button = QPushButton('Train', self) self.train_button.setIcon(QIcon('img/train.png')) self.train_button.setIconSize(QSize(75, 75)) self.train_button.setGeometry(0, self._TRAIN_WIN_H - 100, self._TRAIN_WIN_W, 100) self.train_button.setStyleSheet( 'background-color: rgba(180,180,180,255);') # Set Label List self.list_button = QPushButton('Choose Label List', self) self.list_button.setIcon(QIcon('img/label_list.png')) self.list_button.setIconSize(QSize(75, 75)) self.list_button.setGeometry(0, 100, self._TRAIN_WIN_W, 100) self.list_button.setStyleSheet('background-color: rgba(200,10,0,255);') self.p1_button.clicked.connect(self.setP1) self.p2_button.clicked.connect(self.setP2) self.p3_button.clicked.connect(self.setP3) self.model_selector.activated.connect(self.setModel) self.dataset_button.clicked.connect(self.setDataset) self.label_button.clicked.connect(self.runLabelme) self.generate_button.clicked.connect(self.conformDatasetToCOCO) self.validate_button.clicked.connect(self.validateDataset) self.list_button.clicked.connect(self.setLabelList) def setP1(self): '''A function that is triggered by the button labelled, P1.''' self._precision_level = 1 self.populateModelSelector() self.initModel() self.label_button.hide() self.generate_button.hide() self.p1_button.setStyleSheet( 'background-color: rgba(180,180,180,255);') self.p2_button.setStyleSheet('background-color: white;') self.p3_button.setStyleSheet('background-color: white;') self.disconnectTrainingButton() print('Set Precision Level at: ', self._precision_level) def setP2(self): '''A function that is triggered by the button labelled, P2.''' self._precision_level = 2 self.populateModelSelector() self.initModel() self.label_button.show() self.generate_button.show() self.p2_button.setStyleSheet( 'background-color: rgba(180,180,180,255);') self.p1_button.setStyleSheet('background-color: white;') self.p3_button.setStyleSheet('background-color: white;') self.disconnectTrainingButton() print('Set Precision Level at: ', self._precision_level) def setP3(self): '''A function that is triggered by the button labelled, P3.''' self._precision_level = 3 self.populateModelSelector() self.initModel() self.label_button.show() self.generate_button.show() self.p3_button.setStyleSheet( 'background-color: rgba(180,180,180,255);') self.p1_button.setStyleSheet('background-color: white;') self.p2_button.setStyleSheet('background-color: white;') self.disconnectTrainingButton() print('Set Precision Level at: ', self._precision_level) def setModel(self, index): '''A function that is triggered by the DropDown Menu labelled, Model.''' self.model_name = self.model_selector.itemText(index) self.model_selector.setStyleSheet( 'background-color: rgba(0,200,10,255);') self._is_model_ready = True self.validateTraining() print('Set Model to ', self.model_name) def setLabelList(self): '''A function that is triggered by the button labelled, Choose Label List.''' if not self.debug: input_classes_filepath, ok = QFileDialog.getOpenFileName( self, 'Set the .txt to use', os.path.abspath('../data'), 'Text Files (*.txt)') else: input_classes_filepath = '../data/label_list/coco_classes.txt' ok = True if ok: self._path_to_label_list = input_classes_filepath self._label_list = [ line.rstrip('\n') for line in open(input_classes_filepath) ] else: print('No label list set.') return self.list_button.setStyleSheet('background-color: rgba(0,150,10,255);') self._is_labellist_linked = True self.validateTraining() def setDataset(self): '''A function that is triggered by the button labelled, Choose Dataset.''' if not self.debug: new_filepath_to_dataset = (QFileDialog.getExistingDirectory( self, 'Set directory of the dataset', os.path.abspath('../data'), QFileDialog.ShowDirsOnly | QFileDialog.DontResolveSymlinks)) else: new_filepath_to_dataset = '../data/datasets' if os.path.isdir(new_filepath_to_dataset): self._path_to_dataset = new_filepath_to_dataset # Set button color to green self._is_dataset_linked = True self.dataset_button.setStyleSheet( 'background-color: rgba(0,200,10,255);') else: # Set button color to red print('Dataset path does not exist.') self.dataset_button.setStyleSheet('background-color: red;') self.validateTraining() def runLabelme(self): '''A function that is triggered by the button labelled, Label Dataset.''' self.label_process = subprocess.Popen(['labelme']) self.validateTraining() def initModel(self): ''' A Mutator function that sets the model_name to the first model available whenever the precision level changes. ''' self.model_name = self._model_list[0] self._is_model_ready = True print('Set Model to ', self.model_name) def validateTraining(self): ''' A Mutator function that evaulates necessary boolean flags that are all required to allow proper training given a certain requested precision level. ''' # Perform 4 checks to ensure all data is available for Training to start without issue. if not self._is_model_ready: print('No model provided. Please choose Model.') self.train_button.setStyleSheet( 'background-color: rgba(180,180,180,255);') self.disconnectTrainingButton() return if not self._is_dataset_linked: print('Dataset directory not provided. Please choose Dataset.') self.train_button.setStyleSheet( 'background-color: rgba(180,180,180,255);') self.disconnectTrainingButton() return if not self._is_labellist_linked: print('Label List not provided. Please choose Label List.') self.train_button.setStyleSheet( 'background-color: rgba(180,180,180,255);') self.disconnectTrainingButton() return if not self._is_dataset_labelled: print( 'Dataset not properly restructured. Please restructure Dataset.' ) self.train_button.setStyleSheet( 'background-color: rgba(180,180,180,255);') self.disconnectTrainingButton() return # Precision Level 1 only requires 4 checks. if self._precision_level == 1: print('Precision 1 Training Ready.') self.train_button.setStyleSheet('background-color: white;') self.connectTrainingButton() return if not self._is_dataset_labelled: print('Dataset not labelled properly. Please label Dataset.') self.train_button.setStyleSheet( 'background-color: rgba(180,180,180,255);') self.disconnectTrainingButton() return self.train_button.setStyleSheet('background-color: white;') self.connectTrainingButton() def validateDataset(self): '''A function that is triggered by the button labelled, Validate Dataset.''' if self._precision_level == 1: trainDirExists = os.path.exists(self._path_to_dataset + '/train') valDirExists = os.path.exists(self._path_to_dataset + '/val') # Check if the dataset folder has the following structure if trainDirExists and valDirExists: self._is_dataset_labelled = True self.validate_button.setStyleSheet( 'background-color: rgba(0,200,10,255);') else: self._is_dataset_labelled = False print('[ERROR] - Please ensure there is ' + '/train and /val sub-directories ' + 'in the selected dataset directory.') elif self._precision_level == 2: isDatasetNamedRight = os.path.basename( self._path_to_dataset) == 'custom_dataset' trainDirExists = os.path.exists(self._path_to_dataset + '/train_dataset') valDirExists = os.path.exists(self._path_to_dataset + '/val_dataset') # Check if the dataset folder has the following structure if trainDirExists and valDirExists and isDatasetNamedRight: self._is_dataset_labelled = True self.validate_button.setStyleSheet( 'background-color: rgba(0,200,10,255);') else: self._is_dataset_labelled = False print('[ERROR] - Please ensure there is ' + '/train_dataset and /val_dataset sub-directories' + 'in the selected dataset directory.') elif self._precision_level == 3: isDatasetNamedRight = os.path.basename( self._path_to_dataset) == 'custom_dataset' trainDirExists = os.path.exists(self._path_to_dataset + '/train_dataset') valDirExists = os.path.exists(self._path_to_dataset + '/val_dataset') # Check if the dataset folder has the following structure if trainDirExists and valDirExists and isDatasetNamedRight: self._is_dataset_labelled = True self.validate_button.setStyleSheet( 'background-color: rgba(0,200,10,255);') else: self._is_dataset_labelled = False print('[ERROR] - Please ensure there is ' + '/train_dataset and /val_dataset sub-directories ' + 'in the selected dataset directory.') self.validateTraining() def startTraining(self): '''A function that is triggered by the button labelled, Train.''' self.disconnectTrainingButton() self.train_button.setText('Training In Progress') self.train_button.updateGeometry() if self._precision_level == 1: p1_trainer = P1Trainer(self._path_to_dataset, self.model_name, self._label_list) p1_trainer.train(False) elif self._precision_level == 2: p2_trainer = P2Trainer(self._path_to_dataset, self.model_name, self._label_list) p2_trainer.train(False) else: p3_trainer = P3Trainer(self._path_to_dataset, self.model_name, self._label_list) p3_trainer.train(False) self.train_button.setText('Train') self.train_button.updateGeometry() def conformDatasetToCOCO(self): '''A function that is triggered by the button labelled, Generate Dataset.''' if not self.debug: path_to_labelled = QFileDialog.getExistingDirectory( self, 'Select your labeled dataset.', os.path.abspath('../data'), QFileDialog.ShowDirsOnly | QFileDialog.DontResolveSymlinks) else: path_to_labelled = '../data/datasets/p2p3_dummy_dataset' # Check if every image in given folder trainDirExists = os.path.exists(path_to_labelled + '/train_dataset') valDirExists = os.path.exists(path_to_labelled + '/val_dataset') outputTrainDir = '../data/datasets/custom_dataset/train_dataset' outputValDir = '../data/datasets/custom_dataset/val_dataset' if trainDirExists and valDirExists: self.label_train_process = subprocess.Popen([ 'python', 'dataset/labelme2coco.py', '--labels', self._path_to_label_list, path_to_labelled + '/train_dataset', outputTrainDir ]) if not self.debug: self.label_train_process.communicate() self.label_val_process = subprocess.Popen([ 'python', 'dataset/labelme2coco.py', '--labels', self._path_to_label_list, path_to_labelled + '/val_dataset', outputValDir ]) if not self.debug: self.label_val_process.communicate() else: print('Faulty labelled dataset detected.') def populateModelSelector(self): ''' A Mutator function that populates the DropDown Menu labelled, Choose Model with all available pretrained models from PyTorch model zoo. ''' # Implement different model list based on different precision level. if self._precision_level == 1: self._model_list = [ line.rstrip('\n') for line in open('./lists/p1_model_list.txt') ] elif self._precision_level == 2: self._model_list = [ line.rstrip('\n') for line in open('./lists/p2_model_list.txt') ] elif self._precision_level == 3: self._model_list = [ line.rstrip('\n') for line in open('./lists/p3_model_list.txt') ] self.model_selector.clear() for model in self._model_list: self.model_selector.addItem(model) def connectTrainingButton(self): ''' A Mutator function that allows the button labelled, Train, to be used by the user. ''' if not self.buttonConnected: self.train_button.clicked.connect(self.startTraining) self.buttonConnected = True def disconnectTrainingButton(self): ''' A Mutator function that disallows the button labelled, Train, to be used by the user. ''' if self.buttonConnected: try: self.train_button.clicked.disconnect(self.startTraining) except Exception: pass self.buttonConnected = False
class TriageView(QScrollArea, View): def __init__(self, parent, data): QScrollArea.__init__(self, parent) View.__init__(self) View.setBinaryDataNavigable(self, True) self.setupView(self) self.data = data self.currentOffset = 0 self.byteView = None self.fullAnalysisButton = None self.importsWidget = None container = QWidget(self) layout = QVBoxLayout() entropyGroup = QGroupBox("Entropy", container) entropyLayout = QVBoxLayout() entropyLayout.addWidget( entropy.EntropyWidget(entropyGroup, self, self.data)) entropyGroup.setLayout(entropyLayout) layout.addWidget(entropyGroup) hdr = None try: if self.data.view_type == "PE": hdr = headers.PEHeaders(self.data) elif self.data.view_type != "Raw": hdr = headers.GenericHeaders(self.data) except: log.log_error(traceback.format_exc()) if hdr is not None: headerGroup = QGroupBox("Headers", container) headerLayout = QVBoxLayout() headerWidget = headers.HeaderWidget(headerGroup, hdr) headerLayout.addWidget(headerWidget) headerGroup.setLayout(headerLayout) layout.addWidget(headerGroup) if self.data.executable: importExportSplitter = QSplitter(Qt.Horizontal) importGroup = QGroupBox("Imports", container) importLayout = QVBoxLayout() self.importsWidget = imports.ImportsWidget(importGroup, self, self.data) importLayout.addWidget(self.importsWidget) importGroup.setLayout(importLayout) importExportSplitter.addWidget(importGroup) exportGroup = QGroupBox("Exports", container) exportLayout = QVBoxLayout() exportLayout.addWidget( exports.ExportsWidget(exportGroup, self, self.data)) exportGroup.setLayout(exportLayout) importExportSplitter.addWidget(exportGroup) layout.addWidget(importExportSplitter) if self.data.view_type != "PE": segmentsGroup = QGroupBox("Segments", container) segmentsLayout = QVBoxLayout() segmentsWidget = sections.SegmentsWidget( segmentsGroup, self.data) segmentsLayout.addWidget(segmentsWidget) segmentsGroup.setLayout(segmentsLayout) layout.addWidget(segmentsGroup) if len(segmentsWidget.segments) == 0: segmentsGroup.hide() sectionsGroup = QGroupBox("Sections", container) sectionsLayout = QVBoxLayout() sectionsWidget = sections.SectionsWidget(sectionsGroup, self.data) sectionsLayout.addWidget(sectionsWidget) sectionsGroup.setLayout(sectionsLayout) layout.addWidget(sectionsGroup) if len(sectionsWidget.sections) == 0: sectionsGroup.hide() buttonLayout = QHBoxLayout() buttonLayout.addStretch(1) self.loadDynamicButton = QPushButton("Load Dynamic Imports") self.loadDynamicButton.clicked.connect( self.importsWidget.scanDynamic) buttonLayout.addWidget(self.loadDynamicButton) self.fullAnalysisButton = QPushButton("Start Full Analysis") self.fullAnalysisButton.clicked.connect(self.startFullAnalysis) buttonLayout.addWidget(self.fullAnalysisButton) layout.addLayout(buttonLayout) layout.addStretch(1) else: self.byteView = byte.ByteView(self, self.data) layout.addWidget(self.byteView, 1) container.setLayout(layout) self.setWidgetResizable(True) self.setWidget(container) if self.fullAnalysisButton is not None and Settings().get_string( "analysis.mode", data) == "full": self.fullAnalysisButton.hide() def getData(self): return self.data def getCurrentOffset(self): if self.byteView is not None: return self.byteView.getCurrentOffset() return self.currentOffset def getSelectionOffsets(self): if self.byteView is not None: return self.byteView.getSelectionOffsets() return (self.currentOffset, self.currentOffset) def setCurrentOffset(self, offset): self.currentOffset = offset UIContext.updateStatus(True) def getFont(self): return binaryninjaui.getMonospaceFont(self) def navigate(self, addr): if self.byteView: return self.byteView.navigate(addr) return False def startFullAnalysis(self): Settings().set_string("analysis.mode", "full", self.data) for f in self.data.functions: if f.analysis_skipped: f.reanalyze() self.data.update_analysis() self.fullAnalysisButton.hide() def navigateToFileOffset(self, offset): if self.byteView is None: addr = self.data.get_address_for_data_offset(offset) view_frame = ViewFrame.viewFrameForWidget(self) if view_frame is None: return if addr is None: view_frame.navigate("Hex:Raw", offset) else: view_frame.navigate( "Linear:" + view_frame.getCurrentDataType(), addr) else: if self.data == self.data.file.raw: addr = offset else: addr = self.data.get_address_for_data_offset(offset) if addr is None: view_frame = ViewFrame.viewFrameForWidget(self) if view_frame is not None: view_frame.navigate("Hex:Raw", offset) else: self.byteView.navigate(addr) self.byteView.setFocus(Qt.OtherFocusReason) def focusInEvent(self, event): if self.byteView is not None: self.byteView.setFocus(Qt.OtherFocusReason)
class Form(QDialog): def __init__(self, parent=None): self.round = 0 self.lcd = QLCDNumber(5) self.lcd2 = QLCDNumber(5) self.clock = QLCDNumber(5) super(Form, self).__init__(parent) self.setWindowTitle("Pomodoro") # Create widgets self.slider = QSlider(Qt.Horizontal) self.slider.setRange(1, 99) self.slider.setValue(25) self.slider2 = QSlider(Qt.Horizontal) self.slider2.setRange(1, 99) self.slider2.setValue(5) self.count = self.slider.value() * 60 self.rest = self.slider2.value() * 60 self.taskbar_count = 0 self.taskbar2_count = 0 self.text = QLabel("How long should the work period be?") self.text2 = QLabel("How long should the rest period be?") self.work = QLabel("WORK") self.pause = QLabel("REST") self.rounds = QLabel("Number of rounds: " + str(self.round)) self.work.setAlignment(Qt.AlignHCenter) self.work.setFont(QFont("Times", 18, QFont.Bold)) self.pause.setAlignment(Qt.AlignHCenter) self.pause.setFont(QFont("Times", 18, QFont.Bold)) self.button = QPushButton("Start timer") self.button2 = QPushButton("Stop timer") self.reset = QPushButton("Reset rounds") self.lcd.display("25:00") self.lcd2.display("05:00") mins = 25 secs = "00" self.clock.display(f"{mins}:{secs}") self.slider.valueChanged.connect(self.first_display) self.slider2.valueChanged.connect(self.second_display) self.slider.valueChanged.connect(self.clock_display) self.button2.hide() self.work.hide() self.pause.hide() self.clock.hide() # Create layout and add widgets layout = QVBoxLayout() layout.addWidget(self.text) layout.addWidget(self.lcd) layout.addWidget(self.slider) layout.addWidget(self.text2) layout.addWidget(self.lcd2) layout.addWidget(self.slider2) layout.addWidget(self.button) layout.addWidget(self.button2) layout.addWidget(self.work) layout.addWidget(self.pause) layout.addWidget(self.clock) layout.addWidget(self.rounds) layout.addWidget(self.reset) # Set dialog layout self.setLayout(layout) self.systemtray_icon = QSystemTrayIcon(QIcon("snake.png")) self.systemtray_icon.show() self.systemtray_icon.activated.connect(self.icon_activated) self.menu = QMenu(parent) self.exit_action = self.menu.addAction("Exit") self.systemtray_icon.setContextMenu(self.menu) self.exit_action.triggered.connect(self.slot_exit) # Add signals self.slider.valueChanged.connect(self.count_func) self.slider2.valueChanged.connect(self.count_func) self.button.clicked.connect(self.button_update) self.button.clicked.connect(self.timer_func) self.button.clicked.connect(self.round_count) self.button2.clicked.connect(self.stop) self.reset.clicked.connect(self.reset_rounds) def reset_rounds(self): self.round = 0 self.rounds.setText("Number of rounds: " + str(self.round)) def round_count(self): self.round += 1 self.rounds.setText("Number of rounds: " + str(self.round)) def icon_activated(self, reason): if reason in (QSystemTrayIcon.Trigger, QSystemTrayIcon.DoubleClick): self.show() def closeEvent(self, event): self.hide() event.ignore() def slot_exit(self): QApplication.exit(0) def first_display(self): minute = str(self.slider.sliderPosition()) second = ":00" leading_zero = "0" if self.slider.sliderPosition() >= 10: self.lcd.display(minute + second) else: self.lcd.display(leading_zero + minute + second) def second_display(self): minute = str(self.slider2.sliderPosition()) second = ":00" leading_zero = "0" if self.slider2.sliderPosition() >= 10: self.lcd2.display(minute + second) else: self.lcd2.display(leading_zero + minute + second) def clock_display(self): minute = str(self.slider.sliderPosition()) second = ":00" leading_zero = "0" if self.slider.sliderPosition() >= 10: self.clock.display(minute + second) else: self.clock.display(leading_zero + minute + second) def count_func(self): self.count = self.slider.sliderPosition() * 60 self.rest = self.slider2.sliderPosition() * 60 def countdown(self): minute, second = divmod(self.count, 60) zero = "0" show = self.work.show() if second < 10 and minute < 10: self.clock.display(zero + str(minute) + ":" + zero + str(second)) elif second < 10: self.clock.display(str(minute) + ":" + zero + str(second)) elif minute < 10: self.clock.display(zero + str(minute) + ":" + str(second)) else: self.clock.display(str(minute) + ":" + str(second)) self.count -= 1 if self.count < -1: self.work.hide() self.taskbar_rest() show = self.pause.show() minute, second = divmod(self.rest, 60) zero = "0" if self.rest == self.slider2.value() * 60: self.show() if second < 10 and minute < 10: self.clock.display(zero + str(minute) + ":" + zero + str(second)) elif second < 10: self.clock.display(str(minute) + ":" + zero + str(second)) elif minute < 10: self.clock.display(zero + str(minute) + ":" + str(second)) else: self.clock.display(str(minute) + ":" + str(second)) self.rest -= 1 if self.rest < -1: self.clock.display("00:00") self.taskbar_work() self.timer.stop() self.stop() show def timer_func(self): timer = QTimer() self.timer = timer self.timer.timeout.connect(self.countdown) self.timer.start(1000) def button_update(self): self.button.hide() self.text.hide() self.lcd.hide() self.slider.hide() self.text2.hide() self.lcd2.hide() self.slider2.hide() self.reset.hide() self.clock.show() self.button2.show() self.work.show() def taskbar_rest(self): if self.taskbar_count == 0: self.systemtray_icon.showMessage("PAUSE", "Time to rest!", QSystemTrayIcon.Information, 500000) self.taskbar_count = 1 def taskbar_work(self): if self.taskbar2_count == 0: self.systemtray_icon.showMessage("WORK", "Break over!", QSystemTrayIcon.Information, 500000) self.taskbar2_count = 1 def stop(self): self.timer.stop() self.button2.hide() self.work.hide() self.pause.hide() self.clock.hide() self.count = self.slider.value() * 60 self.rest = self.slider2.value() * 60 self.clock.display(str(self.slider.value()) + ":00") self.button.show() self.text.show() self.lcd.show() self.slider.show() self.text2.show() self.lcd2.show() self.slider2.show() self.reset.show() self.show() self.taskbar_count = 0 self.taskbar2_count = 0
class MainWrapper(QWidget): def __init__(self): super(MainWrapper, self).__init__() self.monthNames = [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' ] self.metrics = None self.state = State() self.addListeners() self.buildUI() self.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum) def loadNewFile(self): fileName = self.getCsvFileName() if fileName: parser = CSVParser() self.transactionMap = parser.parseCsv(fileName) allMonths = list(self.transactionMap.keys()) allMonths.sort() self.monthsList = allMonths self.loadMonth(allMonths[-1]) if len(allMonths) > 1: self.monthDecreaseButton.show() self.addMetricsButton() def getCsvFileName(self): fname, success = QFileDialog.getOpenFileName( None, 'Open CSV/XLSX Statement', '', 'CSV (*.csv *.CSV, *.xlsx)') if success: return fname def addFile(self): fileName = self.getCsvFileName() if fileName: parser = CSVParser() newTransactionMap = parser.parseCsv(fileName) self.transactionMap = parser.addTransactionMap( self.transactionMap, newTransactionMap) allMonths = list(self.transactionMap.keys()) allMonths.sort() self.monthsList = allMonths self.loadMonth(self.currentMonth) def addListeners(self): self.state.addSubscriber(Events.update_total, self.setTotalDisplay) self.state.addSubscriber(Events.metrics_close, self.onMetricsClose) def loadMonth(self, monthText): monthNumber = int(monthText.split('-')[1]) self.monthTitle.setText(self.monthNames[monthNumber - 1] + ' ' + monthText.split('-')[0]) prevCode = None if self.currentMonth == 'Month' else self.currentMonth self.currentMonth = monthText clearLayout(self.dataDisplayWrapper) self.dataDisplay = DataDisplay(self.state, self.transactionMap[self.currentMonth], self.currentMonth, prevCode=prevCode) self.dataDisplay.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Maximum) self.dataDisplayWrapper.addWidget(self.dataDisplay) def loadPreviousMonth(self): currentIndex = self.monthsList.index(self.currentMonth) if currentIndex > 0: newMonth = self.monthsList[currentIndex - 1] self.loadMonth(newMonth) self.monthIncreaseButton.show() if currentIndex == 1: self.monthDecreaseButton.hide() def loadNextMonth(self): currentIndex = self.monthsList.index(self.currentMonth) if currentIndex < len(self.monthsList) - 1: newMonth = self.monthsList[currentIndex + 1] self.loadMonth(newMonth) self.monthDecreaseButton.show() if currentIndex == len(self.monthsList) - 2: self.monthIncreaseButton.hide() def setTotalDisplay(self, transactionTotal, categoryTotal): self.totalDisplay.setText('Total: ${} / {}'.format( transactionTotal, categoryTotal)) if transactionTotal <= categoryTotal: self.totalDisplay.setStyleSheet( "QLabel { color : rgb(67,160,71); }") else: self.totalDisplay.setStyleSheet( "QLabel { color : rgba(244, 67, 54, 255); }") def buildUI(self): self.closeButton = QPushButton('close') self.closeButton.setShortcut('Ctrl+W') self.closeButton.clicked.connect(self.onClose) self.closeButton.setFixedSize(0, 0) self.buildTitleLayout() self.buildMonthDisplayLayout() self.buildHeaderWidget() self.headerLayout = QVBoxLayout() self.headerLayout.addLayout(self.titleLayout) self.headerLayout.addLayout(self.monthDisplayLayout) self.headerLayout.addWidget(self.categoryHeaderWidget) self.headerLayout.addWidget(self.closeButton) self.headerLayoutWrapperWidget = QWidget() self.headerLayoutWrapperWidget.setLayout(self.headerLayout) self.headerLayoutWrapperWidget.setMaximumHeight(100) self.dataDisplayWrapper = QHBoxLayout() self.mainWrapperLayout = QVBoxLayout() self.mainWrapperLayout.addWidget(self.headerLayoutWrapperWidget) self.mainWrapperLayout.addLayout(self.dataDisplayWrapper) self.setLayout(self.mainWrapperLayout) self.setMinimumWidth(450) def buildTitleLayout(self): self.appTitle = QLabel(self) self.appTitle.setText("App Title") self.appTitle.setFont(boldFont) self.chooseFileButton = QPushButton("choose file") self.chooseFileButton.clicked.connect(self.loadNewFile) self.chooseFileButton.setShortcut('Ctrl+O') self.chooseFileButton.setContextMenuPolicy(Qt.CustomContextMenu) self.connect(self.chooseFileButton, SIGNAL('customContextMenuRequested(const QPoint &)'), self.chooseFileContextMenu) self.chooseFileButton.setMaximumWidth(90) self.metricsButton = QPushButton('metrics') self.metricsButton.clicked.connect(self.openMetrics) self.metricsButton.setShortcut('Ctrl+M') self.metricsButton.setMaximumWidth(80) self.titleLayout = QHBoxLayout() self.titleLayout.addWidget(self.appTitle) self.titleLayout.addWidget(self.chooseFileButton) def buildMonthDisplayLayout(self): self.monthTitle = QLabel(self) self.currentMonth = 'Month' self.monthTitle.setText(self.currentMonth) self.monthTitle.setMaximumWidth(115) self.monthTitle.setAlignment(Qt.AlignCenter) self.monthIncreaseButton = QPushButton('>') self.monthIncreaseButton.clicked.connect(self.loadNextMonth) self.monthDecreaseButton = QPushButton('<') self.monthDecreaseButton.clicked.connect(self.loadPreviousMonth) self.monthIncreaseButton.setMaximumWidth(20) self.monthDecreaseButton.setMaximumWidth(20) self.monthIncreaseButton.hide() self.monthDecreaseButton.hide() self.monthDisplayWrapper = QHBoxLayout() self.monthDisplayWrapper.addWidget(self.monthDecreaseButton) self.monthDisplayWrapper.addWidget(self.monthTitle) self.monthDisplayWrapper.addWidget(self.monthIncreaseButton) self.monthDisplayLayout = QHBoxLayout() self.monthDisplayLayout.addLayout(self.monthDisplayWrapper) def buildHeaderWidget(self): self.categoriesTitle = QLabel(self) self.categoriesTitle.setText('Categories') self.categoriesTitle.setMaximumWidth(88) self.categoriesTitle.setFont(boldFont) self.categoriesAddButton = QPushButton('+') self.categoriesAddButton.setMaximumWidth(20) self.categoriesAddButton.clicked.connect(self.promptAddCategory) self.categoriesAddButton.setToolTip('Add Category') self.categoriesAddButton.setContextMenuPolicy(Qt.CustomContextMenu) self.connect(self.categoriesAddButton, SIGNAL('customContextMenuRequested(const QPoint &)'), self.categoriesAddContextMenu) self.sumTransactions = 0 self.totalDisplay = QLabel(self) self.totalDisplay.setText('Total: - / -') self.totalDisplay.setAlignment(Qt.AlignRight | Qt.AlignVCenter) self.totalDisplay.setFont(boldFont) self.categoryHeaderLayout = QHBoxLayout() self.categoryHeaderLayout.addWidget(self.categoriesTitle) self.categoryHeaderLayout.addWidget(self.categoriesAddButton) self.categoryHeaderLayout.addWidget(self.totalDisplay) self.categoryHeaderLayout.setMargin(0) self.categoryHeaderWidget = QWidget() self.categoryHeaderWidget.setLayout(self.categoryHeaderLayout) def promptAddCategory(self): modal = CategoryModal() data = modal.getData() if data: self.state.next(Events.add_category, data) def categoriesAddContextMenu(self): menu = QMenu(self) removeAction = QAction('Remove All') removeAction.triggered.connect(self.removeAllCategories) menu.addAction(removeAction) menu.exec_(QCursor.pos()) def removeAllCategories(self): self.state.next(Events.remove_all_categories) def chooseFileContextMenu(self): if hasattr(self, 'dataDisplay'): menu = QMenu(self) addStatementAction = QAction('Add Statement') addStatementAction.triggered.connect(self.addFile) menu.addAction(addStatementAction) menu.exec_(QCursor.pos()) def openMetrics(self): if not self.metrics: self.metrics = MainMetrics(self.state, self.transactionMap) self.metrics.show() self.metrics.activateWindow() def onMetricsClose(self): self.metrics.hide() self.metrics.deleteLater() self.metrics = None def addMetricsButton(self): self.titleLayout.removeWidget(self.chooseFileButton) self.titleLayout.addWidget(self.metricsButton) self.titleLayout.addWidget(self.chooseFileButton) def onClose(self): sys.exit()
class QClickEdit(QWidget): """Displays text, changes to the specified user input field when clicked on, then reverts to text again when the widget loses focus. Self.input_widget holds the Qt user input widget. Any of the functions inherent to the widget can be executed, though compatability with this module cannot be guaranteed. Self.text holds the value displayed when a QClickEdit object is out of focus. Text is displayed as a QPushButton widget with flat text. When a QClickEdit widget is clicked on, it hides the text and displays the underlying Qt user input widget. When the user clicks elseware or the input widget loses focus, a QClickEdit widget reverts back to the flat text in self.text, displaying the current value of the underlying widget. Input widgets currently supported: QSpinBox, QLineEdit, QTimeEdit, and QComboBox. Values can be set for QClickEdit widgets, regardless of the type of input widget used, with the function setValue(), and values can be returned with getValue(). QLineEdit and QComboBox return as a string, QSpinBox returns as an integer, and QTimeEdit returns as a QTime Object. """ # Registry saves instances of this class to iterate through them and # determine when to freeze edit fields _registry = [] # Registry to keep track of which widgets have modified mousePressEvent() # functions, so they don't get rewritten every time a new QClickEdit object # is created. _top_widgets_modified = [] def __init__(self, current_value, type_of_field=False, suffix=False, parent=None): """Arguments: current_value -- The desired default value of the input widget type_of_field -- Optional argument - This string will precede the displayed current value suffix -- suffix to follow current value displayed when self.text is visible. """ if not (isinstance(type_of_field, str) or (type_of_field is False)): raise TypeError("type_of_field argument is a descriptive prefix and must be a string") if not (isinstance(suffix, str) or (suffix is False)): raise TypeError("Suffix must be a string") QWidget.__init__(self) self.suffix = suffix self.layout = QHBoxLayout() self.setLayout(self.layout) self.layout.setMargin(0) self.layout.setContentsMargins(0, 0, 0, 0) if type_of_field: self.layout.addWidget(QLabel(type_of_field + ": ", self)) self.destroyed.connect(lambda: self._registry.remove(self)) self._registry.append(self) self._createInputWidget() self._createTextWidget() self.setValue(current_value) top_widgets = QApplication.topLevelWidgets() for tw in top_widgets: if tw in self._top_widgets_modified: continue self._top_widgets_modified.append(tw) original_MPE = tw.mousePressEvent MPE = partial(self._topWidgetMousePressEvent, tw, original_MPE) tw.mousePressEvent = MPE # Private Functions # def _topWidgetMousePressEvent(self, parent, original_mousePressEvent, event): """Overrides a class's mouse press event while still triggering the original mouse press event. self._freezeInputPrecheck() runs afterwards. """ original_mousePressEvent(event) self._freezeInputPrecheck() def _setToEdit(self): """When the text of this QClickEdit object is clicked upon, this function is triggered, which reverts the input field of any other QClickEdit object in the window to flat text with _freezeInputPrecheck(), then displays the input field of this object """ self._freezeInputPrecheck() self._showInputField() def _freezeInputPrecheck(self): """Runs _showText() for any input widgets not under mouse""" for ClickEdit_widget in self._registry: if (ClickEdit_widget.input_widget.isVisible()) and \ (not ClickEdit_widget.input_widget.underMouse()): ClickEdit_widget._showText() def _createTextWidget(self): """Creates QPushButton widget that will display the current value as text when self.input_widget is not displayed.""" self.text = QPushButton("-", self) self.text.setStyleSheet("text-align: left; margin: 2") self.text.setFlat(True) text = self._addSuffix(self.getValue()) self.text.setText(text) self.text.clicked.connect(self._setToEdit) self.layout.addWidget(self.text) def _createInputWidget(self): """Creates the user input widget as determined by the child class, changes this object's focusOutEvent function, then hides the input widget.""" self.input_widget = self.widget_() self.input_widget.focusOutEvent = self.focusOutEvent self.layout.addWidget(self.input_widget) self.input_widget.hide() def _addSuffix(self, current_value): """Adds specified suffix to displayed text""" if self.suffix: text = str(current_value) + ' ' + self.suffix else: text = str(current_value) return text def _showInputField(self): """Hides self.text and displays the input widget""" self.text.hide() self.input_widget.show() def _showText(self): """Hides the input widget and displays self.text""" self.input_widget.hide() self.text.show() ########################################## # Public Functions (modified inherited) ### ########################################## def focusOutEvent(self, event): """Runs the original Qt focusOutEvent function, then runs self._freezeInputPrecheck, which determines the display state of each QClickEdit object in the window. The original focusOutEvent function is executed to ensure that any modifications to it from outside the QClickEdit module will still run. """ super(QClickEdit, self).focusOutEvent(event) self._freezeInputPrecheck()
class main(QWidget): def __init__(self, parent=None): super(main, self).__init__(parent) self.setup() # connections, widgets, layouts, etc. self.blksize = 2**20 # 1 MB; must be divisible by 16 self.ext = '.enc' # extension is appended to encrypted files self.path = '' self.encrypted = [] # to highlight done files in list self.decrypted = [] self.clipboard = QApplication.clipboard() self.timeout = None # to clear message label, see setMessage # this program was just an excuse to play with QprogressBar if not hash(os.urandom(11)) % 11: QTimer().singleShot(50, self.windDown) # various random hints hints = [ 'Freshly encrypted files can be renamed in the table!', 'Clipboard is always cleared on program close!', 'Keys can contain emoji if you <em>really</em> want: \U0001f4e6', 'Keys can contain emoji if you <em>really</em> want: \U0001F511', 'This isn\'t a tip, I just wanted to say hello!', 'Keys can be anywhere from 8 to 4096 characters long!', 'This program was just an excuse to play with the progress bars!', 'Select \'Party\' in the hash button for progress bar fun!', ('Did you know you can donate one or all of your vital organs to ' 'the Aperture Science Self-Esteem Fund for Girls? It\'s true!'), ('It\'s been {:,} days since Half-Life 2: Episode ' 'Two'.format(int((time.time() - 1191988800) / 86400))), 'I\'m version {}!'.format(VERSION), 'I\'m version {}.whatever!'.format(VERSION.split('.')[0]), ('Brought to you by me, I\'m <a href="https://orthallelous.word' 'press.com/">Orthallelous!</a>'), #'Brought to you by me, I\'m Htom Sirveaux!', 'I wonder if there\'s beer on the sun', 'Raspberry World: For all your raspberry needs. Off the beltline', #'I\'ve plummented to my death and I can\'t get up', '<em>NOT</em> compatible with the older version!', ('Hello there, fellow space travellers! Until somebody gives me ' 'some new lines in KAS, that is all I can say. - Bentusi Exchange' ) ] if not hash(os.urandom(9)) % 4: self.extraLabel.setText(random.choice(hints)) def genKey(self): "generate a random key" n = self.keySizeSB.value() char = string.printable.rstrip() #map(chr, range(256)) while len(char) < n: char += char key = ''.join(random.sample(char, n)) self.keyInput.setText(key) def showKey(self, state=None): "hide/show key characters" if state is None: state = bool(self.showKeyCB.checkState()) else: state = bool(state) if state: self.keyInput.setEchoMode(QLineEdit.Normal) else: self.keyInput.setEchoMode(QLineEdit.PasswordEchoOnEdit) def getFolder(self): "open file dialog and fill file table" path = QFileDialog(directory=self.path).getExistingDirectory() if not path: return self.path = str(path) self.populateTable(self.path) self.encrypted, self.decrypted = [], [] return def resizeEvent(self, event): self.showFolder(self.path) # update how the folder is shown def splitterChanged(self, pos): self.showFolder(self.path) # likewise def showFolder(self, path): "displays current path, truncating as needed" if not path: return ell, sl = '\u2026', os.path.sep # ellipsis, slash chars lfg, rfg = Qt.ElideLeft, Qt.ElideRight lst, wdh = os.path.basename(path), self.folderLabel.width() path = path.replace(os.path.altsep or '\\', sl) self.folderLabel.setToolTip(path) # truncate folder location fnt = QFontMetrics(self.folderLabel.font()) txt = str(fnt.elidedText(path, lfg, wdh)) if len(txt) <= 1: # label is way too short self.folderLabel.setText('\u22ee' if txt != sl else txt) return # but when would this happen? # truncate some more (don't show part of a folder name) if len(txt) < len(path) and txt[1] != sl: txt = ell + sl + txt.split(sl, 1)[-1] # don't truncate remaining folder name from the left if txt[2:] != lst and len(txt[2:]) < len(lst) + 2: txt = str(fnt.elidedText(ell + sl + lst, rfg, wdh)) # you'd think len(txt) < len(lst) would work, but no; you'd be wrong self.folderLabel.setText(txt) def populateTable(self, path): "fill file table with file names" self.showFolder(path) names = [] for n in os.listdir(path): if os.path.isdir(os.path.join(path, n)): continue # folder names.append(n) self.folderTable.clearContents() self.folderTable.setRowCount(len(names)) self.folderTable.setColumnCount(1) if not names: # no files in this folder, inform user self.setMessage('This folder has no files') return self.folderTable.blockSignals(True) selEnab = Qt.ItemIsSelectable | Qt.ItemIsEnabled for i, n in enumerate(names): item = QTableWidgetItem() item.setText(n) item.setToolTip(n) item.setFlags(selEnab) # color code encrypted/decrypted files if n in self.encrypted: item.setTextColor(QColor(211, 70, 0)) # allowed encrypted filenames to be changed item.setFlags(selEnab | Qt.ItemIsEditable) if n in self.decrypted: item.setForeground(QColor(0, 170, 255)) self.folderTable.setItem(i, 0, item) if len(names) > 5: self.setMessage('{:,} files'.format(len(names)), 7) self.folderTable.blockSignals(False) return def editFileName(self, item): "change file name" new, old = str(item.text()), str(item.toolTip()) result = QMessageBox.question( self, 'Renaming?', ("<p align='center'>Do you wish to rename<br>" + '<span style="color:#d34600;">{}</span>'.format(old) + "<br>to<br>" + '<span style="color:#ef4b00;">{}</span>'.format(new) + '<br>?</p>')) self.folderTable.blockSignals(True) if any(i in new for i in '/?<>:*|"^'): self.setMessage('Invalid character in name', 7) item.setText(old) elif result == QMessageBox.Yes: oold = os.path.join(self.path, old) try: os.rename(oold, os.path.join(self.path, new)) self.encrypted.remove(old) self.encrypted.append(new) item.setToolTip(new) except Exception as err: self.setMessage(str(err), 9) item.setText(old) item.setToolTip(old) self.encrypted.remove(new) self.encrypted.append(old) else: item.setText(old) self.folderTable.blockSignals(False) def setMessage(self, message, secs=4, col=None): "show a message for a few seconds - col must be rgb triplet tuple" if self.timeout: # https://stackoverflow.com/a/21081371 self.timeout.stop() self.timeout.deleteLater() if col is None: color = 'rgb(255, 170, 127)' else: try: color = 'rgb({}, {}, {})'.format(*col) except: color = 'rgb(255, 170, 127)' self.messageLabel.setStyleSheet('background-color: {};'.format(color)) self.messageLabel.setText(message) self.messageLabel.setToolTip(message) self.timeout = QTimer() self.timeout.timeout.connect(self.clearMessage) self.timeout.setSingleShot(True) self.timeout.start(secs * 1000) def clearMessage(self): self.messageLabel.setStyleSheet('') self.messageLabel.setToolTip('') self.messageLabel.setText('') def getName(self): "return file name of selected" items = self.folderTable.selectedItems() names = [str(i.text()) for i in items] if names: return names[0] # only the first selected file else: return '' def showKeyLen(self, string): "displays a tooltip showing length of key" s = len(string) note = '{:,} character{}'.format(s, '' if s == 1 else 's') tip = QToolTip pos = self.genKeyButton.mapToGlobal(QPoint(0, 0)) if s < self.minKeyLen: note = '<span style="color:#c80000;">{}</span>'.format(note) else: note = '<span style="color:#258f22;">{}</span>'.format(note) tip.showText(pos, note) def lock(self, flag=True): "locks buttons if True" stuff = [ self.openButton, self.encryptButton, self.decryptButton, self.genKeyButton, self.hashButton, self.showKeyCB, self.copyButton, self.keyInput, self.keySizeSB, self.folderTable, ] for i in stuff: i.blockSignals(flag) i.setEnabled(not flag) return def _lerp(self, v1, v2, numPts=10): "linearly interpolate from v1 to v2\nFrom Orthallelous" if len(v1) != len(v2): raise ValueError("different dimensions") D, V, n = [], [], abs(numPts) for i, u in enumerate(v1): D.append(v2[i] - u) for i in range(n + 1): vn = [] for j, u in enumerate(v1): vn.append(u + D[j] / float(n + 2) * i) V.append(tuple(vn)) return V def weeeeeee(self): "party time" self.lock() self.setMessage('Party time!', 2.5) a, b, c = self.encryptPbar, self.decryptPbar, self.hashPbar process, sleep = app.processEvents, time.sleep am, bm, cm = a.minimum(), b.minimum(), c.minimum() ax, bx, cx = a.maximum(), b.maximum(), c.maximum() a.reset() b.reset() c.reset() loops = self._lerp((am, bm, cm), (ax, bx, cx), 100) ivops = loops[::-1] # up and up! for i in range(3): for j, k, l in loops: a.setValue(int(j)) b.setValue(int(k)) c.setValue(int(l)) process() sleep(0.01) a.setValue(ax) b.setValue(bx) c.setValue(cx) sleep(0.25) a.setValue(am) b.setValue(bm) c.setValue(cm) # snake! self.setMessage('Snake time!') self.messageLabel.setStyleSheet('background-color: rgb(127,170,255);') for i in range(2): for j, k, l in loops: a.setValue(int(j)) process() sleep(0.002) process() a.setInvertedAppearance(True) process() for j, k, l in ivops: a.setValue(int(j)) process() sleep(0.002) for j, k, l in loops: b.setValue(int(k)) process() sleep(0.002) process() b.setInvertedAppearance(False) process() for j, k, l in ivops: b.setValue(int(k)) process() sleep(0.002) for j, k, l in loops: c.setValue(int(l)) process() sleep(0.002) process() c.setInvertedAppearance(True) process() for j, k, l in ivops: c.setValue(int(l)) process() sleep(0.002) process() b.setInvertedAppearance(True) process() for j, k, l in loops: b.setValue(int(k)) process() sleep(0.002) process() b.setInvertedAppearance(False) process() for j, k, l in ivops: b.setValue(int(k)) process() sleep(0.002) process() a.setInvertedAppearance(False) b.setInvertedAppearance(True) c.setInvertedAppearance(False) for j, k, l in loops: a.setValue(int(j)) process() sleep(0.002) process() a.setInvertedAppearance(True) process() for j, k, l in ivops: a.setValue(int(j)) process() sleep(0.002) # bars sleep(0.5) self.setMessage('Bars!') process() self.messageLabel.setStyleSheet('background-color: rgb(127,255,170);') for i in range(2): a.setValue(ax) time.sleep(0.65) a.setValue(am) sleep(0.25) process() b.setValue(bx) time.sleep(0.65) b.setValue(bm) sleep(0.25) process() c.setValue(cx) time.sleep(0.65) c.setValue(cm) sleep(0.25) process() b.setValue(bx) time.sleep(0.65) b.setValue(bm) sleep(0.25) process() # okay, enough process() a.setValue(ax) b.setValue(bx) c.setValue(cx) #a.setValue(am); b.setValue(bm); c.setValue(cm) a.setInvertedAppearance(False) b.setInvertedAppearance(True) c.setInvertedAppearance(False) self.lock(False) return def windDown(self, note=None): "silly deload on load" if note is None: note = 'Loading...' self.lock() self.setMessage(note) self.messageLabel.setStyleSheet('background-color: rgb(9, 190, 130);') a, b, c = self.encryptPbar, self.decryptPbar, self.hashPbar am, bm, cm = a.minimum(), b.minimum(), c.minimum() ax, bx, cx = a.maximum(), b.maximum(), c.maximum() a.reset() b.reset() c.reset() loops = self._lerp((ax, bx, cx), (am, bm, cm), 100) for j, k, l in loops: a.setValue(int(j)) b.setValue(int(k)) c.setValue(int(l)) app.processEvents() time.sleep(0.02) a.reset() b.reset() c.reset() self.lock(False) self.clearMessage() def genHash(self, action): "generate hash of selected file and display it" name, t0 = self.getName(), time.perf_counter() # mark what hash was used in the drop-down menu for i in self.hashButton.menu().actions(): if i == action: i.setIconVisibleInMenu(True) else: i.setIconVisibleInMenu(False) if str(action.text()) == 'Party': self.weeeeeee() self.windDown('Winding down...') return if not name: self.setMessage('No file selected') return if not os.path.exists(os.path.join(self.path, name)): self.setMessage('File does not exist') return self.lock() hsh = self.hashFile(os.path.join(self.path, name), getattr(hashlib, str(action.text()))) self.lock(False) #hsh = str(action.text()) + ': ' + hsh self.hashLabel.setText(hsh) self.hashLabel.setToolTip(hsh) self.extraLabel.setText( str(action.text()) + ' hash took ' + self.secs_fmt(time.perf_counter() - t0)) def setCancel(self): "cancel operation" self._requestStop = True def showCancelButton(self, state=False): "show/hide cancel button" self.cancelButton.blockSignals(not state) self.cancelButton.setEnabled(state) if state: self.cancelButton.show() self.keyInput.hide() self.genKeyButton.hide() self.keySizeSB.hide() else: self.cancelButton.hide() self.keyInput.show() self.genKeyButton.show() self.keySizeSB.show() def hashFile(self, fn, hasher): "returns the hash value of a file" hsh, blksize = hasher(), self.blksize fsz, csz = os.path.getsize(fn), 0.0 self.hashPbar.reset() self.showCancelButton(True) prog, title = '(# {:.02%}) {}', self.windowTitle() with open(fn, 'rb') as f: while 1: blk = f.read(blksize) if not blk: break hsh.update(blk) csz += blksize self.hashPbar.setValue(int(round(csz * 100.0 / fsz))) app.processEvents() self.setWindowTitle(prog.format(csz / fsz, title)) if self._requestStop: break self.hashPbar.setValue(self.hashPbar.maximum()) self.setWindowTitle(title) self.showCancelButton(False) if self._requestStop: self.setMessage('Hashing canceled!') self.hashPbar.setValue(self.hashPbar.minimum()) self._requestStop = False return return hsh.hexdigest() def hashKey(self, key, salt=b''): "hashes a key for encrypting/decrypting file" salt = salt.encode() if type(salt) != bytes else salt key = key.encode() if type(key) != bytes else key p = app.processEvents self.setMessage('Key Hashing...', col=(226, 182, 249)) p() key = hashlib.pbkdf2_hmac('sha512', key, salt, 444401) p() self.clearMessage() p() return hashlib.sha3_256(key).digest() # AES requires a 32 char key def encrypt(self): "encrypt selected file with key" name, t0 = self.getName(), time.perf_counter() if not name: self.setMessage('No file selected') return if not os.path.exists(os.path.join(self.path, name)): self.setMessage('File does not exist') return key = str(self.keyInput.text()) if len(key) < self.minKeyLen: self.setMessage(('Key must be at least ' '{} characters long').format(self.minKeyLen)) return self.lock() gn = self.encryptFile(key, os.path.join(self.path, name)) if not gn: self.lock(False) return self.encrypted.append(os.path.basename(gn)) self.lock(False) self.populateTable(self.path) # repopulate folder list bn, tt = os.path.basename(gn), time.perf_counter() - t0 self.setMessage('Encrypted, saved "{}"'.format(bn, 13)) self.extraLabel.setText('Encrypting took ' + self.secs_fmt(tt)) def encryptFile(self, key, fn): "encrypts a file using AES (MODE_GCM)" chars = ''.join(map(chr, range(256))).encode() chk = AES.block_size sample = random.sample iv = bytes(sample(chars, chk * 2)) salt = bytes(sample(chars * 2, 256)) vault = AES.new(self.hashKey(key, salt), AES.MODE_GCM, iv) fsz = os.path.getsize(fn) del key blksize = self.blksize gn = fn + self.ext fne = os.path.basename(fn).encode() fnz = len(fne) if len(fne) % chk: fne += bytes(sample(chars, chk - len(fne) % chk)) csz = 0.0 # current processed value self.encryptPbar.reset() prog, title = '({:.02%}) {}', self.windowTitle() self.showCancelButton(True) with open(fn, 'rb') as src, open(gn, 'wb') as dst: dst.write(bytes([0] * 16)) # spacer for MAC written at end dst.write(iv) dst.write(salt) # store iv, salt # is it safe to store MAC, iv, salt plain right in file? # can't really store them encrypted, # or elsewhere in this model of single file encryption? # can't have another file for the file to lug around # store file size, file name length dst.write(vault.encrypt(struct.pack('<2Q', fsz, fnz))) dst.write(vault.encrypt(fne)) # store filename while 1: dat = src.read(blksize) if not dat: break elif len(dat) % chk: # add padding fil = chk - len(dat) % chk dat += bytes(sample(chars, fil)) dst.write(vault.encrypt(dat)) csz += blksize # show progress self.encryptPbar.setValue(int(round(csz * 100.0 / fsz))) self.setWindowTitle(prog.format(csz / fsz, title)) app.processEvents() if self._requestStop: break if not self._requestStop: stuf = random.randrange(23) # pack in more stuffing fing = b''.join(bytes(sample(chars, 16)) for i in range(stuf)) dst.write(vault.encrypt(fing)) # and for annoyance dst.seek(0) dst.write(vault.digest()) # write MAC self.hashLabel.setText('MAC: ' + vault.hexdigest()) self.encryptPbar.setValue(self.encryptPbar.maximum()) self.setWindowTitle(title) self.showCancelButton(False) if self._requestStop: self.setMessage('Encryption canceled!') self.encryptPbar.setValue(self.encryptPbar.minimum()) self._requestStop = False os.remove(gn) return return gn def decrypt(self): "encrypt selected file with key" name, t0 = self.getName(), time.perf_counter() if not name: self.setMessage('No file selected') return if not os.path.exists(os.path.join(self.path, name)): self.setMessage('File does not exist') return key = str(self.keyInput.text()) if len(key) < self.minKeyLen: self.setMessage(('Key must be at least ' '{} characters long').format(self.minKeyLen)) return self.lock() gn = self.decryptFile(key, os.path.join(self.path, name)) if not gn: self.lock(False) return self.decrypted.append(os.path.basename(gn)) self.lock(False) self.populateTable(self.path) # repopulate folder list bn, tt = os.path.basename(gn), time.perf_counter() - t0 self.setMessage('Decrypted, saved "{}"'.format(bn, 13)) self.extraLabel.setText('Decrypting took ' + self.secs_fmt(tt)) def decryptFile(self, key, fn): "decrypts a file using AES (MODE_GCM)" blksize = self.blksize gn = hashlib.md5(os.path.basename(fn).encode()).hexdigest() gn = os.path.join(self.path, gn) # temporary name if os.path.exists(gn): self.setMessage('file already exists') return self.decryptPbar.reset() csz = 0.0 # current processed value chk, fnsz = AES.block_size, os.path.getsize(fn) prog, title = '({:.02%}) {}', self.windowTitle() try: with open(fn, 'rb') as src, open(gn, 'wb') as dst: # extract iv, salt MAC = src.read(16) iv = src.read(AES.block_size * 2) salt = src.read(256) vault = AES.new(self.hashKey(key, salt), AES.MODE_GCM, iv) self.showCancelButton(True) # extract file size, file name length sizes = src.read(struct.calcsize('<2Q')) fsz, fnz = struct.unpack('<2Q', vault.decrypt(sizes)) # extract filename; round up fnz to nearest chk rnz = fnz if not fnz % chk else fnz + chk - fnz % chk rfn = vault.decrypt(src.read(rnz))[:fnz].decode() self.setMessage('Found "{}"'.format(rfn), 13, (255, 211, 127)) while 1: dat = src.read(blksize) if not dat: break dst.write(vault.decrypt(dat)) csz += blksize # show progress self.decryptPbar.setValue(int(round(csz * 100.0 / fnsz))) self.setWindowTitle(prog.format(1 - (csz / fnsz), title)) app.processEvents() if self._requestStop: break if not self._requestStop: dst.truncate(fsz) # remove padding if not self._requestStop: vault.verify(MAC) self.hashLabel.setText('') except (ValueError, KeyError) as err: os.remove(gn) self.setMessage('Invalid decryption!') self.setWindowTitle(title) self.showCancelButton(False) return except Exception as err: os.remove(gn) self.setMessage('Invalid key or file!') self.setWindowTitle(title) self.showCancelButton(False) return self.decryptPbar.setValue(self.decryptPbar.maximum()) self.setWindowTitle(title) self.showCancelButton(False) if self._requestStop: self.setMessage('Decryption canceled!') self.decryptPbar.setValue(self.decryptPbar.minimum()) self._requestStop = False os.remove(gn) return # restore original file name name, ext = os.path.splitext(rfn) count = 1 fn = os.path.join(self.path, name + ext) while os.path.exists(fn): fn = os.path.join(self.path, name + '_{}'.format(count) + ext) count += 1 os.rename(gn, fn) # restore original name return fn # saved name def copyKeyHash(self, action): "copies either the key or the hash to clipboard" act = str(action.text()).lower() if 'key' in act: txt = str(self.keyInput.text()) elif 'hash' in act: txt = str(self.hashLabel.text()) else: self.setMessage('Invalid copy selection') return if not txt: self.setMessage('Empty text; Nothing to copy') return if 'key' in act: self.setMessage('Key copied to clipboard') elif 'hash' in act: self.setMessage('Hash copied to clipboard') else: self.setMessage('Invalid copy selection') return self.clipboard.clear() self.clipboard.setText(txt) def secs_fmt(self, s): "6357 -> '1h 45m 57s'" Y, D, H, M = 31556952, 86400, 3600, 60 y = int(s // Y) s -= y * Y d = int(s // D) s -= d * D h = int(s // H) s -= h * H m = int(s // M) s -= m * M r = (str(int(s)) if int(s) == s else str(round(s, 3))) + 's' if m: r = str(m) + 'm ' + r if h: r = str(h) + 'h ' + r if d: r = str(d) + 'd ' + r if y: r = str(y) + 'y ' + r return r.strip() def closeEvent(self, event): self.clipboard.clear() def setup(self): "constructs the gui" Fixed = QSizePolicy() MinimumExpanding = QSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding) self.minKeyLen = 8 self.maxKeyLen = 4096 self.splitter = QSplitter(self) self.splitter.setOrientation(Qt.Horizontal) self.splitter.splitterMoved.connect(self.splitterChanged) # left column self.leftColumn = QWidget() self.vl01 = QVBoxLayout() # left column - first item (0; horizonal layout 0) self.hl00 = QHBoxLayout() self.hl00.setSpacing(5) self.openButton = QPushButton('&Open') self.openButton.setToolTip('Open folder') self.openButton.setMinimumSize(60, 20) self.openButton.setMaximumSize(60, 20) self.openButton.setSizePolicy(Fixed) self.openButton.clicked.connect(self.getFolder) #ico = self.style().standardIcon(QStyle.SP_DirIcon) #self.openButton.setIcon(ico) self.folderLabel = QLabel() self.folderLabel.setMinimumSize(135, 20) self.folderLabel.setMaximumSize(16777215, 20) self.folderLabel.setSizePolicy(MinimumExpanding) self.hl00.insertWidget(0, self.openButton) self.hl00.insertWidget(1, self.folderLabel) # left column - second item (1) self.folderTable = QTableWidget() self.folderTable.setMinimumSize(200, 32) self.folderTable.horizontalHeader().setVisible(False) self.folderTable.horizontalHeader().setStretchLastSection(True) self.folderTable.verticalHeader().setVisible(False) self.folderTable.verticalHeader().setDefaultSectionSize(15) self.folderTable.itemChanged.connect(self.editFileName) # left column - third item (2) self.extraLabel = QLabel() self.extraLabel.setMinimumSize(200, 20) self.extraLabel.setMaximumSize(16777215, 20) self.extraLabel.setSizePolicy(MinimumExpanding) self.extraLabel.setTextInteractionFlags(Qt.LinksAccessibleByMouse) # finalize left column self.vl01.insertLayout(0, self.hl00) self.vl01.insertWidget(1, self.folderTable) self.vl01.insertWidget(2, self.extraLabel) self.leftColumn.setLayout(self.vl01) # right column self.rightColumn = QWidget() self.vl02 = QVBoxLayout() # right column - first item (0) self.messageLabel = QLabel() self.messageLabel.setMinimumSize(290, 20) self.messageLabel.setMaximumSize(16777215, 20) self.messageLabel.setSizePolicy(MinimumExpanding) self.messageLabel.setAlignment(Qt.AlignCenter) # right column - second item (2; horizontal layout 1) self.hl01 = QHBoxLayout() self.hl01.setSpacing(5) self.encryptButton = QPushButton('&Encrypt') #\U0001F512 self.encryptButton.setToolTip('Encrypt selected file') self.encryptButton.setMinimumSize(60, 20) self.encryptButton.setMaximumSize(60, 20) self.encryptButton.setSizePolicy(Fixed) self.encryptButton.clicked.connect(self.encrypt) self.encryptPbar = QProgressBar() self.encryptPbar.setMinimumSize(225, 20) self.encryptPbar.setMaximumSize(16777215, 20) self.encryptPbar.setSizePolicy(MinimumExpanding) self.encryptPbar.setTextVisible(False) palette = self.encryptPbar.palette() # color of progress bar color = QColor(211, 70, 0) palette.setColor(QPalette.Highlight, color) self.encryptPbar.setPalette(palette) self.hl01.insertWidget(0, self.encryptButton) self.hl01.insertWidget(1, self.encryptPbar) # right column - third item (3; horizontal layout 2) self.hl02 = QHBoxLayout() self.hl02.setSpacing(5) self.cancelButton = QPushButton('C&ANCEL') self.cancelButton.setToolTip('Cancels current operation') self.cancelButton.setMinimumSize(70, 24) self.cancelButton.setMaximumSize(70, 24) self.cancelButton.setSizePolicy(Fixed) self.cancelButton.clicked.connect(self.setCancel) font = self.cancelButton.font() font.setBold(True) self.cancelButton.setFont(font) self.cancelButton.blockSignals(True) self.cancelButton.setEnabled(False) self.cancelButton.hide() self._requestStop = False self.keyInput = QLineEdit() self.keyInput.setMinimumSize(225, 20) self.keyInput.setMaximumSize(16777215, 20) self.keyInput.setSizePolicy(MinimumExpanding) self.keyInput.setPlaceholderText('key') self.keyInput.setMaxLength(self.maxKeyLen) self.keyInput.setAlignment(Qt.AlignCenter) self.keyInput.textEdited.connect(self.showKeyLen) self.genKeyButton = QPushButton('&Gen Key') #\U0001F511 self.genKeyButton.setToolTip('Generate a random key') self.genKeyButton.setMinimumSize(60, 20) self.genKeyButton.setMaximumSize(60, 20) self.genKeyButton.setSizePolicy(Fixed) self.genKeyButton.clicked.connect(self.genKey) self.keySizeSB = QSpinBox() self.keySizeSB.setToolTip('Length of key to generate') self.keySizeSB.setRange(32, 1024) self.keySizeSB.setMinimumSize(40, 20) self.keySizeSB.setMaximumSize(40, 20) self.keySizeSB.setSizePolicy(Fixed) self.keySizeSB.setAlignment(Qt.AlignCenter) self.keySizeSB.setButtonSymbols(QSpinBox.NoButtons) self.keySizeSB.setWrapping(True) self.hl02.insertWidget(0, self.cancelButton) self.hl02.insertWidget(1, self.keyInput) self.hl02.insertWidget(2, self.genKeyButton) self.hl02.insertWidget(3, self.keySizeSB) # right column - fourth item (4; horizontal layout 3) self.hl03 = QHBoxLayout() self.hl03.setSpacing(5) self.decryptButton = QPushButton('&Decrypt') #\U0001F513 self.decryptButton.setToolTip('Decrypt selected file') self.decryptButton.setMinimumSize(60, 20) self.decryptButton.setMaximumSize(60, 20) self.decryptButton.setSizePolicy(Fixed) self.decryptButton.clicked.connect(self.decrypt) self.decryptPbar = QProgressBar() self.decryptPbar.setMinimumSize(225, 20) self.decryptPbar.setMaximumSize(16777215, 20) self.decryptPbar.setSizePolicy(MinimumExpanding) self.decryptPbar.setTextVisible(False) self.decryptPbar.setInvertedAppearance(True) palette = self.decryptPbar.palette() # color of progress bar color = QColor(0, 170, 255) palette.setColor(QPalette.Highlight, color) self.decryptPbar.setPalette(palette) self.hl03.insertWidget(0, self.decryptButton) self.hl03.insertWidget(1, self.decryptPbar) # right column - fifth item (7; horizontal layout 4) self.hl04 = QHBoxLayout() self.hl04.setSpacing(5) self.showKeyCB = QCheckBox('&Show Key') self.showKeyCB.setToolTip('Show/Hide key value') self.showKeyCB.setMinimumSize(75, 20) self.showKeyCB.setMaximumSize(75, 20) self.showKeyCB.setSizePolicy(Fixed) self.showKeyCB.clicked.connect(self.showKey) self.showKeyCB.setChecked(True) self.hashPbar = QProgressBar() self.hashPbar.setMinimumSize(150, 20) self.hashPbar.setMaximumSize(16777215, 20) self.hashPbar.setSizePolicy(MinimumExpanding) self.hashPbar.setTextVisible(False) palette = self.hashPbar.palette() # color of progress bar color = QColor(31, 120, 73) palette.setColor(QPalette.Highlight, color) self.hashPbar.setPalette(palette) self.hashButton = QPushButton('&Hash') self.hashButton.setToolTip('Determine file hash') self.hashButton.setMinimumSize(60, 20) self.hashButton.setMaximumSize(60, 20) self.hashButton.setSizePolicy(Fixed) menu = QMenu(self.hashButton) ico = self.style().standardIcon(QStyle.SP_DialogYesButton) for alg in sorted( filter(lambda x: 'shake' not in x, hashlib.algorithms_guaranteed), key=lambda n: (len(n), sorted(hashlib.algorithms_guaranteed).index(n))): menu.addAction( ico, alg ) # drop shake algs as their .hexdigest requires an argument - the rest don't menu.addAction(ico, 'Party') for i in menu.actions(): i.setIconVisibleInMenu(False) self.hashButton.setMenu(menu) menu.triggered.connect(self.genHash) self.hl04.insertWidget(0, self.showKeyCB) self.hl04.insertWidget(1, self.hashPbar) self.hl04.insertWidget(2, self.hashButton) # right column - sixth item (8; horizontal layout 5) self.hl05 = QHBoxLayout() self.hl05.setSpacing(5) self.copyButton = QPushButton('&Copy') #\U0001F4CB self.copyButton.setToolTip('Copy key or hash to clipboard') self.copyButton.setMinimumSize(60, 20) self.copyButton.setMaximumSize(60, 20) self.copyButton.setSizePolicy(Fixed) menu2 = QMenu(self.copyButton) menu2.addAction('Copy Key') menu2.addAction('Copy Hash') self.copyButton.setMenu(menu2) menu2.triggered.connect(self.copyKeyHash) self.hashLabel = QLabel() self.hashLabel.setMinimumSize(225, 20) self.hashLabel.setMaximumSize(16777215, 20) self.hashLabel.setSizePolicy(MinimumExpanding) self.hashLabel.setTextFormat(Qt.PlainText) self.hashLabel.setAlignment(Qt.AlignCenter) self.hashLabel.setTextInteractionFlags(Qt.TextSelectableByMouse) self.hl05.insertWidget(0, self.copyButton) self.hl05.insertWidget(1, self.hashLabel) # finalize right column self.vl02.insertWidget(0, self.messageLabel) self.vl02.insertSpacerItem(1, QSpacerItem(0, 0)) self.vl02.insertLayout(2, self.hl01) self.vl02.insertLayout(3, self.hl02) self.vl02.insertLayout(4, self.hl03) self.vl02.insertSpacerItem(5, QSpacerItem(0, 0)) self.vl02.insertWidget(6, QFrame()) self.vl02.insertLayout(7, self.hl04) self.vl02.insertLayout(8, self.hl05) self.rightColumn.setLayout(self.vl02) # finalize main window self.splitter.insertWidget(0, self.leftColumn) self.splitter.insertWidget(1, self.rightColumn) layout = QHBoxLayout(self) layout.addWidget(self.splitter) self.setLayout(layout) self.setWindowTitle('Simple File Encryptor/Decryptor') self.resize(self.sizeHint())
class UpdateDialog(QDialog): def __init__(self, *args, **kwargs): super(UpdateDialog, self).__init__(*args, **kwargs) self.setAttribute(Qt.WA_DeleteOnClose) self.setWindowTitle("版本检查") self.setFixedSize(240, 120) layout = QVBoxLayout() self.current_version_message = QLabel("当前版本:", self) self.current_version_message.setAlignment(Qt.AlignCenter) layout.addWidget(self.current_version_message) self.last_version_message = QLabel("最新版本:检查中...", self) self.last_version_message.setAlignment(Qt.AlignCenter) layout.addWidget(self.last_version_message) # 启动更新的信息提示 self.run_message = QLabel("", self) self.run_message.setWordWrap(True) self.run_message.hide() self.run_message.setAlignment(Qt.AlignCenter) self.run_message.setStyleSheet("color:rgb(200,50,50)") layout.addWidget(self.run_message) opts_layout = QHBoxLayout() self.update_button = QPushButton("立即更新") self.update_button.clicked.connect(self.exit_for_updating) self.close_button = QPushButton("下次更新") self.close_button.clicked.connect(self.close) opts_layout.addWidget(self.update_button) opts_layout.addWidget(self.close_button) layout.addLayout(opts_layout) self.update_button.hide() self.setLayout(layout) # 检测最新版本 app = QApplication.instance() self.network_manager = getattr(app, "_network") self._get_last_version() def _get_last_version(self): """ 获取最新版本号 """ # 获取当前版本号 json_file = os.path.join(BASE_DIR, "classini/update_{}.json".format(SYS_BIT)) if not os.path.exists(json_file): self.current_version_message.setText("当前版本:检测失败.") self.last_version_message.setText("最新版本:检测失败.") self.close_button.setText("关闭") return with open(json_file, "r", encoding="utf-8") as jf: update_json = json.load(jf) self.current_version_message.setText("当前版本:{}".format(update_json["VERSION"])) url = SERVER + "check_version/?version={}&sys_bit={}".format(update_json["VERSION"], SYS_BIT) request = QNetworkRequest(url=QUrl(url)) reply = self.network_manager.get(request) reply.finished.connect(self.last_version_back) def last_version_back(self): """ 检测版本结果 """ reply = self.sender() if reply.error(): reply.deleteLater() self.last_version_message.setText("最新版本:检测失败.") return data = reply.readAll().data() u_data = json.loads(data.decode("utf-8")) if u_data["update_needed"]: for_update_file = os.path.join(BASE_DIR, "classini/for_update_{}.json".format(SYS_BIT)) # 写入待更新信息 f_data = { "VERSION": u_data["last_version"], "SERVER": u_data["file_server"], "FILES": u_data["update_files"] } with open(for_update_file, "w", encoding="utf-8") as f: json.dump(f_data, f, indent=4, ensure_ascii=False) self.update_button.show() else: self.update_button.hide() self.close_button.setText("关闭") self.last_version_message.setText("最新版本:{}".format(u_data["last_version"])) reply.deleteLater() def exit_for_updating(self): """ 退出当前程序,启动更新更新 """ script_file = os.path.join(BASE_DIR, "AutoUpdate.exe") if SYS_BIT == "admin": script_file = os.path.join(BASE_DIR, "AutoAdminUpdate.exe") is_close = True if os.path.exists(script_file): try: Popen(script_file, shell=False) except OSError as e: self.run_message.setText(str(e)) is_close = False else: self.run_message.setText("更新程序丢失...") is_close = False self.run_message.show() if is_close: sys.exit() def closeEvent(self, event): """ 下次更新 """ for_update_file = os.path.join(BASE_DIR, "classini/for_update_{}.json".format(SYS_BIT)) if os.path.exists(for_update_file): os.remove(for_update_file) super(UpdateDialog, self).closeEvent(event)
class RootWindow(QWidget): """Root window for Enigma Qt GUI""" def __init__(self, enigma_api, cursor_handler): """Initializes Root QT window widgets :param enigma_api: {EnigmaAPI} Shared EnigmaAPI object """ super().__init__() # QT WINDOW SETTINGS ================================================== self.setWindowIcon(QIcon(BASE_DIR + "enigma_200px.png")) main_layout = QVBoxLayout(self) self.setLayout(main_layout) # SAVE ATTRIBUTES ===================================================== self.__enigma_api = enigma_api logging.info("Qt GUI initialized with EnigmaAPI settings:\n%s", str(enigma_api)) # MENU BAR ============================================================ menu = QMenuBar(self) save_load_menu = menu.addMenu("Save/load settings") save_load_menu.addAction("Save settings", self.save_config) save_load_menu.addAction("Load settings", self.load_config) menu.addAction("Export message", self.export_message) # ROTORS INDICATOR ==================================================== logging.info("Generating rotor and reflector indicators...") self.__rotors = _RotorsHandlerWidget( self, self.__enigma_api.positions, self.__enigma_api.generate_rotate_callback, self.__enigma_api.rotate_reflector, enigma_api, self.refresh_gui, enigma_api.reflector_position, ) # LIGHTBOARD FRAME ==================================================== logging.info("Adding Lightboard...") self.__lightboard = _LightboardWidget(self, self.__enigma_api.charset) # INPUT OUTPUT FOR ENCRYPTION/DECRYPTION ============================== logging.info("Adding I/O textboxes...") self.__output_textbox = _OutputTextBoxWidget( self, self.__lightboard.light_up, enigma_api.letter_group) self.__input_textbox = _InputTextBoxWidget( self, enigma_api.encrypt, self.__output_textbox.insert, self.__output_textbox, self.__rotors.set_positions, enigma_api.letter_group, enigma_api.revert_by, enigma_api.buffer_full, enigma_api.data()["charset"], cursor_handler) # PLUGBOARD BUTTONS =================================================== logging.info("Adding Plugboard button") self.__plug_button = QPushButton("Plugboard") self.__plug_button.setToolTip("Edit plugboard letter pairs") self.__plug_button.clicked.connect(self.__open_plugboard_window) # SHOW WIDGETS ======================================================== logging.info("Showing all widgets...") main_layout.addWidget(menu, alignment=Qt.AlignTop) main_layout.addWidget(self.__rotors, alignment=Qt.AlignBottom) main_layout.addWidget(self.__lightboard) main_layout.addWidget( QLabel("INPUT", self, styleSheet="font-size: 20px"), alignment=Qt.AlignCenter, ) main_layout.addWidget(self.__input_textbox) main_layout.addWidget( QLabel("OUTPUT", self, styleSheet="font-size: 20px"), alignment=Qt.AlignCenter, ) main_layout.addWidget(self.__output_textbox) main_layout.addWidget(self.__plug_button) self.refresh_gui() self.show() def __open_plugboard_window(self): """Opens the plugboard menu""" logging.info("Opening Plugboard menu...") old_pairs = self.__enigma_api.plug_pairs() plugboard = PlugboardWindow(self, self.__enigma_api) plugboard.exec() logging.info("Closing plugboard...") new_pairs = self.__enigma_api.plug_pairs() if old_pairs != new_pairs: logging.info('New plug pairs set to "%s"', str(new_pairs)) else: logging.info("No changes to plug pairs...") del plugboard def refresh_gui(self): """Refreshes main window GUI based on new EnigmaAPI settings""" logging.info("Refreshing GUI components...") self.setWindowTitle(self.__enigma_api.model()) self.__input_textbox.clear() self.__input_textbox.set_charset(self.__enigma_api.data()["charset"]) if self.__enigma_api.data( )["plugboard"]: # If current model has a plugboard logging.info("Showing Plugboard button...") self.__plug_button.show() else: logging.info("Hiding Plugboard button...") self.__plug_button.hide() # Arrange lightbulbs to new layout self.__lightboard.regenerate_bulbs(self.__enigma_api.data()["layout"]) def load_config(self): """Loads EnigmaAPI settings from a config file and refershes GUI""" dialog = QFileDialog(self) filename = dialog.getOpenFileName(self, "Load settings", QDir.homePath(), "Enigma config (*.json)")[0] if filename: try: self.__enigma_api.load_from(filename) logging.info('Successfully loaded config from file "%s"', filename) except (FileNotFoundError, JSONDecodeError) as error: QMessageBox.critical( self, "Load config", "Error retrieving data from " "selected file!\nError message:\n\n %s" % repr(error), ) logging.error('Failed to load config from file "%s"', filename, exc_info=True) return except Exception as error: QMessageBox.critical( self, "Load config", "Following error occured during " "applying loaded settings:\n%s" % repr(error), ) logging.error( "Unable to load config from file, keeping old settings...", exc_info=True, ) return # Refresh gui after loading setings self.__rotors.generate_rotors() self.__input_textbox.blockSignals(True) self.refresh_gui() self.__input_textbox.blockSignals(False) self.__rotors.set_positions() logging.info('Checkpoint set to "%s"', str(self.__enigma_api.positions())) self.__enigma_api.set_checkpoint() else: logging.info("No load file selected...") def save_config(self): """Collects data from EnigmaAPI and saves it to selected filename""" dialog = QFileDialog(self) dialog.setDefaultSuffix("json") filename = dialog.getSaveFileName(self, "Save settings", QDir.homePath(), "Enigma config (*.json)")[0] # To prevent from saving files without a file extension... if filename and not findall(r"\.json$", filename.lower()): filename += ".json" logging.info( ".json file extension for save file not found, adding...") if filename: self.__enigma_api.save_to(filename) else: logging.info("No save file selected...") def export_message(self): """Opens a dialog to get the save location, exports current Enigma settings and encrypted message to the file""" dialog = QFileDialog(self) filename = dialog.getSaveFileName(self, "Save Enigma message", QDir.homePath(), "*.txt")[0] if filename and not findall(r"\.txt$", filename): filename += ".txt" logging.info( ".txt file extension for save file not found, adding...") if filename: logging.info('Exporing message to "%s"...', filename) with open(filename, "w") as file: message = "\n".join(wrap(self.__output_textbox.text(), 29)) file.write("%s\n%s\n" % (str(self.__enigma_api), message))
class PenSetWidget(QWidget): penSizeTrigger = Signal(int) penColorTrigger = Signal(str) fontChangeTrigger = Signal(QFont) def __init__(self, parent=None): super().__init__(parent) self.paddingX = 5 self.paddingY = 2 self.iconWidth = self.iconHeight = 24 self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed) self.setWindowFlags(Qt.ToolTip) self.initWindows() self.prevSizeButton = self.penSize1 self.penSize1.setChecked(True) self.presentColor.setStyleSheet('QPushButton { background-color: %s; }' % PENCOLOR) def generateButtons(self, parent=None): """ Generate buttons due to colorDic """ self.colorButtons = [] for color in self.colorList: button = QPushButton(parent) button.setObjectName(color[0]) button.setStyleSheet('QPushButton { background-color: %s; }' % color[1]) button.setFixedSize(self.iconWidth / 2, self.iconHeight / 2) button.setCheckable(True) self.colorButtons.append(button) def initWindows(self): self.mainLayout = QHBoxLayout() self.setLayout(self.mainLayout) self.mainLayout.setSpacing(0) self.mainLayout.setContentsMargins(5, 2, 5, 2) self.initPenSizeButtons() self.initFontWidget() self.initPenColorButtons() self.separator = QFrame(self) self.separator.setFrameShape(QFrame.VLine) self.separator.setFrameShadow(QFrame.Sunken) self.mainLayout.addWidget(self.penSize) self.mainLayout.addWidget(self.changeFontButton) self.mainLayout.addWidget(self.separator) self.mainLayout.addWidget(self.colorSet) def initPenSizeButtons(self): self.penSize = QWidget(self) self.penSizeLayout = QHBoxLayout() self.penSize.setLayout(self.penSizeLayout) # adjust pen size self.penSize1 = QPushButton(self.penSize) self.penSize1.setIcon(QIcon(":/resource/icon/pensize1.png")) self.penSize1.setObjectName('1') self.penSize1.setFixedSize(self.iconWidth, self.iconHeight) self.penSize1.setCheckable(True) self.penSize2 = QPushButton(self.penSize) self.penSize2.setIcon(QIcon(":/resource/icon/pensize2.png")) self.penSize2.setObjectName('2') self.penSize2.setFixedSize(self.iconWidth, self.iconHeight) self.penSize2.setCheckable(True) self.penSize3 = QPushButton(self.penSize) self.penSize3.setIcon(QIcon(":/resource/icon/pensize3.png")) self.penSize3.setObjectName('3') self.penSize3.setFixedSize(self.iconWidth, self.iconHeight) self.penSize3.setCheckable(True) self.sizeButtonGroup = QButtonGroup(self.penSize) self.sizeButtonGroup.addButton(self.penSize1) self.sizeButtonGroup.addButton(self.penSize2) self.sizeButtonGroup.addButton(self.penSize3) self.sizeButtonGroup.buttonClicked.connect(self.sizeButtonToggled) self.penSizeLayout.addWidget(self.penSize1) self.penSizeLayout.addWidget(self.penSize2) self.penSizeLayout.addWidget(self.penSize3) self.penSizeLayout.setSpacing(5) self.penSizeLayout.setContentsMargins(0, 0, 0, 0) def initPenColorButtons(self): self.colorSet = QWidget(self) self.colorLayout = QHBoxLayout() self.colorLayout.setSpacing(5) self.colorLayout.setContentsMargins(5, 0, 5, 0) self.colorSet.setLayout(self.colorLayout) self.presentColor = QPushButton(self.colorSet) self.presentColor.setFixedSize(self.iconWidth, self.iconHeight) self.presentColor.setEnabled(False) # adjust pen color self.colorPick = QWidget(self.colorSet) self.colorGrid = QGridLayout() self.colorGrid.setSpacing(0) self.colorGrid.setContentsMargins(5, 0, 5, 0) self.colorPick.setLayout(self.colorGrid) self.colorList = [('white' , '#ffffff'), ('red' , '#ff0000'), ('green' , '#00ff00'), ('blue' , '#0000ff'), ('cyan' , '#00ffff'), ('magenta' , '#ff00ff'), ('yellow' , '#ffff00'), ('gray' , '#a0a0a4'), ('black' , '#000000'), ('darkRed' , '#800000'), ('darkGreen' , '#008000'), ('darkBlue' , '#000080'), ('darkCyan' , '#008080'), ('darkMagenta' , '#800080'), ('darkYellow' , '#808000'), ('darkGray' , '#808080')] self.generateButtons() self.colorButtonGroup = QButtonGroup(self) for button in self.colorButtons: self.colorButtonGroup.addButton(button) self.colorButtonGroup.buttonClicked.connect(self.colorButtonToggled) # set the layout tmp = 0 for x in range(0, 2): for y in range(0, int(len(self.colorList) / 2)): self.colorGrid.addWidget(self.colorButtons[tmp], x, y) tmp += 1 self.colorGrid.setSpacing(0) self.colorGrid.setContentsMargins(0, 0, 0, 0) self.colorLayout.addWidget(self.presentColor) self.colorLayout.addWidget(self.colorPick) def initFontWidget(self): self.fontDialog = QFontDialog() self.changeFontButton = QPushButton(self) self.fontDialog.setCurrentFont(QFont('Sans serif')) self.changeFontButton.setText('{0} {1}'.format(self.fontDialog.currentFont().family(), self.fontDialog.currentFont().pointSize())) self.changeFontButton.clicked.connect(self.fontButtonClicked) def showFontWidget(self): self.changeFontButton.show() self.penSize1.hide() self.penSize2.hide() self.penSize3.hide() def showPenWidget(self): self.changeFontButton.hide() self.penSize1.show() self.penSize2.show() self.penSize3.show() # slots def colorButtonToggled(self, button): self.presentColor.setStyleSheet('QPushButton { background-color: %s; }' % button.objectName()) self.penColorTrigger.emit(button.objectName()) def sizeButtonToggled(self, button): self.penSizeTrigger.emit(int(button.objectName()) * 2) def fontButtonClicked(self): ok = True font = QFontDialog.getFont(self) if font[1]: self.changeFontButton.setText('{0} {1}'.format(font[0].family(), font[0].pointSize())) self.fontChangeTrigger.emit(font[0])