class DirectoryWidget(QToolButton): Sg_double_clicked = Signal(str) def __init__(self): super(DirectoryWidget, self).__init__() self.timer = QTimer() self.timer.setSingleShot(True) self.clicked.connect(self.Sl_check_double_click) self.setAccessibleName('Directory') self.setIcon(QIcon(resource_path("icons/Cartella.png"))) self.setIconSize(QSize(45, 45)) self.setToolButtonStyle(Qt.ToolButtonTextUnderIcon) @Slot() def Sl_check_double_click(self): success = False if self.timer.isActive(): time = self.timer.remainingTime() if time > 0: self.double_clicked_action() success = True self.timer.stop() if time <= 0: self.timer.start(250) if not self.timer.isActive() and not success: self.timer.start(250) def double_clicked_action(self) -> None: pass
class FileWidget(QToolButton): Sg_double_clicked = Signal() def __init__(self, file: File): super(FileWidget, self).__init__() self.timer = QTimer() self.timer.setSingleShot(True) self.clicked.connect(self.check_double_click) self.Sg_double_clicked.connect(self.Sl_on_double_click) self.setAccessibleName('File') self.name = file.get_name() self.creation_date = file.get_creation_date() self.last_modified_date = file.get_last_modified_date() self.extension = self.get_extension() self.set_icon() self.setText(self.name) def get_extension(self) -> str: if self.name.find('.') != -1: e = self.name.split(".") return e[-1] else: return "no" def set_icon(self): self.setIconSize(QSize(45, 45)) self.setToolButtonStyle(Qt.ToolButtonTextUnderIcon) if self.extension in ["txt", "xml", "json", "docx", "xlsx"]: self.setIcon(QIcon(resource_path('icons/Txt.png'))) elif self.extension in ["mp4", "avi", "mpeg", "wmv"]: self.setIcon(QIcon(resource_path('icons/Video.png'))) elif self.extension in ["jpg", "png", "gif"]: self.setIcon(QIcon(resource_path('icons/Immagine.png'))) elif self.extension in ["mp3", "wav", "ogg"]: self.setIcon(QIcon(resource_path('icons/Audio.png'))) else: self.setIcon(QIcon(resource_path('icons/DocGenerico.png'))) def check_double_click(self): if self.timer.isActive(): time = self.timer.remainingTime() if time > 0: self.Sg_double_clicked.emit() self.timer.stop() if time <= 0: self.timer.start(250) if self.timer.isActive() is False: self.timer.start(250) @Slot() def Sl_on_double_click(self): pass
class WinForm(QWidget): def __init__(self, parent=None): super(WinForm, self).__init__(parent) self.setWindowTitle("QTimer demo") self.listFile = QListWidget() self.label = QLabel('显示当前时间') self.startBtn = QPushButton('开始') self.endBtn = QPushButton('结束') layout = QGridLayout(self) # 初始化一个定时器 self.timer = QTimer(self) # showTime()方法 self.timer.timeout.connect(self.showTime) layout.addWidget(self.label, 0, 0, 1, 2) layout.addWidget(self.startBtn, 1, 0) layout.addWidget(self.endBtn, 1, 1) self.startBtn.clicked.connect(self.startTimer) self.endBtn.clicked.connect(self.endTimer) self.setLayout(layout) def showTime(self): # 获取系统现在的时间 time = QDateTime.currentDateTime() # 设置系统时间显示格式 timeDisplay = time.toString("yyyy-MM-dd hh:mm:ss dddd") # 在标签上显示时间 self.label.setText(timeDisplay) def startTimer(self): # 设置计时间隔并启动 self.timer.start(1000) self.startBtn.setEnabled(False) self.endBtn.setEnabled(True) def endTimer(self): self.timer.stop() self.startBtn.setEnabled(True) self.endBtn.setEnabled(False)
class TimerQT(TimerBase): def __init__(self, *args, **kwargs): self._timer = QTimer() self._timer.timeout.connect(self._on_timer) TimerBase.__init__(self, *args, **kwargs) def __del__(self): self._timer_stop() def _timer_set_single_shot(self): self._timer.setSingleShot(self._single) def _timer_set_interval(self): self._timer.setInterval(self._interval) def _timer_start(self): self._timer.start() def _timer_stop(self): self._timer.stop()
class Widget(QWidget): def __init__(self): QWidget.__init__(self) self.setMinimumSize(800, 600) self.donuts = [] self.chart_view = QChartView() self.chart_view.setRenderHint(QPainter.Antialiasing) self.chart = self.chart_view.chart() self.chart.legend().setVisible(False) self.chart.setTitle("Nested donuts demo") self.chart.setAnimationOptions(QChart.AllAnimations) self.min_size = 0.1 self.max_size = 0.9 self.donut_count = 5 self.setup_donuts() # create main layout self.main_layout = QGridLayout(self) self.main_layout.addWidget(self.chart_view, 1, 1) self.setLayout(self.main_layout) self.update_timer = QTimer(self) self.update_timer.timeout.connect(self.update_rotation) self.update_timer.start(1250) def setup_donuts(self): for i in range(self.donut_count): donut = QPieSeries() slccount = randrange(3, 6) for j in range(slccount): value = randrange(100, 200) slc = QPieSlice(str(value), value) slc.setLabelVisible(True) slc.setLabelColor(Qt.white) slc.setLabelPosition(QPieSlice.LabelInsideTangential) # Connection using an extra parameter for the slot slc.hovered[bool].connect(partial(self.explode_slice, slc=slc)) donut.append(slc) size = (self.max_size - self.min_size) / self.donut_count donut.setHoleSize(self.min_size + i * size) donut.setPieSize(self.min_size + (i + 1) * size) self.donuts.append(donut) self.chart_view.chart().addSeries(donut) def update_rotation(self): for donut in self.donuts: phase_shift = randrange(-50, 100) donut.setPieStartAngle(donut.pieStartAngle() + phase_shift) donut.setPieEndAngle(donut.pieEndAngle() + phase_shift) def explode_slice(self, exploded, slc): if exploded: self.update_timer.stop() slice_startangle = slc.startAngle() slice_endangle = slc.startAngle() + slc.angleSpan() donut = slc.series() idx = self.donuts.index(donut) for i in range(idx + 1, len(self.donuts)): self.donuts[i].setPieStartAngle(slice_endangle) self.donuts[i].setPieEndAngle(360 + slice_startangle) else: for donut in self.donuts: donut.setPieStartAngle(0) donut.setPieEndAngle(360) self.update_timer.start() slc.setExploded(exploded)
class AlarmClock(QWidget): started = Signal() aborted = Signal() finished = Signal() def __init__(self, parent=None): super().__init__(parent) self.alarm = QTime() self.timer = QTimer(self) self.timer.setSingleShot(True) self.update_timer = QTimer(self) self.update_timer.setInterval(1000) self.current_label = QLabel('00:00:00') font = QFont() font.setPointSize(50) self.current_label.setFont(font) self.time_edit = QTimeEdit(self) self.time_edit.setDisplayFormat('H:mm') self.set_around_btn = QPushButton('Set around time', self) self.set_ui() self.set_connection() self.reset() self.update_timer.start() self.update_label() self.set_around_time() def set_ui(self): self.vlayout = QVBoxLayout(self) self.vlayout.addWidget(self.current_label) self.vlayout.addWidget(self.time_edit) self.vlayout.addWidget(self.set_around_btn) def set_connection(self): self.timer.timeout.connect(self.stop) self.update_timer.timeout.connect(self.update_label) self.set_around_btn.clicked.connect(self.set_around_time) def set_around_time(self): current = QTime.currentTime() if current.minute() < 29: self.time_edit.setTime(QTime(current.hour(), 30)) else: self.time_edit.setTime(QTime(current.hour() + 1, 0)) def update_label(self): current = QTime.currentTime() self.current_label.setText('{hour:02}:{min:02}:{sec:02}'.format( hour=current.hour(), min=current.minute(), sec=current.second())) def start(self): self.time_edit.setEnabled(False) self.alarm = self.time_edit.time() self.timer.start(QTime.currentTime().msecsTo(self.alarm)) self.started.emit() def abort(self): self.reset() self.aborted.emit() def stop(self): self.reset() self.finished.emit() def reset(self): self.timer.stop() self.time_edit.setEnabled(True) def get_notify_message(self): return '' @property def name(self): return 'Alarm Clock'
class MineSweeper(QMainWindow): def __init__(self): super().__init__() self.undiscovered_color: str = "#000000" self.discovered_color: str = "#d9d9d9" self.win_timer_color: str = "#e30e0e" self.lose_timer_color: str = "#0cc431" self.curr_time = QTime(00,00,00) self.timer = QTimer() self.timer.timeout.connect(self.time) self.timer_already_started = False self.solved: bool = False self.player_ended: bool = False self.theme: str = "dark" self.list_of_mines: list = [] self.difficulty_slider_default_value: int = 2 self.number_of_mines: int = mines_number(NUMBER_OF_LABELS, self.difficulty_slider_default_value) self.create_GUI() def create_GUI(self) -> None: self.setWindowTitle("MineSweeper 1.1") self.win_massage = QMessageBox(self) self.win_massage.setText("Gratuluji, dokázal jsi nalézt všechny miny") self.setMouseTracking(True) centralWidget = QWidget(self) centralWidget.setStyleSheet("background: white") self.setCentralWidget(centralWidget) self.setFixedSize(X_WINDOW_SIZE + 20, Y_WINDOW_SIZE) layout = QGridLayout(centralWidget) layout.setSpacing(0) layout.setContentsMargins(0, 0, 0, 0) centralWidget.setLayout(layout) self.list_of_labels: list = [] self.list_of_mines = generate_mines(self.number_of_mines, X_SIZE, Y_SIZE) # RESET BUTTON self.reset_button = QPushButton(centralWidget) self.reset_button.setText("RESET") self.reset_button.clicked.connect(self.reset) self.reset_button.setStyleSheet("margin: 3px") self.reset_button.setMinimumSize(0, 50) # TIMER LABEL self.timer_label = QLabel(centralWidget) self.timer_label.setText(f"{self.curr_time.minute():0>2}:{self.curr_time.second():0>2}") self.timer_label.setAlignment(Qt.AlignHCenter) self.timer_label.setStyleSheet("font: 34px") # DIFFICULTY SLIDER self.difficulty_slider = QSlider(centralWidget) self.difficulty_slider.setOrientation(Qt.Horizontal) self.difficulty_slider.setFixedHeight(30) self.difficulty_slider.setRange(1, 10) self.difficulty_slider.setTickInterval(1) self.difficulty_slider.setValue(self.difficulty_slider_default_value) self.difficulty_slider.valueChanged.connect(self.difficulty_label_set) self.difficulty_slider.sliderReleased.connect(self.new_mines_set) # DIFFICULTY LABEL self.difficulty_label = QLabel(centralWidget) self.difficulty_label.setText(str(self.difficulty_slider_default_value)) self.difficulty_label.setAlignment(Qt.AlignCenter) self.difficulty_label.setStyleSheet("font: 20px") for i in range(Y_SIZE): row = [] for j in range(X_SIZE): if (i, j) in self.list_of_mines: mine = True else: mine = False label = Chunk(j, i, mine) label.setFixedSize(FIELD_SQUARE_SIZE, FIELD_SQUARE_SIZE) label.setStyleSheet(f"background: {self.undiscovered_color}; border: 1px solid grey") layout.addWidget(label, i, j) row.append(label) self.list_of_labels.append(row) self.color_theme_combobox = QComboBox(centralWidget) self.color_theme_combobox.addItem("Dark theme", "dark") self.color_theme_combobox.addItem("Light theme", "light") self.color_theme_combobox.addItem("Color theme", "colorful") self.color_theme_combobox.currentIndexChanged.connect(self.theme_change) self.color_theme_combobox.setMinimumHeight(FIELD_SQUARE_SIZE * 2) if self.theme == "dark": self.color_theme_combobox.setCurrentIndex(0) elif self.theme == "light": self.color_theme_combobox.setCurrentIndex(1) else: self.color_theme_combobox.setCurrentIndex(2) layout.addWidget(self.color_theme_combobox, Y_SIZE - 2, X_SIZE, 2, 1) layout.addWidget(self.timer_label, 0, X_SIZE, 3, 1) layout.addWidget(self.reset_button, 2, X_SIZE, 3, 1) layout.addWidget(self.difficulty_slider, Y_SIZE, 1, 1, X_SIZE - 2) layout.addWidget(self.difficulty_label, Y_SIZE, X_SIZE, 1, 1) self.mines_number_surroundings_calculate() def theme_change(self) -> None: if self.color_theme_combobox.currentData() == "light": self.undiscovered_color = LIGHT_THEME["undiscovered_color"] self.discovered_color = LIGHT_THEME["discovered_color"] self.win_timer_color = LIGHT_THEME["win_timer_color"] self.lose_timer_color = LIGHT_THEME["lose_timer_color"] self.theme = "light" if self.color_theme_combobox.currentData() == "dark": self.undiscovered_color = DARK_THEME["undiscovered_color"] self.discovered_color = DARK_THEME["discovered_color"] self.win_timer_color = DARK_THEME["win_timer_color"] self.lose_timer_color = DARK_THEME["lose_timer_color"] self.theme = "dark" if self.color_theme_combobox.currentData() == "colorful": self.undiscovered_color = COLOR_THEME["undiscovered_color"] self.discovered_color = COLOR_THEME["discovered_color"] self.win_timer_color = COLOR_THEME["win_timer_color"] self.lose_timer_color = COLOR_THEME["lose_timer_color"] self.theme = "colorful" for y in range(Y_SIZE): for x in range(X_SIZE): if self.list_of_labels[y][x].marked: pass elif not self.list_of_labels[y][x].discovered: self.list_of_labels[y][x].setStyleSheet(f"background: {self.undiscovered_color}; border: 1px solid grey") elif self.list_of_labels[y][x].discovered: self.list_of_labels[y][x].setStyleSheet(f"background: {self.discovered_color}; border: 1px solid grey") def difficulty_label_set(self): self.difficulty_label.setText(str(self.difficulty_slider.value())) def mousePressEvent(self, QMouseEvent) -> None: if not self.player_ended: y = QMouseEvent.pos().x() x = QMouseEvent.pos().y() if not (x > X_GRID_SIZE or y > Y_GRID_SIZE): x = closest_smaller_number(x, Y_POSSIBLE_VALUES) y = closest_smaller_number(y, X_POSSIBLE_VALUES) x = int(x // FIELD_SQUARE_SIZE) y = int(y // FIELD_SQUARE_SIZE) if QMouseEvent.button() == Qt.LeftButton: if self.list_of_labels[x][y].mine: self.stop_timer() if not self.player_ended: self.list_of_labels[x][y].discovered = True self.list_of_labels[x][y].setStyleSheet(f"background: {self.discovered_color}; border: 1px solid grey") self.list_of_labels[x][y].setPixmap(QPixmap("C:/Data/python/miny/pracovní verze/pictures/bomb_small.png")) self.win_massage.about(self, "PROHRA", "Tentokrát se to bohužel nepovedlo, snad to vyjde příště.") self.player_ended = True else: if not self.timer_already_started: self.start_timer() self.timer_already_started = True self.list_of_labels[x][y].discovered = True self.list_of_labels[x][y].setStyleSheet(f"background: {self.discovered_color}; border: 1px solid grey") self.reveal_area(y, x) self.solved_check() else: if not self.list_of_labels[x][y].discovered: if self.list_of_labels[x][y].marked: self.list_of_labels[x][y].setStyleSheet(f"background: {self.undiscovered_color}; border: 1px solid grey") self.list_of_labels[x][y].marked = False else: self.list_of_labels[x][y].setStyleSheet("background: orange; border: 1px solid grey") self.list_of_labels[x][y].marked = True def mines_number_surroundings_calculate(self) -> None: for x in range(X_SIZE): for y in range(Y_SIZE): self.list_of_labels[x][y].mines_number_surroundings = 0 for i in range(x - 1, x + 2): for j in range(y - 1, y + 2): try: if self.list_of_labels[i][j].mine and i >= 0 and j >= 0: if not (i == x and j == y): self.list_of_labels[x][y].mines_number_surroundings += 1 except IndexError: pass def new_mines_set(self): self.number_of_mines = mines_number(NUMBER_OF_LABELS, self.difficulty_slider.value()) self.list_of_mines = generate_mines(self.number_of_mines, X_SIZE, Y_SIZE) for y in range(Y_SIZE): for x in range(X_SIZE): if (y, x) in self.list_of_mines: self.list_of_labels[y][x].mine = True else: self.list_of_labels[y][x].mine = False self.mines_number_surroundings_calculate() self.label_set() def label_set(self) -> None: for y in range(Y_SIZE): for x in range(X_SIZE): if self.list_of_labels[y][x].discovered: if self.list_of_labels[y][x].mines_number_surroundings == 0: pass else: self.list_of_labels[y][x].setText(str(self.list_of_labels[y][x].mines_number_surroundings)) def reveal_area(self, x: int, y: int) -> None: if self.list_of_labels[y][x].mines_number_surroundings == 0: try: extract = self.list_of_labels[y - 1][x] if not extract.mine and Y_SIZE > y - 1 >= 0 and not extract.discovered: self.list_of_labels[y - 1][x].setStyleSheet(f"background: {self.discovered_color}; border: 1px solid grey") self.list_of_labels[y - 1][x].discovered = True if extract.mines_number_surroundings == 0: self.reveal_area(x, y - 1) except IndexError: pass try: extract = self.list_of_labels[y + 1][x] if not extract.mine and Y_SIZE > y + 1 >= 0 and not extract.discovered: self.list_of_labels[y + 1][x].setStyleSheet(f"background: {self.discovered_color}; border: 1px solid grey") self.list_of_labels[y + 1][x].discovered = True if extract.mines_number_surroundings == 0: self.reveal_area(x, y + 1) except IndexError: pass try: extract = self.list_of_labels[y][x + 1] if not extract.mine and X_SIZE > x + 1 >= 0 and not extract.discovered: self.list_of_labels[y][x + 1].setStyleSheet(f"background: {self.discovered_color}; border: 1px solid grey") self.list_of_labels[y][x + 1].discovered = True if extract.mines_number_surroundings == 0: self.reveal_area(x + 1, y) except IndexError: pass try: extract = self.list_of_labels[y][x - 1] if not extract.mine and X_SIZE - 1 > x - 1 >= 0 and not extract.discovered: self.list_of_labels[y][x - 1].setStyleSheet(f"background: {self.discovered_color}; border: 1px solid grey") self.list_of_labels[y][x - 1].discovered = True if extract.mines_number_surroundings == 0: self.reveal_area(x - 1, y) except IndexError: pass self.label_set() def solved_check(self) -> None: for element in self.list_of_labels: for part in element: if not part.mine and not part.discovered: return self.solved = True self.stop_timer() if not self.player_ended: self.player_ended = True self.win_massage.about(self, "VÝHRA", f"Gratuluji, zvládl/a jsi vyřešit tento problém. Zvládl/a jsi to za {self.curr_time.minute():0>2}:{self.curr_time.second():0>2}") # TIMER FUNCIONS def start_timer(self) -> None: self.difficulty_slider.setDisabled(True) self.timer.start(1000) def stop_timer(self) -> None: self.timer.stop() if not self.solved: self.timer_label.setStyleSheet(f"font: 34px; color: {self.win_timer_color}") else: self.timer_label.setStyleSheet(f"font: 34px; color: {self.lose_timer_color}") def time(self) -> None: self.curr_time = self.curr_time.addSecs(1) self.timer_label.setText(f"{self.curr_time.minute():0>2}:{self.curr_time.second():0>2}") #RESET def reset(self) -> None: self.timer = QTimer() self.curr_time = QTime(00,00,00) self.timer.timeout.connect(self.time) self.solved = False self.timer_already_started = False self.player_ended = False self.difficulty_slider.setDisabled(False) self.difficulty_slider_default_value = self.difficulty_slider.value() self.create_GUI()
class QVTKRenderWindowInteractor(QVTKRWIBaseClass): """ A QVTKRenderWindowInteractor for Python and Qt. Uses a vtkGenericRenderWindowInteractor to handle the interactions. Use GetRenderWindow() to get the vtkRenderWindow. Create with the keyword stereo=1 in order to generate a stereo-capable window. The user interface is summarized in vtkInteractorStyle.h: - Keypress j / Keypress t: toggle between joystick (position sensitive) and trackball (motion sensitive) styles. In joystick style, motion occurs continuously as long as a mouse button is pressed. In trackball style, motion occurs when the mouse button is pressed and the mouse pointer moves. - Keypress c / Keypress o: toggle between camera and object (actor) modes. In camera mode, mouse events affect the camera position and focal point. In object mode, mouse events affect the actor that is under the mouse pointer. - Button 1: rotate the camera around its focal point (if camera mode) or rotate the actor around its origin (if actor mode). The rotation is in the direction defined from the center of the renderer's viewport towards the mouse position. In joystick mode, the magnitude of the rotation is determined by the distance the mouse is from the center of the render window. - Button 2: pan the camera (if camera mode) or translate the actor (if object mode). In joystick mode, the direction of pan or translation is from the center of the viewport towards the mouse position. In trackball mode, the direction of motion is the direction the mouse moves. (Note: with 2-button mice, pan is defined as <Shift>-Button 1.) - Button 3: zoom the camera (if camera mode) or scale the actor (if object mode). Zoom in/increase scale if the mouse position is in the top half of the viewport; zoom out/decrease scale if the mouse position is in the bottom half. In joystick mode, the amount of zoom is controlled by the distance of the mouse pointer from the horizontal centerline of the window. - Keypress 3: toggle the render window into and out of stereo mode. By default, red-blue stereo pairs are created. Some systems support Crystal Eyes LCD stereo glasses; you have to invoke SetStereoTypeToCrystalEyes() on the rendering window. Note: to use stereo you also need to pass a stereo=1 keyword argument to the constructor. - Keypress e: exit the application. - Keypress f: fly to the picked point - Keypress p: perform a pick operation. The render window interactor has an internal instance of vtkCellPicker that it uses to pick. - Keypress r: reset the camera view along the current view direction. Centers the actors and moves the camera so that all actors are visible. - Keypress s: modify the representation of all actors so that they are surfaces. - Keypress u: invoke the user-defined function. Typically, this keypress will bring up an interactor that you can type commands in. - Keypress w: modify the representation of all actors so that they are wireframe. """ # Map between VTK and Qt cursors. _CURSOR_MAP = { 0: Qt.ArrowCursor, # VTK_CURSOR_DEFAULT 1: Qt.ArrowCursor, # VTK_CURSOR_ARROW 2: Qt.SizeBDiagCursor, # VTK_CURSOR_SIZENE 3: Qt.SizeFDiagCursor, # VTK_CURSOR_SIZENWSE 4: Qt.SizeBDiagCursor, # VTK_CURSOR_SIZESW 5: Qt.SizeFDiagCursor, # VTK_CURSOR_SIZESE 6: Qt.SizeVerCursor, # VTK_CURSOR_SIZENS 7: Qt.SizeHorCursor, # VTK_CURSOR_SIZEWE 8: Qt.SizeAllCursor, # VTK_CURSOR_SIZEALL 9: Qt.PointingHandCursor, # VTK_CURSOR_HAND 10: Qt.CrossCursor, # VTK_CURSOR_CROSSHAIR } def __init__(self, parent=None, **kw): # the current button self._ActiveButton = Qt.NoButton # private attributes self.__saveX = 0 self.__saveY = 0 self.__saveModifiers = Qt.NoModifier self.__saveButtons = Qt.NoButton self.__wheelDelta = 0 # do special handling of some keywords: # stereo, rw try: stereo = bool(kw['stereo']) except KeyError: stereo = False try: rw = kw['rw'] except KeyError: rw = None # create base qt-level widget if QVTKRWIBase == "QWidget": if "wflags" in kw: wflags = kw['wflags'] else: wflags = Qt.WindowFlags() QWidget.__init__(self, parent, wflags | Qt.MSWindowsOwnDC) elif QVTKRWIBase == "QOpenGLWidget": QOpenGLWidget.__init__(self, parent) if rw: # user-supplied render window self._RenderWindow = rw else: self._RenderWindow = vtkRenderWindow() WId = self.winId() # Python2 if type(WId).__name__ == 'PyCObject': from ctypes import pythonapi, c_void_p, py_object pythonapi.PyCObject_AsVoidPtr.restype = c_void_p pythonapi.PyCObject_AsVoidPtr.argtypes = [py_object] WId = pythonapi.PyCObject_AsVoidPtr(WId) # Python3 elif type(WId).__name__ == 'PyCapsule': from ctypes import pythonapi, c_void_p, py_object, c_char_p pythonapi.PyCapsule_GetName.restype = c_char_p pythonapi.PyCapsule_GetName.argtypes = [py_object] name = pythonapi.PyCapsule_GetName(WId) pythonapi.PyCapsule_GetPointer.restype = c_void_p pythonapi.PyCapsule_GetPointer.argtypes = [py_object, c_char_p] WId = pythonapi.PyCapsule_GetPointer(WId, name) self._RenderWindow.SetWindowInfo(str(int(WId))) if stereo: # stereo mode self._RenderWindow.StereoCapableWindowOn() self._RenderWindow.SetStereoTypeToCrystalEyes() try: self._Iren = kw['iren'] except KeyError: self._Iren = vtkGenericRenderWindowInteractor() self._Iren.SetRenderWindow(self._RenderWindow) # do all the necessary qt setup self.setAttribute(Qt.WA_OpaquePaintEvent) self.setAttribute(Qt.WA_PaintOnScreen) self.setMouseTracking(True) # get all mouse events self.setFocusPolicy(Qt.WheelFocus) self.setSizePolicy( QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)) self._Timer = QTimer(self) self._Timer.timeout.connect(self.TimerEvent) self._Iren.AddObserver('CreateTimerEvent', self.CreateTimer) self._Iren.AddObserver('DestroyTimerEvent', self.DestroyTimer) self._Iren.GetRenderWindow().AddObserver('CursorChangedEvent', self.CursorChangedEvent) # If we've a parent, it does not close the child when closed. # Connect the parent's destroyed signal to this widget's close # slot for proper cleanup of VTK objects. if self.parent(): self.parent().destroyed.connect(self.close, Qt.DirectConnection) def __getattr__(self, attr): """Makes the object behave like a vtkGenericRenderWindowInteractor""" if attr == '__vtk__': return lambda t=self._Iren: t elif hasattr(self._Iren, attr): return getattr(self._Iren, attr) else: raise AttributeError(self.__class__.__name__ + " has no attribute named " + attr) def Finalize(self): ''' Call internal cleanup method on VTK objects ''' self._RenderWindow.Finalize() def CreateTimer(self, obj, evt): self._Timer.start(10) def DestroyTimer(self, obj, evt): self._Timer.stop() return 1 def TimerEvent(self): self._Iren.TimerEvent() def CursorChangedEvent(self, obj, evt): """Called when the CursorChangedEvent fires on the render window.""" # This indirection is needed since when the event fires, the current # cursor is not yet set so we defer this by which time the current # cursor should have been set. QTimer.singleShot(0, self.ShowCursor) def HideCursor(self): """Hides the cursor.""" self.setCursor(Qt.BlankCursor) def ShowCursor(self): """Shows the cursor.""" vtk_cursor = self._Iren.GetRenderWindow().GetCurrentCursor() qt_cursor = self._CURSOR_MAP.get(vtk_cursor, Qt.ArrowCursor) self.setCursor(qt_cursor) def closeEvent(self, evt): self.Finalize() def sizeHint(self): return QSize(400, 400) def paintEngine(self): return None def paintEvent(self, ev): self._Iren.Render() def resizeEvent(self, ev): scale = self._getPixelRatio() w = int(round(scale * self.width())) h = int(round(scale * self.height())) self._RenderWindow.SetDPI(int(round(72 * scale))) vtkRenderWindow.SetSize(self._RenderWindow, w, h) self._Iren.SetSize(w, h) self._Iren.ConfigureEvent() self.update() def _GetKeyCharAndKeySym(self, ev): """ Convert a Qt key into a char and a vtk keysym. This is essentially copied from the c++ implementation in GUISupport/Qt/QVTKInteractorAdapter.cxx. """ # if there is a char, convert its ASCII code to a VTK keysym try: keyChar = ev.text()[0] keySym = _keysyms_for_ascii[ord(keyChar)] except IndexError: keyChar = '\0' keySym = None # next, try converting Qt key code to a VTK keysym if keySym is None: try: keySym = _keysyms[ev.key()] except KeyError: keySym = None # use "None" as a fallback if keySym is None: keySym = "None" return keyChar, keySym def _GetCtrlShift(self, ev): ctrl = shift = False if hasattr(ev, 'modifiers'): if ev.modifiers() & Qt.ShiftModifier: shift = True if ev.modifiers() & Qt.ControlModifier: ctrl = True else: if self.__saveModifiers & Qt.ShiftModifier: shift = True if self.__saveModifiers & Qt.ControlModifier: ctrl = True return ctrl, shift @staticmethod def _getPixelRatio(): if PyQtImpl in ["PyQt5", "PySide2", "PySide6"]: # Source: https://stackoverflow.com/a/40053864/3388962 pos = QCursor.pos() for screen in QApplication.screens(): rect = screen.geometry() if rect.contains(pos): return screen.devicePixelRatio() # Should never happen, but try to find a good fallback. return QApplication.instance().devicePixelRatio() else: # Qt4 seems not to provide any cross-platform means to get the # pixel ratio. return 1. def _setEventInformation(self, x, y, ctrl, shift, key, repeat=0, keysum=None): scale = self._getPixelRatio() self._Iren.SetEventInformation( int(round(x * scale)), int(round((self.height() - y - 1) * scale)), ctrl, shift, key, repeat, keysum) def enterEvent(self, ev): ctrl, shift = self._GetCtrlShift(ev) self._setEventInformation(self.__saveX, self.__saveY, ctrl, shift, chr(0), 0, None) self._Iren.EnterEvent() def leaveEvent(self, ev): ctrl, shift = self._GetCtrlShift(ev) self._setEventInformation(self.__saveX, self.__saveY, ctrl, shift, chr(0), 0, None) self._Iren.LeaveEvent() def mousePressEvent(self, ev): ctrl, shift = self._GetCtrlShift(ev) repeat = 0 if ev.type() == QEvent.MouseButtonDblClick: repeat = 1 self._setEventInformation(ev.x(), ev.y(), ctrl, shift, chr(0), repeat, None) self._ActiveButton = ev.button() if self._ActiveButton == Qt.LeftButton: self._Iren.LeftButtonPressEvent() elif self._ActiveButton == Qt.RightButton: self._Iren.RightButtonPressEvent() elif self._ActiveButton == Qt.MiddleButton: self._Iren.MiddleButtonPressEvent() def mouseReleaseEvent(self, ev): ctrl, shift = self._GetCtrlShift(ev) self._setEventInformation(ev.x(), ev.y(), ctrl, shift, chr(0), 0, None) if self._ActiveButton == Qt.LeftButton: self._Iren.LeftButtonReleaseEvent() elif self._ActiveButton == Qt.RightButton: self._Iren.RightButtonReleaseEvent() elif self._ActiveButton == Qt.MiddleButton: self._Iren.MiddleButtonReleaseEvent() def mouseMoveEvent(self, ev): self.__saveModifiers = ev.modifiers() self.__saveButtons = ev.buttons() self.__saveX = ev.x() self.__saveY = ev.y() ctrl, shift = self._GetCtrlShift(ev) self._setEventInformation(ev.x(), ev.y(), ctrl, shift, chr(0), 0, None) self._Iren.MouseMoveEvent() def keyPressEvent(self, ev): key, keySym = self._GetKeyCharAndKeySym(ev) ctrl, shift = self._GetCtrlShift(ev) self._setEventInformation(self.__saveX, self.__saveY, ctrl, shift, key, 0, keySym) self._Iren.KeyPressEvent() self._Iren.CharEvent() def keyReleaseEvent(self, ev): key, keySym = self._GetKeyCharAndKeySym(ev) ctrl, shift = self._GetCtrlShift(ev) self._setEventInformation(self.__saveX, self.__saveY, ctrl, shift, key, 0, keySym) self._Iren.KeyReleaseEvent() def wheelEvent(self, ev): if hasattr(ev, 'delta'): self.__wheelDelta += ev.delta() else: self.__wheelDelta += ev.angleDelta().y() if self.__wheelDelta >= 120: self._Iren.MouseWheelForwardEvent() self.__wheelDelta = 0 elif self.__wheelDelta <= -120: self._Iren.MouseWheelBackwardEvent() self.__wheelDelta = 0 def GetRenderWindow(self): return self._RenderWindow def Render(self): self.update()
class GameConnection(QObject, ConnectionBackend): Updated = Signal() _dt: float = 2.5 _last_status: Any = None _permanent_pickups: List[Tuple[str, PickupEntry]] def __init__(self, executor: MemoryOperationExecutor): super().__init__() ConnectionBackend.__init__(self, executor) self._permanent_pickups = [] self._timer = QTimer(self) self._timer.timeout.connect(self._auto_update) self._timer.setInterval(self._dt * 1000) self._timer.setSingleShot(True) self._notify_status() def set_executor(self, executor: MemoryOperationExecutor): self.executor = executor self._notify_status() async def start(self): self._timer.start() async def stop(self): self._timer.stop() @asyncSlot() async def _auto_update(self): try: await self.update(self._dt) self._notify_status() finally: self._timer.start() def _notify_status(self): new_status = self.current_status inventory = self.get_current_inventory() if self._last_status != (new_status, self.executor, inventory): self._last_status = (new_status, self.executor, copy.copy(inventory)) self.Updated.emit() @property def pretty_current_status(self) -> str: return f"{self.backend_choice.pretty_text}: {self.current_status.pretty_text}" @property def current_game_name(self) -> Optional[str]: if self.connector is not None: return self.connector.game_enum.long_name @property def name(self) -> str: raise ValueError("bleh") def set_location_collected_listener(self, listener: Optional[LocationListener]): super(ConnectionBackend, self).set_location_collected_listener(listener) self.checking_for_collected_index = listener is not None
class DebugView(QWidget, View): class DebugViewHistoryEntry(HistoryEntry): def __init__(self, memory_addr, address, is_raw): HistoryEntry.__init__(self) self.memory_addr = memory_addr self.address = address self.is_raw = is_raw def __repr__(self): if self.is_raw: return "<raw history: {}+{:0x} (memory: {:0x})>".format(self.address['module'], self.address['offset'], self.memory_addr) return "<code history: {:0x} (memory: {:0x})>".format(self.address, self.memory_addr) def __init__(self, parent, data): if not type(data) == BinaryView: raise Exception('expected widget data to be a BinaryView') self.bv = data self.debug_state = binjaplug.get_state(data) memory_view = self.debug_state.memory_view self.debug_state.ui.debug_view = self QWidget.__init__(self, parent) self.controls = ControlsWidget.DebugControlsWidget(self, "Controls", data, self.debug_state) View.__init__(self) self.setupView(self) self.current_offset = 0 self.splitter = QSplitter(Qt.Orientation.Horizontal, self) frame = ViewFrame.viewFrameForWidget(self) self.memory_editor = LinearView(memory_view, frame) self.binary_editor = DisassemblyContainer(frame, data, frame) self.binary_text = TokenizedTextView(self, memory_view) self.is_raw_disassembly = False self.raw_address = 0 self.is_navigating_history = False self.memory_history_addr = 0 # TODO: Handle these and change views accordingly # Currently they are just disabled as the DisassemblyContainer gets confused # about where to go and just shows a bad view self.binary_editor.getDisassembly().actionHandler().bindAction("View in Hex Editor", UIAction()) self.binary_editor.getDisassembly().actionHandler().bindAction("View in Linear Disassembly", UIAction()) self.binary_editor.getDisassembly().actionHandler().bindAction("View in Types View", UIAction()) self.memory_editor.actionHandler().bindAction("View in Hex Editor", UIAction()) self.memory_editor.actionHandler().bindAction("View in Disassembly Graph", UIAction()) self.memory_editor.actionHandler().bindAction("View in Types View", UIAction()) small_font = QApplication.font() small_font.setPointSize(11) bv_layout = QVBoxLayout() bv_layout.setSpacing(0) bv_layout.setContentsMargins(0, 0, 0, 0) bv_label = QLabel("Loaded File") bv_label.setFont(small_font) bv_layout.addWidget(bv_label) bv_layout.addWidget(self.binary_editor) self.bv_widget = QWidget() self.bv_widget.setLayout(bv_layout) disasm_layout = QVBoxLayout() disasm_layout.setSpacing(0) disasm_layout.setContentsMargins(0, 0, 0, 0) disasm_label = QLabel("Raw Disassembly at PC") disasm_label.setFont(small_font) disasm_layout.addWidget(disasm_label) disasm_layout.addWidget(self.binary_text) self.disasm_widget = QWidget() self.disasm_widget.setLayout(disasm_layout) memory_layout = QVBoxLayout() memory_layout.setSpacing(0) memory_layout.setContentsMargins(0, 0, 0, 0) memory_label = QLabel("Debugged Process") memory_label.setFont(small_font) memory_layout.addWidget(memory_label) memory_layout.addWidget(self.memory_editor) self.memory_widget = QWidget() self.memory_widget.setLayout(memory_layout) self.splitter.addWidget(self.bv_widget) self.splitter.addWidget(self.memory_widget) # Equally sized self.splitter.setSizes([0x7fffffff, 0x7fffffff]) layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) layout.addWidget(self.controls) layout.addWidget(self.splitter, 100) self.setLayout(layout) self.needs_update = True self.update_timer = QTimer(self) self.update_timer.setInterval(200) self.update_timer.setSingleShot(False) self.update_timer.timeout.connect(lambda: self.updateTimerEvent()) self.add_scripting_ref() # set initial breakpoint when view is switched if self.debug_state.bv and self.debug_state.bv.entry_point: local_entry_offset = self.debug_state.bv.entry_point - self.debug_state.bv.start if not self.debug_state.breakpoints.contains_offset(self.debug_state.bv.file.original_filename, local_entry_offset): self.debug_state.breakpoints.add_offset(self.debug_state.bv.file.original_filename, local_entry_offset) if self.debug_state.ui is not None: self.debug_state.ui.breakpoint_tag_add(self.debug_state.bv.entry_point) self.debug_state.ui.update_highlights() self.debug_state.ui.update_breakpoints() def add_scripting_ref(self): # Hack: The interpreter is just a thread, so look through all threads # and assign our state to the interpreter's locals for thread in threading.enumerate(): if type(thread) == PythonScriptingInstance.InterpreterThread: thread.locals["dbg"] = self.debug_state def getData(self): return self.bv def getFont(self): return binaryninjaui.getMonospaceFont(self) def getCurrentOffset(self): if not self.is_raw_disassembly: return self.binary_editor.getDisassembly().getCurrentOffset() return self.raw_address def getSelectionOffsets(self): if not self.is_raw_disassembly: return self.binary_editor.getDisassembly().getSelectionOffsets() return (self.raw_address, self.raw_address) def getCurrentFunction(self): if not self.is_raw_disassembly: return self.binary_editor.getDisassembly().getCurrentFunction() return None def getCurrentBasicBlock(self): if not self.is_raw_disassembly: return self.binary_editor.getDisassembly().getCurrentBasicBlock() return None def getCurrentArchitecture(self): if not self.is_raw_disassembly: return self.binary_editor.getDisassembly().getCurrentArchitecture() return None def getCurrentLowLevelILFunction(self): if not self.is_raw_disassembly: return self.binary_editor.getDisassembly().getCurrentLowLevelILFunction() return None def getCurrentMediumLevelILFunction(self): if not self.is_raw_disassembly: return self.binary_editor.getDisassembly().getCurrentMediumLevelILFunction() return None def getHistoryEntry(self): if self.is_navigating_history: return None memory_addr = self.memory_editor.getCurrentOffset() if memory_addr != self.memory_history_addr: self.memory_history_addr = memory_addr if self.is_raw_disassembly and self.debug_state.connected: rel_addr = self.debug_state.modules.absolute_addr_to_relative(self.raw_address) return DebugView.DebugViewHistoryEntry(memory_addr, rel_addr, True) else: address = self.binary_editor.getDisassembly().getCurrentOffset() return DebugView.DebugViewHistoryEntry(memory_addr, address, False) def navigateToFunction(self, func, offset): return self.navigate(offset) def navigateToHistoryEntry(self, entry): self.is_navigating_history = True if hasattr(entry, 'is_raw'): self.memory_editor.navigate(entry.memory_addr) if entry.is_raw: if self.debug_state.connected: address = self.debug_state.modules.relative_addr_to_absolute(entry.address) self.navigate_raw(address) else: self.navigate_live(entry.address) View.navigateToHistoryEntry(self, entry) self.is_navigating_history = False def navigate(self, addr): # If we're not connected we cannot even check if the address is remote if not self.debug_state.connected: return self.navigate_live(addr) if self.debug_state.memory_view.is_local_addr(addr): local_addr = self.debug_state.memory_view.remote_addr_to_local(addr) if self.debug_state.bv.read(local_addr, 1) and len(self.debug_state.bv.get_functions_containing(local_addr)) > 0: return self.navigate_live(local_addr) # This runs into conflicts if some other address space is mapped over # where the local BV is currently loaded, but this is was less likely # than the user navigating to a function from the UI if self.debug_state.bv.read(addr, 1) and len(self.debug_state.bv.get_functions_containing(addr)) > 0: return self.navigate_live(addr) return self.navigate_raw(addr) def navigate_live(self, addr): self.show_raw_disassembly(False) return self.binary_editor.getDisassembly().navigate(addr) def navigate_raw(self, addr): if not self.debug_state.connected: # Can't navigate to remote addr when disconnected return False self.raw_address = addr self.show_raw_disassembly(True) self.load_raw_disassembly(addr) return True def notifyMemoryChanged(self): self.needs_update = True def updateTimerEvent(self): if self.needs_update: self.needs_update = False # Refresh the editor if not self.debug_state.connected: self.memory_editor.navigate(0) return # self.memory_editor.navigate(self.debug_state.stack_pointer) def showEvent(self, event): if not event.spontaneous(): self.update_timer.start() self.add_scripting_ref() def hideEvent(self, event): if not event.spontaneous(): self.update_timer.stop() def shouldBeVisible(self, view_frame): if view_frame is None: return False else: return True def load_raw_disassembly(self, start_ip): # Read a few instructions from rip and disassemble them inst_count = 50 arch_dis = self.debug_state.remote_arch rip = self.debug_state.ip # Assume the worst, just in case read_length = arch_dis.max_instr_length * inst_count data = self.debug_state.memory_view.read(start_ip, read_length) lines = [] # Append header line tokens = [InstructionTextToken(InstructionTextTokenType.TextToken, "(Code not backed by loaded file, showing only raw disassembly)")] contents = DisassemblyTextLine(tokens, start_ip) line = LinearDisassemblyLine(LinearDisassemblyLineType.BasicLineType, None, None, contents) lines.append(line) total_read = 0 for i in range(inst_count): line_addr = start_ip + total_read (insn_tokens, length) = arch_dis.get_instruction_text(data[total_read:], line_addr) if insn_tokens is None: insn_tokens = [InstructionTextToken(InstructionTextTokenType.TextToken, "??")] length = arch_dis.instr_alignment if length == 0: length = 1 # terrible libshiboken workaround, see #101 for tok in insn_tokens: if tok.value.bit_length() == 64: tok.value ^= 0x8000000000000000 tokens = [] color = HighlightStandardColor.NoHighlightColor if line_addr == rip: if self.debug_state.breakpoints.contains_absolute(start_ip + total_read): # Breakpoint & pc tokens.append(InstructionTextToken(InstructionTextTokenType.TagToken, self.debug_state.ui.get_breakpoint_tag_type().icon + ">", width=5)) color = HighlightStandardColor.RedHighlightColor else: # PC tokens.append(InstructionTextToken(InstructionTextTokenType.TextToken, " ==> ")) color = HighlightStandardColor.BlueHighlightColor else: if self.debug_state.breakpoints.contains_absolute(start_ip + total_read): # Breakpoint tokens.append(InstructionTextToken(InstructionTextTokenType.TagToken, self.debug_state.ui.get_breakpoint_tag_type().icon, width=5)) color = HighlightStandardColor.RedHighlightColor else: # Regular line tokens.append(InstructionTextToken(InstructionTextTokenType.TextToken, " ")) # Address tokens.append(InstructionTextToken(InstructionTextTokenType.AddressDisplayToken, hex(line_addr)[2:], line_addr)) tokens.append(InstructionTextToken(InstructionTextTokenType.TextToken, " ")) tokens.extend(insn_tokens) # Convert to linear disassembly line contents = DisassemblyTextLine(tokens, line_addr, color=color) line = LinearDisassemblyLine(LinearDisassemblyLineType.CodeDisassemblyLineType, None, None, contents) lines.append(line) total_read += length self.binary_text.setLines(lines) def show_raw_disassembly(self, raw): if raw != self.is_raw_disassembly: self.splitter.replaceWidget(0, self.disasm_widget if raw else self.bv_widget) self.is_raw_disassembly = raw def refresh_raw_disassembly(self): if not self.debug_state.connected: # Can't navigate to remote addr when disconnected return if self.is_raw_disassembly: self.load_raw_disassembly(self.getCurrentOffset())
class SettingsTree(QTreeWidget): def __init__(self, parent=None): super(SettingsTree, self).__init__(parent) self._type_checker = TypeChecker() self.setItemDelegate(VariantDelegate(self._type_checker, self)) self.setHeaderLabels(("Setting", "Type", "Value")) self.header().setSectionResizeMode(0, QHeaderView.Stretch) self.header().setSectionResizeMode(2, QHeaderView.Stretch) self.settings = None self.refresh_timer = QTimer() self.refresh_timer.setInterval(2000) self.auto_refresh = False self.group_icon = QIcon() style = self.style() self.group_icon.addPixmap(style.standardPixmap(QStyle.SP_DirClosedIcon), QIcon.Normal, QIcon.Off) self.group_icon.addPixmap(style.standardPixmap(QStyle.SP_DirOpenIcon), QIcon.Normal, QIcon.On) self.key_icon = QIcon() self.key_icon.addPixmap(style.standardPixmap(QStyle.SP_FileIcon)) self.refresh_timer.timeout.connect(self.maybe_refresh) def set_settings_object(self, settings): self.settings = settings self.clear() if self.settings is not None: self.settings.setParent(self) self.refresh() if self.auto_refresh: self.refresh_timer.start() else: self.refresh_timer.stop() def sizeHint(self): return QSize(800, 600) def set_auto_refresh(self, autoRefresh): self.auto_refresh = autoRefresh if self.settings is not None: if self.auto_refresh: self.maybe_refresh() self.refresh_timer.start() else: self.refresh_timer.stop() def set_fallbacks_enabled(self, enabled): if self.settings is not None: self.settings.setFallbacksEnabled(enabled) self.refresh() def maybe_refresh(self): if self.state() != QAbstractItemView.EditingState: self.refresh() def refresh(self): if self.settings is None: return # The signal might not be connected. try: self.itemChanged.disconnect(self.update_setting) except: pass self.settings.sync() self.update_child_items(None) self.itemChanged.connect(self.update_setting) def event(self, event): if event.type() == QEvent.WindowActivate: if self.isActiveWindow() and self.auto_refresh: self.maybe_refresh() return super(SettingsTree, self).event(event) def update_setting(self, item): key = item.text(0) ancestor = item.parent() while ancestor: key = ancestor.text(0) + '/' + key ancestor = ancestor.parent() d = item.data(2, Qt.UserRole) self.settings.setValue(key, item.data(2, Qt.UserRole)) if self.auto_refresh: self.refresh() def update_child_items(self, parent): divider_index = 0 for group in self.settings.childGroups(): child_index = self.find_child(parent, group, divider_index) if child_index != -1: child = self.child_at(parent, child_index) child.setText(1, '') child.setText(2, '') child.setData(2, Qt.UserRole, None) self.move_item_forward(parent, child_index, divider_index) else: child = self.create_item(group, parent, divider_index) child.setIcon(0, self.group_icon) divider_index += 1 self.settings.beginGroup(group) self.update_child_items(child) self.settings.endGroup() for key in self.settings.childKeys(): child_index = self.find_child(parent, key, 0) if child_index == -1 or child_index >= divider_index: if child_index != -1: child = self.child_at(parent, child_index) for i in range(child.childCount()): self.delete_item(child, i) self.move_item_forward(parent, child_index, divider_index) else: child = self.create_item(key, parent, divider_index) child.setIcon(0, self.key_icon) divider_index += 1 else: child = self.child_at(parent, child_index) value = self.settings.value(key) if value is None: child.setText(1, 'Invalid') else: # Try to convert to type unless a QByteArray is received if isinstance(value, str): value_type = self._type_checker.type_from_text(value) if value_type: value = self.settings.value(key, type=value_type) child.setText(1, value.__class__.__name__) child.setText(2, VariantDelegate.displayText(value)) child.setData(2, Qt.UserRole, value) while divider_index < self.child_count(parent): self.delete_item(parent, divider_index) def create_item(self, text, parent, index): after = None if index != 0: after = self.child_at(parent, index - 1) if parent is not None: item = QTreeWidgetItem(parent, after) else: item = QTreeWidgetItem(self, after) item.setText(0, text) item.setFlags(item.flags() | Qt.ItemIsEditable) return item def delete_item(self, parent, index): if parent is not None: item = parent.takeChild(index) else: item = self.takeTopLevelItem(index) del item def child_at(self, parent, index): if parent is not None: return parent.child(index) else: return self.topLevelItem(index) def child_count(self, parent): if parent is not None: return parent.childCount() else: return self.topLevelItemCount() def find_child(self, parent, text, startIndex): for i in range(self.child_count(parent)): if self.child_at(parent, i).text(0) == text: return i return -1 def move_item_forward(self, parent, oldIndex, newIndex): for int in range(oldIndex - newIndex): self.delete_item(parent, newIndex)
class Emotion: def __init__(self, main_window): self.main_window = main_window self.timer = QTimer() self.timer.timeout.connect(self.view_cam) self.cap = cv2.VideoCapture(0) self.data_path = os.path.join(os.path.dirname(__file__), 'Data') self.detector = dlib.get_frontal_face_detector() self.predictor = dlib.shape_predictor( os.path.join(self.data_path, 'shape_predictor_68_face_landmarks.dat')) self.face_rec = dlib.face_recognition_model_v1( os.path.join(self.data_path, 'dlib_face_recognition_resnet_model_v1.dat')) self.emotions = [ 'Радость', 'Удивление', 'Грусть', 'Злость', 'Отвращение', 'Презрение', 'Страх' ] self.fdi = [] self.ind_d = 0 self.ind_u = 0 self.ind_p = 0 self.image = None self.P_em = 0.5 self.f = open(os.path.join(self.data_path, 'svm_dat.dat'), 'rb') self.clf = pickle.load(self.f) self.emotion_result = 0 self.nn = 20 self.e = None self.label = None self.font = ImageFont.truetype( os.path.join(os.path.dirname(__file__), 'pictures', 'DejaVuSans.ttf'), 18) self.shape = None self.i = 0 self.step = 0 self.width = 0 self.height = 0 self.em_n = 0 def view_cam(self): ret, self.image = self.cap.read() self.image = cv2.cvtColor(self.image, cv2.COLOR_BGR2RGB) self.height, self.width, channel = self.image.shape self.step = channel * self.width self.rectangle_face() q_img = QImage(self.image.data, self.width, self.height, self.step, QImage.Format_RGB888) self.main_window.ui.label_3.setPixmap(QPixmap.fromImage(q_img)) def start(self, e): self.e = e self.main_window.ui = Ui_Simulator() self.main_window.ui.setupUi(self.main_window) callback = functools.partial(self.emotion, e=e, cmd='stop') self.main_window.ui.pushButton.clicked.connect(callback) self.main_window.ui.label.setPixmap( QPixmap( os.path.join(os.path.dirname(__file__), 'pictures', f'{str(e)}.jpg'))) self.main_window.setWindowTitle('Тренажер') self.timer.start(30) def emotion(self, e: int, cmd='sim'): if cmd == 'sim': self.start(e) elif cmd == 'stop': self.timer.stop() self.main_window.main_window.select_emotions_window() def rectangle_face(self): detection = self.detector(self.image, 1) if len(detection) == 0: self.main_window.ui.label_2.setText('Нет никого в кадре') self.em_n = 0 for k, d in enumerate(detection): if self.i % 3 == 0: self.shape = self.predictor(self.image, d) face_description = self.face_rec.compute_face_descriptor( self.image, self.shape) dat = [face_description] self.em_n = self.clf.predict(dat) utils.rectangle_face(self.image, detection, self.shape) img_pil = Image.fromarray(self.image) draw = ImageDraw.Draw(img_pil) if self.em_n == self.e: self.emotion_result += 1 draw.text((detection[0].left(), detection[0].top() - 20), self.emotions[int(self.em_n) - 1], font=self.font, fill=(0, 255, 0)) else: draw.text((detection[0].left(), detection[0].top() - 20), self.emotions[int(self.em_n) - 1], font=self.font, fill=(255, 0, 0)) self.image = np.array(img_pil) cv2.waitKey(10) if self.i == self.nn: self.emotion_result = self.emotion_result / self.nn if self.emotion_result >= self.P_em: self.main_window.ui.label_2.setText( "Молодец: " + "{0:.2f}".format(self.emotion_result * 100) + '%') self.emotion(self.e, 'stop') else: self.main_window.ui.label_2.setText( "Попробуй ещё: " + "{0:.2f}".format(self.emotion_result * 100) + '%') self.i = 0 else: self.i += 1
class MainWindow(QMainWindow): def __init__(self): super(MainWindow, self).__init__() self.ui = Ui_TrainControllerSW() self.ui.setupUi(self) #automatic mode stuff self.autoMode = True self.ui.automaticMode.toggled.connect(self.onAutoManual) self.onAutoManual() #commanded speed stuff self.setPointSpeed = 0 self.ui.setPointSpeedVal.display(self.setPointSpeed) self.ui.speedUpButton.clicked.connect(self.increaseSetPoint) self.ui.speedDownButton.clicked.connect(self.decreaseSetPoint) #service brake stuff self.serviceBrake = False self.ui.serviceBrake.pressed.connect(self.serviceBrakeActivated) self.ui.serviceBrake.released.connect(self.serviceBrakeDeactivated) #service Brake Failure Stuff self.ui.causeBrakeFailure.clicked.connect(self.causeBrakeFailure) #emergency brake stuff self.ui.emergencyBrake.pressed.connect(self.onEmergencyBrake) self.ui.emergencyBrake.released.connect(self.onEmergencyBrakeOff) self.emergencyBrake = False #encoded Track Circuit stuff self.actualSpeed = float(0) self.actualSpeedLast = float(0) self.actualSpeedSecond = float(0) self.commandedSpeed = float(0) self.authority = float(0) self.encodedTC = 0 self.ui.updateTCFake.clicked.connect(self.onUpdateTCFake) self.power = float(0) #passenger brake fake stuff self.ui.causePassenger.clicked.connect(self.onPassengerBrakeActivated) #actual speed fake stuff self.ui.updateActualFake.clicked.connect(self.onUpdateActualFake) #beacon stuff self.ui.updateBeaconFake.clicked.connect(self.onUpdateBeaconFake) self.encodedBeacon = 0 self.upcomingStation = False self.leftDoorsOpen = False self.rightDoorsOpen = False self.exteriorLightsOn = False self.station = " " #setting up stations self.stationArray = ("Shadyside", "Herron Ave", "Swissville", "Penn Station", "Steel Plaza", "First Ave", "Station Square", "South Hills Junction", "Pioneer", "Edgebrook", "Whited", "South Bank", "Central", "Inglewood", "Overbrook", "Glenburry", "Dormont", "Mt Lebanon", "Poplar", "Castle Shannon") #pid loop updates self.ui.updatePID.clicked.connect(self.onUpdateKpKi) #pid loop stuff self.kp = 0 self.ki = 0 self.pid = PID(self.kp, self.ki, 0) self.pid.output_limits = (0, 120000) self.pidTimer = QTimer() self.pidTimer.timeout.connect(self.pidLoop) self.pidTimer.start(1000) #cause engine failure self.ui.causeEngineFailure.clicked.connect(self.causeEngineFailure) #Announcements self.announcement = "" self.ui.intercom.clicked.connect(self.onAnnouncement) self.ui.temperature.valueChanged.connect(self.onTemperatureChange) self.temperature = 70 #auto mode loop self.alreadyWaiting = False #manual mode loop self.openedl = False self.openedr = False #lights sending to test ui self.ui.interiorLights_2.setEnabled(False) self.ui.exteriorLights_2.setEnabled(False) self.ui.interiorLights.stateChanged.connect(self.onIntLightsChanged) self.ui.exteriorLights.stateChanged.connect(self.onExtLightsChanged) #increase: used to increase setpoint speed def increaseSetPoint(self): if (self.setPointSpeed + 1 <= self.commandedSpeed): self.setPointSpeed = self.setPointSpeed + 1 print(self.setPointSpeed) self.displayUpdate() #decreaseSetPoint: used to decrease setpoint speed def decreaseSetPoint(self): if (self.setPointSpeed > 0): self.setPointSpeed = self.setPointSpeed - 1 print(self.setPointSpeed) self.displayUpdate() #displayUpdate: used to update all display features def displayUpdate(self): self.ui.setPointSpeedVal.display(self.setPointSpeed) self.ui.commandedSpeedVal.display(self.commandedSpeed) self.ui.authority.display(self.authority) self.ui.actualSpeedVal.display(self.actualSpeed) self.ui.encodedTC.setPlainText(str(bin(self.encodedTC))) if (self.serviceBrake): self.ui.textBrowser_9.setPlainText("S Brake Active") self.ui.textBrowser_11.setPlainText("S Brake Active") else: self.ui.textBrowser_9.setPlainText("S Brake Inactive") self.ui.textBrowser_11.setPlainText("S Brake Inactive") if (self.emergencyBrake): self.ui.textBrowser_8.setPlainText("E Brake Active") self.ui.textBrowser_10.setPlainText("E Brake Active") else: self.ui.textBrowser_8.setPlainText("E Brake Inactive") self.ui.textBrowser_10.setPlainText("E Brake Inactive") #to be removed once fake inputs are stripped if (self.autoMode == True): self.ui.leftDoors.setChecked(self.leftDoorsOpen) self.ui.rightDoors.setChecked(self.rightDoorsOpen) #when you are in not auto mode if (self.autoMode == False): self.leftDoors = self.ui.leftDoors.isChecked() self.rightDoors = self.ui.rightDoors.isChecked() self.openDoorsManual() if (self.autoMode): self.ui.exteriorLights.setChecked(self.exteriorLightsOn) self.ui.upcomingStation.setPlainText(str(self.station)) self.ui.encodedBeacon.setPlainText(str(bin(self.encodedBeacon))) self.ui.power.display(self.power / 1000) self.ui.textBrowser_17.setPlainText(str(int(self.power))) if (self.upcomingStation): self.ui.upcomingStation.setStyleSheet( u"background-color: rgb(124,252,0);") #going through our arrival procedures if (self.alreadyWaiting == False): self.onArrival() else: self.ui.upcomingStation.setStyleSheet( u"background-color: rgb(255,255,255);") #onEmergencyBrake: used to enable emergency brake def onEmergencyBrake(self): print("Emergency Brake Activated") self.setpointSpeed = 0 self.power = 0 self.emergencyBrake = True self.displayUpdate() #onEmergencyBrakeOff: used to disable emergency brake upon release def onEmergencyBrakeOff(self): print("Emergency Brake Released") self.emergencyBrake = False self.displayUpdate() #serviceBrakeActivated: used when service Brake activated def serviceBrakeActivated(self): self.serviceBrake = True print("Service Brake Activated") self.setPointSpeed = 0 self.power = 0 self.displayUpdate() #serviceBrakeDeactivated: used when service brake released def serviceBrakeDeactivated(self): self.serviceBrake = False print("Service Brake Released") self.displayUpdate() #onPassengerBrakeActivated: act upon passenger brake def onPassengerBrakeActivated(self): print("Passenger Brake Activated!") self.ui.textBrowser_16.setStyleSheet( u"background-color: rgb(255, 0, 0);") self.emergencyBrake = True self.setPointSpeed = 0 self.commandedSpeed = 0 #onUpdateTCFake: used to act upon TC Values def onUpdateTCFake(self): self.commandedSpeed = (2.237 * float(self.ui.inputCommanded.toPlainText())) self.authority = float( float(self.ui.inputAuthority.toPlainText()) * 3.28084) print("Fake Commanded" + str(self.commandedSpeed)) print("Fake Authority" + str(self.authority)) if (self.authority <= 0): self.onAuthorityExpiration() #encoding the track circuit stuff cmdInt = int(float(self.commandedSpeed)) cmdFloat = int(((float(self.commandedSpeed) - cmdInt) * 10)) authInt = int(float(self.authority)) authFloat = int(((float(self.authority) - authInt) * 10)) if (self.ui.causePickupFailure.checkState()): self.encodedTC = (cmdInt - 6 & 255) else: self.encodedTC = (cmdInt & 255) self.encodedTC += (cmdFloat & 15) << 8 self.encodedTC += (authInt & 255) << 12 self.encodedTC += (authFloat & 15) << 20 self.encodedTC += ( (cmdInt + cmdFloat + authFloat + authInt) & 1023) << 24 print(self.encodedTC) self.decodeTC() #updating the display self.displayUpdate() #onUpdateActualFake: used for updating current speed def onUpdateActualFake(self): self.actualSpeedSecond = self.actualSpeedLast self.actualSpeedLast = self.actualSpeed self.actualSpeed = (2.237 * float(self.ui.actualSpeed.toPlainText())) #checking to see if there is train engine failure self.detectEngineFailure() self.detectBrakeFailure() self.displayUpdate() #onBrakeFailure: used to detect engine failures def detectEngineFailure(self): #defined by Train Model #stephen send a value as our current velocity symbolizing problem with engine if (self.actualSpeed == 666): self.onEngineFailure() #causeEngineFailure: used to cause engine failures def causeEngineFailure(self): self.actualSpeed = 666 self.detectEngineFailure() #onEngineFailure: used to act upon engine failures def onEngineFailure(self): self.ui.textBrowser_13.setStyleSheet( u"background-color: rgb(255, 0, 0);") self.emergencyBrake = true #detectBrakeFailure: used to detect brake failures def detectBrakeFailure(self): # a brake failure is actually when the brakes are stuck on, so if we're slowing down, no brakes are applied and power is not 0 #print("Brake Failure Detection Occuring") #print("Service Brake: " + str(self.serviceBrake)) if (not self.serviceBrake and (self.actualSpeedSecond > self.actualSpeedLast and self.actualSpeedLast > self.actualSpeed) and not self.power == 0): self.onBrakeFailure() #causeBrakeFailure: used to simulate brake failures def causeBrakeFailure(self): self.actualSpeedSecond = 17 self.actualSpeedLast = 16 self.actualSpeed = 15 self.serviceBrake = False self.detectBrakeFailure() #onBrakeFailure: used to act on brake failures def onBrakeFailure(self): self.ui.textBrowser_14.setStyleSheet( u"background-color: rgb(255, 0, 0);") self.emergencyBrake = True #onUpdateBeaconFake: Used to update beacon values def onUpdateBeaconFake(self): beaconNum = self.ui.stationNameFake.currentIndex() #encoding my beacon self.encodedBeacon = int(self.ui.stationUpcoming.checkState()) >> 1 print(bin(self.encodedBeacon)) self.encodedBeacon += ( int(self.ui.leftDoorsFake.checkState()) >> 1) << 1 self.encodedBeacon += ( int(self.ui.rightDoorsFake.checkState()) >> 1) << 2 self.encodedBeacon += ( int(self.ui.exteriorLightsFake.checkState()) >> 1) << 3 self.encodedBeacon += (beaconNum & 31) << 4 print(bin(self.encodedBeacon)) self.displayUpdate() self.decodeBeacon() #decodeBeacon: Used to decode beacon def decodeBeacon(self): self.upcomingStation = (self.encodedBeacon & 1) self.leftDoorsOpen = (self.encodedBeacon >> 1) & 1 self.rightDoorsOpen = (self.encodedBeacon >> 2) & 1 self.exteriorLightsOn = (self.encodedBeacon >> 3) & 1 self.station = self.stationArray[((self.encodedBeacon >> 4) & 31)] self.buildAnnouncement() if (self.autoMode): self.onAnnouncement() self.displayUpdate() #buildAnnouncement: Used to build announcement def buildAnnouncement(self): if (self.upcomingStation): self.announcement = str("Arriving at " + self.station + " Station. The doors will open on the ") if (self.leftDoorsOpen and self.rightDoorsOpen): self.announcement += "left and right.\n" elif (self.leftDoorsOpen): self.announcement += "left.\n" else: self.announcement += "right.\n" else: self.announcement = "" #onUpdateKpKi: Used to update Kp/Ki Values def onUpdateKpKi(self): self.pid.Ki = float(self.ui.kiInput.toPlainText()) self.pid.Kp = float(self.ui.kpInput.toPlainText()) print("Kp Ki values updated" + str(self.pid.Ki) + str(self.pid.Kp)) #checkSignalPickup: Used to detect and act on signal pickup failures def checkSignalPickup(self, tempCmdInt, tempCmdFloat, tempAuthInt, tempAuthFloat, tempCheckSum): if (tempCheckSum != tempCmdInt + tempCmdFloat + tempAuthInt + tempAuthFloat): print("Signal Pickup Failure") self.ui.textBrowser_15.setStyleSheet( u"background-color: rgb(255, 0, 0);") self.emergencyBrake = True else: self.ui.textBrowser_15.setStyleSheet( u"background-color: rgb(255, 255, 255);") #decodeTC: Used to decode track circuit def decodeTC(self): tempCmdInt = self.encodedTC & 255 tempCmdFloat = (self.encodedTC >> 8) & 15 tempAuthInt = (self.encodedTC >> 12) & 255 tempAuthFloat = (self.encodedTC >> 20) & 15 tempCheckSum = (self.encodedTC >> 24) & 1023 self.checkSignalPickup(tempCmdInt, tempCmdFloat, tempAuthInt, tempAuthFloat, tempCheckSum) #if((tempAuthInt == 0 and tempAuthFloat == 0)): #self.onAuthorityExpiration() #onAuthorityExpiration: Used as emergency procedure if authority has expired def onAuthorityExpiration(self): print("Authority Expired") self.setPointSpeed = 0 self.commandedSpeed = 0 self.emergencyBrake = True self.ui.textBrowser_5.setStyleSheet(u"background-color: rgb(255,0,0);") #pidLoop: used to calculate power def pidLoop(self): #print("INPID") print("auto mode = " + str(self.autoMode)) print("self.upComingStation = " + str(self.upcomingStation)) print("service brake" + str(self.serviceBrake)) print("emergency brake" + str(self.emergencyBrake)) if (self.autoMode and not self.upcomingStation and ((not self.serviceBrake) and (not self.emergencyBrake))): print("PID Auto") self.pid.setpoint = self.commandedSpeed self.power = self.pid(self.actualSpeed, dt=1) elif (not (self.setPointSpeed == 0) and not self.serviceBrake and not self.emergencyBrake and not self.ui.leftDoors.isChecked() and not self.ui.rightDoors.isChecked()): print("PID Manual") self.pid.setpoint = self.setPointSpeed self.power = self.pid(self.actualSpeed, dt=1) else: self.pid.setpoint = 0 self.power = 0 print("inPIDOff") self.displayUpdate() # print(self.power) #onAnnouncement: Used to display announcement def onAnnouncement(self): self.ui.announcementDisplay.setPlainText(str(self.announcement)) self.ui.intercomFake.setPlainText(str(self.announcement)) #onAutoManual: Used to differentiate between operation modes def onAutoManual(self): if (self.ui.automaticMode.isChecked()): self.autoMode = True print("Auto Mode Entered") #making the door checkboxes not usable self.ui.leftDoors.setEnabled(False) self.ui.rightDoors.setEnabled(False) else: self.autoMode = False print("Manual Mode Entered") #making the door checkboxes usable self.ui.leftDoors.setEnabled(True) self.ui.rightDoors.setEnabled(True) def onArrival(self): #if we are in autoMode, and we have upComingStation = true, stop the train if (self.autoMode == True): self.alreadyWaiting = True self.previousCommanded = self.commandedSpeed self.serviceBrake = True self.commandedSpeed = 0 self.power = 0 self.waitTimer = QTimer() self.waitTimer.timeout.connect(self.checkVel) self.waitTimer.start(1000) else: ... #Letting use open the doors on their own #openDoorsManual: used in Manual Mode to open/close doors def openDoorsManual(self): if (self.autoMode == False): if (self.actualSpeed == 0): if (self.leftDoors): self.ui.leftDoorsStatus.setPlainText("Open") self.ui.leftDoorsStatus.setStyleSheet( u"background-color: rgb(124,252,0);") if (self.rightDoors): self.ui.rightDoorsStatus.setPlainText("Open") self.ui.rightDoorsStatus.setStyleSheet( u"background-color: rgb(124,252,0);") self.openedr = True if (not self.leftDoors): self.ui.leftDoorsStatus.setPlainText("Closed") self.ui.leftDoorsStatus.setStyleSheet( u"background-color: rgb(255,255,255);") if (not self.rightDoors): self.ui.rightDoorsStatus.setPlainText("Closed") self.ui.rightDoorsStatus.setStyleSheet( u"background-color: rgb(255,255,255);") #checkVel: Used in Auto mode to see if doors can be opened def checkVel(self): if (self.actualSpeed == 0): self.waitTimer.stop() self.upcomingStation = False self.ui.stationUpcoming.setChecked(False) self.openDoors() #openDoors: Used in Auto mode to open doors def openDoors(self): #opening the doors if (self.leftDoorsOpen): self.ui.leftDoorsStatus.setPlainText("Open") self.ui.leftDoorsStatus.setStyleSheet( u"background-color: rgb(124,252,0);") if (self.rightDoorsOpen): self.ui.rightDoorsStatus.setPlainText("Open") self.ui.rightDoorsStatus.setStyleSheet( u"background-color: rgb(124,252,0);") self.waitForDisembark() #waitForDisembark: Used in Auto mode to close doors def waitForDisembark(self): #waiting for passengers to disembark self.waitTimer2 = QTimer() self.waitTimer2.timeout.connect(self.closeDoors) self.waitTimer2.start(4000) #where you would send out a signal to close doors #closeDoors: Used in Auto mode to close doors and keep going def closeDoors(self): self.waitTimer2.stop() print("Done with disembarking.") self.ui.stationUpcoming.setChecked(False) self.upcomingStation = False self.ui.rightDoorsStatus.setPlainText("Closed") self.ui.leftDoorsStatus.setPlainText("Closed") self.ui.leftDoorsStatus.setStyleSheet( u"background-color: rgb(255,255,255);") self.ui.rightDoorsStatus.setStyleSheet( u"background-color: rgb(255,255,255);") self.leftDoorsOpen = False self.rightDoorsOpen = False self.commandedSpeed = self.previousCommanded self.alreadyWaiting = False self.serviceBrake = False print("upcomingStation flag is" + str(self.upcomingStation)) def onTemperatureChange(self): self.temperature = int(self.ui.temperature.value()) print("Temp is " + str(self.temperature)) self.ui.textBrowser_12.setPlainText(str(self.temperature)) self.displayUpdate() def onExtLightsChanged(self): self.ui.exteriorLights_2.setChecked( self.ui.exteriorLights.checkState()) self.displayUpdate() def onIntLightsChanged(self): self.ui.interiorLights_2.setChecked( self.ui.interiorLights.checkState()) self.displayUpdate()
class SimpleTimer(QWidget, BaseTimer): paused = Signal() def __init__(self, parent=None): super().__init__(parent) self.timer = QTimer(self) self.timer.setSingleShot(True) self.update_timer = QTimer(self) self.setting_time = 0 self.remaining = 0 self.remain_label = QLabel('00:00', self) self.remain_label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.remain_label.setScaledContents(True) font = QFont() font.setPointSize(86) self.remain_label.setFont(font) self.timer_edit = QSpinBox(self) self.timer_edit.setRange(1, 99) self.timer_edit.setValue(25) self.set_ui() self.set_connection() self.show() def set_ui(self): vlayout = QVBoxLayout() vlayout.addWidget(self.remain_label, alignment=Qt.AlignHCenter) vlayout.addWidget(self.timer_edit) self.setLayout(vlayout) def set_connection(self): self.timer.timeout.connect(self.stop) self.update_timer.timeout.connect(self.remain_update) def start(self): self.setting_time = self.timer_edit.value() self.timer.start(self.setting_time * 60 * 1000) self.set_remain_update() self.started.emit() def set_remain_update(self): self.remain_update() self.update_timer.start(250) def stop(self): self.reset() self.finished.emit() def abort(self): self.reset() self.timer.stop() self.aborted.emit() def pause(self): self.update_timer.stop() self.remaining = self.timer.remainingTime() self.timer.stop() self.paused.emit() def resume(self): self.timer.start(self.remaining) self.set_remain_update() self.started.emit() def reset(self): self.timer.stop() self.update_timer.stop() self.remain_label.setText('00:00') self.remaining = 0 def get_notify_message(self): remaining = self.timer.remainingTime() / 1000 return '{0} minutes have passed.'.format(self.setting_time - int(remaining/60)) def remain_update(self): remaining = self.timer.remainingTime() / 1000 self.remain_label.setText('{min:02}:{sec:02}'.format( min=int(remaining / 60), sec=int(remaining % 60))) @property def name(self): return 'Simple Timer'
class AudioTest(QMainWindow): PUSH_MODE_LABEL = "Enable push mode" PULL_MODE_LABEL = "Enable pull mode" SUSPEND_LABEL = "Suspend playback" RESUME_LABEL = "Resume playback" DurationSeconds = 1 ToneSampleRateHz = 600 DataSampleRateHz = 44100 def __init__(self): super(AudioTest, self).__init__() self.m_device = QAudioDeviceInfo.defaultOutputDevice() self.m_output = None self.initializeWindow() self.initializeAudio() def initializeWindow(self): layout = QVBoxLayout() self.m_deviceBox = QComboBox() self.m_deviceBox.activated[int].connect(self.deviceChanged) for deviceInfo in QAudioDeviceInfo.availableDevices( QAudio.AudioOutput): self.m_deviceBox.addItem(deviceInfo.deviceName(), deviceInfo) layout.addWidget(self.m_deviceBox) self.m_modeButton = QPushButton() self.m_modeButton.clicked.connect(self.toggleMode) self.m_modeButton.setText(self.PUSH_MODE_LABEL) layout.addWidget(self.m_modeButton) self.m_suspendResumeButton = QPushButton( clicked=self.toggleSuspendResume) self.m_suspendResumeButton.setText(self.SUSPEND_LABEL) layout.addWidget(self.m_suspendResumeButton) volumeBox = QHBoxLayout() volumeLabel = QLabel("Volume:") self.m_volumeSlider = QSlider(Qt.Horizontal, minimum=0, maximum=100, singleStep=10) self.m_volumeSlider.valueChanged.connect(self.volumeChanged) volumeBox.addWidget(volumeLabel) volumeBox.addWidget(self.m_volumeSlider) layout.addLayout(volumeBox) window = QWidget() window.setLayout(layout) self.setCentralWidget(window) def initializeAudio(self): self.m_pullTimer = QTimer(self) self.m_pullTimer.timeout.connect(self.pullTimerExpired) self.m_pullMode = True self.m_format = QAudioFormat() self.m_format.setSampleRate(self.DataSampleRateHz) self.m_format.setChannelCount(1) self.m_format.setSampleSize(16) self.m_format.setCodec('audio/pcm') self.m_format.setByteOrder(QAudioFormat.LittleEndian) self.m_format.setSampleType(QAudioFormat.SignedInt) info = QAudioDeviceInfo(QAudioDeviceInfo.defaultOutputDevice()) if not info.isFormatSupported(self.m_format): qWarning("Default format not supported - trying to use nearest") self.m_format = info.nearestFormat(self.m_format) self.m_generator = Generator(self.m_format, self.DurationSeconds * 1000000, self.ToneSampleRateHz, self) self.createAudioOutput() def createAudioOutput(self): self.m_audioOutput = QAudioOutput(self.m_device, self.m_format) self.m_audioOutput.notify.connect(self.notified) self.m_audioOutput.stateChanged.connect(self.handleStateChanged) self.m_generator.start() self.m_audioOutput.start(self.m_generator) self.m_volumeSlider.setValue(self.m_audioOutput.volume() * 100) def deviceChanged(self, index): self.m_pullTimer.stop() self.m_generator.stop() self.m_audioOutput.stop() self.m_device = self.m_deviceBox.itemData(index) self.createAudioOutput() def volumeChanged(self, value): if self.m_audioOutput is not None: self.m_audioOutput.setVolume(value / 100.0) def notified(self): qWarning( "bytesFree = %d, elapsedUSecs = %d, processedUSecs = %d" % (self.m_audioOutput.bytesFree(), self.m_audioOutput.elapsedUSecs(), self.m_audioOutput.processedUSecs())) def pullTimerExpired(self): if self.m_audioOutput is not None and self.m_audioOutput.state( ) != QAudio.StoppedState: chunks = self.m_audioOutput.bytesFree( ) // self.m_audioOutput.periodSize() for _ in range(chunks): data = self.m_generator.read(self.m_audioOutput.periodSize()) if data is None or len( data) != self.m_audioOutput.periodSize(): break self.m_output.write(data) def toggleMode(self): self.m_pullTimer.stop() self.m_audioOutput.stop() if self.m_pullMode: self.m_modeButton.setText(self.PULL_MODE_LABEL) self.m_output = self.m_audioOutput.start() self.m_pullMode = False self.m_pullTimer.start(20) else: self.m_modeButton.setText(self.PUSH_MODE_LABEL) self.m_pullMode = True self.m_audioOutput.start(self.m_generator) self.m_suspendResumeButton.setText(self.SUSPEND_LABEL) def toggleSuspendResume(self): if self.m_audioOutput.state() == QAudio.SuspendedState: qWarning("status: Suspended, resume()") self.m_audioOutput.resume() self.m_suspendResumeButton.setText(self.SUSPEND_LABEL) elif self.m_audioOutput.state() == QAudio.ActiveState: qWarning("status: Active, suspend()") self.m_audioOutput.suspend() self.m_suspendResumeButton.setText(self.RESUME_LABEL) elif self.m_audioOutput.state() == QAudio.StoppedState: qWarning("status: Stopped, resume()") self.m_audioOutput.resume() self.m_suspendResumeButton.setText(self.SUSPEND_LABEL) elif self.m_audioOutput.state() == QAudio.IdleState: qWarning("status: IdleState") stateMap = { QAudio.ActiveState: "ActiveState", QAudio.SuspendedState: "SuspendedState", QAudio.StoppedState: "StoppedState", QAudio.IdleState: "IdleState" } def handleStateChanged(self, state): qWarning("state = " + self.stateMap.get(state, "Unknown"))
class TapList(QMainWindow): def __init__(self, parent=None): # init super(TapList, self).__init__(parent) self.infoOutput() self.loadConfig() self.initUI() self.initLayout() self.makeTapList(getFrom=self.data_source, menuSide=self.side) self.createContextMenu() # start renew timer self.timer = QTimer(self) self.timer.timeout.connect(self.renewTimer) self.startTimer() # load config parameter def loadConfig(self): self.config = ConfigParser() self.config.read('config.ini') self.window_title = self.config.get('default', 'window_title') self.side = int(self.config.get('default', 'side')) self.renew_period = int(self.config.get('default', 'renew_period')) self.data_source = int(self.config.get('default', 'data_source')) self.notic_0 = self.config.get('default', 'notic_0') self.notic_1 = self.config.get('default', 'notic_1') self.logo_path = self.config.get('default', 'logo_path') # setup GUI def initUI(self): # setup UI self.setWindowTitle("Taplist - Let's Beer Brewpub") # get screen width and height self.screen = QGuiApplication.primaryScreen().geometry() self.width = self.screen.width() self.height = self.screen.height() # set screen size self.resize(1920, 1080) # self.setMinimumWidth(800) self.layout = QGridLayout() # setup layout def initLayout(self): self.layout = QGridLayout() self.window = QWidget() self.window.setLayout(self.layout) # set header(logo) layout self.layoutH = QLabel() self.layoutH.setObjectName('header') self.logo = QPixmap('img/logo_w.png') self.layoutH.setPixmap(self.logo) self.layoutH.setFixedHeight(100) self.layoutH.setAlignment(Qt.AlignCenter) # set tap list item layout self.layout0 = QLabel() self.layout0.setObjectName('itme_0') self.layout4 = QLabel() self.layout4.setObjectName('itme_4') self.layout1 = QLabel() self.layout1.setObjectName('itme_1') self.layout5 = QLabel() self.layout5.setObjectName('itme_5') self.layout2 = QLabel() self.layout2.setObjectName('itme_2') self.layout6 = QLabel() self.layout6.setObjectName('itme_6') self.layout3 = QLabel() self.layout3.setObjectName('itme_3') self.layout7 = QLabel() self.layout7.setObjectName('itme_7') self.layout8 = QLabel() # splitter line self.layout8.setObjectName('splitter') self.layout8.setFixedWidth(3) # set footer(message bar) layout self.layoutF = QLabel() self.layoutF.setObjectName('footer') self.layoutF.setFixedHeight(120) self.layoutF.setAlignment(Qt.AlignCenter) if self.side==0: notic_text = self.notic_0 else: notic_text = self.notic_1 self.layoutF.setText(notic_text) # gridding self.layout.addWidget(self.layoutH, 0, 0, 1, 3) self.layout.addWidget(self.layout0, 1, 0) self.layout.addWidget(self.layout4, 1, 2) self.layout.addWidget(self.layout1, 2, 0) self.layout.addWidget(self.layout5, 2, 2) self.layout.addWidget(self.layout2, 3, 0) self.layout.addWidget(self.layout6, 3, 2) self.layout.addWidget(self.layout3, 4, 0) self.layout.addWidget(self.layout7, 4, 2) self.layout.addWidget(self.layout8, 1, 1, 4, 1) self.layout.addWidget(self.layoutF, 5, 0, 1, 3) self.setCentralWidget(self.window) def draw(self, targetLayout, itemData): self.layoutItemDetail = QGridLayout(parent=targetLayout) self.layoutItem_no = QLabel() self.layoutItem_no.setObjectName('layoutItem_no') self.layoutItem_no.setAlignment(Qt.AlignCenter) self.layoutItem_namecn = QLabel() self.layoutItem_namecn.setObjectName('layoutItem_namecn') self.layoutItem_namecn.setAlignment(Qt.AlignBottom) self.layoutItem_nameen = QLabel() self.layoutItem_nameen.setObjectName('layoutItem_nameen') self.layoutItem_data = QLabel() self.layoutItem_data.setObjectName('layoutItem_data') self.layoutItem_data.setAlignment(Qt.AlignTop) self.layoutItem_price = QLabel() self.layoutItem_price.setObjectName('layoutItem_price') self.layoutItem_price.setAlignment(Qt.AlignBottom | Qt.AlignCenter) self.layoutItem_capacity = QLabel() self.layoutItem_capacity.setObjectName('layoutItem_capacity') self.layoutItem_capacity.setAlignment(Qt.AlignCenter | Qt.AlignTop) self.layoutItemDetail.addWidget(self.layoutItem_no, 0, 0, 4, 2) self.layoutItemDetail.addWidget(self.layoutItem_namecn,0, 3, 2, 12) self.layoutItemDetail.addWidget(self.layoutItem_nameen,2, 3, 1, 12) self.layoutItemDetail.addWidget(self.layoutItem_data, 3, 3, 1, 12) self.layoutItemDetail.addWidget(self.layoutItem_price, 0, 15, 2, 2) self.layoutItemDetail.addWidget(self.layoutItem_capacity, 2, 15, 2, 2) self.write2Layout(itemData) def write2Layout(self, itemData): tapnum = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'] self.layoutItem_no.setText('#' + tapnum[int(itemData['tapid'])]) self.layoutItem_namecn.setText( itemData['brewery'] + ' ' + itemData['beername'] + ' ' + itemData['beerstyle']) self.layoutItem_nameen.setText(itemData['beernameen']) self.layoutItem_data.setText( '酒精度 ' + itemData['abv'] + '% ABV 苦度 ' + itemData['ibu'] + ' IBU') self.layoutItem_price.setText('¥' + itemData['price']) self.layoutItem_capacity.setText( '杯型<br>' + itemData['glass_type'] + 'mL') def makeTapList(self, getFrom, menuSide=0): # get data self.data = self.getData(getdatafrom=getFrom, side=menuSide) # draw for i in range(8): exec('self.draw(targetLayout=self.layout%s,itemData=self.data[i])'%str(i)) # check item status if self.data[i]['status'] == '1': exec('self.layout%s.setStyleSheet("color: #373737; border: #373737; ")' % str(i)) def getData(self, getdatafrom, side): # get data from: # 0-test json data # 1-wechat interface\ # 2-dragon head database data = [] if getdatafrom==0: # load data from json file with open("sandbox/tapdata.json", "r") as f: data = json.load(f) if side==1: data = data[8:] elif getdatafrom==1: data = self.getDataFromWx(side=side) return data def getDataFromWx(self,side): data = [] token = self.wx_get_access_token() # tap info if side == 0: query_str = ''' db.collection("tapinfo").where({'tapid':_.lt(8)}).orderBy( 'tapid', 'asc').limit(8).get() ''' elif side == 1: query_str = ''' db.collection("tapinfo").where({'tapid':_.gte(8)}).orderBy('tapid', 'asc').limit(8).get() ''' try: query_result = self.wx_query_data(token=token, query=query_str) except: data = self.data print('err_log:get query data from wx failed.') else: for row in query_result: r = json.loads(row) if r['status'] == True: status = '0' elif r['status'] == False: status = '1' data.append({ 'tapid': str(r['tapid']), 'brewery': r['brewery'], 'beername': r['beername'], 'beerstyle': r['beerstyle'], 'beernameen': r['ebeername'], 'abv': r['abv'], 'ibu': r['ibu'], 'price': r['price'], 'glass_type': r['glass_type'], 'country': r['country'], 'status': status }) return data def wx_get_access_token(self): cs_url = 'https://api.weixin.qq.com/cgi-bin/token?' param = { 'grant_type': 'client_credential', 'appid': self.config.get('wx', 'appid'), 'secret': self.config.get('wx', 'secret') } headers = {'Accept': 'application/json'} try: r = requests.get(cs_url, params=param, headers=headers) except: print('err_log:get access token failed.') else: data = json.loads(r.text) if 'errcode' in data: print(data['errmsg']) else: return data['access_token'] def wx_get_collection(self, token): cs_url = 'https://api.weixin.qq.com/tcb/databasecollectionget?' params = { 'access_token': token } body = { 'limit': '10', 'offset': '0', 'env': self.config.get('wx', 'envid') } headers = {'content-type': 'application/json'} try: r = requests.post(cs_url, params=params, data=json.dumps(body), headers=headers) except: return self.data else: data = json.loads(r.text) if data['errcode'] == 0: return data['collections'] else: return data['errmsg'] def wx_query_data(self, token, query): cs_url = 'https://api.weixin.qq.com/tcb/databasequery?' params = { 'access_token': token } body = { 'env': self.config.get('wx', 'envid'), 'query': query } headers = {'content-type': 'application/json'} try: r = requests.post(cs_url, params=params, data=json.dumps(body), headers=headers) except: return self.data else: data = json.loads(r.text) if data['errcode'] == 0: return data['data'] else: return data['errmsg'] # remake menu (update/switch) def reNewMenu(self): self.window.setParent(None) self.initLayout() self.makeTapList(getFrom=self.data_source, menuSide=self.side) print(str(datetime.datetime.now()) + ' ==> refresh taplist side: ' + str(self.side) + ' from: ' + str(self.data_source) ) def renewTimer(self): self.endTimer() self.reNewMenu() self.startTimer() def startTimer(self): self.timer.start(self.renew_period) # 5000 单位是毫秒, 即 5 秒 def endTimer(self): self.timer.stop() # creat menu and bind action def createContextMenu(self): self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) self.customContextMenuRequested.connect(self.showContextMenu) self.contextMenu = QMenu(self) self.actionSWitch = self.contextMenu.addAction(u'Switch') self.actionRefresh = self.contextMenu.addAction(u'Refresh') self.actionSWitch.triggered.connect(self.switchSide) self.actionRefresh.triggered.connect(self.reNewMenu) # show menu def showContextMenu(self, pos): self.contextMenu.move(QCursor().pos()) self.contextMenu.show() # menu action switch menu side def switchSide(self): if self.side == 0: self.side = 1 elif self.side == 1: self.side = 0 self.reNewMenu() # output information def infoOutput(self): print(datetime.datetime.now()) print("Python version is " + sys.version) print("PySide6 version is " + PySide6.__version__)
class PomoTimer(QWidget, BaseTimer): """ PomoTimer: Pomodoro タイマーを提供する。 1 pomodoro: 25min short break: 5min long break: 15min long break after 4pomodoro. """ paused = Signal() def __init__(self): super().__init__() self.simple_timer = SimpleTimer(self) self.settings = { 'pomo': 25, 'short_break': 5, 'long_break': 15, 'long_break_after': 4 } self.state_dict = { 'work': 'Work!', 'break': 'Break.', 'pause': 'Pause', 'wait': "Let's start." } self.work_timer = QTimer(self) self.work_timer.setSingleShot(True) self.break_timer = QTimer(self) self.break_timer.setSingleShot(True) self.pomo_count = 0 self.pomo_count_lbl = QLabel(self) self.pomo_count_lbl.setText(f'{self.pomo_count} pomodoro finished.') self.state_lbl = QLabel(self) self.state_lbl.setText(self.state_dict['wait']) self.estimate_pomo = 0 self.estimate_label = QLabel(self) self.estimate_label.setText('Estimate: ') self.estimate_pomo_widget = QSpinBox(self) self.estimate_pomo_widget.setValue(4) self.estimate_pomo_widget.setSuffix(' pomo') self.estimate_pomo_widget.setRange(1, 20) self.set_ui() self.set_connection() def set_ui(self): layout = self.simple_timer.layout() i = layout.indexOf(self.simple_timer.timer_edit) layout.takeAt(i) hlayout = QHBoxLayout() hlayout.addWidget(self.estimate_label) hlayout.addWidget(self.estimate_pomo_widget) vlayout = QVBoxLayout() vlayout.addLayout(hlayout) vlayout.addWidget(self.pomo_count_lbl) vlayout.addWidget(self.state_lbl) vlayout.addWidget(self.simple_timer) self.setLayout(vlayout) def set_connection(self): self.work_timer.timeout.connect(self.timeout) self.break_timer.timeout.connect(self.start_work) def start(self): self.estimate_pomo = self.estimate_pomo_widget.value() self.start_work() def start_work(self): """ Start timer for working on the task. :return: """ self.state_lbl.setText(self.state_dict['work']) self.simple_timer.timer = self.work_timer self.simple_timer.timer_edit.setValue(self.settings['pomo']) self.simple_timer.start() self.started.emit() def start_break(self): """ Start timer for working on the rest. Short break is normal break, long break comes every some tasks(default 4). :return: """ self.state_lbl.setText(self.state_dict['break']) self.simple_timer.timer = self.break_timer if self.pomo_count % self.settings['long_break_after']: self.simple_timer.timer_edit.setValue(self.settings['short_break']) else: self.simple_timer.timer_edit.setValue(self.settings['long_break']) self.simple_timer.start() self.started.emit() def abort(self): self.reset() self.aborted.emit() def pause(self): self.state_lbl.setText(self.state_dict['pause']) self.simple_timer.pause() self.paused.emit() def resume(self): self.state_lbl.setText(self.state_dict['work']) self.simple_timer.resume() self.started.emit() def timeout(self): self.pomo_count += 1 if self.pomo_count >= self.estimate_pomo: self.reset() self.finished.emit() else: self.start_break() def reset(self): self.pomo_count = 0 self.simple_timer.reset() self.work_timer.stop() self.break_timer.stop() self.state_lbl.setText(self.state_dict['wait']) def get_notify_message(self): return '' @property def name(self): return 'Pomo Timer' def fake_start(self): self.simple_timer.setting_time = self.simple_timer.timer_edit.value() self.simple_timer.timer.start(self.simple_timer.setting_time * 1000) self.simple_timer.set_remain_update() self.simple_timer.started.emit()
class ModList(QTableView): def __init__(self, parent: QWidget, model: Model) -> None: super().__init__(parent) settings = QSettings() self.hoverIndexRow = -1 self.modmodel = model self.installLock = asyncio.Lock() self.setMouseTracking(True) self.setSelectionMode(QAbstractItemView.ExtendedSelection) self.setSelectionBehavior(QAbstractItemView.SelectRows) self.setWordWrap(False) self.setSortingEnabled(True) self.setFocusPolicy(Qt.StrongFocus) self.setAcceptDrops(True) self.setEditTriggers(QTableView.EditKeyPressed | QTableView.DoubleClicked) self.setShowGrid(False) self.setStyleSheet(''' QTableView { gridline-color: rgba(255,255,255,1); } QTableView::item:!selected:hover { background-color: rgb(217, 235, 249); } ''') self.setVerticalScrollMode(QAbstractItemView.ScrollPerPixel) self.setHorizontalScrollMode(QAbstractItemView.ScrollPerPixel) self.setContextMenuPolicy(Qt.CustomContextMenu) self.customContextMenuRequested.connect(self.showContextMenu) self.verticalHeader().hide() self.verticalHeader().setVisible(False) self.setSectionSize(settings.value('compactMode', 'False') == 'True') self.setCornerButtonEnabled(False) self.horizontalHeader().setHighlightSections(False) self.horizontalHeader().setStretchLastSection(True) self.horizontalHeader().setSectionsMovable(True) self.listmodel = ModListModel(self, model) self.filtermodel = ModListFilterModel(self, self.listmodel) self.setModel(self.filtermodel) self.setItemDelegate(ModListItemDelegate(self)) self.setSelectionModel(ModListSelectionModel(self, self.filtermodel)) if len(model): self.modCountLastUpdate = len(model) self.resizeColumnsToContents() else: self.modCountLastUpdate = -1 if settings.value('modlistHorizontalHeaderState'): self.horizontalHeader().restoreState( settings.value('modlistHorizontalHeaderState')) # type: ignore self.horizontalHeader().sectionMoved.connect( lambda: self.headerChangedEvent()) self.horizontalHeader().sectionResized.connect( lambda: self.headerChangedEvent()) self.setFocus() self.sortByColumn(3, Qt.AscendingOrder, False) self.sortByColumn(2, Qt.AscendingOrder, False) self.sortByColumn(1, Qt.AscendingOrder, False) if settings.value('modlistSortColumn') is not None and \ settings.value('modlistSortOrder') is not None: try: self.sortByColumn( cast(int, settings.value('modlistSortColumn', 1, int)), Qt.DescendingOrder if cast(int, settings.value('modlistSortOrder', 1, int)) else Qt.AscendingOrder, False) except Exception as e: logger.exception(f'could not restore sort order: {e}') self.horizontalHeader().sortIndicatorChanged.connect(self.sortByColumn) self.doubleClicked.connect(self.doubleClickEvent) model.updateCallbacks.append(self.modelUpdateEvent) # setup viewport caching to counter slow resizing with many table elements self.resizeTimer = QTimer(self) self.resizeTimer.setSingleShot(True) self.resizeTimer.setInterval(250) self.resizeTimer.timeout.connect(lambda: [ self.resizeTimer.stop(), self.viewport().repaint(), ]) self.viewportCache = QPixmap() self.viewportCacheSize = QSize(0, 0) # TODO: enhancement: offer option to read readme and other additional text files def setSectionSize(self, compact: bool) -> None: if compact: self.verticalHeader().setDefaultSectionSize(25) else: self.verticalHeader().setDefaultSectionSize(30) @debounce(200) async def headerChangedEvent(self) -> None: settings = QSettings() state = self.horizontalHeader().saveState() # call later to work around pyqt5 StopIteration exception asyncio.get_running_loop().call_later( 25 / 1000.0, lambda: settings.setValue('modlistHorizontalHeaderState', state)) def modelUpdateEvent(self, model: Model) -> None: if not self.modCountLastUpdate and len(self.modmodel): # if list was empty before, auto resize columns self.resizeColumnsToContents() self.modCountLastUpdate = len(self.modmodel) def mouseMoveEvent(self, event: QMouseEvent) -> None: self.hoverIndexRow = self.indexAt(event.pos()).row() return super().mouseMoveEvent(event) def wheelEvent(self, event: QWheelEvent) -> None: result = super().wheelEvent(event) # repaint previously hovered row on scroll avoid repaint artifacts index = self.model().index(self.hoverIndexRow, 0) self.hoverIndexRow = self.indexAt(event.position().toPoint()).row() rect = self.visualRect(index) rect.setLeft(0) rect.setRight(self.viewport().width()) self.viewport().repaint(rect) return result def leaveEvent(self, event: QEvent) -> None: index = self.model().index(self.hoverIndexRow, 0) # unset row hover state and repaint previously hovered row self.hoverIndexRow = -1 rect = self.visualRect(index) rect.setLeft(0) rect.setRight(self.viewport().width()) self.viewport().repaint(rect) return super().leaveEvent(event) def doubleClickEvent(self, index: QModelIndex) -> None: if self.filtermodel.mapToSource(index).column() == 0: mod = self.modmodel[self.filtermodel.mapToSource(index).row()] if mod.enabled: asyncio.create_task(self.modmodel.disable(mod)) else: asyncio.create_task(self.modmodel.enable(mod)) def resizeEvent(self, event: QResizeEvent) -> None: super().resizeEvent(event) if not self.resizeTimer.isActive( ) and event.size() != self.viewportCacheSize: self.viewportCacheSize = event.size() self.viewportCache = self.viewport().grab() self.resizeTimer.start() def paintEvent(self, event: QPaintEvent) -> None: if self.resizeTimer.isActive(): painter = QPainter(self.viewport()) painter.drawPixmap(0, 0, self.viewportCache) else: super().paintEvent(event) def selectionChanged(self, selected: QItemSelection, deselected: QItemSelection) -> None: super().selectionChanged(selected, deselected) def eventFilter(self, obj: QObject, event: QEvent) -> bool: return super().eventFilter(obj, event) def sortByColumn(self, col: int, order: Qt.SortOrder, save: bool = True) -> None: # type: ignore if save and col is not None and order is not None: settings = QSettings() settings.setValue('modlistSortColumn', col) settings.setValue('modlistSortOrder', 0 if order == Qt.AscendingOrder else 1) super().sortByColumn(col, order) def showContextMenu(self, pos: QPoint) -> None: mods = self.getSelectedMods() if not mods: return menu = QMenu(self) actionOpen = menu.addAction('&Open Directory') actionOpen.setIcon( QIcon(str(getRuntimePath('resources/icons/open-folder.ico')))) actionOpen.triggered.connect(lambda: [ util.openDirectory(self.modmodel.getModPath(mod)) # type: ignore for mod in mods ]) menu.addSeparator() actionEnable = menu.addAction('&Enable') actionEnable.triggered.connect( lambda: [asyncio.create_task(self.enableSelectedMods(True))]) actionEnable.setEnabled(not all(mod.enabled for mod in mods)) actionDisable = menu.addAction('&Disable') actionDisable.triggered.connect( lambda: [asyncio.create_task(self.enableSelectedMods(False))]) actionDisable.setEnabled(not all(not mod.enabled for mod in mods)) menu.addSeparator() actionUninstall = menu.addAction('&Uninstall') actionUninstall.triggered.connect( lambda: [asyncio.create_task(self.deleteSelectedMods())]) menu.addSeparator() actionOpenNexus = menu.addAction('Open &Nexus Mods page') actionOpenNexus.setIcon( QIcon(str(getRuntimePath('resources/icons/browse.ico')))) actionOpenNexus.triggered.connect(lambda: [ QDesktopServices.openUrl( QUrl(f'https://www.nexusmods.com/witcher3/mods/{modid}')) for modid in {mod.modid for mod in mods if mod.modid > 0} ]) actionOpenNexus.setEnabled(not all(mod.modid <= 0 for mod in mods)) menu.popup(self.viewport().mapToGlobal(pos)) def selectRowChecked(self, row: int) -> None: nums: int = self.filtermodel.rowCount() if row < nums and row >= 0: self.selectRow(row) elif nums > 0: self.selectRow(nums - 1) def getSelectedMods(self) -> List[Mod]: return [ self.modmodel[self.filtermodel.mapToSource(index).row()] for index in self.selectionModel().selectedRows() ] async def enableSelectedMods(self, enable: bool = True) -> None: if not self.selectionModel().hasSelection(): return mods = self.getSelectedMods() self.setDisabled(True) for mod in mods: try: if enable: await self.modmodel.enable(mod) else: await self.modmodel.disable(mod) except Exception as e: logger.bind(name=mod.filename).exception( f'Could not enable/disable mod: {e}') self.setDisabled(False) self.setFocus() async def deleteSelectedMods(self) -> None: if not self.selectionModel().hasSelection(): return self.setDisabled(True) mods = self.getSelectedMods() # TODO: incomplete: ask if selected mods should really be removed inds = self.selectedIndexes() self.selectionModel().clear() for mod in mods: try: await self.modmodel.remove(mod) except Exception as e: logger.bind( name=mod.filename).exception(f'Could not delete mod: {e}') asyncio.get_running_loop().call_later( 100 / 1000.0, partial(self.selectRowChecked, inds[0].row())) self.setDisabled(False) self.setFocus() async def updateModDetails(self, mod: Mod) -> bool: logger.bind(name=mod.filename, dots=True).debug('Requesting details for mod') if not mod.md5hash: logger.bind(name=mod.filename).warning( 'Could not get details for mod not installed from archive') return False try: details = await getModInformation(mod.md5hash) except Exception as e: logger.bind(name=mod.filename).warning(f'{e}') return False try: package = str(details[0]['mod']['name']) summary = str(details[0]['mod']['summary']) modid = int(details[0]['mod']['mod_id']) category = int(details[0]['mod']['category_id']) version = str(details[0]['file_details']['version']) fileid = int(details[0]['file_details']['file_id']) uploadname = str(details[0]['file_details']['name']) uploadtime = str(details[0]['file_details']['uploaded_time']) mod.package = package mod.summary = summary mod.modid = modid mod.category = getCategoryName(category) mod.version = version mod.fileid = fileid mod.uploadname = uploadname uploaddate = dateparser.parse(uploadtime) if uploaddate: mod.uploaddate = uploaddate.astimezone(tz=timezone.utc) else: logger.bind(name=mod.filename).debug( f'Could not parse date {uploadtime} in mod information response' ) except KeyError as e: logger.bind(name=mod.filename).exception( f'Could not find key "{str(e)}" in mod information response') return False try: await self.modmodel.update(mod) except Exception as e: logger.bind( name=mod.filename).exception(f'Could not update mod: {e}') return False return True async def updateSelectedModsDetails(self) -> None: if not self.selectionModel().hasSelection(): return self.setDisabled(True) updatetime = datetime.now(tz=timezone.utc) mods = self.getSelectedMods() logger.bind( newline=True, output=False).debug(f'Requesting details for {len(mods)} mods') results = await asyncio.gather( *[self.updateModDetails(mod) for mod in mods], loop=asyncio.get_running_loop(), return_exceptions=True) successes = sum(results) errors = len(results) - successes message = 'Updated details for {0} mods{1}'.format( successes, f' ({errors} errors)' if errors else '') if errors: logger.warning(message) else: logger.success(message) self.modmodel.setLastUpdateTime(updatetime) self.setDisabled(False) self.setFocus() async def changeSelectedModsPriority(self, delta: int) -> None: mods = self.getSelectedMods() await asyncio.gather(*[ self.modmodel.setPriority( mod, max(-1, min(9999, int(mod.priority + delta)))) for mod in mods if mod.datatype in ( 'mod', 'udf', ) ], loop=asyncio.get_running_loop()) self.modmodel.setLastUpdateTime(datetime.now(tz=timezone.utc)) def keyPressEvent(self, event: QKeyEvent) -> None: if event.key() == Qt.Key_Escape: self.selectionModel().clear() elif event.matches(QKeySequence.Delete): asyncio.create_task(self.deleteSelectedMods()) elif event.modifiers( ) & Qt.ControlModifier == Qt.ControlModifier and event.key( ) == Qt.Key_Up: asyncio.create_task(self.changeSelectedModsPriority(1)) elif event.modifiers( ) & Qt.ControlModifier == Qt.ControlModifier and event.key( ) == Qt.Key_Down: asyncio.create_task(self.changeSelectedModsPriority(-1)) elif event.modifiers( ) & Qt.ControlModifier == Qt.ControlModifier and event.key( ) == Qt.Key_P: index = self.selectionModel().selectedRows()[0] index = index.sibling(index.row(), 5) if index.flags() & Qt.ItemIsEditable: self.setCurrentIndex(index) self.edit(index) else: super().keyPressEvent(event) def setFilter(self, search: str) -> None: self.filtermodel.setFilterRegularExpression( QRegularExpression(search, QRegularExpression.CaseInsensitiveOption)) async def checkInstallFromURLs(self, paths: List[Union[str, QUrl]], local: bool = True, web: bool = True) -> None: await self.installLock.acquire() installed = 0 errors = 0 installtime = datetime.now(tz=timezone.utc) # remove duplicate paths paths = list(set(paths)) logger.bind(newline=True, output=False).debug('Starting install from URLs') try: results = await asyncio.gather(*[ self.installFromURL(path, local, web, installtime) for path in paths ], loop=asyncio.get_running_loop()) for result in results: installed += result[0] errors += result[1] except Exception as e: # we should never land here, but don't lock up the UI if it happens logger.exception(str(e)) errors += 1 if installed > 0 or errors > 0: log = logger.bind(modlist=bool(installed)) message = 'Installed {0} mods{1}'.format( installed, f' ({errors} errors)' if errors else '') if installed > 0 and errors > 0: log.warning(message) elif installed > 0: log.success(message) else: log.error(message) self.setDisabled(False) self.setFocus() self.installLock.release() async def installFromURL( self, path: Union[str, QUrl], local: bool = True, web: bool = True, installtime: Optional[datetime] = None) -> Tuple[int, int]: installed = 0 errors = 0 if not installtime: installtime = datetime.now(tz=timezone.utc) if isinstance(path, QUrl): path = path.toString() if web and isValidModDownloadUrl(path): self.setDisabled(True) logger.bind(dots=True, path=path).info(f'Installing mods from') i, e = await self.installFromFileDownload(path, installtime) installed += i errors += e elif local and isValidFileUrl(path): self.setDisabled(True) path = QUrl(path) logger.bind(dots=True, path=Path( path.toLocalFile())).info(f'Installing mods from') i, e = await self.installFromFile(Path(path.toLocalFile()), installtime) installed += i errors += e else: logger.bind(path=path).error('Could not install mods from') return installed, errors async def installFromFileDownload( self, url: str, installtime: Optional[datetime] = None) -> Tuple[int, int]: installed = 0 errors = 0 if not installtime: installtime = datetime.now(tz=timezone.utc) try: target = Path(urlparse(url).path) filename = re.sub(r'[^\w\-_\. ]', '_', unquote(target.name)) target = Path(tempfile.gettempdir()).joinpath( 'w3modmanager/download').joinpath(f'{filename}') except ValueError: logger.bind(name=url).exception('Wrong request URL') return 0, 1 try: target.parent.mkdir(parents=True, exist_ok=True) logger.bind(name=url).info('Starting to download file') await downloadFile(url, target) installed, errors = await self.installFromFile(target, installtime) except (RequestError, ResponseError, Exception) as e: logger.bind(name=url).exception(f'Failed to download file: {e}') return 0, 1 except Exception as e: logger.exception(str(e)) return 0, 1 finally: if target.is_file(): target.unlink() return installed, errors async def installFromFile( self, path: Path, installtime: Optional[datetime] = None) -> Tuple[int, int]: originalpath = path installed = 0 errors = 0 archive = path.is_file() source = None md5hash = '' details = None detailsrequest: Optional[asyncio.Task] = None if not installtime: installtime = datetime.now(tz=timezone.utc) try: if archive: # unpack archive, set source and request details md5hash = getMD5Hash(path) source = path settings = QSettings() if settings.value('nexusGetInfo', 'False') == 'True': logger.bind( path=str(path), dots=True).debug('Requesting details for archive') detailsrequest = asyncio.create_task( getModInformation(md5hash)) logger.bind(path=str(path), dots=True).debug('Unpacking archive') path = await extractMod(source) # validate and read mod valid, exhausted = containsValidMod(path, searchlimit=8) if not valid: if not exhausted and self.showContinueSearchDialog( searchlimit=8): if not containsValidMod(path): raise InvalidPathError(path, 'Invalid mod') elif not exhausted: raise InvalidPathError(path, 'Stopped searching for mod') else: raise InvalidPathError(path, 'Invalid mod') mods = await Mod.fromDirectory(path, searchCommonRoot=not archive) installedMods = [] # update mod details and add mods to the model for mod in mods: mod.md5hash = md5hash try: # TODO: incomplete: check if mod is installed, ask if replace await self.modmodel.add(mod) installedMods.append(mod) installed += 1 except ModExistsError: logger.bind(path=source if source else mod.source, name=mod.filename).error(f'Mod exists') errors += 1 continue # wait for details response if requested if detailsrequest: try: details = await detailsrequest except (RequestError, ResponseError, Exception) as e: logger.warning( f'Could not get information for {source.name if source else path.name}: {e}' ) # update mod with additional information if source or details: for mod in installedMods: if source: # set source if it differs from the scan directory, e.g. an archive mod.source = source if details: # set additional details if requested and available try: package = str(details[0]['mod']['name']) summary = str(details[0]['mod']['summary']) modid = int(details[0]['mod']['mod_id']) category = int(details[0]['mod']['category_id']) version = str( details[0]['file_details']['version']) fileid = int(details[0]['file_details']['file_id']) uploadname = str( details[0]['file_details']['name']) uploadtime = str( details[0]['file_details']['uploaded_time']) mod.package = package mod.summary = summary mod.modid = modid mod.category = getCategoryName(category) mod.version = version mod.fileid = fileid mod.uploadname = uploadname uploaddate = dateparser.parse(uploadtime) if uploaddate: mod.uploaddate = uploaddate.astimezone( tz=timezone.utc) else: logger.bind(name=mod.filename).debug( f'Could not parse date {uploadtime} in mod information response' ) except KeyError as e: logger.bind(name=mod.filename).exception( f'Could not find key "{str(e)}" in mod information response' ) try: await self.modmodel.update(mod) except Exception: logger.bind(name=mod.filename).warning( 'Could not update mod details') except ModelError as e: logger.bind(path=e.path).error(e.message) errors += 1 except InvalidPathError as e: # TODO: enhancement: better install error message logger.bind(path=e.path).error(e.message) errors += 1 except FileNotFoundError as e: logger.bind( path=e.filename).error(e.strerror if e.strerror else str(e)) errors += 1 except OSError as e: logger.bind( path=e.filename).error(e.strerror if e.strerror else str(e)) errors += 1 except Exception as e: logger.exception(str(e)) errors += 1 finally: if detailsrequest and not detailsrequest.done(): detailsrequest.cancel() if archive and not path == originalpath: try: util.removeDirectory(path) except Exception: logger.bind(path=path).warning( 'Could not remove temporary directory') self.modmodel.setLastUpdateTime(installtime) self.repaint() return installed, errors def showContinueSearchDialog(self, searchlimit: int) -> bool: messagebox = QMessageBox(self) messagebox.setWindowTitle('Unusual search depth') messagebox.setText(f''' <p>No mod detected after searching through {searchlimit} directories.</p> <p>Are you sure this is a valid mod?</p> ''') messagebox.setTextFormat(Qt.RichText) messagebox.setStandardButtons(QMessageBox.Cancel) yes: QPushButton = QPushButton(' Yes, continue searching ', messagebox) yes.setAutoDefault(True) yes.setDefault(True) messagebox.addButton(yes, QMessageBox.YesRole) messagebox.exec_() return messagebox.clickedButton() == yes def dropEvent(self, event: QDropEvent) -> None: event.accept() self.setDisabled(True) self.repaint() asyncio.create_task(self.checkInstallFromURLs(event.mimeData().urls())) def dragEnterEvent(self, event: QDragEnterEvent) -> None: self.setDisabled(True) self.repaint() urls = event.mimeData().urls() if not urls: self.setDisabled(False) self.setFocus() event.ignore() return for url in urls: try: parse = urlparse(url.toString()) if parse.scheme not in ['file']: self.setDisabled(False) event.ignore() return filepath = Path(url.toLocalFile()) if isArchive(filepath) or containsValidMod(filepath, searchlimit=8)[0]: self.setDisabled(False) event.accept() return except Exception as e: logger.debug(str(e)) self.setDisabled(False) self.setFocus() event.ignore() def dragMoveEvent(self, event: QDragMoveEvent) -> None: event.accept() def dragLeaveEvent(self, event: QDragLeaveEvent) -> None: event.accept()
class ByteView(QAbstractScrollArea, View): def __init__(self, parent, data): QAbstractScrollArea.__init__(self, parent) View.__init__(self) View.setBinaryDataNavigable(self, True) self.setupView(self) self.data = data self.byte_mapping = [ u' ', u'☺', u'☻', u'♥', u'♦', u'♣', u'♠', u'•', u'◘', u'○', u'◙', u'♂', u'♀', u'♪', u'♫', u'☼', u'▸', u'◂', u'↕', u'‼', u'¶', u'§', u'▬', u'↨', u'↑', u'↓', u'→', u'←', u'∟', u'↔', u'▴', u'▾', u' ', u'!', u'"', u'#', u'$', u'%', u'&', u'\'', u'(', u')', u'*', u'+', u',', u'-', u'.', u'/', u'0', u'1', u'2', u'3', u'4', u'5', u'6', u'7', u'8', u'9', u':', u';', u'<', u'=', u'>', u'?', u'@', u'A', u'B', u'C', u'D', u'E', u'F', u'G', u'H', u'I', u'J', u'K', u'L', u'M', u'N', u'O', u'P', u'Q', u'R', u'S', u'T', u'U', u'V', u'W', u'X', u'Y', u'Z', u'[', u'\\', u']', u'^', u'_', u'`', u'a', u'b', u'c', u'd', u'e', u'f', u'g', u'h', u'i', u'j', u'k', u'l', u'm', u'n', u'o', u'p', u'q', u'r', u's', u't', u'u', u'v', u'w', u'x', u'y', u'z', u'{', u'|', u'}', u'~', u'⌂', u'Ç', u'ü', u'é', u'â', u'ä', u'à', u'å', u'ç', u'ê', u'ë', u'è', u'ï', u'î', u'ì', u'Ä', u'Å', u'É', u'æ', u'Æ', u'ô', u'ö', u'ò', u'û', u'ù', u'ÿ', u'Ö', u'Ü', u'¢', u'£', u'¥', u'₧', u'ƒ', u'á', u'í', u'ó', u'ú', u'ñ', u'Ñ', u'ª', u'º', u'¿', u'⌐', u'¬', u'½', u'¼', u'¡', u'«', u'»', u'░', u'▒', u'▓', u'│', u'┤', u'╡', u'╢', u'╖', u'╕', u'╣', u'║', u'╗', u'╝', u'╜', u'╛', u'┐', u'└', u'┴', u'┬', u'├', u'─', u'┼', u'╞', u'╟', u'╚', u'╔', u'╩', u'╦', u'╠', u'═', u'╬', u'╧', u'╨', u'╤', u'╥', u'╙', u'╘', u'╒', u'╓', u'╫', u'╪', u'┘', u'┌', u'█', u'▄', u'▌', u'▐', u'▀', u'α', u'ß', u'Γ', u'π', u'Σ', u'σ', u'µ', u'τ', u'Φ', u'Θ', u'Ω', u'δ', u'∞', u'φ', u'ε', u'∩', u'≡', u'±', u'≥', u'≤', u'⌠', u'⌡', u'÷', u'≈', u'°', u'∙', u'·', u'√', u'ⁿ', u'²', u'■', u' ' ] self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn) self.setFocusPolicy(Qt.StrongFocus) self.cursorAddr = self.data.start self.prevCursorAddr = self.cursorAddr self.selectionStartAddr = self.cursorAddr self.topAddr = self.cursorAddr self.topLine = 0 self.selectionVisible = False self.caretVisible = False self.caretBlink = True self.leftButtonDown = False self.cols = 128 self.updatesRequired = False self.visibleRows = 1 self.lines = [] self.updateRanges() areaSize = self.viewport().size() self.adjustSize(areaSize.width(), areaSize.height()) if self.allocatedLength > 0x7fffffff: self.scrollBarMultiplier = (self.allocatedLength // 0x7fffffff) + 1 else: self.scrollBarMultiplier = 1 self.wheelDelta = 0 self.updatingScrollBar = False self.verticalScrollBar().setRange(0, (self.allocatedLength - 1) // self.scrollBarMultiplier) self.verticalScrollBar().sliderMoved.connect(self.scrollBarMoved) self.verticalScrollBar().actionTriggered.connect(self.scrollBarAction) self.cursorTimer = QTimer(self) self.cursorTimer.setInterval(500) self.cursorTimer.setSingleShot(False) self.cursorTimer.timeout.connect(self.cursorTimerEvent) self.cursorTimer.start() self.updateTimer = QTimer(self) self.updateTimer.setInterval(200) self.updateTimer.setSingleShot(False) #self.updateTimer.timeout.connect(self.updateTimerEvent) self.actionHandler().bindAction("Move Cursor Up", UIAction(lambda ctxt: self.up(False))) self.actionHandler().bindAction( "Move Cursor Down", UIAction(lambda ctxt: self.down(False))) self.actionHandler().bindAction( "Move Cursor Left", UIAction(lambda ctxt: self.left(1, False))) self.actionHandler().bindAction( "Move Cursor Right", UIAction(lambda ctxt: self.right(1, False))) self.actionHandler().bindAction( "Move Cursor Word Left", UIAction(lambda ctxt: self.left(8, False))) self.actionHandler().bindAction( "Move Cursor Word Right", UIAction(lambda ctxt: self.right(8, False))) self.actionHandler().bindAction("Extend Selection Up", UIAction(lambda ctxt: self.up(True))) self.actionHandler().bindAction("Extend Selection Down", UIAction(lambda ctxt: self.down(True))) self.actionHandler().bindAction( "Extend Selection Left", UIAction(lambda ctxt: self.left(1, True))) self.actionHandler().bindAction( "Extend Selection Right", UIAction(lambda ctxt: self.right(1, True))) self.actionHandler().bindAction( "Extend Selection Word Left", UIAction(lambda ctxt: self.left(8, True))) self.actionHandler().bindAction( "Extend Selection Word Right", UIAction(lambda ctxt: self.right(8, True))) self.actionHandler().bindAction( "Page Up", UIAction(lambda ctxt: self.pageUp(False))) self.actionHandler().bindAction( "Page Down", UIAction(lambda ctxt: self.pageDown(False))) self.actionHandler().bindAction( "Extend Selection Page Up", UIAction(lambda ctxt: self.pageUp(True))) self.actionHandler().bindAction( "Extend Selection Page Down", UIAction(lambda ctxt: self.pageDown(True))) self.actionHandler().bindAction( "Move Cursor to Start of Line", UIAction(lambda ctxt: self.moveToStartOfLine(False))) self.actionHandler().bindAction( "Move Cursor to End of Line", UIAction(lambda ctxt: self.moveToEndOfLine(False))) self.actionHandler().bindAction( "Move Cursor to Start of View", UIAction(lambda ctxt: self.moveToStartOfView(False))) self.actionHandler().bindAction( "Move Cursor to End of View", UIAction(lambda ctxt: self.moveToEndOfView(False))) self.actionHandler().bindAction( "Extend Selection to Start of Line", UIAction(lambda ctxt: self.moveToStartOfLine(True))) self.actionHandler().bindAction( "Extend Selection to End of Line", UIAction(lambda ctxt: self.moveToEndOfLine(True))) self.actionHandler().bindAction( "Extend Selection to Start of View", UIAction(lambda ctxt: self.moveToStartOfView(True))) self.actionHandler().bindAction( "Extend Selection to End of View", UIAction(lambda ctxt: self.moveToEndOfView(True))) def getData(self): return self.data def getStart(self): return self.data.start def getEnd(self): return self.data.end def getLength(self): return self.getEnd() - self.getStart() def getCurrentOffset(self): return self.cursorAddr def getSelectionOffsets(self): start = self.selectionStartAddr end = self.cursorAddr if end < start: t = start start = end end = t return (start, end) def updateRanges(self): self.ranges = self.data.allocated_ranges # Remove regions not backed by the file for i in self.data.segments: if i.data_length < len(i): self.removeRange(i.start + i.data_length, i.end) self.allocatedLength = 0 for i in self.ranges: self.allocatedLength += i.end - i.start def removeRange(self, begin, end): newRanges = [] for i in self.ranges: if (end <= i.start) or (begin >= i.end): newRanges.append(i) elif (begin <= i.start) and (end >= i.end): continue elif (begin <= i.start) and (end < i.end): newRanges.append(AddressRange(end, i.end)) elif (begin > i.start) and (end >= i.end): newRanges.append(AddressRange(i.start, begin)) else: newRanges.append(AddressRange(i.start, begin)) newRanges.append(AddressRange(end, i.end)) self.ranges = newRanges def setTopToAddress(self, addr): for i in self.ranges: if (addr >= i.start) and (addr <= i.end): self.topAddr = addr - ((addr - i.start) % self.cols) if self.topAddr < i.start: self.topAddr = i.start return if i.start > addr: self.topAddr = i.start return self.topAddr = self.data.end def navigate(self, addr): if addr < self.getStart(): return False if addr > self.getEnd(): return False self.cursorAddr = self.getStart() for i in self.ranges: if i.start > addr: break if addr > i.end: self.cursorAddr = i.end elif addr >= i.start: self.cursorAddr = addr else: self.cursorAddr = i.start self.setTopToAddress(self.cursorAddr) self.refreshLines() self.showContextAroundTop() self.selectNone() self.repositionCaret() return True def updateFonts(self): areaSize = self.viewport().size() self.adjustSize(areaSize.width(), areaSize.height()) def getFont(self): userFont = binaryninjaui.getMonospaceFont(self) if sys.platform == "darwin": # Some fonts aren't fixed width across all characters, use a known good one font = QFont("Menlo", userFont.pointSize()) font.setKerning(False) else: font = userFont return font def createRenderContext(self): render = RenderContext(self) render.setFont(self.getFont()) return render def adjustSize(self, width, height): self.addrWidth = max(len("%x" % self.data.end), 8) render = self.createRenderContext() cols = ((width - 4) // render.getFontWidth()) - (self.addrWidth + 2) if cols < 1: cols = 1 if cols != self.cols: self.cols = cols if self.topLine < len(self.lines): self.setTopToAddress(self.lines[self.topLine].address) else: self.setTopToAddress(self.cursorAddr) self.refreshLines() self.visibleRows = (height - 4) // render.getFontHeight() self.verticalScrollBar().setPageStep(self.visibleRows * self.cols // self.scrollBarMultiplier) self.refreshAtCurrentLocation() self.viewport().update() def getContiguousOffsetForAddress(self, addr): offset = 0 for i in self.ranges: if (addr >= i.start) and (addr <= i.end): offset += addr - i.start break offset += i.end - i.start return offset def getAddressForContiguousOffset(self, offset): cur = 0 for i in self.ranges: if offset < (cur + (i.end - i.start)): return i.start + (offset - cur) cur += i.end - i.start return self.data.end def refreshLines(self): addr = self.topAddr self.lines = [] self.topLine = 0 self.bottomAddr = self.topAddr self.updateRanges() if self.allocatedLength > 0x7fffffff: self.scrollBarMultiplier = (self.allocatedLength // 0x7fffffff) + 1 else: self.scrollBarMultiplier = 1 self.updatingScrollBar = True self.verticalScrollBar().setRange(0, (self.allocatedLength - 1) // self.scrollBarMultiplier) self.verticalScrollBar().setValue( self.getContiguousOffsetForAddress(addr) // self.scrollBarMultiplier) self.updatingScrollBar = False self.updateCache() self.viewport().update() UIContext.updateStatus() def refreshAtCurrentLocation(self): if self.topLine < len(self.lines): self.topAddr = self.lines[self.topLine].address self.refreshLines() def createLine(self, addr, length, separator): if separator: return ByteViewLine(addr, length, u'', True) else: data = self.data.read(addr, length) text = u''.join([self.byte_mapping[value] for value in data]) return ByteViewLine(addr, length, text, False) def cachePreviousLines(self): prevEnd = None for i in self.ranges: if (self.topAddr > i.start) and (self.topAddr <= i.end): startLine = self.topAddr - ( (self.topAddr - i.start) % self.cols) if startLine == self.topAddr: startLine -= self.cols if startLine < i.start: startLine = i.start line = self.createLine(startLine, self.topAddr - startLine, False) self.lines.insert(0, line) self.topLine += 1 self.topAddr = startLine return True elif i.start >= self.topAddr: if prevEnd is None: return False line = self.createLine(prevEnd, i.start - prevEnd, True) self.lines.insert(0, line) self.topLine += 1 self.topAddr = prevEnd return True prevEnd = i.end if prevEnd is None: return False line = self.createLine(prevEnd, self.topAddr - prevEnd, True) self.lines.insert(0, line) self.topLine += 1 self.topAddr = prevEnd return True def cacheNextLines(self): lastAddr = self.data.start for i in self.ranges: if (self.bottomAddr >= i.start) and (self.bottomAddr < i.end): endLine = self.bottomAddr + self.cols if endLine > i.end: endLine = i.end line = self.createLine(self.bottomAddr, endLine - self.bottomAddr, False) self.lines.append(line) self.bottomAddr = endLine return True elif i.start > self.bottomAddr: line = self.createLine(self.bottomAddr, i.start - self.bottomAddr, True) self.lines.append(line) self.bottomAddr = i.start return True lastAddr = i.end if self.bottomAddr == lastAddr: # Ensure there is a place for the cursor at the end of the file if (len(self.lines) > 0) and (self.lines[-1].length != self.cols): return False line = self.createLine(lastAddr, 0, False) self.lines.append(line) self.bottomAddr += 1 return True return False def updateCache(self): # Cache enough for the current page and the next page while (len(self.lines) - self.topLine) <= (self.visibleRows * 2): if not self.cacheNextLines(): break # Cache enough for the previous page while self.topLine <= self.visibleRows: if not self.cachePreviousLines(): break # Trim cache if self.topLine > (self.visibleRows * 4): self.lines = self.lines[self.topLine - (self.visibleRows * 4):] self.topLine = self.visibleRows * 4 self.topAddr = self.lines[0].address if (len(self.lines) - self.topLine) > (self.visibleRows * 5): self.bottomAddr = self.lines[self.topLine + (self.visibleRows * 5)].address self.lines = self.lines[0:self.topLine + (self.visibleRows * 5)] def scrollLines(self, count): newOffset = self.topLine + count if newOffset < 0: self.topLine = 0 elif newOffset >= len(self.lines): self.topLine = len(self.lines) - 1 else: self.topLine = newOffset self.updateCache() self.viewport().update() if self.topLine < len(self.lines): self.updatingScrollBar = True addr = self.lines[self.topLine].address self.verticalScrollBar().setValue( self.getContiguousOffsetForAddress(addr) // self.scrollBarMultiplier) self.updatingScrollBar = False def showContextAroundTop(self): scroll = self.visibleRows // 4 if scroll > self.topLine: self.topLine = 0 else: self.topLine -= scroll if self.topLine < len(self.lines): self.updatingScrollBar = True addr = self.lines[self.topLine].address self.verticalScrollBar().setValue( self.getContiguousOffsetForAddress(addr) // self.scrollBarMultiplier) self.updatingScrollBar = False self.updateCache() def repositionCaret(self): self.updateCache() found = False for i in range(0, len(self.lines)): if (((self.cursorAddr >= self.lines[i].address) and (self.cursorAddr < (self.lines[i].address + self.lines[i].length))) or (((i + 1) == len(self.lines)) and (self.cursorAddr == (self.lines[i].address + self.lines[i].length)))): if i < self.topLine: self.topLine = i elif i > (self.topLine + self.visibleRows - 1): self.topLine = i - (self.visibleRows - 1) self.updatingScrollBar = True addr = self.lines[self.topLine].address self.verticalScrollBar().setValue( self.getContiguousOffsetForAddress(addr) // self.scrollBarMultiplier) self.updatingScrollBar = False self.updateCache() self.viewport().update() found = True break if not found: self.setTopToAddress(self.cursorAddr) self.refreshLines() self.showContextAroundTop() # Force caret to be visible and repaint self.caretBlink = True self.cursorTimer.stop() self.cursorTimer.start() self.updateCaret() UIContext.updateStatus() def updateCaret(self): # Rerender both the old caret position and the new caret position render = self.createRenderContext() for i in range(self.topLine, min(len(self.lines), self.topLine + self.visibleRows)): if (((self.prevCursorAddr >= self.lines[i].address) and (self.prevCursorAddr <= (self.lines[i].address + self.lines[i].length))) or ((self.cursorAddr >= self.lines[i].address) and (self.cursorAddr <= (self.lines[i].address + self.lines[i].length)))): self.viewport().update(0, (i - self.topLine) * render.getFontHeight(), self.viewport().size().width(), render.getFontHeight() + 3) def resizeEvent(self, event): self.adjustSize(event.size().width(), event.size().height()) def paintEvent(self, event): p = QPainter(self.viewport()) render = self.createRenderContext() render.init(p) charWidth = render.getFontWidth() charHeight = render.getFontHeight() # Compute range that needs to be updated topY = event.rect().y() botY = topY + event.rect().height() topY = (topY - 2) // charHeight botY = ((botY - 2) // charHeight) + 1 # Compute selection range selection = False selStart, selEnd = self.getSelectionOffsets() if selStart != selEnd: selection = True # Draw selection if selection: startY = None endY = None startX = None endX = None for i in range(0, len(self.lines)): if selStart >= self.lines[i].address: startY = i - self.topLine startX = selStart - self.lines[i].address if startX > self.cols: startX = self.cols if selEnd >= self.lines[i].address: endY = i - self.topLine endX = selEnd - self.lines[i].address if endX > self.cols: endX = self.cols if startY is not None and endY is not None: p.setPen(binaryninjaui.getThemeColor( ThemeColor.SelectionColor)) p.setBrush( binaryninjaui.getThemeColor(ThemeColor.SelectionColor)) if startY == endY: p.drawRect(2 + (self.addrWidth + 2 + startX) * charWidth, 2 + startY * charHeight, (endX - startX) * charWidth, charHeight + 1) else: p.drawRect(2 + (self.addrWidth + 2 + startX) * charWidth, 2 + startY * charHeight, (self.cols - startX) * charWidth, charHeight + 1) if endX > 0: p.drawRect(2 + (self.addrWidth + 2) * charWidth, 2 + endY * charHeight, endX * charWidth, charHeight + 1) if (endY - startY) > 1: p.drawRect(2 + (self.addrWidth + 2) * charWidth, 2 + (startY + 1) * charHeight, self.cols * charWidth, ((endY - startY) - 1) * charHeight + 1) # Paint each line color = self.palette().color(QPalette.WindowText) for y in range(topY, botY): if (y + self.topLine) < 0: continue if (y + self.topLine) >= len(self.lines): break if self.lines[y + self.topLine].separator: render.drawLinearDisassemblyLineBackground( p, LinearDisassemblyLineType.NonContiguousSeparatorLineType, QRect(0, 2 + y * charHeight, event.rect().width(), charHeight), 0) continue lineStartAddr = self.lines[y + self.topLine].address addrStr = "%.8x" % lineStartAddr length = self.lines[y + self.topLine].length text = self.lines[y + self.topLine].text cursorCol = None if (((self.cursorAddr >= lineStartAddr) and (self.cursorAddr < (lineStartAddr + length))) or (((y + self.topLine + 1) >= len(self.lines)) and (self.cursorAddr == (lineStartAddr + length)))): cursorCol = self.cursorAddr - lineStartAddr render.drawText( p, 2, 2 + y * charHeight, binaryninjaui.getThemeColor(ThemeColor.AddressColor), addrStr) render.drawText(p, 2 + (self.addrWidth + 2) * charWidth, 2 + y * charHeight, color, text) if self.caretVisible and self.caretBlink and not selection and cursorCol is not None: p.setPen(Qt.NoPen) p.setBrush(self.palette().color(QPalette.WindowText)) p.drawRect(2 + (self.addrWidth + 2 + cursorCol) * charWidth, 2 + y * charHeight, charWidth, charHeight + 1) caretTextColor = self.palette().color(QPalette.Base) byteValue = self.data.read(lineStartAddr + cursorCol, 1) if len(byteValue) == 1: byteStr = self.byte_mapping[byteValue[0]] render.drawText( p, 2 + (self.addrWidth + 2 + cursorCol) * charWidth, 2 + y * charHeight, caretTextColor, byteStr) def wheelEvent(self, event): if event.orientation() == Qt.Horizontal: return self.wheelDelta -= event.delta() if (self.wheelDelta <= -40) or (self.wheelDelta >= 40): lines = self.wheelDelta // 40 self.wheelDelta -= lines * 40 self.scrollLines(lines) def scrollBarMoved(self, value): if self.updatingScrollBar: return self.wheelDelta = 0 addr = self.getAddressForContiguousOffset(value * self.scrollBarMultiplier) self.setTopToAddress(addr) self.refreshLines() for i in range(1, len(self.lines)): if (self.lines[i].address + self.lines[i].length) > addr: self.topLine = i - 1 break self.updateCache() def scrollBarAction(self, action): if action == QAbstractSlider.SliderSingleStepAdd: self.wheelDelta = 0 self.scrollLines(1) elif action == QAbstractSlider.SliderSingleStepSub: self.wheelDelta = 0 self.scrollLines(-1) elif action == QAbstractSlider.SliderPageStepAdd: self.wheelDelta = 0 self.scrollLines(self.visibleRows) elif action == QAbstractSlider.SliderPageStepSub: self.wheelDelta = 0 self.scrollLines(-self.visibleRows) elif action == QAbstractSlider.SliderToMinimum: self.wheelDelta = 0 self.setTopToAddress(self.getStart()) self.verticalScrollBar().setValue( self.getContiguousOffsetForAddress(self.topAddr) // self.scrollBarMultiplier) self.refreshLines() elif action == QAbstractSlider.SliderToMaximum: self.wheelDelta = 0 self.setTopToAddress(self.getEnd()) self.verticalScrollBar().setValue( self.getContiguousOffsetForAddress(self.topAddr) // self.scrollBarMultiplier) self.refreshLines() def cursorTimerEvent(self): self.caretBlink = not self.caretBlink self.updateCaret() def focusInEvent(self, event): self.caretVisible = True self.updateCaret() def focusOutEvent(self, event): self.caretVisible = False self.leftButtonDown = False self.updateCaret() def selectNone(self): for i in self.lines: if (self.cursorAddr >= i.address) and (self.cursorAddr < (i.address + i.length)) and i.separator: self.cursorAddr = i.address + i.length break self.selectionStartAddr = self.cursorAddr if self.selectionVisible: self.viewport().update() self.repositionCaret() UIContext.updateStatus() def selectAll(self): self.selectionStartAddr = self.getStart() self.cursorAddr = self.getEnd() self.viewport().update() UIContext.updateStatus() def adjustAddressAfterBackwardMovement(self): lastAddr = self.getStart() for i in self.ranges: if (self.cursorAddr >= i.start) and (self.cursorAddr < i.end): break if i.start > self.cursorAddr: self.cursorAddr = lastAddr break lastAddr = i.end - 1 def adjustAddressAfterForwardMovement(self): for i in self.ranges: if (self.cursorAddr >= i.start) and (self.cursorAddr < i.end): break if i.start > self.cursorAddr: self.cursorAddr = i.start break def left(self, count, selecting): if self.cursorAddr > (self.getStart() + count): self.cursorAddr -= count else: self.cursorAddr = self.getStart() self.adjustAddressAfterBackwardMovement() if not selecting: self.selectNone() self.repositionCaret() if self.selectionVisible or selecting: self.viewport().update() def right(self, count, selecting): if self.cursorAddr <= (self.getEnd() - count): self.cursorAddr += count else: self.cursorAddr = self.getEnd() self.adjustAddressAfterForwardMovement() if not selecting: self.selectNone() self.repositionCaret() if self.selectionVisible or selecting: self.viewport().update() def up(self, selecting): self.left(self.cols, selecting) def down(self, selecting): self.right(self.cols, selecting) def pageUp(self, selecting): for i in range(0, len(self.lines)): if (((self.cursorAddr >= self.lines[i].address) and (self.cursorAddr < (self.lines[i].address + self.lines[i].length))) or (((i + 1) == len(self.lines)) and (self.cursorAddr == (self.lines[i].address + self.lines[i].length)))): if i < self.visibleRows: self.cursorAddr = self.getStart() else: lineOfs = self.cursorAddr - self.lines[i].address self.cursorAddr = self.lines[ i - self.visibleRows].address + lineOfs if self.cursorAddr < self.lines[i - self.visibleRows].address: self.cursorAddr = self.lines[i - self.visibleRows].address elif self.cursorAddr >= ( self.lines[i - self.visibleRows].address + self.lines[i - self.visibleRows].length): self.cursorAddr = self.lines[ i - self.visibleRows].address + self.lines[ i - self.visibleRows].length - 1 break self.adjustAddressAfterBackwardMovement() if self.topLine > self.visibleRows: self.topLine -= self.visibleRows else: self.topLine = 0 if self.topLine < len(self.lines): self.updatingScrollBar = True addr = self.lines[self.topLine].address self.verticalScrollBar().setValue( self.getContiguousOffsetForAddress(addr) // self.scrollBarMultiplier) self.updatingScrollBar = False if not selecting: self.selectNone() self.repositionCaret() self.viewport().update() def pageDown(self, selecting): for i in range(0, len(self.lines)): if (((self.cursorAddr >= self.lines[i].address) and (self.cursorAddr < (self.lines[i].address + self.lines[i].length))) or (((i + 1) == len(self.lines)) and (self.cursorAddr == (self.lines[i].address + self.lines[i].length)))): if i >= (len(self.lines) - self.visibleRows): self.cursorAddr = self.getEnd() else: lineOfs = self.cursorAddr - self.lines[i].address self.cursorAddr = self.lines[ i + self.visibleRows].address + lineOfs if self.cursorAddr < self.lines[i + self.visibleRows].address: self.cursorAddr = self.lines[i + self.visibleRows].address elif self.cursorAddr >= ( self.lines[i + self.visibleRows].address + self.lines[i + self.visibleRows].length): self.cursorAddr = self.lines[ i + self.visibleRows].address + self.lines[ i + self.visibleRows].length - 1 break self.adjustAddressAfterForwardMovement() if (self.topLine + self.visibleRows) < len(self.lines): self.topLine += self.visibleRows elif len(self.lines) > 0: self.topLine = len(self.lines) - 1 if self.topLine < len(self.lines): self.updatingScrollBar = True addr = self.lines[self.topLine].address self.verticalScrollBar().setValue( self.getContiguousOffsetForAddress(addr) // self.scrollBarMultiplier) self.updatingScrollBar = False if not selecting: self.selectNone() self.repositionCaret() self.viewport().update() def moveToStartOfLine(self, selecting): for i in self.lines: if (self.cursorAddr >= i.address) and (self.cursorAddr < (i.address + i.length)): self.cursorAddr = i.address break if not selecting: self.selectNone() self.repositionCaret() if self.selectionVisible or selecting: self.viewport().update() def moveToEndOfLine(self, selecting): for i in self.lines: if (self.cursorAddr >= i.address) and (self.cursorAddr < (i.address + i.length)): self.cursorAddr = i.address + i.length - 1 break if not selecting: self.selectNone() self.repositionCaret() if self.selectionVisible or selecting: self.viewport().update() def moveToStartOfView(self, selecting): self.cursorAddr = self.getStart() if not selecting: self.selectNone() self.repositionCaret() if self.selectionVisible or selecting: self.viewport().update() def moveToEndOfView(self, selecting): self.cursorAddr = self.getEnd() if not selecting: self.selectNone() self.repositionCaret() if self.selectionVisible or selecting: self.viewport().update() def addressFromLocation(self, x, y): if y < 0: y = 0 if x < 0: x = 0 if x > self.cols: x = self.cols if (y + self.topLine) >= len(self.lines): return self.getEnd() if self.lines[y + self.topLine].separator: return self.lines[y + self.topLine].address - 1 result = self.lines[y + self.topLine].address + x if result >= (self.lines[y + self.topLine].address + self.lines[y + self.topLine].length): if (y + self.topLine) == (len(self.lines) - 1): return self.getEnd() else: return self.lines[y + self.topLine].address + self.lines[ y + self.topLine].length - 1 return result def mousePressEvent(self, event): if event.button() != Qt.LeftButton: return render = self.createRenderContext() x = (event.x() - 2) // render.getFontWidth() - (self.addrWidth + 2) y = (event.y() - 2) // render.getFontHeight() self.lastMouseX = x self.lastMouseY = y self.cursorAddr = self.addressFromLocation(x, y) if (event.modifiers() & Qt.ShiftModifier) == 0: self.selectNone() self.repositionCaret() if (event.modifiers() & Qt.ShiftModifier) != 0: self.viewport().update() self.leftButtonDown = True def mouseMoveEvent(self, event): if not self.leftButtonDown: return render = self.createRenderContext() x = (event.x() - 2) // render.getFontWidth() - (self.addrWidth + 2) y = (event.y() - 2) // render.getFontHeight() if (x == self.lastMouseX) and (y == self.lastMouseY): return self.lastMouseX = x self.lastMouseY = y self.cursorAddr = self.addressFromLocation(x, y) self.repositionCaret() self.viewport().update() def mouseReleaseEvent(self, event): if event.button() != Qt.LeftButton: return self.leftButtonDown = False
class Controller: def __init__(self): # gui self.app = QApplication([]) self.main_window = MainWindow(controller=self) # device self.device = Device() # fps stats self.fps_timer = QTimer() self.fps_timer.timeout.connect(self.update_ui_fps) self.spf = 1 # seconds per frame self.timestamp_last_capture = 0 # acquisition thread self.continuous_acquisition = False self.worker_wait_condition = QWaitCondition() self.acquisition_worker = AcquisitionWorker(self.worker_wait_condition, device=self.device) self.acquisition_thread = QThread() self.acquisition_worker.moveToThread(self.acquisition_thread) self.acquisition_thread.started.connect(self.acquisition_worker.run) self.acquisition_worker.finished.connect(self.acquisition_thread.quit) # self.acquisition_worker.finished.connect(self.acquisition_thread.deleteLater) # self.acquisition_thread.finished.connect(self.acquisition_worker.deleteLater) self.acquisition_worker.data_ready.connect(self.data_ready_callback) self.acquisition_thread.start() # default timebase self.set_timebase("20 ms") # on app exit self.app.aboutToQuit.connect(self.on_app_exit) def run_app(self): self.main_window.show() return self.app.exec_() def get_ports_names(self): return [p.device for p in serial.tools.list_ports.comports()] def update_ui_fps(self): fps = 1 / self.spf self.main_window.control_panel.stats_panel.fps_label.setText( f"{fps:.2f} fps") def set_timebase(self, timebase: str): # send timebase to device self.device.timebase = timebase if self.is_device_connected(): self.device.write_timebase() # adjust timebase in the screen seconds_per_sample = (float(timebase.split()[0]) / 10 * { "ms": 1e-3, "us": 1e-6 }[timebase.split()[1]]) self.data_time_array = (np.arange(0, self.device.BUFFER_SIZE) * seconds_per_sample) self.main_window.screen.setXRange(0, self.device.BUFFER_SIZE * seconds_per_sample, padding=0.02) self.main_window.screen.setYRange(0, 5) def set_trigger_state(self, on): self.device.trigger_on = on if self.is_device_connected(): self.device.write_trigger_state() def set_trigger_slope(self, slope): self.device.trigger_slope = slope if self.is_device_connected(): self.device.write_trigger_slope() def connect_to_device(self, port): if port == "": QMessageBox.about( self.main_window, "Connection failed", "Could not connect to device. No port is selected.", ) elif port not in self.get_ports_names(): QMessageBox.about( self.main_window, "Connection failed", f"Could not connect to device. Port {port} not available. Refresh and try again.", ) else: self.device.connect(port) def disconnect_device(self): self.device.disconnect() def is_device_connected(self): return self.device.is_connected() def show_no_connection_message(self): QMessageBox.about( self.main_window, "Device not connected", "No device is connected. Connect a device first.", ) def oscilloscope_single_run(self): if self.device.is_connected(): self.continuous_acquisition = False self.device.clean_buffers() self.worker_wait_condition.notify_one() return True else: self.show_no_connection_message() return False def oscilloscope_continuous_run(self): if self.device.is_connected(): self.timestamp_last_capture = time.time() self.spf = 1 self.fps_timer.start(500) self.continuous_acquisition = True self.device.clean_buffers() self.worker_wait_condition.notify_one() return True else: self.show_no_connection_message() return False def oscilloscope_stop(self): self.continuous_acquisition = False self.fps_timer.stop() def data_ready_callback(self): curr_time = time.time() self.spf = 0.9 * (curr_time - self.timestamp_last_capture) + 0.1 * self.spf self.timestamp_last_capture = curr_time self.main_window.screen.update_ch(self.data_time_array, self.acquisition_worker.data) if self.continuous_acquisition == True: self.worker_wait_condition.notify_one() def on_app_exit(self): print("exiting")
class SelectUser: def __init__(self, main_window): self.main_window = main_window self.cap = cv2.VideoCapture(0) self.image = None self.data_path = os.path.join(os.path.dirname(__file__), 'Data') self.detector = dlib.get_frontal_face_detector() self.predictor = dlib.shape_predictor( os.path.join(self.data_path, 'shape_predictor_68_face_landmarks.dat')) self.face_rec = dlib.face_recognition_model_v1( os.path.join(self.data_path, 'dlib_face_recognition_resnet_model_v1.dat')) self.names = ['Даня', 'Никита', 'Белла', 'Вадимчик'] self.fdi = [] self.ind_d = 0 self.ind_u = 0 self.ind_p = 0 self.j = 0 self.timer = QTimer() self.timer.timeout.connect(self.view_cam) self.shape = None def select_user(self): self.main_window.ui = Ui_SelectUser() self.main_window.ui.setupUi(self.main_window) self.main_window.ui.pushButton.clicked.connect(self.next_window) self.main_window.setWindowTitle('Обнаружение пользователя') self.timer.start(20) def view_cam(self): ret, self.image = self.cap.read() self.image = cv2.cvtColor(self.image, cv2.COLOR_BGR2RGB) height, width, channel = self.image.shape step = channel * width self.rectangle_face() q_img = QImage(self.image.data, width, height, step, QImage.Format_RGB888) self.main_window.ui.label.setPixmap(QPixmap.fromImage(q_img)) def rectangle_face(self): detection = self.detector(self.image, 1) face_descriptor_frame = 0 if len(detection) == 0 and self.ind_d == 0: self.main_window.ui.label_2.setText('Нет никого в кадре') self.ind_d, self.ind_p, self.ind_u = 1, 0, 0 for k, d in enumerate(detection): if self.j % 3 == 0: self.shape = self.predictor(self.image, d) face_descriptor_frame = self.face_rec.compute_face_descriptor( self.image, self.shape) utils.rectangle_face(self.image, detection, self.shape) if self.j % 3 == 0: if not self.ind_p: for c, fd in enumerate(self.fdi): q = distance.euclidean(fd, face_descriptor_frame) if q < 0.6: self.main_window.ui.label_2.setText( f'Привет {self.names[c]}!') self.ind_p = 1 self.ind_d = 0 self.ind_u = 1 if not self.ind_u: self.main_window.ui.label_2.setText( 'Пользователь не определен') self.ind_u = 1 self.ind_d = 0 cv2.waitKey(5) self.j += 1 def next_window(self): self.timer.stop() self.main_window.main_window.main_window()
class MainWindow(QMainWindow): """ Main application entry-point for Wa-Tor. """ def __init__(self, parent=None): super(MainWindow, self).__init__(parent) self._playing = False self._tickpause = 50 self._size = QSize(80, 40) self._settings = Settings() self._world = World(self._size, self._settings) self._ticks = 0 self._updater = QTimer(self) self._updater.timeout.connect(self._tick) self.setWindowTitle("Planet Wa-Tor") self.setWindowIcon(QIcon("res/icon_wator.png")) self.setCentralWidget(QWidget()) self._wator_widget = WaTorWidget(self._world) self._wator_graph = WaTorGraph(self._world) self.home() def home(self): """ Add the GUI elements to the window that represent the home state of the application. """ toolbar = self.addToolBar("File") play = QAction(QIcon("res/icon_play.png"), "Play", self) toolbar.addAction(play) pause = QAction(QIcon("res/icon_pause.png"), "Pause", self) toolbar.addAction(pause) toolbar.addSeparator() quit_app = QAction(QIcon("res/icon_quit.png"), "Quit", self) toolbar.addAction(quit_app) toolbar.addSeparator() reset = QAction(QIcon("res/icon_reset.png"), "Reset", self) toolbar.addAction(reset) toolbar.actionTriggered[QAction].connect(self.toolbar_pressed) layout = QVBoxLayout() layout.addWidget(self._wator_widget) layout.addSpacing(10) slider_layout = QHBoxLayout() slider_layout.addWidget(QLabel("Tick Speed", self)) slider = QSlider() slider.setTickPosition(QSlider.TicksBothSides) slider.setTickInterval(25) slider.setRange(0, 500) slider.setSingleStep(1) slider.setOrientation(Qt.Horizontal) slider.valueChanged.connect(self._set_tick_value) slider.setValue(self._tickpause) slider_layout.addWidget(slider) layout.addLayout(slider_layout) layout.addSpacing(10) layout.addWidget(self._wator_graph) self.centralWidget().setLayout(layout) def _quit(self): """ Quit the application. """ QtCore.QCoreApplication.instance().quit() def play(self): """ Start running (or resume) the simulation. """ self._updater.start(self._tickpause * 5) self._playing = True def pause(self): """ Pause the running of the simulation. """ self._playing = False self._updater.stop() def reset(self): """ Reset the application. """ self._ticks = 0 self.pause() if self._settings.exec_() == QDialog.Accepted: self._world.reset(self._settings) self._wator_widget.repaint() self._wator_graph.reset() def toolbar_pressed(self, action): """ Handle a button being pressed on the toolbar. """ actions = { "Play": self.play, "Pause": self.pause, "Quit": self._quit, "Reset": self.reset } actions[action.text()]() def _set_tick_value(self, value): self._tickpause = value if self._playing: self.pause() self.play() def _tick(self): """ Tick the world simulation. """ self._ticks += 1 self._world.update(self._ticks) self._wator_widget.repaint() self._wator_graph.repaint() fish, sharks = self._world.stats() if fish == 0 and sharks == 0: print("Both sharks and fish have become extinct.") self.pause() elif fish == 0 and sharks > 0: print("No more fish. Wa-Tor is overrun with sharks.") self.pause() elif sharks == 0: print("No more sharks. Wa-Tor will become overrun with fish.") self.pause()
class MainWindow(QMainWindow): """Voice Changer main window.""" def __init__(self, parent=None): super(MainWindow, self).__init__() self.statusBar().showMessage("Move Dial to Deform Microphone Voice !.") self.setWindowTitle(__doc__) self.setMinimumSize(240, 240) self.setMaximumSize(480, 480) self.resize(self.minimumSize()) self.setWindowIcon(QIcon.fromTheme("audio-input-microphone")) self.tray = QSystemTrayIcon(self) self.center() QShortcut("Ctrl+q", self, activated=lambda: self.close()) self.menuBar().addMenu("&File").addAction("Quit", lambda: exit()) self.menuBar().addMenu("Sound").addAction( "STOP !", lambda: call('killall rec', shell=True)) windowMenu = self.menuBar().addMenu("&Window") windowMenu.addAction("Hide", lambda: self.hide()) windowMenu.addAction("Minimize", lambda: self.showMinimized()) windowMenu.addAction("Maximize", lambda: self.showMaximized()) windowMenu.addAction("Restore", lambda: self.showNormal()) windowMenu.addAction("FullScreen", lambda: self.showFullScreen()) windowMenu.addAction("Center", lambda: self.center()) windowMenu.addAction("Top-Left", lambda: self.move(0, 0)) windowMenu.addAction("To Mouse", lambda: self.move_to_mouse_position()) # widgets group0 = QGroupBox("Voice Deformation") self.setCentralWidget(group0) self.process = QProcess(self) self.process.error.connect( lambda: self.statusBar().showMessage("Info: Process Killed", 5000)) self.control = QDial() self.control.setRange(-10, 20) self.control.setSingleStep(5) self.control.setValue(0) self.control.setCursor(QCursor(Qt.OpenHandCursor)) self.control.sliderPressed.connect( lambda: self.control.setCursor(QCursor(Qt.ClosedHandCursor))) self.control.sliderReleased.connect( lambda: self.control.setCursor(QCursor(Qt.OpenHandCursor))) self.control.valueChanged.connect( lambda: self.control.setToolTip(f"<b>{self.control.value()}")) self.control.valueChanged.connect(lambda: self.statusBar().showMessage( f"Voice deformation: {self.control.value()}", 5000)) self.control.valueChanged.connect(self.run) self.control.valueChanged.connect(lambda: self.process.kill()) # Graphic effect self.glow = QGraphicsDropShadowEffect(self) self.glow.setOffset(0) self.glow.setBlurRadius(99) self.glow.setColor(QColor(99, 255, 255)) self.control.setGraphicsEffect(self.glow) self.glow.setEnabled(False) # Timer to start self.slider_timer = QTimer(self) self.slider_timer.setSingleShot(True) self.slider_timer.timeout.connect(self.on_slider_timer_timeout) # an icon and set focus QLabel(self.control).setPixmap( QIcon.fromTheme("audio-input-microphone").pixmap(32)) self.control.setFocus() QVBoxLayout(group0).addWidget(self.control) self.menu = QMenu(__doc__) self.menu.addAction(__doc__).setDisabled(True) self.menu.setIcon(self.windowIcon()) self.menu.addSeparator() self.menu.addAction( "Show / Hide", lambda: self.hide() if self.isVisible() else self.showNormal()) self.menu.addAction("STOP !", lambda: call('killall rec', shell=True)) self.menu.addSeparator() self.menu.addAction("Quit", lambda: exit()) self.tray.setContextMenu(self.menu) self.make_trayicon() def run(self): """Run/Stop the QTimer.""" if self.slider_timer.isActive(): self.slider_timer.stop() self.glow.setEnabled(True) call('killall rec ; killall play', shell=True) self.slider_timer.start(3000) def on_slider_timer_timeout(self): """Run subprocess to deform voice.""" self.glow.setEnabled(False) value = int(self.control.value()) * 100 command = f'play -q -V0 "|rec -q -V0 -n -d -R riaa bend pitch {value} "' print(f"Voice Deformation Value: {value}") print(f"Voice Deformation Command: {command}") self.process.start(command) if self.isVisible(): self.statusBar().showMessage("Minimizing to System TrayIcon", 3000) print("Minimizing Main Window to System TrayIcon now...") sleep(3) self.hide() def center(self): """Center Window on the Current Screen,with Multi-Monitor support.""" window_geometry = self.frameGeometry() mousepointer_position = QApplication.desktop().cursor().pos() screen = QApplication.desktop().screenNumber(mousepointer_position) centerPoint = QApplication.desktop().screenGeometry(screen).center() window_geometry.moveCenter(centerPoint) self.move(window_geometry.topLeft()) def move_to_mouse_position(self): """Center the Window on the Current Mouse position.""" window_geometry = self.frameGeometry() window_geometry.moveCenter(QApplication.desktop().cursor().pos()) self.move(window_geometry.topLeft()) def make_trayicon(self): """Make a Tray Icon.""" if self.windowIcon() and __doc__: self.tray.setIcon(self.windowIcon()) self.tray.setToolTip(__doc__) self.tray.activated.connect( lambda: self.hide() if self.isVisible() else self.showNormal()) return self.tray.show()
class RenderWindow(QWindow): def __init__(self, format): super(RenderWindow, self).__init__() self.setSurfaceType(QWindow.OpenGLSurface) self.setFormat(format) self.context = QOpenGLContext(self) self.context.setFormat(self.requestedFormat()) if not self.context.create(): raise Exception("Unable to create GL context") self.program = None self.timer = None self.angle = 0 def initGl(self): self.program = QOpenGLShaderProgram(self) self.vao = QOpenGLVertexArrayObject() self.vbo = QOpenGLBuffer() format = self.context.format() useNewStyleShader = format.profile() == QSurfaceFormat.CoreProfile # Try to handle 3.0 & 3.1 that do not have the core/compatibility profile # concept 3.2+ has. This may still fail since version 150 (3.2) is # specified in the sources but it's worth a try. if (format.renderableType() == QSurfaceFormat.OpenGL and format.majorVersion() == 3 and format.minorVersion() <= 1): useNewStyleShader = not format.testOption( QSurfaceFormat.DeprecatedFunctions) vertexShader = vertexShaderSource if useNewStyleShader else vertexShaderSource110 fragmentShader = fragmentShaderSource if useNewStyleShader else fragmentShaderSource110 if not self.program.addShaderFromSourceCode(QOpenGLShader.Vertex, vertexShader): raise Exception("Vertex shader could not be added: {} ({})".format( self.program.log(), vertexShader)) if not self.program.addShaderFromSourceCode(QOpenGLShader.Fragment, fragmentShader): raise Exception( "Fragment shader could not be added: {} ({})".format( self.program.log(), fragmentShader)) if not self.program.link(): raise Exception("Could not link shaders: {}".format( self.program.log())) self.posAttr = self.program.attributeLocation("posAttr") self.colAttr = self.program.attributeLocation("colAttr") self.matrixUniform = self.program.uniformLocation("matrix") self.vbo.create() self.vbo.bind() self.verticesData = vertices.tobytes() self.colorsData = colors.tobytes() verticesSize = 4 * vertices.size colorsSize = 4 * colors.size self.vbo.allocate(VoidPtr(self.verticesData), verticesSize + colorsSize) self.vbo.write(verticesSize, VoidPtr(self.colorsData), colorsSize) self.vbo.release() vaoBinder = QOpenGLVertexArrayObject.Binder(self.vao) if self.vao.isCreated(): # have VAO support, use it self.setupVertexAttribs() def setupVertexAttribs(self): self.vbo.bind() self.program.setAttributeBuffer(self.posAttr, GL.GL_FLOAT, 0, 2) self.program.setAttributeBuffer(self.colAttr, GL.GL_FLOAT, 4 * vertices.size, 3) self.program.enableAttributeArray(self.posAttr) self.program.enableAttributeArray(self.colAttr) self.vbo.release() def exposeEvent(self, event): if self.isExposed(): self.render() if self.timer is None: self.timer = QTimer(self) self.timer.timeout.connect(self.slotTimer) if not self.timer.isActive(): self.timer.start(10) else: if self.timer and self.timer.isActive(): self.timer.stop() def render(self): if not self.context.makeCurrent(self): raise Exception("makeCurrent() failed") functions = self.context.functions() if self.program is None: functions.glEnable(GL.GL_DEPTH_TEST) functions.glClearColor(0, 0, 0, 1) self.initGl() retinaScale = self.devicePixelRatio() functions.glViewport(0, 0, self.width() * retinaScale, self.height() * retinaScale) functions.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT) self.program.bind() matrix = QMatrix4x4() matrix.perspective(60, 4 / 3, 0.1, 100) matrix.translate(0, 0, -2) matrix.rotate(self.angle, 0, 1, 0) self.program.setUniformValue(self.matrixUniform, matrix) if self.vao.isCreated(): self.vao.bind() else: # no VAO support, set the vertex attribute arrays now self.setupVertexAttribs() functions.glDrawArrays(GL.GL_TRIANGLES, 0, 3) self.vao.release() self.program.release() # swapInterval is 1 by default which means that swapBuffers() will (hopefully) block # and wait for vsync. self.context.swapBuffers(self) self.context.doneCurrent() def slotTimer(self): self.render() self.angle += 1 def glInfo(self): if not self.context.makeCurrent(self): raise Exception("makeCurrent() failed") functions = self.context.functions() text = """Vendor: {}\nRenderer: {}\nVersion: {}\nShading language: {} \nContext Format: {}\n\nSurface Format: {}""".format( functions.glGetString(GL.GL_VENDOR), functions.glGetString(GL.GL_RENDERER), functions.glGetString(GL.GL_VERSION), functions.glGetString(GL.GL_SHADING_LANGUAGE_VERSION), print_surface_format(self.context.format()), print_surface_format(self.format())) self.context.doneCurrent() return text
class EvelynDesktop(QStackedWidget): INTERVAL_SECS = 30 ALERT_SECS = 5 signal_get_ping = Signal() signal_post_history = Signal(int, QDateTime) def __init__( self, config_file: str ) -> None: super().__init__() # load config try: self.config = Config(config_file) except Exception as e: QMessageBox.critical(self, 'Config error', str(e)) QTimer.singleShot(0, self.close) return # load settings self.settings = Settings() # state self.state_key: Optional[int] = None # label widget self.label_ping = ClickableLabel('Loading ...', self.post_history) self.label_ping.setTextFormat(Qt.RichText) self.label_ping.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum) layout_ping = QGridLayout() layout_ping.setContentsMargins(0, 0, 0, 0) layout_ping.addWidget(self.label_ping) self.widget_ping = QWidget() self.widget_ping.setLayout(layout_ping) self.addWidget(self.widget_ping) # alert widget self.label_alert = QLabel() self.label_alert.setWordWrap(True) self.label_alert.setAlignment(Qt.AlignCenter) self.label_alert.setStyleSheet(f'background: #dddddd;') self.addWidget(self.label_alert) # context menu self.action_report_done = QAction('Report done ...') self.action_report_done.triggered.connect(self.report_done) self.action_exit = QAction('Exit') self.action_exit.triggered.connect(self.close) self.action_frameless = QAction('Frameless window') self.action_frameless.setCheckable(True) self.action_frameless.triggered.connect(self.set_frameless_window) self.action_homepage = QAction('Open homepage') self.action_homepage.triggered.connect(self.open_homepage) self.context_menu = QMenu() self.context_menu.addAction(self.action_report_done) self.context_menu.addAction(self.action_exit) self.context_menu.addAction(self.action_frameless) self.context_menu.addAction(self.action_homepage) # threads self.thread_communication = QThread() self.thread_communication.start() # workers self.worker_communication = CommunicationWorker( netloc=self.config.netloc, base_path=self.config.base_path, api_key=self.config.api_key, guild=self.config.guild, member=self.config.member) self.worker_communication.moveToThread(self.thread_communication) # signals self.worker_communication.signal_get_ping_done.connect(self.get_ping_done) self.worker_communication.signal_post_history_done.connect(self.post_history_done) self.signal_get_ping.connect(self.worker_communication.get_ping) self.signal_post_history.connect(self.worker_communication.post_history) # get ping timer QTimer.singleShot(0, self.get_ping) self.timer_ping = QTimer() self.timer_ping.timeout.connect(self.get_ping) self.timer_ping.setTimerType(Qt.VeryCoarseTimer) self.timer_ping.start(self.INTERVAL_SECS * 1000) # switch label timer self.timer_label = QTimer() self.timer_label.timeout.connect(lambda: self.setCurrentWidget(self.widget_ping)) self.timer_label.setSingleShot(True) self.timer_label.setTimerType(Qt.CoarseTimer) # window attributes size = self.settings.get('window', 'size', type_=QSize) if size is not None: self.resize(size) pos = self.settings.get('window', 'pos', type_=QPoint) if pos is not None: self.move(pos) frameless = self.settings.get('window', 'frameless', type_=bool) if frameless is not None and frameless: QTimer.singleShot(100, self.action_frameless.trigger) self.setWindowFlag(Qt.WindowStaysOnTopHint, self.config.window_stays_on_top) self.setAttribute(Qt.WA_TranslucentBackground) self.setWindowTitle('Evelyn Reminder') def closeEvent( self, event: QCloseEvent ) -> None: # save settings with suppress_and_log_exception(): self.settings.set('window', 'size', self.size()) self.settings.set('window', 'pos', self.pos()) self.settings.set('window', 'frameless', bool(self.windowFlags() & Qt.FramelessWindowHint)) # stop communication thread with suppress_and_log_exception(): self.thread_communication.quit() self.thread_communication.wait() # done super().closeEvent(event) def contextMenuEvent( self, event: QContextMenuEvent ) -> None: self.context_menu.exec_(event.globalPos()) @Slot() def get_ping(self) -> None: logging.info('Get ping ...') self.signal_get_ping.emit() @Slot(int, str, str) def get_ping_done( self, key: int, text: str, color: str ) -> None: logging.info('Get ping done') if key == -1: self.state_key = None self.label_ping.setWordWrap(True) else: self.state_key = key self.label_ping.setWordWrap(False) self.label_ping.setText(text) self.widget_ping.setStyleSheet(f'background : {color}; ') @Slot() def post_history( self, date_time: QDateTime = QDateTime() ) -> None: # this method is called as Slot by ClickableLabel.mouseReleaseEvent() without arguments # this method is called directly by EvelynDesktop.report_done() with a date_time if self.state_key is None: return logging.info('Post history ...') self.label_alert.setText('Sending ...') self.label_alert.setStyleSheet(f'background: #dddddd;') self.setCurrentWidget(self.label_alert) self.signal_post_history.emit(self.state_key, date_time) @Slot(str, bool) def post_history_done( self, text: str, error: bool ) -> None: logging.info('Post history done') self.label_alert.setText(text) if error: self.label_alert.setStyleSheet(f'background: #dd4b4b;') self.timer_label.start(self.ALERT_SECS * 1000) # trigger instant ping update to avoid outdated info self.timer_ping.stop() self.timer_ping.start(self.INTERVAL_SECS * 1000) self.get_ping() @Slot() def report_done(self) -> None: self.timer_ping.stop() # stop ping update while dialog is open report_done_dialog = ReportDoneDialog(self) response = report_done_dialog.exec() if response != QDialog.Accepted: self.timer_ping.start(self.INTERVAL_SECS * 1000) self.get_ping() return date_time = report_done_dialog.get_date_time() self.post_history(date_time) @Slot(bool) def set_frameless_window( self, value: bool ) -> None: pos = self.pos() self.setWindowFlag(Qt.FramelessWindowHint, value) # workaround: window goes invisible otherwise self.setVisible(True) # workaround: window would move up otherwise if value: QTimer.singleShot(100, lambda: self.move(pos)) @Slot() def open_homepage(self) -> None: webbrowser.open('https://github.com/stefs/evelyn-reminder')