def __init__(self): super().__init__() self.log.debug("Initializing ClientGUI instance") self.input_buffer = [] self.status_bar = self.statusBar() self.input_dock = QDockWidget() self.client = Engine() self.__init_ui() self.client.connect() self.gui_reactor()
def displayImagePreviewDock(self): """Dock widget that displays a selected image in a scrollable area and uses its file name as the dock's title.""" self.image_preview_dock = QDockWidget() self.image_preview_dock.setObjectName("PreviewDock") self.image_preview_dock.setWindowTitle("Show Image View") self.image_preview_dock.setAllowedAreas(Qt.DockWidgetArea.RightDockWidgetArea) self.display_image_label = QLabel() self.display_image_label.setAlignment(Qt.AlignmentFlag.AlignCenter) self.view_scroll_area = QScrollArea() self.view_scroll_area.setMinimumWidth(300) self.view_scroll_area.setWidgetResizable(True) self.image_preview_dock.setWidget(self.view_scroll_area) # Set initial location of dock widget self.addDockWidget(Qt.DockWidgetArea.RightDockWidgetArea, self.image_preview_dock)
def create_log_view(self): _dock = QDockWidget("Log", self) _dock.setFloating(True) _view = QTextEdit() _dock.setWindowModality(Qt.WindowModality.NonModal) for row in SwaVanLogRecorder.reading_log(): _pre = _view.toPlainText() _view.setPlainText(f"{_pre}\n{row}") _view.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff) _view.setHorizontalScrollBarPolicy( Qt.ScrollBarPolicy.ScrollBarAlwaysOff) _dock.setWidget(_view) self.mock_right_size.addWidget(_dock)
def displayFilesDock(self): """Dock widget that displays the movie file location in a QLineEdit widget, provides a button for opening directories with images and GIFs, and shows the media from the selected folder in a QTreeWidget.""" self.files_dock = QDockWidget() self.files_dock.setWindowTitle("Media Folder") self.files_dock.setAllowedAreas(Qt.DockWidgetArea.LeftDockWidgetArea) folder_label = QLabel("Media Location:") # The QLineEdit widget is set to read-only as a quick way to display # the folder path self.folder_line = QLineEdit() self.folder_line.setMinimumWidth(100) self.folder_line.setReadOnly(True) open_button = QPushButton("Open...") open_button.clicked.connect(self.openDirectory) folder_h_box = QHBoxLayout() folder_h_box.addWidget(folder_label) folder_h_box.addWidget(self.folder_line) folder_h_box.addWidget(open_button) self.files_tree = QTreeWidget() self.files_tree.setHeaderLabel("Media Files") self.files_tree.setColumnCount(1) # Set up the dock's layout dock_v_box = QVBoxLayout() dock_v_box.addLayout(folder_h_box) dock_v_box.addWidget(self.files_tree) dock_container = QWidget() dock_container.setLayout(dock_v_box) self.files_dock.setWidget(dock_container) self.addDockWidget(Qt.DockWidgetArea.LeftDockWidgetArea, self.files_dock)
class MainWindow(QMainWindow): def __init__(self): """MainWindow Constructor for Image Manager""" super().__init__() # Constructor for QMainWindow self.initializeUI() def initializeUI(self): """Set up the GUI's main window.""" self.setWindowTitle("Image Manager") self.setObjectName("ImageManager") # Set up the main window, menu, and dock widgets self.setUpMainWindow() self.displayImagePreviewDock() self.createActions() self.createMenus() self.show() # Display the main window def setUpMainWindow(self): """Set up the application's main window containing the QListWidget.""" self.image_view_lw = ImageViewerListWidget(self) self.setCentralWidget(self.image_view_lw) def createActions(self): """Create the application's menu actions.""" # Create actions for File menu self.import_act = QAction("Import Images...", self, triggered=self.importImages) self.import_act.setShortcut("Ctrl+I") self.preferences_act = QAction("Preferences...", self, triggered=self.showPreferencesDialog) self.quit_act = QAction("Quit Task Manager", self, triggered=self.close) self.quit_act.setShortcut(QKeySequence.StandardKey.Quit) # Ctrl+Q # Create actions for Edit menu self.select_all_act = QAction("Select All", self, triggered=self.image_view_lw.selectAll) self.select_all_act.setShortcut(QKeySequence.StandardKey.SelectAll) # Ctrl+A self.delete_act = QAction("Delete Images", self, triggered=self.deleteImages) self.delete_act.setShortcut(QKeySequence.StandardKey.Delete) # Del self.delete_act.setEnabled(False) # Create actions for View menu # Handle the visibility of the dock widget self.show_dock_act = self.image_preview_dock.toggleViewAction() self.show_dock_act.setText("Show Image View") self.sort_ascend_act = QAction("Sort Ascending", self, triggered=lambda: self.sortListItems(Qt.SortOrder.AscendingOrder)) self.sort_ascend_act.setEnabled(False) self.sort_descend_act = QAction("Sort Descending", self, triggered=lambda: self.sortListItems(Qt.SortOrder.DescendingOrder)) self.sort_descend_act.setEnabled(False) self.fullscreen_act = QAction("Show Fullscreen", self, triggered=self.displayFullScreen, checkable=True) # Create actions for Help menu self.about_act = QAction("About Image Manager", self, triggered=self.showAboutDialog) def createMenus(self): """Create the application's menu.""" self.file_menu = self.menuBar().addMenu("&File") self.file_menu.addAction(self.import_act) self.file_menu.addSeparator() self.file_menu.addAction(self.preferences_act) self.file_menu.addSeparator() self.file_menu.addAction(self.quit_act) self.edit_menu = self.menuBar().addMenu("&Edit") self.edit_menu.addAction(self.select_all_act) self.edit_menu.addSeparator() self.edit_menu.addAction(self.delete_act) self.view_menu = self.menuBar().addMenu("&View") self.view_menu.addAction(self.show_dock_act) self.view_menu.addSeparator() self.view_menu.addAction(self.sort_ascend_act) self.view_menu.addAction(self.sort_descend_act) self.view_menu.addSeparator() self.view_menu.addAction(self.fullscreen_act) self.help_menu = self.menuBar().addMenu("&Help") self.help_menu.addAction(self.about_act) def displayImagePreviewDock(self): """Dock widget that displays a selected image in a scrollable area and uses its file name as the dock's title.""" self.image_preview_dock = QDockWidget() self.image_preview_dock.setObjectName("PreviewDock") self.image_preview_dock.setWindowTitle("Show Image View") self.image_preview_dock.setAllowedAreas(Qt.DockWidgetArea.RightDockWidgetArea) self.display_image_label = QLabel() self.display_image_label.setAlignment(Qt.AlignmentFlag.AlignCenter) self.view_scroll_area = QScrollArea() self.view_scroll_area.setMinimumWidth(300) self.view_scroll_area.setWidgetResizable(True) self.image_preview_dock.setWidget(self.view_scroll_area) # Set initial location of dock widget self.addDockWidget(Qt.DockWidgetArea.RightDockWidgetArea, self.image_preview_dock) def displayFullScreen(self, state): """Check the state of checkable fullscreen_act. If True, show the main window as fullscreen.""" if state: self.showFullScreen() else: self.showNormal() def importImages(self): """Placeholder method.""" pass def deleteImages(self): """Placeholder method.""" pass def showPreferencesDialog(self): """Placeholder method.""" pass def showAboutDialog(self): """Placeholder method.""" pass
def __init__(self, parent = None): super(MainWindow, self).__init__() D = self.screen().availableGeometry() self.move(0,0)#center.x() + .25*D.width() , center.y() - .5*D.height() ) self.resize( int(.95*D.width()), int(6*D.height()) ) #qr = self.frameGeometry() #cp = self.screen().availableGeometry().center() #qr.moveCenter(cp) #self.move(qr.topLeft()) self.setWindowState(self.windowState() & ~QtCore.Qt.WindowState.WindowMinimized | QtCore.Qt.WindowState.WindowActive) self.activateWindow() self.subWin = Window() self.iw = imwin() self.Manual = Manual() self.setCentralWidget(self.iw) #Stacked dock widgets docked1 = QDockWidget("", self) docked2 = QDockWidget("", self) self.addDockWidget(QtCore.Qt.DockWidgetArea.LeftDockWidgetArea, docked1) self.addDockWidget(QtCore.Qt.DockWidgetArea.LeftDockWidgetArea, docked2) docked1.setWidget(self.subWin) docked2.setWidget(self.Manual) docked1.setFeatures(QDockWidget.DockWidgetFeature.DockWidgetFloatable) self.setCorner(QtCore.Qt.Corner.TopLeftCorner, QtCore.Qt.DockWidgetArea.LeftDockWidgetArea); self.setCorner(QtCore.Qt.Corner.TopRightCorner, QtCore.Qt.DockWidgetArea.RightDockWidgetArea) self.setCorner(QtCore.Qt.Corner.BottomLeftCorner, QtCore.Qt.DockWidgetArea.LeftDockWidgetArea); self.setCorner(QtCore.Qt.Corner.BottomRightCorner, QtCore.Qt.DockWidgetArea.RightDockWidgetArea) self.resizeDocks( (docked1,docked2), (400,400), QtCore.Qt.Orientation.Horizontal ) self.exportButton = QPushButton("Export Measurements", self) self.exportButton.clicked.connect(self.export_measurements) self.exportButton.setEnabled(False) self.importImage = QPushButton("New Image", self) self.importImage.clicked.connect(self.file_open) self.lengthButton = QPushButton("Measure Length", self) self.lengthButton.clicked.connect(self.measure_length) self.lengthButton.setEnabled(False) self.lengthButton.setCheckable(True) self.lengthNames = [] self.widthsButton = QPushButton("Measure Widths", self) self.widthsButton.clicked.connect(self.iw.measure_widths) self.widthsButton.setEnabled(False) self.widthsButton.setCheckable(True) self.areaButton = QPushButton("Measure Area", self) self.areaButton.clicked.connect(self.measure_area) self.areaButton.setEnabled(False) self.areaButton.setCheckable(True) self.areaNames = [] self.angleButton = QPushButton("Measure Angle", self) self.angleButton.clicked.connect(self.measure_angle) self.angleButton.setEnabled(False) self.angleButton.setCheckable(True) self.angleNames = [] shortcut_polyClose = QShortcut(QtGui.QKeySequence(QtCore.Qt.Key.Key_Tab), self) shortcut_polyClose.activated.connect(self.iw.polyClose) self.undoButton = QPushButton("Undo", self) self.undoButton.clicked.connect(self.undo) self.undoButton.setEnabled(False) shortcut_undo = QShortcut(QtGui.QKeySequence('Ctrl+Z'), self) shortcut_undo.activated.connect(self.undo) self.bezier = QRadioButton("Bezier fit", self) self.bezier.setEnabled(True) self.bezier.setChecked(True) #self.bezier.toggled.connect(self.onClicked) self.piecewise = QRadioButton("Piecewise", self) self.statusbar = self.statusBar() self.statusbar.showMessage('Select new image to begin') self.tb = QToolBar('Toolbar') #self.addToolBar(QtCore.Qt.RightToolBarArea,self.tb) spacer = QWidget(self) spacer.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) self.tb.addWidget(spacer) self.addToolBar(self.tb) self.tb.addWidget(self.importImage) self.tb.addWidget(self.exportButton) self.tb.addWidget(self.lengthButton) self.tb.addWidget(self.widthsButton) self.tb.addWidget(self.areaButton) self.tb.addWidget(self.angleButton) self.tb.addWidget(self.undoButton) self.tb.addWidget(self.bezier) self.tb.addWidget(self.piecewise)
class MainWindow(QMainWindow): def __init__(self): """ MainWindow Constructor """ super().__init__() self.initializeUI() def initializeUI(self): """Initialize settings, call functions that define UI elements, and display the main window.""" self.setMinimumSize(700, 400) self.setWindowTitle("GIF and Image Viewer") # Set up the main window, menu, and dock widget self.setUpMainWindow() self.displayFilesDock() self.createActions() self.createMenus() self.createToolbar() self.show() # Display the main window def setUpMainWindow(self): """Set up the application's main window and widgets.""" self.movie = QMovie() # Create movie object self.movie.stateChanged.connect(self.changeButtonStates) self.media_label = QLabel() # Create label to place images/GIFs on self.media_label.setPixmap(QPixmap("icons/image_label.png")) self.media_label.setFrameShape(QFrame.Shape.StyledPanel) self.media_label.setAlignment(Qt.AlignmentFlag.AlignCenter) self.setCentralWidget(self.media_label) def createActions(self): """Create the application's menu actions.""" # Create actions for File menu self.open_act = QAction("Open...", self, triggered=self.openDirectory) self.open_act.setShortcut(QKeySequence.StandardKey.Open) self.quit_act = QAction("Quit Viewer", self, triggered=self.close) self.quit_act.setShortcut(QKeySequence.StandardKey.Quit) # Ctrl+Q # Create actions for View menu # Handle the visibility of the dock widget self.show_dock_act = self.files_dock.toggleViewAction() self.show_dock_act.setText("Show Media Folder") # Create actions for the toolbar (These actions could also be # added to the GUI's menu bar or to a context menu) self.play_act = QAction(QIcon("icons/play.png"), "Play", self, triggered=self.startMovie) self.pause_act = QAction(QIcon("icons/pause.png"), "Pause", self, triggered=self.pauseMovie) self.stop_act = QAction(QIcon("icons/stop.png"), "Stop/Reset", self, triggered=self.stopMovie) self.disableMovieButtons() def createMenus(self): """Create the application's menu.""" # Make the toolbar appear in the main window for macOS users. # More information about this in Chapter 2 - Building the Foundation for GUIs if QSysInfo.productType() == "macos" or "osx": self.menuBar().setNativeMenuBar(False) self.file_menu = self.menuBar().addMenu("&File") self.file_menu.addAction(self.open_act) self.file_menu.addSeparator() self.file_menu.addAction(self.quit_act) self.view_menu = self.menuBar().addMenu("&View") self.view_menu.addAction(self.show_dock_act) def createToolbar(self): """Create the application's toolbar for playing GIFs.""" toolbar = self.addToolBar("GIF Controls Toolbar") toolbar.setIconSize(QSize(24, 24)) # Add actions to the toolbar toolbar.addAction(self.play_act) toolbar.addAction(self.pause_act) toolbar.addAction(self.stop_act) def displayFilesDock(self): """Dock widget that displays the movie file location in a QLineEdit widget, provides a button for opening directories with images and GIFs, and shows the media from the selected folder in a QTreeWidget.""" self.files_dock = QDockWidget() self.files_dock.setWindowTitle("Media Folder") self.files_dock.setAllowedAreas(Qt.DockWidgetArea.LeftDockWidgetArea) folder_label = QLabel("Media Location:") # The QLineEdit widget is set to read-only as a quick way to display # the folder path self.folder_line = QLineEdit() self.folder_line.setMinimumWidth(100) self.folder_line.setReadOnly(True) open_button = QPushButton("Open...") open_button.clicked.connect(self.openDirectory) folder_h_box = QHBoxLayout() folder_h_box.addWidget(folder_label) folder_h_box.addWidget(self.folder_line) folder_h_box.addWidget(open_button) self.files_tree = QTreeWidget() self.files_tree.setHeaderLabel("Media Files") self.files_tree.setColumnCount(1) # Set up the dock's layout dock_v_box = QVBoxLayout() dock_v_box.addLayout(folder_h_box) dock_v_box.addWidget(self.files_tree) dock_container = QWidget() dock_container.setLayout(dock_v_box) self.files_dock.setWidget(dock_container) self.addDockWidget(Qt.DockWidgetArea.LeftDockWidgetArea, self.files_dock) def openDirectory(self): """Open a QFileDialog for selecting a local directory. Only display image and GIF files.""" directory = QFileDialog.getExistingDirectory( self, "Choose Directory", "", QFileDialog.Option.ShowDirsOnly ) # Specify the file mode to only select directories if directory: self.movie.setFileName(directory) # Check if image data is valid before playing if self.movie.isValid(): # Use setMovie() to set the label's contents as the selected GIF self.media_label.setMovie(self.movie) self.startMovie() # Call method to begin playing def startMovie(self): """Start playing the movie.""" self.movie.start() def pauseMovie(self): """Pause the movie.""" self.movie.setPaused(True) def stopMovie(self): """Stop playing the movie and reset the movie back to the first frame.""" self.movie.stop() self.movie.jumpToFrame(0) def changeButtonStates(self, state): """Slot that handles enabling/disabling buttons in the toolbar based on the state of QMovie.""" if state == QMovie.MovieState.Running: # The animation begins playing once control returns to the event loop self.play_act.setEnabled(False) self.pause_act.setEnabled(True) self.stop_act.setEnabled(True) if state == QMovie.MovieState.Paused: self.play_act.setEnabled(True) self.pause_act.setEnabled(False) self.stop_act.setEnabled(False) if state == QMovie.MovieState.NotRunning: self.play_act.setEnabled(True) self.pause_act.setEnabled(False) self.stop_act.setEnabled(False) def disableMovieButtons(self): """Simple method to disable the movie buttons in the toolbar.""" self.play_act.setEnabled(False) self.pause_act.setEnabled(False) self.stop_act.setEnabled(False)
def createEditingBar(self): """Create dock widget for editing tools.""" #TODO: Add a tab widget for the different editing tools self.editing_bar = QDockWidget("Tools") self.editing_bar.setAllowedAreas( Qt.DockWidgetAreas.LeftDockWidgetArea | Qt.DockWidgetAreas.RightDockWidgetArea) self.editing_bar.setMinimumWidth(90) # Create editing tool buttons filters_label = QLabel("Filters") convert_to_grayscale = QToolButton() convert_to_grayscale.setIcon( QIcon(os.path.join(icon_path, "grayscale.png"))) convert_to_grayscale.clicked.connect(self.image_label.convertToGray) convert_to_RGB = QToolButton() convert_to_RGB.setIcon(QIcon(os.path.join(icon_path, "rgb.png"))) convert_to_RGB.clicked.connect(self.image_label.convertToRGB) convert_to_sepia = QToolButton() convert_to_sepia.setIcon(QIcon(os.path.join(icon_path, "sepia.png"))) convert_to_sepia.clicked.connect(self.image_label.convertToSepia) change_hue = QToolButton() change_hue.setIcon(QIcon(os.path.join(icon_path, ""))) change_hue.clicked.connect(self.image_label.changeHue) brightness_label = QLabel("Brightness") self.brightness_slider = QSlider(Qt.Orientations.Horizontal) self.brightness_slider.setRange(-255, 255) self.brightness_slider.setTickInterval(35) self.brightness_slider.setTickPosition(QSlider.TickPosition.TicksAbove) self.brightness_slider.valueChanged.connect( self.image_label.changeBrighteness) contrast_label = QLabel("Contrast") self.contrast_slider = QSlider(Qt.Orientations.Horizontal) self.contrast_slider.setRange(-255, 255) self.contrast_slider.setTickInterval(35) self.contrast_slider.setTickPosition(QSlider.TickPosition.TicksAbove) self.contrast_slider.valueChanged.connect( self.image_label.changeContrast) # Set layout for dock widget editing_grid = QGridLayout() #editing_grid.addWidget(filters_label, 0, 0, 0, 2, Qt.AlignTop) editing_grid.addWidget(convert_to_grayscale, 1, 0) editing_grid.addWidget(convert_to_RGB, 1, 1) editing_grid.addWidget(convert_to_sepia, 2, 0) editing_grid.addWidget(change_hue, 2, 1) editing_grid.addWidget(brightness_label, 3, 0) editing_grid.addWidget(self.brightness_slider, 4, 0, 1, 0) editing_grid.addWidget(contrast_label, 5, 0) editing_grid.addWidget(self.contrast_slider, 6, 0, 1, 0) editing_grid.setRowStretch(7, 10) container = QWidget() container.setLayout(editing_grid) self.editing_bar.setWidget(container) self.addDockWidget(Qt.DockWidgetAreas.LeftDockWidgetArea, self.editing_bar) self.tools_menu_act = self.editing_bar.toggleViewAction()
class PhotoEditorGUI(QMainWindow): def __init__(self): super().__init__() self.initializeUI() self.image = QImage() def initializeUI(self): self.setMinimumSize(300, 200) self.setWindowTitle("Photo Editor") self.showMaximized() self.zoom_factor = 1 self.createMainLabel() self.createEditingBar() self.createMenu() self.createToolBar() self.show() def createMenu(self): """Set up the menubar.""" # Actions for Photo Editor menu about_act = QAction('About', self) about_act.triggered.connect(self.aboutDialog) self.exit_act = QAction(QIcon(os.path.join(icon_path, "exit.png")), 'Quit Photo Editor', self) self.exit_act.setShortcut('Ctrl+Q') self.exit_act.triggered.connect(self.close) # Actions for File menu self.new_act = QAction(QIcon(os.path.join(icon_path, "new.png")), 'New...') self.open_act = QAction(QIcon(os.path.join(icon_path, "open.png")), 'Open...', self) self.open_act.setShortcut('Ctrl+O') self.open_act.triggered.connect(self.image_label.openImage) self.print_act = QAction(QIcon(os.path.join(icon_path, "print.png")), "Print...", self) self.print_act.setShortcut('Ctrl+P') #self.print_act.triggered.connect(self.printImage) self.print_act.setEnabled(False) self.save_act = QAction(QIcon(os.path.join(icon_path, "save.png")), "Save...", self) self.save_act.setShortcut('Ctrl+S') self.save_act.triggered.connect(self.image_label.saveImage) self.save_act.setEnabled(False) # Actions for Edit menu self.revert_act = QAction("Revert to Original", self) self.revert_act.triggered.connect(self.image_label.revertToOriginal) self.revert_act.setEnabled(False) # Actions for Tools menu self.crop_act = QAction(QIcon(os.path.join(icon_path, "crop.png")), "Crop", self) self.crop_act.setShortcut('Shift+X') self.crop_act.triggered.connect(self.image_label.cropImage) self.resize_act = QAction(QIcon(os.path.join(icon_path, "resize.png")), "Resize", self) self.resize_act.setShortcut('Shift+Z') self.resize_act.triggered.connect(self.image_label.resizeImage) self.rotate90_cw_act = QAction( QIcon(os.path.join(icon_path, "rotate90_cw.png")), 'Rotate 90º CW', self) self.rotate90_cw_act.triggered.connect( lambda: self.image_label.rotateImage90("cw")) self.rotate90_ccw_act = QAction( QIcon(os.path.join(icon_path, "rotate90_ccw.png")), 'Rotate 90º CCW', self) self.rotate90_ccw_act.triggered.connect( lambda: self.image_label.rotateImage90("ccw")) self.flip_horizontal = QAction( QIcon(os.path.join(icon_path, "flip_horizontal.png")), 'Flip Horizontal', self) self.flip_horizontal.triggered.connect( lambda: self.image_label.flipImage("horizontal")) self.flip_vertical = QAction( QIcon(os.path.join(icon_path, "flip_vertical.png")), 'Flip Vertical', self) self.flip_vertical.triggered.connect( lambda: self.image_label.flipImage('vertical')) self.zoom_in_act = QAction( QIcon(os.path.join(icon_path, "zoom_in.png")), 'Zoom In', self) self.zoom_in_act.setShortcut('Ctrl++') self.zoom_in_act.triggered.connect(lambda: self.zoomOnImage(1.25)) self.zoom_in_act.setEnabled(False) self.zoom_out_act = QAction( QIcon(os.path.join(icon_path, "zoom_out.png")), 'Zoom Out', self) self.zoom_out_act.setShortcut('Ctrl+-') self.zoom_out_act.triggered.connect(lambda: self.zoomOnImage(0.8)) self.zoom_out_act.setEnabled(False) self.normal_size_Act = QAction("Normal Size", self) self.normal_size_Act.setShortcut('Ctrl+=') self.normal_size_Act.triggered.connect(self.normalSize) self.normal_size_Act.setEnabled(False) # Actions for Views menu #self.tools_menu_act = QAction(QIcon(os.path.join(icon_path, "edit.png")),'Tools View...', self, checkable=True) # Create menubar menu_bar = self.menuBar() menu_bar.setNativeMenuBar(False) # Create Photo Editor menu and add actions main_menu = menu_bar.addMenu('Photo Editor') main_menu.addAction(about_act) main_menu.addSeparator() main_menu.addAction(self.exit_act) # Create file menu and add actions file_menu = menu_bar.addMenu('File') file_menu.addAction(self.open_act) file_menu.addAction(self.save_act) file_menu.addSeparator() file_menu.addAction(self.print_act) edit_menu = menu_bar.addMenu('Edit') edit_menu.addAction(self.revert_act) tool_menu = menu_bar.addMenu('Tools') tool_menu.addAction(self.crop_act) tool_menu.addAction(self.resize_act) tool_menu.addSeparator() tool_menu.addAction(self.rotate90_cw_act) tool_menu.addAction(self.rotate90_ccw_act) tool_menu.addAction(self.flip_horizontal) tool_menu.addAction(self.flip_vertical) tool_menu.addSeparator() tool_menu.addAction(self.zoom_in_act) tool_menu.addAction(self.zoom_out_act) tool_menu.addAction(self.normal_size_Act) views_menu = menu_bar.addMenu('Views') views_menu.addAction(self.tools_menu_act) def createToolBar(self): """Set up the toolbar.""" tool_bar = QToolBar("Main Toolbar") tool_bar.setIconSize(QSize(26, 26)) self.addToolBar(tool_bar) # Add actions to the toolbar tool_bar.addAction(self.open_act) tool_bar.addAction(self.save_act) tool_bar.addAction(self.print_act) tool_bar.addAction(self.exit_act) tool_bar.addSeparator() tool_bar.addAction(self.crop_act) tool_bar.addAction(self.resize_act) tool_bar.addSeparator() tool_bar.addAction(self.rotate90_ccw_act) tool_bar.addAction(self.rotate90_cw_act) tool_bar.addAction(self.flip_horizontal) tool_bar.addAction(self.flip_vertical) tool_bar.addSeparator() tool_bar.addAction(self.zoom_in_act) tool_bar.addAction(self.zoom_out_act) def createEditingBar(self): """Create dock widget for editing tools.""" #TODO: Add a tab widget for the different editing tools self.editing_bar = QDockWidget("Tools") self.editing_bar.setAllowedAreas( Qt.DockWidgetAreas.LeftDockWidgetArea | Qt.DockWidgetAreas.RightDockWidgetArea) self.editing_bar.setMinimumWidth(90) # Create editing tool buttons filters_label = QLabel("Filters") convert_to_grayscale = QToolButton() convert_to_grayscale.setIcon( QIcon(os.path.join(icon_path, "grayscale.png"))) convert_to_grayscale.clicked.connect(self.image_label.convertToGray) convert_to_RGB = QToolButton() convert_to_RGB.setIcon(QIcon(os.path.join(icon_path, "rgb.png"))) convert_to_RGB.clicked.connect(self.image_label.convertToRGB) convert_to_sepia = QToolButton() convert_to_sepia.setIcon(QIcon(os.path.join(icon_path, "sepia.png"))) convert_to_sepia.clicked.connect(self.image_label.convertToSepia) change_hue = QToolButton() change_hue.setIcon(QIcon(os.path.join(icon_path, ""))) change_hue.clicked.connect(self.image_label.changeHue) brightness_label = QLabel("Brightness") self.brightness_slider = QSlider(Qt.Orientations.Horizontal) self.brightness_slider.setRange(-255, 255) self.brightness_slider.setTickInterval(35) self.brightness_slider.setTickPosition(QSlider.TickPosition.TicksAbove) self.brightness_slider.valueChanged.connect( self.image_label.changeBrighteness) contrast_label = QLabel("Contrast") self.contrast_slider = QSlider(Qt.Orientations.Horizontal) self.contrast_slider.setRange(-255, 255) self.contrast_slider.setTickInterval(35) self.contrast_slider.setTickPosition(QSlider.TickPosition.TicksAbove) self.contrast_slider.valueChanged.connect( self.image_label.changeContrast) # Set layout for dock widget editing_grid = QGridLayout() #editing_grid.addWidget(filters_label, 0, 0, 0, 2, Qt.AlignTop) editing_grid.addWidget(convert_to_grayscale, 1, 0) editing_grid.addWidget(convert_to_RGB, 1, 1) editing_grid.addWidget(convert_to_sepia, 2, 0) editing_grid.addWidget(change_hue, 2, 1) editing_grid.addWidget(brightness_label, 3, 0) editing_grid.addWidget(self.brightness_slider, 4, 0, 1, 0) editing_grid.addWidget(contrast_label, 5, 0) editing_grid.addWidget(self.contrast_slider, 6, 0, 1, 0) editing_grid.setRowStretch(7, 10) container = QWidget() container.setLayout(editing_grid) self.editing_bar.setWidget(container) self.addDockWidget(Qt.DockWidgetAreas.LeftDockWidgetArea, self.editing_bar) self.tools_menu_act = self.editing_bar.toggleViewAction() def createMainLabel(self): """Create an instance of the imageLabel class and set it as the main window's central widget.""" self.image_label = imageLabel(self) self.image_label.resize(self.image_label.pixmap().size()) self.scroll_area = QScrollArea() self.scroll_area.setBackgroundRole(QPalette.ColorRole.Dark) self.scroll_area.setAlignment(Qt.Alignment.AlignCenter) #self.scroll_area.setWidgetResizable(False) #scroll_area.setMinimumSize(800, 800) self.scroll_area.setWidget(self.image_label) #self.scroll_area.setVisible(False) self.setCentralWidget(self.scroll_area) #self.resize(QApplication.primaryScreen().availableSize() * 3 / 5) def updateActions(self): """Update the values of menu and toolbar items when an image is loaded.""" self.save_act.setEnabled(True) self.revert_act.setEnabled(True) self.zoom_in_act.setEnabled(True) self.zoom_out_act.setEnabled(True) self.normal_size_Act.setEnabled(True) def zoomOnImage(self, zoom_value): """Zoom in and zoom out.""" self.zoom_factor *= zoom_value self.image_label.resize(self.zoom_factor * self.image_label.pixmap().size()) self.adjustScrollBar(self.scroll_area.horizontalScrollBar(), zoom_value) self.adjustScrollBar(self.scroll_area.verticalScrollBar(), zoom_value) self.zoom_in_act.setEnabled(self.zoom_factor < 4.0) self.zoom_out_act.setEnabled(self.zoom_factor > 0.333) def normalSize(self): """View image with its normal dimensions.""" self.image_label.adjustSize() self.zoom_factor = 1.0 def adjustScrollBar(self, scroll_bar, value): """Adjust the scrollbar when zooming in or out.""" scroll_bar.setValue( int(value * scroll_bar.value()) + ((value - 1) * scroll_bar.pageStep() / 2)) def aboutDialog(self): QMessageBox.about( self, "About Photo Editor", "Photo Editor\nVersion 0.2\n\nCreated by Joshua Willman") def keyPressEvent(self, event): """Handle key press events.""" if event.key() == Qt.Key_Escape: self.close() if event.key() == Qt.Key_F1: # fn + F1 on Mac if self.isMaximized(): self.showNormal() else: self.showMaximized() def closeEvent(self, event): pass
class MainWindow(QMainWindow): def __init__(self): """ MainWindow Constructor """ super().__init__() self.initializeUI() def initializeUI(self): """Initialize settings, call functions that define UI elements, and display the main window.""" self.setMinimumSize(700, 400) self.setWindowTitle("GIF and Image Viewer") # Set up the main window and dock widget self.setUpMainWindow() self.displayFilesDock() self.show() # Display the main window def setUpMainWindow(self): """Set up the application's main window and widgets.""" self.movie = QMovie() # Create movie object self.media_label = QLabel() # Create label to place images/GIFs on self.media_label.setPixmap(QPixmap("icons/image_label.png")) self.media_label.setFrameShape(QFrame.Shape.StyledPanel) self.media_label.setAlignment(Qt.AlignmentFlag.AlignCenter) self.setCentralWidget(self.media_label) def displayFilesDock(self): """Dock widget that displays the movie file location in a QLineEdit widget, provides a button for opening directories with images and GIFs, and shows the media from the selected folder in a QTreeWidget.""" self.files_dock = QDockWidget() self.files_dock.setWindowTitle("Media Folder") self.files_dock.setAllowedAreas(Qt.DockWidgetArea.LeftDockWidgetArea) folder_label = QLabel("Media Location:") # The QLineEdit widget is set to read-only as a quick way to display # the folder path self.folder_line = QLineEdit() self.folder_line.setMinimumWidth(100) self.folder_line.setReadOnly(True) open_button = QPushButton("Open...") folder_h_box = QHBoxLayout() folder_h_box.addWidget(folder_label) folder_h_box.addWidget(self.folder_line) folder_h_box.addWidget(open_button) self.files_tree = QTreeWidget() self.files_tree.setHeaderLabel("Media Files") self.files_tree.setColumnCount(1) # Set up the dock's layout dock_v_box = QVBoxLayout() dock_v_box.addLayout(folder_h_box) dock_v_box.addWidget(self.files_tree) dock_container = QWidget() dock_container.setLayout(dock_v_box) self.files_dock.setWidget(dock_container) self.addDockWidget(Qt.DockWidgetArea.LeftDockWidgetArea, self.files_dock)
class MainWindow(QMainWindow): # Create a QSettings object rather than storing values. Pass in a company # name and an application name settings = QSettings("Custom GUIs", "Image Manager GUI") #print(settings.fileName()) # NOTE: Uncomment to print the path to settings images_path = "Images" # File path to the Images directory image_dir = QDir(images_path) info_dialog = None # Create variable for modeless dialog def __init__(self): """MainWindow Constructor for Image Manager""" super().__init__() # Constructor for QMainWindow self.initializeUI() def initializeUI(self): """Set up the GUI's main window and load initial settings and data.""" self.setWindowTitle("Image Manager") self.setObjectName("ImageManager") # Set up the main window, menu, dock widgets, and initialize the GUI's settings self.setUpMainWindow() self.displayImagePreviewDock() self.createActions() self.createMenus() self.loadStoredImageData() self.getInitialSettings() self.show() # Display the main window def setUpMainWindow(self): """Set up the application's main window containing the QListWidget.""" self.image_view_lw = ImageViewerListWidget(self) # Use signals/slots to interact with the list widget self.image_view_lw.itemSelectionChanged.connect(self.updateDockInfo) self.image_view_lw.itemDoubleClicked.connect(self.displayImageInfoDialog) # Use the list widget's internal model to enable/disable menu items self.image_view_lw.model().rowsInserted.connect(self.manageMenuItems) self.image_view_lw.model().rowsRemoved.connect(self.manageMenuItems) self.setCentralWidget(self.image_view_lw) def createActions(self): """Create the application's menu actions.""" # Create actions for File menu self.import_act = QAction("Import Images...", self, triggered=self.importImages) self.import_act.setShortcut("Ctrl+I") self.preferences_act = QAction("Preferences...", self, triggered=self.showPreferencesDialog) self.quit_act = QAction("Quit Task Manager", self, triggered=self.close) self.quit_act.setShortcut(QKeySequence.StandardKey.Quit) # Ctrl+Q # Create actions for Edit menu self.select_all_act = QAction("Select All", self, triggered=self.image_view_lw.selectAll) self.select_all_act.setShortcut(QKeySequence.StandardKey.SelectAll) # Ctrl+A self.delete_act = QAction("Delete Images", self, triggered=self.deleteImages) self.delete_act.setShortcut(QKeySequence.StandardKey.Delete) # Del self.delete_act.setEnabled(False) # Create actions for View menu # Handle the visibility of the dock widget that displays images self.show_dock_act = self.image_preview_dock.toggleViewAction() self.show_dock_act.setText("Show Image View") self.sort_ascend_act = QAction("Sort Ascending", self, triggered=lambda: self.sortListItems(Qt.SortOrder.AscendingOrder)) self.sort_ascend_act.setEnabled(False) self.sort_descend_act = QAction("Sort Descending", self, triggered=lambda: self.sortListItems(Qt.SortOrder.DescendingOrder)) self.sort_descend_act.setEnabled(False) self.fullscreen_act = QAction("Show Fullscreen", self, triggered=self.displayFullScreen, checkable=True) # Create actions for Help menu self.about_act = QAction("About Image Manager", self, triggered=self.showAboutDialog) def createMenus(self): """Create the application's menu.""" if QSysInfo.productType() == "macos" or "osx": self.menuBar().setNativeMenuBar(False) self.file_menu = self.menuBar().addMenu("&File") self.file_menu.addAction(self.import_act) self.file_menu.addSeparator() self.file_menu.addAction(self.preferences_act) self.file_menu.addSeparator() self.file_menu.addAction(self.quit_act) self.edit_menu = self.menuBar().addMenu("&Edit") self.edit_menu.addAction(self.select_all_act) self.edit_menu.addSeparator() self.edit_menu.addAction(self.delete_act) self.view_menu = self.menuBar().addMenu("&View") self.view_menu.addAction(self.show_dock_act) self.view_menu.addSeparator() self.view_menu.addAction(self.sort_ascend_act) self.view_menu.addAction(self.sort_descend_act) self.view_menu.addSeparator() self.view_menu.addAction(self.fullscreen_act) self.help_menu = self.menuBar().addMenu("&Help") self.help_menu.addAction(self.about_act) def manageMenuItems(self, parent, first, last): """Slot to enable/disable menu items if rows have been added/deleted to QListWidget. The rowsInserted() and rowsRemoved() that trigger this slot return the 'parent', 'first', and 'last' values, but they are not used in this method.""" if self.image_view_lw.count() == 0: self.delete_act.setEnabled(False) self.sort_ascend_act.setEnabled(False) self.sort_descend_act.setEnabled(False) elif self.image_view_lw.count() > 0: self.delete_act.setEnabled(True) self.sort_ascend_act.setEnabled(True) self.sort_descend_act.setEnabled(True) def displayImagePreviewDock(self): """Dock widget that displays a selected image in a scrollable area and uses its file name as the dock's title.""" self.image_preview_dock = QDockWidget() self.image_preview_dock.setObjectName("PreviewDock") self.image_preview_dock.setWindowTitle("Show Image View") self.image_preview_dock.setAllowedAreas(Qt.DockWidgetAreas.RightDockWidgetArea) self.display_image_label = QLabel() self.display_image_label.setAlignment(Qt.Alignment.AlignCenter) self.view_scroll_area = QScrollArea() self.view_scroll_area.setMinimumWidth(300) self.view_scroll_area.setWidgetResizable(True) self.image_preview_dock.setWidget(self.view_scroll_area) # Set initial location of dock widget in the main window self.addDockWidget(Qt.DockWidgetAreas.RightDockWidgetArea, self.image_preview_dock) def updateDockInfo(self): """Slot to update the image that the dock widget displays.""" # Only display an image if one item is selected if (len(self.image_view_lw.selectedItems()) == 0 or len(self.image_view_lw.selectedItems()) > 1): self.image_preview_dock.setWindowTitle("Show Image View") self.display_image_label.clear() else: curr_item = self.image_view_lw.currentItem() self.image_preview_dock.setWindowTitle(curr_item.text()) self.show_dock_act.setText("Show Image View") # Get the current height of the dock widget dock_height = self.image_preview_dock.height() # Get the size of the original image/item icon_size = curr_item.icon().availableSizes()[0] icon_width = icon_size.width() # Return a pixmap from the item's icon and display in the scroll area pixmap = curr_item.icon().pixmap(QSize(icon_width, dock_height)) self.display_image_label.setPixmap(pixmap) self.view_scroll_area.setWidget(self.display_image_label) def importImages(self): """Import the images a user selects, remove duplicates, and add items to the QListWidget.""" duplicate_images = [] # Store the names of duplicate images image_paths, _ = QFileDialog.getOpenFileNames(self, "Select Image Files", "", "Images (*.png *.xpm *.jpg *.jpeg)") if image_paths: if self.image_dir.exists(): for image_path in image_paths: # Pass image path to QFileInfo object image_info = QFileInfo(image_path) file_name = image_info.fileName() item_name = image_info.baseName() # Copy the files into the Images directory, check for files # with the same name new_name = self.image_dir.absolutePath() + f"/{file_name}" file_exists = QFile.copy(image_path, new_name) if file_exists == False: duplicate_images.append(image_path) else: self.createListItems(image_path, item_name, image_info, new_name) if self.is_delete_checked == True: # Handle deleting images QFile.moveToTrash(image_path) else: QMessageBox.warning(self, "Images Location Not Found", """<p>The Images Location cannot be found. Restart the application to recreate the directory.</p>""") # Display a custom dialog to inform the user of duplicate images if len(duplicate_images) != 0: duplicates_dialog = QMessageBox(self) duplicates_dialog.setIcon(QMessageBox.Icon.Information) duplicates_dialog.setWindowTitle("Duplicate Images") duplicates_dialog.setText("""<p>Some images were not imported because they already exist.</p>""") details = '\n'.join([item for item in duplicate_images]) duplicates_dialog.setDetailedText(details) duplicates_dialog.exec() duplicate_images.clear() # Clear the list # Check if window is still in focus. If not, give it focus if self.isActiveWindow() == False: self.activateWindow() def createListItems(self, image_path, item_name, image_info, new_name=None): """Simple method for creating QListWidgetItem objects. 'image_path': the path to the file. 'item_name': the base name used for QListWidgetItem objects. 'image_info': the QFileInfo object. 'new_name': used when importing new photos, making sure the program points to the new image location.""" list_item = QListWidgetItem(QIcon(image_path), item_name) self.image_view_lw.setIconSize(QSize(80, 80)) self.image_view_lw.addItem(list_item) if new_name != None: image_info.setFile(new_name) self.image_view_lw.images_info_list.append(image_info) def sortListItems(self, order): """First, sort the items in the QListWidget using sortItems(). Then handle sorting the QFileInfo objects in the images_info_list using Python's sort() to match how the QListWidget sorts items.""" self.image_view_lw.sortItems(order) if order == Qt.SortOrder.AscendingOrder: self.image_view_lw.images_info_list.sort(key=lambda item: (item.baseName().upper(), item.baseName()[0].islower())) elif order == Qt.SortOrder.DescendingOrder: self.image_view_lw.images_info_list.sort(reverse=True, key=lambda item: (item.baseName().upper(), item.baseName()[0].islower())) def deleteImages(self): """Delete images from the QListWidget and from where images are stored on disk.""" number_of_photos = len(self.image_view_lw.selectedItems()) answer = QMessageBox.warning(self, "Delete Image(s)", f"Are you sure you want to delete {number_of_photos} image(s)?", QMessageBox.StandardButtons.No | QMessageBox.StandardButtons.Yes, QMessageBox.StandardButtons.No) if answer == QMessageBox.StandardButtons.Yes: for item in self.image_view_lw.selectedItems(): index = self.image_view_lw.indexFromItem(item).row() # Get the image's information before deletion image_info = self.image_view_lw.images_info_list[index] # Remove items from the Images directory, from the list widget, # and the images_info_list that stores QFileInfo objects QFile.moveToTrash(image_info.absoluteFilePath()) self.image_view_lw.takeItem(index) del self.image_view_lw.images_info_list[index] del item def loadStoredImageData(self): """Load images from the Images directory. The Images directory is created the first time running the application.""" if not(self.image_dir.exists()): QDir().mkdir(self.images_path) elif self.image_dir.exists(): # Create a list of the files in the Images directory images = self.image_dir.entryInfoList(QDir.Filters.AllEntries | QDir.Filters.NoDotAndDotDot) for image in images: # Imported files are QFileInfo objects item_name = image.baseName() path = image.absoluteFilePath() self.createListItems(path, item_name, image) def displayImageInfoDialog(self, item): """Display image metadata in a modeless dialog box. 'index' is the index of the item that is clicked on.""" index = self.image_view_lw.indexFromItem(item).row() if self.info_dialog == None: self.info_dialog = ImageInfoDialog(self, self.image_view_lw.images_info_list[index]) elif self.info_dialog != None: self.info_dialog.close() self.info_dialog = ImageInfoDialog(self, self.image_view_lw.images_info_list[index]) self.info_dialog.show() def showPreferencesDialog(self): """Display the application's preferences dialog. Save the value of the delete_images_checkbox in the settings.""" prefs_dialog = PreferencesDialog(self, self.image_dir, self.is_delete_checked) response = prefs_dialog.exec() if response == 1: # QDialog.DialogCode.Accepted == 1 self.settings.setValue("delete_images", prefs_dialog.delete_images_checkbox.isChecked()) self.is_delete_checked = self.settings.value("delete_images", type=bool) def displayFullScreen(self, state): """Check the state of checkable fullscreen_act. If True, show the main window as fullscreen.""" if state: self.showFullScreen() else: self.showNormal() def showAboutDialog(self): """Display the application's about dialog.""" QMessageBox.about(self, "Image Manager", """<h3 style='text-align:center'>Image Manager</h3> <p style='font-weight: normal'>The <b><i>Image Manager GUI</i></b> demonstrates how to build an application for managing photos. This program also examines some of the common features found in many GUIs.</p> <p style='font-weight: normal'>This application is part of <b><i>Building Custom UIs with PyQt</i></b>.</p> <p style='font-weight: normal'>Designed by: <b>Joshua Willman</b></p> <p style='font-weight: normal'>Icons created by: <b>Joshua Willman</b></p>""") def getInitialSettings(self): """Get initial settings of the application using QSettings upon startup.""" position = self.settings.value("position", QPoint(200, 0)) size = self.settings.value("size", QSize(800, 500)) self.is_delete_checked = self.settings.value("delete_images", type=bool) # restoreState() is used here to restore the image_preview_dock widget self.restoreState(self.settings.value("window_state", bytes(QByteArray()))) self.resize(size) self.move(position) return self.is_delete_checked def saveSettings(self): """Save the settings of the application.""" self.settings.setValue("position", self.pos()) self.settings.setValue("size", self.size()) self.settings.setValue("window_state", self.saveState()) def closeEvent(self, event): """Save the application's settings in the closeEvent().""" self.saveSettings() event.setAccepted(True)
class ClientGUI(QMainWindow, ClientLogger): def __init__(self): super().__init__() self.log.debug("Initializing ClientGUI instance") self.input_buffer = [] self.status_bar = self.statusBar() self.input_dock = QDockWidget() self.client = Engine() self.__init_ui() self.client.connect() self.gui_reactor() def __init_ui(self): self.log.debug("Initializing UI") self.setWindowTitle("Revenant") # TODO: Update this with some sort of connection string when connected self.status_bar.showMessage("Not Connected") self.__add_output_window() self.__add_input_field() exit_action = QAction(QIcon("exit.png"), "&Exit", self) exit_action.setShortcut("Ctrl-Q") exit_action.setStatusTip("Exit") exit_action.triggered.connect(QApplication.instance().quit) view_status_bar = QAction("Status Bar", self, checkable=True) view_status_bar.setStatusTip("Show the status bar") view_status_bar.setChecked(True) view_status_bar.triggered.connect(self.toggle_menu) menubar = self.menuBar() file_menu = menubar.addMenu("&File") file_menu.addAction(exit_action) view_menu = menubar.addMenu("View") view_menu.addAction(view_status_bar) self.show() def __add_output_window(self): self.main_window = QTextEdit(readOnly=True) self.setCentralWidget(self.main_window) def __add_input_field(self): self.input = QLineEdit() # TODO: Fix the bottom dock. BottomDock thingy is incompatible with Qt6 self.input_dock.setAllowedAreas(Qt.DockWidgetArea.BottomDockWidgetArea | Qt.DockWidgetArea.TopDockWidgetArea) self.input_dock.setWidget(self.input) self.addDockWidget(Qt.DockWidgetArea.BottomDockWidgetArea, self.input_dock) self.input.returnPressed.connect(self.write_to_input_buffer) def write_to_input_buffer(self): self.input_buffer.append(self.input.text()) def toggle_menu(self, state): if state: self.status_bar.show() else: self.status_bar.hide() def write_to_main_window(self, text: str): if not text.endswith("\n"): text = text + "\n" self.main_window.insertPlainText(text) self.main_window.moveCursor(QTextCursor.MoveOperation.End) def write(self, write_data: str): write_data = write_data + "\n" self.client.connection.write(write_data.encode("ASCII")) self.write_to_main_window(f">{write_data}") self.input.clear() def contextMenuEvent(self, event): context_menu = QMenu(self) exit_action = context_menu.addAction("Quit") action = context_menu.exec_(self.mapToGlobal(event.pos())) if action == exit_action: QApplication.instance().quit() def gui_reactor(self): def input_loop(): while True: if self.input_buffer: self.write(self.input_buffer.pop(0)) sleep(0.01) def output_loop(): callback = self.write_to_main_window while True: self.client.read(output_callback=callback) sleep(0.01) Thread(target=output_loop).start() Thread(target=input_loop).start()
class MainWindow(QMainWindow): def __init__(self): """ MainWindow Constructor """ super().__init__() self.initializeUI() def initializeUI(self): """Set up the GUI's main window.""" self.setMinimumSize(700, 400) self.setWindowTitle("GIF and Image Viewer") # Set up the main window, menu, and dock widget self.setUpMainWindow() self.displayFilesDock() self.createActions() self.createMenus() self.createToolbar() self.show() # Display the main window def setUpMainWindow(self): """Set up the application's main window and widgets.""" self.movie = QMovie() # Create movie object self.movie.stateChanged.connect(self.changeButtonStates) self.media_label = QLabel() # Create label to place images/GIFs on self.media_label.setPixmap(QPixmap("icons/image_label.png")) self.media_label.setFrameShape(QFrame.Shape.StyledPanel) self.media_label.setAlignment(Qt.AlignmentFlag.AlignCenter) # Prevent the label from resizing and affecting the # dock widget when viewing images self.media_label.setSizePolicy(QSizePolicy.Policy.Ignored, QSizePolicy.Policy.Expanding) self.setCentralWidget(self.media_label) def createActions(self): """Create the application's menu actions.""" # Create actions for File menu self.open_act = QAction("Open...", self, triggered=self.openDirectory) self.open_act.setShortcut(QKeySequence.StandardKey.Open) self.quit_act = QAction("Quit Viewer", self, triggered=self.close) self.quit_act.setShortcut(QKeySequence.StandardKey.Quit) # Ctrl+Q # Create actions for View menu # Handle the visibility of the dock widget self.show_dock_act = self.files_dock.toggleViewAction() self.show_dock_act.setText("Show Media Folder") # Create actions for the toolbar (These actions could also be # added to the GUI's menu bar or to a context menu) self.play_act = QAction(QIcon("icons/play.png"), "Play", self, triggered=self.startMovie) self.pause_act = QAction(QIcon("icons/pause.png"), "Pause", self, triggered=self.pauseMovie) self.stop_act = QAction(QIcon("icons/stop.png"), "Stop/Reset", self, triggered=self.stopMovie) self.disableMovieButtons() def createMenus(self): """Create the application's menu.""" # Make the toolbar appear in the main window for macOS users. # More information about this in Chapter 2 - Building the Foundation for GUIs if QSysInfo.productType() == "macos" or "osx": self.menuBar().setNativeMenuBar(False) self.file_menu = self.menuBar().addMenu("&File") self.file_menu.addAction(self.open_act) self.file_menu.addSeparator() self.file_menu.addAction(self.quit_act) self.view_menu = self.menuBar().addMenu("&View") self.view_menu.addAction(self.show_dock_act) def createToolbar(self): """Create the application's toolbar for playing GIFs.""" toolbar = self.addToolBar("GIF Controls Toolbar") toolbar.setIconSize(QSize(24, 24)) # Add actions to the toolbar toolbar.addAction(self.play_act) toolbar.addAction(self.pause_act) toolbar.addAction(self.stop_act) def displayFilesDock(self): """Dock widget that displays the movie file location in a QLineEdit widget, provides a button for opening directories with images and GIFs, and shows the media from the selected folder in a QTreeWidget.""" self.files_dock = QDockWidget() self.files_dock.setWindowTitle("Media Folder") self.files_dock.setAllowedAreas(Qt.DockWidgetArea.LeftDockWidgetArea) folder_label = QLabel("Media Location:") # The QLineEdit widget is set to read-only as a quick way to display # the folder path self.folder_line = QLineEdit() self.folder_line.setMinimumWidth(100) self.folder_line.setReadOnly(True) open_button = QPushButton("Open...") open_button.clicked.connect(self.openDirectory) folder_h_box = QHBoxLayout() folder_h_box.addWidget(folder_label) folder_h_box.addWidget(self.folder_line) folder_h_box.addWidget(open_button) self.files_tree = QTreeWidget() self.files_tree.setHeaderLabel("Media Files") self.files_tree.setColumnCount(1) self.files_tree.itemSelectionChanged.connect(self.displayMediaFile) # Set up the dock's layout dock_v_box = QVBoxLayout() dock_v_box.addLayout(folder_h_box) dock_v_box.addWidget(self.files_tree) dock_container = QWidget() dock_container.setLayout(dock_v_box) self.files_dock.setWidget(dock_container) self.addDockWidget(Qt.DockWidgetArea.LeftDockWidgetArea, self.files_dock) def openDirectory(self): """Open a QFileDialog for selecting a local directory. Only display image and GIF files.""" directory = QFileDialog.getExistingDirectory(self, "Choose Directory", "", QFileDialog.Option.ShowDirsOnly) # Specify the file mode to only select directories if directory: # Display the selected folder text in the QLineEdit. There are other, and # possibly better, ways to handle displaying the text, but for simplicity, # this is the method used in this GUI self.folder_line.setText(directory) # Get the contents of the directory and only display files with # specified extensions file_dir = QDir(directory) filters = ["*.gif", "*.png", "*.jpg", "*.jpeg"] files_info = file_dir.entryInfoList(filters, QDir.Filter.Files) # Clear the contents of the QTreeWidget if self.files_tree.model().rowCount() > 0: # NOTE: Since files_tree is connected to the itemSelectionChanged signal, # using the clear() method below will also cause the signal to be emitted. # This causes an undesired issue because of how the items are deleted. To avoid # the signal being called, QObject.blockSignals() will halt files_tree from # emitting signals while removing items self.files_tree.blockSignals(True) # Use the convenience method clear() provided by QTreeWidget to remove # all items and selections self.files_tree.clear() self.files_tree.blockSignals(False) # Reset the QLabel and its image, and disable the movie buttons (in case the # last item selected was a GIF) self.media_label.clear() self.media_label.setPixmap(QPixmap("icons/image_label.png")) self.disableMovieButtons() # Create items for each file and add them to the tree for file in files_info: item = QTreeWidgetItem() item.setText(0, file.fileName()) self.files_tree.addTopLevelItem(item) def displayMediaFile(self): """Display the selected media file on the QLabel. Used instead of the itemClicked signal to handle whether the user clicks on an item or if arrow keys are used to navigate the items in the tree.""" # Check the state of the movie, i.e. check if a movie is playing. If it is, # stop the movie if self.movie.state() == QMovie.MovieState.Running: self.stopMovie() # Get the text from the QLineEdit, folder_line, and concatenate it with # the selected item's text column = self.files_tree.currentColumn() media_location = self.folder_line.text() + "/" + self.files_tree.selectedItems()[0].text(column) if media_location.split(".")[1] == "gif": self.movie.setFileName(media_location) # Check if image data is valid before playing if self.movie.isValid(): # Use setMovie() to set the label's contents as the selected GIF self.media_label.setMovie(self.movie) self.startMovie() # Call method to begin playing else: # Disable all buttons when an image is selected self.disableMovieButtons() # Set the label's pixmap (have the image fit the current size of the image label) self.media_label.setPixmap(QPixmap(media_location).scaled(self.media_label.size(), Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation)) def startMovie(self): """Start playing the movie.""" self.movie.start() def pauseMovie(self): """Pause the movie.""" self.movie.setPaused(True) def stopMovie(self): """Stop playing the movie and reset the movie back to the first frame.""" self.movie.stop() self.movie.jumpToFrame(0) def changeButtonStates(self, state): """Slot that handles enabling/disabling buttons in the toolbar based on the state of QMovie.""" if state == QMovie.MovieState.Running: # The animation begins playing once control returns to the event loop self.play_act.setEnabled(False) self.pause_act.setEnabled(True) self.stop_act.setEnabled(True) if state == QMovie.MovieState.Paused: self.play_act.setEnabled(True) self.pause_act.setEnabled(False) self.stop_act.setEnabled(False) if state == QMovie.MovieState.NotRunning: self.play_act.setEnabled(True) self.pause_act.setEnabled(False) self.stop_act.setEnabled(False) def disableMovieButtons(self): """Simple method to disable the movie buttons in the toolbar.""" self.play_act.setEnabled(False) self.pause_act.setEnabled(False) self.stop_act.setEnabled(False)