def init_menu_bar(self): menu_bar = self.menuBar() file_menu = menu_bar.addMenu(_('File')) options_menu = menu_bar.addMenu(_('Options')) manga_menu = QMenu(_('Manga DB'), self) file_menu.addMenu(manga_menu) save_act = QAction(_('Save '), self) save_act.triggered.connect(self.save_sites) manga_menu.addAction(save_act) imp_act = QAction(_('Import'), self) imp_act.triggered.connect(self.import_db) manga_menu.addAction(imp_act) relocate_act = QAction(_('Relocate'), self) relocate_act.triggered.connect(self.relocate_db) manga_menu.addAction(relocate_act) aot_menu = QAction(_('Stay on top'), self) aot_menu.setCheckable(True) aot_menu.triggered.connect( lambda: self.change_sot(aot_menu.isChecked())) options_menu.addAction(aot_menu) aot_menu.setChecked(self.config.sot) dark_mode = QAction(_('Dark mode'), self) dark_mode.setCheckable(True) dark_mode.triggered.connect( lambda: self.redraw_palette(dark_mode.isChecked())) options_menu.addAction(dark_mode) dark_mode.setChecked(self.config.dark_mode)
class ObjectViewSelectionToggle(object): def __init__(self, name, menuparent): self.name = name self.menuparent = menuparent self.action_view_toggle = QAction("{0} visible".format(name), menuparent) self.action_select_toggle = QAction("{0} selectable".format(name), menuparent) self.action_view_toggle.setCheckable(True) self.action_view_toggle.setChecked(True) self.action_select_toggle.setCheckable(True) self.action_select_toggle.setChecked(True) self.action_view_toggle.triggered.connect(self.handle_view_toggle) self.action_select_toggle.triggered.connect(self.handle_select_toggle) menuparent.addAction(self.action_view_toggle) menuparent.addAction(self.action_select_toggle) def handle_view_toggle(self, val): if not val: self.action_select_toggle.setChecked(False) else: self.action_select_toggle.setChecked(True) def handle_select_toggle(self, val): if val: self.action_view_toggle.setChecked(True) def is_visible(self): return self.action_view_toggle.isChecked() def is_selectable(self): return self.action_select_toggle.isChecked()
class Example(QWidget): def __init__(self): super().__init__() self.initUI() def initUI(self): grid = QGridLayout() self.setLayout(grid) self.txtBox = QLineEdit() self.menu = QMenu() self.btn = QPushButton("Dropdown") self.cmbox = CheckableComboBox() self.btn2 = QPushButton("Test checked") self.Act1 = QAction("Action 1", self.menu) self.Act1.setCheckable(True) self.Act2 = QAction("Action 2", self.menu) self.Act2.setCheckable(True) self.menu.addAction(self.Act1) self.menu.addAction(self.Act2) self.btn.setMenu(self.menu) grid.addWidget(self.txtBox, 0, 0, 1, 3) grid.addWidget(self.btn, 1, 0) grid.addWidget(self.cmbox, 1, 1) grid.addWidget(self.btn2, 1, 2) for i in range(3, 5): self.cmbox.addItem("Action " + str(i)) self.btn2.clicked.connect(self.test_checked) self.cmbox.view().pressed.connect(self.handle_item_pressed) # WIP self.move(300, 150) self.setMinimumWidth(800) self.setWindowTitle('checkComboBox') self.show() def test_checked(self, attr1): print(self.Act1.isChecked()) print(self.Act2.isChecked()) print(self.cmbox.isChecked(0)) print(self.cmbox.isChecked(1)) self.txtBox.setText("Action 1 is: " + str(self.Act1.isChecked()) + " and Action 2 is: " + str(self.Act2.isChecked()) + " and Action 3 is: " + str(self.cmbox.isChecked(0)) + " and Action 4 is: " + str(self.cmbox.isChecked(1))) # TODO - Fix for chkbox and not only text. def handle_item_pressed(self, index): item = self.cmbox.model().itemFromIndex(index) print(item.checkState())
def init_menu_bar(self): menu_bar = self.menuBar() file_menu = menu_bar.addMenu(_('File')) entry_menu = menu_bar.addMenu(_('Entry')) filter_menu = menu_bar.addMenu(_('Filter')) view_menu = menu_bar.addMenu(_('View')) redraw_on_release_grid_action = QAction(_('Refresh on slider release'), self) redraw_on_release_grid_action.setCheckable(True) redraw_on_release_grid_action.setChecked(True) view_menu.addAction(redraw_on_release_grid_action) redraw_on_release_grid_action.triggered.connect( lambda: self.change_redraw_on_release(redraw_on_release_grid_action .isChecked())) redraw_on_release_grid_action.setChecked(self.config.redraw_on_release) self.change_redraw_on_release(self.config.redraw_on_release) imp_act = QAction(_('Import db'), self) imp_act.triggered.connect(self.import_db) file_menu.addAction(imp_act) exp_act = QAction(_('Export db'), self) exp_act.triggered.connect(self.export_db) file_menu.addAction(exp_act) aot_menu = QAction(_('Stay on top'), self) aot_menu.setCheckable(True) aot_menu.triggered.connect( lambda: self.change_sot(aot_menu.isChecked())) view_menu.addAction(aot_menu) aot_menu.setChecked(self.config.stay_on_top) dark_mode = QAction(_('Dark mode'), self) dark_mode.setCheckable(True) dark_mode.triggered.connect( lambda: self.redraw_palette(dark_mode.isChecked())) view_menu.addAction(dark_mode) dark_mode.setChecked(self.config.dark_mode) new_entry = QAction(_('Add new entry'), self) new_entry.triggered.connect(self.add_new_entry) entry_menu.addAction(new_entry) filter_menu.addSection('Entry type') for entry_type in EntryType: self.add_filter(entry_type.value, filter_menu, self.filter_type_changed) filter_menu.addSeparator() filter_menu.addSection('Progress status') for entry_status in ProgressStatus: self.add_filter(entry_status.value, filter_menu, self.filter_status_changed)
def fnProcesaSeleccionRats(self, action: QAction): def llenarTabla(msg): self.fillTableWidget(self.pandasUtils.tempDf) self.overlay.killAndHide() self.ratsSeleccionados[str(action.data())] = action.isChecked() print(f"Fn procesa seleccion Rat {action} {self.ratsSeleccionados}") if (sum([1 for rat, v in self.ratsSeleccionados.items() if v is True]) == 0): action.setChecked(True) self.ratsSeleccionados[str(action.data())] = action.isChecked() self.fnMuestraCantidadEnRats() self.fnAplicaFiltrosDfOk(llenarTabla)
def add_filter(self, name, filter_menu, function): a = QAction(_(name), self) a.setCheckable(True) a.triggered.connect(lambda: function(name, a.isChecked())) filter_menu.addAction(a) a.setChecked(not getattr(self.config, name.lower())) a.trigger()
class MenuBarWidget(QMenuBar): def __init__(self, mainWidget): super().__init__() self.mainWidget: MainWidget = mainWidget self.file = self.addMenu("File") self.audio = self.addMenu("Audio") self.configDialog = QAction('Settings', self) self.configDialog.triggered.connect(ConfigDialog.showDialog) self.autoPlay = QAction('Autoplay disorder') self.autoPlay.setCheckable(True) self.autoPlay.setChecked(False) self.autoPlay.triggered.connect(self.changeBtn) self.playAudio = QAction('Play disorder') self.playAudio.triggered.connect(self.playSample) self.file.addAction(self.configDialog) self.audio.addAction(self.autoPlay) self.audio.addAction(self.playAudio) def playSample(self): QSound.play(ConfigControl.get_disorderPath()) def changeBtn(self): if self.autoPlay.isChecked(): self.mainWidget.playDisorder.setDisabled(1) else: self.mainWidget.playDisorder.setEnabled(1)
class MenuBarWidget(QMenuBar): def __init__(self, mainWidget): super().__init__() self.mainWidget: MainWidget = mainWidget self.file = self.addMenu("File") self.edit = self.addMenu("Edit") self.show = self.addMenu("Show") self.showFileTreeAct = QAction('Show file tree', shortcut="Ctrl+D", enabled=True, checkable=True, triggered=self.showFileTree) self.setDirect = QAction('Select main directory', triggered=self.setActiveDirectory) self.file.addAction(self.setDirect) self.show.addAction(self.showFileTreeAct) def showFileTree(self): showFiles = self.showFileTreeAct.isChecked() self.mainWidget.tree.setHidden(not showFiles) def setActiveDirectory(self): directory = str( QFileDialog.getExistingDirectory(self, "Select Directory")) self.mainWidget.model.setRootPath(directory) tmpIdx = self.mainWidget.model.index(directory) self.mainWidget.tree.setRootIndex(tmpIdx)
class ribbon_optics_files(QToolBar): def __init__(self): QToolBar.__init__(self) self.setToolButtonStyle(Qt.ToolButtonTextUnderIcon) self.setIconSize(QSize(42, 42)) self.run = play(self, "optics_ribbon_run", run_text=wrap_text(_("Run optical simulation"), 5)) self.addAction(self.run) self.fx_box = mode_selector() self.fx_box.show_all = True self.fx_box.update() self.addWidget(self.fx_box) self.spectrum = tb_spectrum() self.addWidget(self.spectrum) self.configwindow = QAction(icon_get("preferences-system"), _("Configure"), self) self.addAction(self.configwindow) self.optical_filter = QAction(icon_get("optical_filter"), _("Optical\nFilter"), self) self.optical_filter.setCheckable(True) self.optical_filter.triggered.connect(self.callback_filter_clicked) self.menu_optical_filter = QMenu(self) self.optical_filter.setMenu(self.menu_optical_filter) self.filter_edit = QAction(_("Edit"), self) self.filter_edit.triggered.connect(self.callback_filter_window) self.menu_optical_filter.addAction(self.filter_edit) self.addAction(self.optical_filter) spacer = QWidget() spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.addWidget(spacer) self.help = QAction(icon_get("help"), _("Help"), self) self.addAction(self.help) def callback_filter_clicked(self): f = inp() f.load(os.path.join(get_sim_path(), "filter.inp")) enabled = f.set_token("#filter_enabled", str(self.optical_filter.isChecked())) f.save() def callback_filter_window(self): widget = tab_class(os.path.join(get_sim_path(), "filter.inp")) widget.setWindowIcon(icon_get("filter_wheel")) widget.setWindowTitle(_("Filter editor") + " (https://www.gpvdm.com)") widget.show()
def setUpBotAction(self): botSetting = QAction('&Bot', self) #botSetting.triggered.connect(self.joinChannel) botSetting.triggered.connect(self.openBotSetting) self.fileMenu.addAction(botSetting) botOnAndOff = QAction('&Bot Running', self) botOnAndOff.setCheckable(True) #change later botOnAndOff.setChecked(False) self.botRunning = botOnAndOff.isChecked() botOnAndOff.triggered.connect(self.initializeBot) self.fileMenu.addAction(botOnAndOff)
class ToolsMenu(QMenu): """Menu with file actions.""" def __init__(self, parent, diagram, title='&Settings'): """Initializes the class.""" super(ToolsMenu, self).__init__(parent) self.setTitle(title) self._diagram = diagram self.parent = parent self._selmode_action = QAction('&Selection Mode', self) self._selmode_action.setCheckable(True) self._selmode_action.setStatusTip('On/off selection mode') self._selmode_action.triggered.connect(self._select_mode) self.addAction(self._selmode_action) self.addSeparator() self._operation_mode_group = QActionGroup(self, exclusive=True) self._operation_mode_group.triggered.connect(self._select_operation) self._point_op_action = QAction('&Point Operations', self) self._point_op_action.setCheckable(True) self._point_op_action.setChecked(True) self._point_op_action.setData(OperatinState.point) self._operation_mode_group.addAction(self._point_op_action) self._point_op_action.setStatusTip('Set point operation mode') self.addAction(self._point_op_action) self._line_op_action = QAction('&Line Operations', self) self._line_op_action.setCheckable(True) self._line_op_action.setData(OperatinState.line) self._operation_mode_group.addAction(self._line_op_action) self._line_op_action.setStatusTip('Set line operation mode') self.addAction(self._line_op_action) def _select_mode(self): """set diagram mode""" state = self._selmode_action.isChecked() self._diagram.set_select_mode(state) self._enable_operation(not state) def _select_operation(self): """set diagram operation""" action = self._operation_mode_group.checkedAction() self._diagram.set_operation_state(action.data()) def _enable_operation(self, enable): """enable operations items""" self._line_op_action.setEnabled(enable) self._point_op_action.setEnabled(enable)
def __init__(self, parent): super().__init__(parent) self.parent, unitMeasure, rulerSize, colors, zoom= parent, QMenu(gettext("Unit Measure"), self), QMenu(gettext("Ruler Size"), self), QMenu(gettext("Colors"), self), QMenu(gettext("Zoom"), self) self.addAction(QAction(parent.orientation[1 if not parent.oH else 0], self, triggered= partial(parent.changeOrientation, 1 if not parent.oH else 0))) for menu in [ [ zoom, parent.zooms, parent.changeMode, parent.zoom ], [ unitMeasure, parent.unitMeasure, parent.changeUnitMeasure, parent.cUM ], [ rulerSize, parent.rulerSize, parent.changeRulerSize, parent.sXY ], [ colors, parent.colors, parent.changeRulerColor, parent.defaultColors ] ]: for i, item in enumerate(menu[1]): item= QAction(item[0][0], self, triggered= partial(menu[2], i)) if type(menu[3]) == type(list()) and i == len(menu[1]) - 1: menu[0].addSeparator() item.setEnabled(False if menu[1] == menu[3] else True) else: item.setCheckable(True) item.setChecked(True if i == menu[3] else False) item.setEnabled(not item.isChecked()) menu[0].addAction(item) if menu[0] in [ unitMeasure, colors ]: menu[0].setEnabled(not bool(parent.zoom)) self.addMenu(menu[0]) self.addSeparator() self.addAction(QAction(parent.about, self, triggered= self.AboutDialog(parent).exec_)) self.addSeparator() self.addAction(QAction(gettext("Exit"), self, triggered= parent.close))
class ImageViewer(QMainWindow): def __init__(self): super(ImageViewer, self).__init__() self.printer = QPrinter() self.scaleFactor = 0.0 self.imageLabel = QLabel() self.imageLabel.setBackgroundRole(QPalette.Base) self.imageLabel.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored) self.imageLabel.setScaledContents(True) self.scrollArea = QScrollArea() self.scrollArea.setBackgroundRole(QPalette.Dark) self.scrollArea.setWidget(self.imageLabel) self.setCentralWidget(self.scrollArea) self.createActions() self.createMenus() self.setWindowTitle("Image Viewer") self.resize(500, 400) def open(self): fileName, _ = QFileDialog.getOpenFileName(self, "Open File", QDir.currentPath()) if fileName: image = QImage(fileName) if image.isNull(): QMessageBox.information(self, "Image Viewer", "Cannot load %s." % fileName) return self.imageLabel.setPixmap(QPixmap.fromImage(image)) self.scaleFactor = 1.0 self.printAct.setEnabled(True) self.fitToWindowAct.setEnabled(True) self.updateActions() if not self.fitToWindowAct.isChecked(): self.imageLabel.adjustSize() def print_(self): dialog = QPrintDialog(self.printer, self) if dialog.exec_(): painter = QPainter(self.printer) rect = painter.viewport() size = self.imageLabel.pixmap().size() size.scale(rect.size(), Qt.KeepAspectRatio) painter.setViewport(rect.x(), rect.y(), size.width(), size.height()) painter.setWindow(self.imageLabel.pixmap().rect()) painter.drawPixmap(0, 0, self.imageLabel.pixmap()) def zoomIn(self): self.scaleImage(1.25) def zoomOut(self): self.scaleImage(0.8) def normalSize(self): self.imageLabel.adjustSize() self.scaleFactor = 1.0 def fitToWindow(self): fitToWindow = self.fitToWindowAct.isChecked() self.scrollArea.setWidgetResizable(fitToWindow) if not fitToWindow: self.normalSize() self.updateActions() def about(self): QMessageBox.about( self, "About Image Viewer", "<p>The <b>Image Viewer</b> example shows how to combine " "QLabel and QScrollArea to display an image. QLabel is " "typically used for displaying text, but it can also display " "an image. QScrollArea provides a scrolling view around " "another widget. If the child widget exceeds the size of the " "frame, QScrollArea automatically provides scroll bars.</p>" "<p>The example demonstrates how QLabel's ability to scale " "its contents (QLabel.scaledContents), and QScrollArea's " "ability to automatically resize its contents " "(QScrollArea.widgetResizable), can be used to implement " "zooming and scaling features.</p>" "<p>In addition the example shows how to use QPainter to " "print an image.</p>") def createActions(self): self.openAct = QAction("&Open...", self, shortcut="Ctrl+O", triggered=self.open) self.printAct = QAction("&Print...", self, shortcut="Ctrl+P", enabled=False, triggered=self.print_) self.exitAct = QAction("E&xit", self, shortcut="Ctrl+Q", triggered=self.close) self.zoomInAct = QAction("Zoom &In (25%)", self, shortcut="Ctrl++", enabled=False, triggered=self.zoomIn) self.zoomOutAct = QAction("Zoom &Out (25%)", self, shortcut="Ctrl+-", enabled=False, triggered=self.zoomOut) self.normalSizeAct = QAction("&Normal Size", self, shortcut="Ctrl+S", enabled=False, triggered=self.normalSize) self.fitToWindowAct = QAction("&Fit to Window", self, enabled=False, checkable=True, shortcut="Ctrl+F", triggered=self.fitToWindow) self.aboutAct = QAction("&About", self, triggered=self.about) self.aboutQtAct = QAction("About &Qt", self, triggered=QApplication.instance().aboutQt) def createMenus(self): self.fileMenu = QMenu("&File", self) self.fileMenu.addAction(self.openAct) self.fileMenu.addAction(self.printAct) self.fileMenu.addSeparator() self.fileMenu.addAction(self.exitAct) self.viewMenu = QMenu("&View", self) self.viewMenu.addAction(self.zoomInAct) self.viewMenu.addAction(self.zoomOutAct) self.viewMenu.addAction(self.normalSizeAct) self.viewMenu.addSeparator() self.viewMenu.addAction(self.fitToWindowAct) self.helpMenu = QMenu("&Help", self) self.helpMenu.addAction(self.aboutAct) self.helpMenu.addAction(self.aboutQtAct) self.menuBar().addMenu(self.fileMenu) self.menuBar().addMenu(self.viewMenu) self.menuBar().addMenu(self.helpMenu) def updateActions(self): self.zoomInAct.setEnabled(not self.fitToWindowAct.isChecked()) self.zoomOutAct.setEnabled(not self.fitToWindowAct.isChecked()) self.normalSizeAct.setEnabled(not self.fitToWindowAct.isChecked()) def scaleImage(self, factor): self.scaleFactor *= factor self.imageLabel.resize(self.scaleFactor * self.imageLabel.pixmap().size()) self.adjustScrollBar(self.scrollArea.horizontalScrollBar(), factor) self.adjustScrollBar(self.scrollArea.verticalScrollBar(), factor) self.zoomInAct.setEnabled(self.scaleFactor < 3.0) self.zoomOutAct.setEnabled(self.scaleFactor > 0.333) def adjustScrollBar(self, scrollBar, factor): scrollBar.setValue( int(factor * scrollBar.value() + ((factor - 1) * scrollBar.pageStep() / 2)))
class Florodoro(QWidget): def parseArguments(self): parser = argparse.ArgumentParser( description="A pomodoro timer that grows procedurally generated trees and flowers while you're studying.", ) parser.add_argument( "-d", "--debug", action="store_true", help="run the app in debug mode", ) return parser.parse_args() def __init__(self): super().__init__() arguments = self.parseArguments() self.DEBUG = arguments.debug os.chdir(os.path.dirname(os.path.realpath(__file__))) self.MIN_WIDTH = 600 self.MIN_HEIGHT = 350 self.setMinimumWidth(self.MIN_WIDTH) self.setMinimumHeight(self.MIN_HEIGHT) self.ROOT_FOLDER = os.path.expanduser("~/.florodoro/") self.HISTORY_FILE_PATH = self.ROOT_FOLDER + "history" + ("" if not self.DEBUG else "-debug") + ".yaml" self.CONFIGURATION_FILE_PATH = self.ROOT_FOLDER + "config" + ("" if not self.DEBUG else "-debug") + ".yaml" self.history = History(self.HISTORY_FILE_PATH) self.SOUNDS_FOLDER = "sounds/" self.PLANTS_FOLDER = "plants/" self.IMAGE_FOLDER = "images/" self.TEXT_COLOR = self.palette().text().color() self.BREAK_COLOR = "#B37700" self.APP_NAME = "Florodoro" self.STUDY_ICON = qtawesome.icon('fa5s.book', color=self.TEXT_COLOR) self.BREAK_ICON = qtawesome.icon('fa5s.coffee', color=self.BREAK_COLOR) self.CONTINUE_ICON = qtawesome.icon('fa5s.play', color=self.TEXT_COLOR) self.PAUSE_ICON = qtawesome.icon('fa5s.pause', color=self.TEXT_COLOR) self.RESET_ICON = qtawesome.icon('fa5s.undo', color=self.TEXT_COLOR) self.PLANTS = [GreenTree, DoubleGreenTree, OrangeTree, CircularFlower] self.PLANT_NAMES = ["Spruce", "Double spruce", "Maple", "Flower"] self.MAX_PLANT_AGE = 90 # maximum number of minutes to make the plant optimal in size self.WIDGET_SPACING = 10 self.MAX_TIME = 180 self.STEP = 5 self.INITIAL_TEXT = "Start!" self.menuBar = QMenuBar(self) self.presets_menu = self.menuBar.addMenu('&Presets') self.presets = { "Classic": (25, 5, 4), "Extended": (45, 12, 2), "Sitcomodoro": (65, 25, 1), } for name in self.presets: study_time, break_time, cycles = self.presets[name] self.presets_menu.addAction( QAction(f"{name} ({study_time} : {break_time} : {cycles})", self, triggered=partial(self.load_preset, study_time, break_time, cycles))) self.DEFAULT_PRESET = "Classic" self.options_menu = self.menuBar.addMenu('&Options') self.notify_menu = self.options_menu.addMenu("&Notify") self.sound_action = QAction("&Sound", self, checkable=True, checked=not self.DEBUG, triggered=lambda _: self.volume_slider.setDisabled( not self.sound_action.isChecked())) self.notify_menu.addAction(self.sound_action) self.volume_slider = QSlider(Qt.Horizontal, minimum=0, maximum=100, value=85) slider_action = QWidgetAction(self) slider_action.setDefaultWidget(SpacedQWidget(self.volume_slider)) self.notify_menu.addAction(slider_action) self.popup_action = QAction("&Pop-up", self, checkable=True, checked=True) self.notify_menu.addAction(self.popup_action) self.menuBar.addAction( QAction( "&Statistics", self, triggered=lambda: self.statistics.show() if self.statistics.isHidden() else self.statistics.hide() ) ) self.menuBar.addAction( QAction( "&About", self, triggered=lambda: QMessageBox.information( self, "About", "This application was created by Tomáš Sláma. It is heavily inspired by the Android app Forest, " "but with all of the plants generated procedurally. It's <a href='https://github.com/xiaoxiae/Florodoro'>open source</a> and licensed " "under MIT, so do as you please with the code and anything else related to the project.", ), ) ) self.plant_menu = self.options_menu.addMenu("&Plants") self.overstudy_action = QAction("Overstudy", self, checkable=True) self.options_menu.addAction(self.overstudy_action) self.plant_images = [] self.plant_checkboxes = [] # dynamically create widgets for each plant for plant, name in zip(self.PLANTS, self.PLANT_NAMES): self.plant_images.append(tempfile.NamedTemporaryFile(suffix=".svg")) tmp = plant() tmp.set_max_age(1) tmp.set_age(1) tmp.save(self.plant_images[-1].name, 200, 200) setattr(self.__class__, name, QAction(self, icon=QIcon(self.plant_images[-1].name), text=name, checkable=True, checked=True)) action = getattr(self.__class__, name) self.plant_menu.addAction(action) self.plant_checkboxes.append(action) # the current plant that we're growing # if set to none, no plant is growing self.plant = None self.menuBar.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Maximum) main_vertical_layout = QVBoxLayout(self) main_vertical_layout.setContentsMargins(0, 0, 0, 0) main_vertical_layout.setSpacing(0) main_vertical_layout.addWidget(self.menuBar) self.canvas = Canvas(self) self.statistics = Statistics(self.history) font = self.font() font.setPointSize(100) self.main_label = QLabel(self, alignment=Qt.AlignCenter) self.main_label.setFont(font) self.main_label.setText(self.INITIAL_TEXT) font.setPointSize(26) self.cycle_label = QLabel(self) self.cycle_label.setAlignment(Qt.AlignTop) self.cycle_label.setMargin(20) self.cycle_label.setFont(font) main_horizontal_layout = QHBoxLayout(self) self.study_time_spinbox = QSpinBox(self, prefix="Study for: ", suffix="min.", minimum=1, maximum=self.MAX_TIME, singleStep=self.STEP) self.break_time_spinbox = QSpinBox(self, prefix="Break for: ", suffix="min.", minimum=1, maximum=self.MAX_TIME, singleStep=self.STEP, styleSheet=f'color:{self.BREAK_COLOR};') self.cycles_spinbox = QSpinBox(self, prefix="Cycles: ", minimum=1, value=1) # keep track of remaining number of cycles and the starting number of cycles self.remaining_cycles = 0 self.total_cycles = 0 # whether we're currently studying self.is_study_ongoing = False # whether we notified the user already during overstudy self.already_notified_during_overstudy = False stacked_layout = QStackedLayout(self, stackingMode=QStackedLayout.StackAll) stacked_layout.addWidget(self.main_label) stacked_layout.addWidget(self.cycle_label) stacked_layout.addWidget(self.canvas) main_vertical_layout.addLayout(stacked_layout) self.setStyleSheet("") self.study_button = QPushButton(self, clicked=self.start, icon=self.STUDY_ICON) self.break_button = QPushButton(self, clicked=self.start_break, icon=self.BREAK_ICON) self.pause_button = QPushButton(self, clicked=self.toggle_pause, icon=self.PAUSE_ICON) self.reset_button = QPushButton(self, clicked=self.reset, icon=self.RESET_ICON) main_horizontal_layout.addWidget(self.study_time_spinbox) main_horizontal_layout.addWidget(self.break_time_spinbox) main_horizontal_layout.addWidget(self.cycles_spinbox) main_horizontal_layout.addWidget(self.study_button) main_horizontal_layout.addWidget(self.break_button) main_horizontal_layout.addWidget(self.pause_button) main_horizontal_layout.addWidget(self.reset_button) main_vertical_layout.addLayout(main_horizontal_layout) self.setLayout(main_vertical_layout) self.study_timer_frequency = 1 / 60 * 1000 self.study_timer = QTimer(self, interval=int(self.study_timer_frequency), timeout=self.decrease_remaining_time) self.player = QMediaPlayer(self) self.setWindowIcon(QIcon(self.IMAGE_FOLDER + "icon.svg")) self.setWindowTitle(self.APP_NAME) # set initial UI state self.reset() # a list of name, getter and setter things to load/save when the app opens/closes # also dynamically get settings for selecting/unselecting plants self.CONFIGURATION_ATTRIBUTES = [("study-time", self.study_time_spinbox.value, self.study_time_spinbox.setValue), ("break-time", self.break_time_spinbox.value, self.break_time_spinbox.setValue), ("cycles", self.cycles_spinbox.value, self.cycles_spinbox.setValue), ("sound", self.sound_action.isChecked, self.sound_action.setChecked), ("sound-volume", self.volume_slider.value, self.volume_slider.setValue), ("pop-ups", self.popup_action.isChecked, self.popup_action.setChecked), ("overstudy", self.overstudy_action.isChecked, self.overstudy_action.setChecked)] + \ [(name.lower(), getattr(self.__class__, name).isChecked, getattr(self.__class__, name).setChecked) for _, name in zip(self.PLANTS, self.PLANT_NAMES)] # load the default preset self.load_preset(*self.presets[self.DEFAULT_PRESET]) self.load_settings() self.show() def load_settings(self): """Loads the settings file (if it exists).""" if os.path.exists(self.CONFIGURATION_FILE_PATH): with open(self.CONFIGURATION_FILE_PATH) as file: configuration = yaml.load(file, Loader=yaml.FullLoader) # don't crash if config is broken if not isinstance(configuration, dict): return for key in configuration: for name, _, setter in self.CONFIGURATION_ATTRIBUTES: if key == name: setter(configuration[key]) def save_settings(self): """Saves the settings file (if it exists).""" if not os.path.exists(self.ROOT_FOLDER): os.mkdir(self.ROOT_FOLDER) with open(self.CONFIGURATION_FILE_PATH, 'w') as file: configuration = {} for name, getter, _ in self.CONFIGURATION_ATTRIBUTES: configuration[name] = getter() file.write(yaml.dump(configuration)) def closeEvent(self, event): """Called when the app is being closed. Overridden to also save Florodoro settings.""" self.save_settings() super().closeEvent(event) def load_preset(self, study_value: int, break_value: int, cycles: int): """Load a pomodoro preset.""" self.study_time_spinbox.setValue(study_value) self.break_time_spinbox.setValue(break_value) self.cycles_spinbox.setValue(cycles) def keyPressEvent(self, event: QKeyEvent) -> None: """Debug-related keyboard controls.""" if self.DEBUG: if event.key() == Qt.Key_Escape: possible_plants = [plant for i, plant in enumerate(self.PLANTS) if self.plant_checkboxes[i].isChecked()] if len(possible_plants) != 0: self.plant = choice(possible_plants)() self.canvas.set_drawable(self.plant) self.plant.set_max_age(1) self.plant.set_age(1) self.canvas.update() def start_break(self): """Starts the break, instead of the study.""" # if we're overstudying, this can be pressed when studying, so save that we did so if self.overstudy_action.isChecked() and self.is_study_ongoing: self.save_study(ignore_remainder=False) self.start(do_break=True) def start(self, do_break=False): """The function for starting either the study or break timer (depending on do_break).""" self.study_button.setEnabled(do_break) self.break_button.setEnabled(self.overstudy_action.isChecked() and not do_break) self.reset_button.setDisabled(False) self.pause_button.setDisabled(False) self.pause_button.setIcon(self.PAUSE_ICON) # if we're initially starting to do cycles, reset their count # don't reset on break, because we could be doing a standalone break if self.remaining_cycles == 0 and not do_break: self.remaining_cycles = self.cycles_spinbox.value() self.total_cycles = self.remaining_cycles else: # if we're not studing and are about to when there is still leftover time, we're ending the break quicker than intended # therefore, reduce cycles by 1, since they would have been if the timer were to run out during break if not self.is_study_ongoing and not do_break and self.get_leftover_time() / self.total_time > 0: self.remaining_cycles -= 1 if self.remaining_cycles == 0: self.reset() return # set depending on whether we are currently studying or not self.is_study_ongoing = not do_break self.already_notified_during_overstudy = False self.main_label.setStyleSheet('' if not do_break else f'color:{self.BREAK_COLOR};') # the total time to study for (spinboxes are minutes) # since it's rounded down and it looks better to start at the exact time, 0.99 is added self.total_time = (self.study_time_spinbox if not do_break else self.break_time_spinbox).value() * 60 + 0.99 self.ending_time = datetime.now() + timedelta(minutes=self.total_time / 60) # so it's displayed immediately self.update_time_label(self.total_time) self.update_cycles_label() # don't start showing canvas and growing the plant when we're not studying if not do_break: possible_plants = [plant for i, plant in enumerate(self.PLANTS) if self.plant_checkboxes[i].isChecked()] if len(possible_plants) != 0: self.plant = choice(possible_plants)() self.canvas.set_drawable(self.plant) self.plant.set_max_age(min(1, (self.total_time / 60) / self.MAX_PLANT_AGE)) self.plant.set_age(0) else: self.plant = None self.study_timer.stop() # it could be running - we could be currently in a break self.study_timer.start() def toggle_pause(self): """Called when the pause button is pressed. Either stops the timer or starts it again, while also doing stuff to the pause icons.""" # stop the timer, if it's running if self.study_timer.isActive(): self.study_timer.stop() self.pause_button.setIcon(self.CONTINUE_ICON) self.pause_time = datetime.now() # if not, resume else: self.ending_time += datetime.now() - self.pause_time self.study_timer.start() self.pause_button.setIcon(self.PAUSE_ICON) def reset(self): """Reset the UI.""" self.study_timer.stop() self.pause_button.setIcon(self.PAUSE_ICON) self.main_label.setStyleSheet('') self.study_button.setDisabled(False) self.break_button.setDisabled(False) self.pause_button.setDisabled(True) self.reset_button.setDisabled(True) if self.plant is not None: self.plant.set_age(0) self.remaining_cycles = 0 self.main_label.setText(self.INITIAL_TEXT) self.cycle_label.setText('') def update_time_label(self, time): """Update the text of the time label, given some time in seconds.""" sign = -1 if time < 0 else 1 time = abs(time) hours = int(time // 3600) minutes = int((time // 60) % 60) seconds = int(time % 60) # smooth timer: hide minutes/hours if there are none result = "-" if sign == -1 else "" if hours == 0: if minutes == 0: result += str(seconds) else: result += str(minutes) + QTime(0, 0, seconds).toString(":ss") else: result += str(hours) + QTime(0, minutes, seconds).toString(":mm:ss") self.main_label.setText(result) def play_sound(self, name: str): """Play a file from the sound directory. Extension is not included, will be added automatically.""" for file in os.listdir(self.SOUNDS_FOLDER): # if the file starts with the provided name and only contains an extension after, try to play it if file.startswith(name) and file[len(name):][0] == ".": path = QDir.current().absoluteFilePath(self.SOUNDS_FOLDER + file) url = QUrl.fromLocalFile(path) content = QMediaContent(url) self.player.setMedia(content) self.player.setVolume(self.volume_slider.value()) self.player.play() def show_notification(self, message: str): """Show the specified notification using plyer.""" notification.notify(self.APP_NAME, message, self.APP_NAME, os.path.abspath(self.IMAGE_FOLDER + "icon.svg")) def update_cycles_label(self): """Update the cycles label, if we're currently studying and it wouldn't be 1/1.""" if self.total_cycles != 1 and self.is_study_ongoing: self.cycle_label.setText(f"{self.total_cycles - self.remaining_cycles + 1}/{self.total_cycles}") def get_leftover_time(self): """Return time until the timer runs out.""" return (self.ending_time - datetime.now()).total_seconds() def decrease_remaining_time(self): """Decrease the remaining time by the timer frequency. Updates clock/plant growth.""" if self.DEBUG: self.ending_time -= timedelta(seconds=30) self.update_time_label(self.get_leftover_time()) if self.get_leftover_time() <= 0: if self.is_study_ongoing: # only notify once per study, since this would be called all the time during overstudy if not self.already_notified_during_overstudy: if self.sound_action.isChecked(): self.play_sound("study_done") if self.popup_action.isChecked(): self.show_notification("Studying finished, take a break!") self.already_notified_during_overstudy = True if not self.overstudy_action.isChecked(): self.save_study() # save before break! self.start_break() else: self.history.add_break(datetime.now(), self.total_time // 60) self.statistics.refresh() if self.sound_action.isChecked(): self.play_sound("break_done") if self.popup_action.isChecked(): self.show_notification("Break is over!") self.remaining_cycles -= 1 if self.remaining_cycles <= 0: # <=, because we could have just started a simple break self.reset() else: self.start() self.update_cycles_label() else: # if there is leftover time and we haven't finished studying, grow the plant if self.is_study_ongoing: if self.plant is not None: self.plant.set_age(1 - (self.get_leftover_time() / self.total_time)) self.canvas.update() def save_study(self, ignore_remainder=True): """Save the record of the current study to the history file. By default, ignore the leftover time, since it will be a tiny number.""" date = datetime.now() duration = (self.total_time - self.get_leftover_time()) / 60 if ignore_remainder: duration = float(int(duration)) self.history.add_study(date, duration, self.plant) self.statistics.move() # move to the last plant self.statistics.refresh()
class PlotterWindow(QMainWindow): def __init__(self, get_transfer_callback): super(PlotterWindow, self).__init__() self.setWindowTitle('UAVCAN Plotter') self.setWindowIcon(get_app_icon()) self._active_data_types = set() self._get_transfer = get_transfer_callback self._update_timer = QTimer(self) self._update_timer.setSingleShot(False) self._update_timer.timeout.connect(self._update) self._update_timer.start(50) self._base_time = time.monotonic() self._plot_containers = [] # # Control menu # control_menu = self.menuBar().addMenu('&Control') self._stop_action = QAction(get_icon('stop'), '&Stop Updates', self) self._stop_action.setStatusTip('While stopped, all new data will be discarded') self._stop_action.setShortcut(QKeySequence('Ctrl+Shift+S')) self._stop_action.setCheckable(True) self._stop_action.toggled.connect(self._on_stop_toggled) control_menu.addAction(self._stop_action) self._pause_action = QAction(get_icon('pause'), '&Pause Updates', self) self._pause_action.setStatusTip('While paused, new data will be accumulated in memory ' 'to be processed once un-paused') self._pause_action.setShortcut(QKeySequence('Ctrl+Shift+P')) self._pause_action.setCheckable(True) self._pause_action.toggled.connect(self._on_pause_toggled) control_menu.addAction(self._pause_action) control_menu.addSeparator() self._reset_time_action = QAction(get_icon('history'), '&Reset', self) self._reset_time_action.setStatusTip('Base time will be reset; all plots will be reset') self._reset_time_action.setShortcut(QKeySequence('Ctrl+Shift+R')) self._reset_time_action.triggered.connect(self._do_reset) control_menu.addAction(self._reset_time_action) # # New Plot menu # plot_menu = self.menuBar().addMenu('&New Plot') for idx, pl_name in enumerate(PLOT_AREAS.keys()): new_plot_action = QAction('Add ' + pl_name, self) new_plot_action.setStatusTip('Add new plot window') new_plot_action.setShortcut(QKeySequence('Ctrl+Alt+' + str(idx))) new_plot_action.triggered.connect(partial(self._do_add_new_plot, pl_name)) plot_menu.addAction(new_plot_action) # # Window stuff # self.statusBar().showMessage('Use the "New Plot" menu to add plots') self.setCentralWidget(None) self.resize(600, 400) def _on_stop_toggled(self, checked): self._pause_action.setChecked(False) self.statusBar().showMessage('Stopped' if checked else 'Un-stopped') def _on_pause_toggled(self, checked): self.statusBar().showMessage('Paused' if checked else 'Un-paused') def _do_add_new_plot(self, plot_area_name): def remove(): self._plot_containers.remove(plc) plc = PlotContainerWidget(self, PLOT_AREAS[plot_area_name], self._active_data_types) plc.on_close = remove self._plot_containers.append(plc) docks = [ Qt.LeftDockWidgetArea, Qt.LeftDockWidgetArea, Qt.RightDockWidgetArea, Qt.RightDockWidgetArea, ] dock_to = docks[(len(self._plot_containers) - 1) % len(docks)] self.addDockWidget(dock_to, plc) if len(self._plot_containers) > 1: self.statusBar().showMessage('Drag plots by the header to rearrange or detach them') def _do_reset(self): self._base_time = time.monotonic() for plc in self._plot_containers: try: plc.reset() except Exception: logger.error('Failed to reset plot container', exc_info=True) logger.info('Reset done, new time base %r', self._base_time) def _update(self): if self._stop_action.isChecked(): while self._get_transfer() is not None: # Discarding everything pass return if not self._pause_action.isChecked(): while True: tr = self._get_transfer() if not tr: break self._active_data_types.add(tr.data_type_name) for plc in self._plot_containers: try: plc.process_transfer(tr.ts_mono - self._base_time, tr) except Exception: logger.error('Plot container failed to process a transfer', exc_info=True) for plc in self._plot_containers: try: plc.update() except Exception: logger.error('Plot container failed to update', exc_info=True)
class ToolBox(QMainWindow): def __init__(self, parent=None, view_manager=AbstractViewManager): QMainWindow.__init__(self, parent) self.manager = view_manager self.controller = view_manager.get_controller() self.favorites = self.controller.get_favorites() self.favorites.add_listener(self.on_fav_changed) self.ui = Ui_ToolBox() self.ui.setupUi(self) self.setWindowFlags(QtCore.Qt.Tool | QtCore.Qt.WindowStaysOnTopHint) self.edit = None self.move(40, 985) self.bind_all() self.action_thumb_filter = QAction("Show thumbnail page", self, checkable=True, triggered=self.url_combo_load) self.action_thumb_filter.setChecked(True) self.action_pix_filter = QAction("Show pictures page", self, checkable=True, triggered=self.url_combo_load) self.action_pix_filter.setChecked(True) self.action_video_filter = QAction("Show video page", self, checkable=True, triggered=self.url_combo_load) self.action_video_filter.setChecked(True) menu = QMenu(self) menu.addAction(self.action_thumb_filter) menu.addAction(self.action_pix_filter) menu.addAction(self.action_video_filter) self.ui.bn_filter.setMenu(menu) self.curr_urls = list() self.category_combo_load() def bind_all(self): self.ui.combo_category.currentIndexChanged.connect(self.url_combo_load) self.ui.bn_go.clicked.connect(lambda: self.controller.goto_url( self.curr_urls[self.ui.combo_url.currentIndex()].url)) self.ui.bn_edit.clicked.connect(self.favorite_edit) self.ui.bn_add_thumb.clicked.connect(self.add_thumb) self.ui.bn_add_page.clicked.connect(self.add_full) self.ui.bn_config.clicked.connect(self.manager.show_config_dialog) def panic(self): self.hide() if self.edit is not None: self.edit.hide() def add_thumb(self): self.controller.add_thumb_page_to_fav( self.ui.combo_category.currentText()) def add_full(self): self.controller.add_full_page_to_fav( self.ui.combo_category.currentText()) def favorite_edit(self): curr_record = self.curr_urls[self.ui.combo_url.currentIndex()] self.edit = FavoriteChangeDialog(None, curr_record, self.favorites) # self.edit.addAction(self.panic_action) self.edit.show() def on_fav_changed(self, record): if record is None: save_cat_index = self.ui.combo_category.currentIndex() save_url_index = self.ui.combo_url.currentIndex() self.category_combo_load() self.ui.combo_category.setCurrentIndex(save_cat_index) self.url_combo_load() self.ui.combo_url.setCurrentIndex(save_url_index) else: self.category_combo_load() self.ui.combo_category.setCurrentText(record.category) self.url_combo_load() self.ui.combo_url.setCurrentText(record.combo_view) def category_combo_load(self): self.ui.combo_category.clear() self.ui.combo_category.addItems(self.favorites.get_categories()) # def connect_to(self, tool_button): # self.tb = tool_button # tool_button.clicked.connect(self.on_clicked_show) # # def on_clicked_show(self): # if self.isHidden(): # self.show() # else: # self.hide() def url_combo_load(self): fav_list = self.favorites.get(self.ui.combo_category.currentText()) self.ui.combo_url.clear() self.curr_urls = list() for item in fav_list: if self.url_filter(item): self.ui.combo_url.addItem(item.combo_view) self.curr_urls.append(item) def url_filter(self, item): if self.action_thumb_filter.isChecked(): if item.is_thumb(): return True if self.action_pix_filter.isChecked(): if item.is_pix(): return True if self.action_video_filter.isChecked(): if item.is_video(): return True return False def addAction(self, action): super().addAction(action) self.panic_action = action
class MainWindow(QMainWindow): def __init__(self, url): super(MainWindow, self).__init__() self.progress = 0 fd = QFile(":/jquery.min.js") if fd.open(QIODevice.ReadOnly | QFile.Text): self.jQuery = QTextStream(fd).readAll() fd.close() else: self.jQuery = '' QNetworkProxyFactory.setUseSystemConfiguration(True) self.view = QWebView(self) self.view.load(url) self.view.loadFinished.connect(self.adjustLocation) self.view.titleChanged.connect(self.adjustTitle) self.view.loadProgress.connect(self.setProgress) self.view.loadFinished.connect(self.finishLoading) self.locationEdit = QLineEdit(self) self.locationEdit.setSizePolicy(QSizePolicy.Expanding, self.locationEdit.sizePolicy().verticalPolicy()) self.locationEdit.returnPressed.connect(self.changeLocation) toolBar = self.addToolBar("Navigation") toolBar.addAction(self.view.pageAction(QWebPage.Back)) toolBar.addAction(self.view.pageAction(QWebPage.Forward)) toolBar.addAction(self.view.pageAction(QWebPage.Reload)) toolBar.addAction(self.view.pageAction(QWebPage.Stop)) toolBar.addWidget(self.locationEdit) viewMenu = self.menuBar().addMenu("&View") viewSourceAction = QAction("Page Source", self) viewSourceAction.triggered.connect(self.viewSource) viewMenu.addAction(viewSourceAction) effectMenu = self.menuBar().addMenu("&Effect") effectMenu.addAction("Highlight all links", self.highlightAllLinks) self.rotateAction = QAction( self.style().standardIcon(QStyle.SP_FileDialogDetailedView), "Turn images upside down", self, checkable=True, toggled=self.rotateImages) effectMenu.addAction(self.rotateAction) toolsMenu = self.menuBar().addMenu("&Tools") toolsMenu.addAction("Remove GIF images", self.removeGifImages) toolsMenu.addAction("Remove all inline frames", self.removeInlineFrames) toolsMenu.addAction("Remove all object elements", self.removeObjectElements) toolsMenu.addAction("Remove all embedded elements", self.removeEmbeddedElements) self.setCentralWidget(self.view) def viewSource(self): accessManager = self.view.page().networkAccessManager() request = QNetworkRequest(self.view.url()) reply = accessManager.get(request) reply.finished.connect(self.slotSourceDownloaded) def slotSourceDownloaded(self): reply = self.sender() self.textEdit = QTextEdit() self.textEdit.setAttribute(Qt.WA_DeleteOnClose) self.textEdit.show() self.textEdit.setPlainText(QTextStream(reply).readAll()) self.textEdit.resize(600, 400) reply.deleteLater() def adjustLocation(self): self.locationEdit.setText(self.view.url().toString()) def changeLocation(self): url = QUrl.fromUserInput(self.locationEdit.text()) self.view.load(url) self.view.setFocus() def adjustTitle(self): if 0 < self.progress < 100: self.setWindowTitle("%s (%s%%)" % (self.view.title(), self.progress)) else: self.setWindowTitle(self.view.title()) def setProgress(self, p): self.progress = p self.adjustTitle() def finishLoading(self): self.progress = 100 self.adjustTitle() self.view.page().mainFrame().evaluateJavaScript(self.jQuery) self.rotateImages(self.rotateAction.isChecked()) def highlightAllLinks(self): code = """$('a').each( function () { $(this).css('background-color', 'yellow') } )""" self.view.page().mainFrame().evaluateJavaScript(code) def rotateImages(self, invert): if invert: code = """ $('img').each( function () { $(this).css('-webkit-transition', '-webkit-transform 2s'); $(this).css('-webkit-transform', 'rotate(180deg)') } )""" else: code = """ $('img').each( function () { $(this).css('-webkit-transition', '-webkit-transform 2s'); $(this).css('-webkit-transform', 'rotate(0deg)') } )""" self.view.page().mainFrame().evaluateJavaScript(code) def removeGifImages(self): code = "$('[src*=gif]').remove()" self.view.page().mainFrame().evaluateJavaScript(code) def removeInlineFrames(self): code = "$('iframe').remove()" self.view.page().mainFrame().evaluateJavaScript(code) def removeObjectElements(self): code = "$('object').remove()" self.view.page().mainFrame().evaluateJavaScript(code) def removeEmbeddedElements(self): code = "$('embed').remove()" self.view.page().mainFrame().evaluateJavaScript(code)
class ImageViewer(QMainWindow): def __init__(self): super(ImageViewer, self).__init__() self.printer = QPrinter() self.scaleFactor = 0.0 self.directory="" self.imageLabel = QLabel() self.imageLabel.setBackgroundRole(QPalette.Base) self.imageLabel.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored) self.imageLabel.setScaledContents(True) self.scrollArea = QScrollArea() self.scrollArea.setBackgroundRole(QPalette.Dark) self.scrollArea.setWidget(self.imageLabel) self.setCentralWidget(self.scrollArea) self.createActions() self.createMenus() self.setWindowTitle("IOA Image Viewer") self.resize(800, 600) def openImg(self,fileName): self.imageLabel = QLabel() self.imageLabel.setBackgroundRole(QPalette.Base) self.imageLabel.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored) self.imageLabel.setScaledContents(True) image = QImage(fileName) self.imageLabel.setPixmap(QPixmap.fromImage(image)) self.scaleFactor = 1.0 self.printAct.setEnabled(True) self.fitToWindowAct.setEnabled(True) self.updateActions() def open(self): fileName, _ = QFileDialog.getOpenFileName(self, "Open File", QDir.currentPath()) self.directory=fileName print(fileName) if fileName: image = QImage(fileName) if image.isNull(): QMessageBox.information(self, "Image Viewer", "Cannot load %s." % fileName) return self.imageLabel.setPixmap(QPixmap.fromImage(image)) self.scaleFactor = 1.0 self.printAct.setEnabled(True) self.fitToWindowAct.setEnabled(True) self.updateActions() if not self.fitToWindowAct.isChecked(): self.imageLabel.adjustSize() def print_(self): dialog = QPrintDialog(self.printer, self) if dialog.exec_(): painter = QPainter(self.printer) rect = painter.viewport() size = self.imageLabel.pixmap().size() size.scale(rect.size(), Qt.KeepAspectRatio) painter.setViewport(rect.x(), rect.y(), size.width(), size.height()) painter.setWindow(self.imageLabel.pixmap().rect()) painter.drawPixmap(0, 0, self.imageLabel.pixmap()) def zoomIn(self): self.scaleImage(1.25) def zoomOut(self): self.scaleImage(0.8) def normalSize(self): self.imageLabel.adjustSize() self.scaleFactor = 1.0 def fitToWindow(self): fitToWindow = self.fitToWindowAct.isChecked() self.scrollArea.setWidgetResizable(fitToWindow) if not fitToWindow: self.normalSize() self.updateActions() def faceDetect(self): try: face_cascade = cv2.CascadeClassifier(r"FaceRcognition-master\haarcascade_frontalface_default.xml") img = cv2.imread(self.directory) grey_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) faces = face_cascade.detectMultiScale(grey_img, scaleFactor=1.05, minNeighbors=5) c=0 #crop=[] #print(type(faces)) #print(faces) for x, y, w, h in faces: #crop.append(img[y:y+h,x:x+w]) #crimg=cv2.circle(img, (x+int(h/2), y+int(w/2)), int(h/2), (0, 255, 0), 3) img = cv2.rectangle(img, (x,y), (x+w, y+w), (0,255,0), 2) c=c+1 #a=0 #print(c) # for cimg in crop: # name="face detected image"+str(c) # cv2.imshow(name, cimg) # c+=1 #self.imageLabel.setPixmap(img) #print(QDir.currentPath) cv2.imwrite("Detected.jpg",img) #print(os.getcwd()) cdr=str(os.getcwd())+'\Detected.jpg' #pic = cdr[:-2]+"Detected.jpg'" image = QImage(cdr) if image.isNull(): QMessageBox.information(self, "IOA Image Viewer", "Cannot load %s." % cdr) return self.imageLabel.setPixmap(QPixmap.fromImage(image)) # cv2.imshow("face detected image", img) # cv2.waitKey(0) # cv2.destroyAllWindows() except: pass def imgResize(self): try: img = cv2.imread(self.directory,1) oheight, owidth = img.shape[0:2] iheight, hokPressed = QInputDialog.getInt(self, "Get Height","Height :", 0, 0, int(oheight), 1) iwidth , okPressed = QInputDialog.getInt(self, "Get Width","Width :", 0, 0, int(owidth), 1) resizedImg = cv2.resize(img,(int(iwidth),int(iheight))) print(iheight) print(iwidth) cv2.imshow("Resized.jpg",resizedImg) cv2.imwrite("Resized.jpg",resizedImg) cdr=str(QDir.currentPath())+'/Resized.jpg' print(cdr) self.openImg(cdr) # cdr=str(os.getcwd())+'\\Resized.jpg' # Rimage = QImage(cdr) # if Rimage.isNull(): # QMessageBox.information(self, "IOA Image Viewer", # "Cannot load %s." % cdr) # return # self.imageLabel.setPixmap(QPixmap.fromImage(Rimage)) # self.scaleFactor = 1.0 # self.imageLabel.setScaledContents(True) except: pass # scale_percent = 100 # percent of original size # width = int(img.shape[1] * scale_percent / 100) # height = int(img.shape[0] * scale_percent / 100) # dim = (width, height) # resized = cv2.resize(img, dim) def imgRotate(self): try: colorImage = Image.open(self.directory) transposed = colorImage.transpose(Image.ROTATE_90) transposed = transposed.save('Rotated.jpg') cdr=str(os.getcwd())+'\\Rotated.jpg' image = QImage(cdr) if image.isNull(): QMessageBox.information(self, "IOA Image Viewer", "Cannot load %s." % cdr) return self.imageLabel.setPixmap(QPixmap.fromImage(image)) except: pass def imgCropFace(self): try: img = cv2.imread(self.directory) face_cascade = cv2.CascadeClassifier(r"FaceRcognition-master\haarcascade_frontalface_default.xml") grey_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) faces = face_cascade.detectMultiScale(grey_img, scaleFactor=1.05, minNeighbors=5) c=0 for x,y,w,h in faces: img = cv2.rectangle(img,(x,y),(x+w, y+w),(0,255,0),1) cropped = img[y:y+h, x:x+w] cv2.imwrite("Rotated.jpg",cropped) cdr=str(os.getcwd())+'\\Rotated.jpg' image = QImage(cdr) if image.isNull(): QMessageBox.information(self, "IOA Image Viewer", "Cannot load %s." % cdr) return self.imageLabel.setPixmap(QPixmap.fromImage(image)) except: pass # def imgBlur(self): # img = cv2.imread(self.directory) # blur_image = cv2.GaussianBlur(img, (7,7), 0) #scale 1,2,3,4,5 # def imgEdge(self): # edge_img = cv2.Canny(img,100,200) #def imgDenoise(self): # result = cv2.fastNlMeansDenoisingColored(img,None,20,10,7,21) def about(self): QMessageBox.about(self, "About Image Viewer", "<p>The <b>Image Viewer</b> example shows how to combine " "QLabel and QScrollArea to display an image. QLabel is " "typically used for displaying text, but it can also display " "an image. QScrollArea provides a scrolling view around " "another widget. If the child widget exceeds the size of the " "frame, QScrollArea automatically provides scroll bars.</p>" "<p>The example demonstrates how QLabel's ability to scale " "its contents (QLabel.scaledContents), and QScrollArea's " "ability to automatically resize its contents " "(QScrollArea.widgetResizable), can be used to implement " "zooming and scaling features.</p>" "<p>In addition the example shows how to use QPainter to " "print an image.</p>") def createActions(self): self.openAct = QAction("&Open...", self, shortcut="Ctrl+O", triggered=self.open) self.printAct = QAction("&Print...", self, shortcut="Ctrl+P", enabled=False, triggered=self.print_) self.exitAct = QAction("E&xit", self, shortcut="Ctrl+Q", triggered=self.close) self.zoomInAct = QAction("Zoom &In (25%)", self, shortcut="Ctrl++", enabled=False, triggered=self.zoomIn) self.zoomOutAct = QAction("Zoom &Out (25%)", self, shortcut="Ctrl+-", enabled=False, triggered=self.zoomOut) self.normalSizeAct = QAction("&Normal Size", self, shortcut="Ctrl+S", enabled=False, triggered=self.normalSize) self.fitToWindowAct = QAction("&Fit to Window", self, enabled=False, checkable=True, shortcut="Ctrl+F", triggered=self.fitToWindow) self.aboutAct = QAction("&About", self, triggered=self.about) self.faceDetectAct = QAction("Face Detect",self,triggered=self.faceDetect,enabled=False) self.resizeAct = QAction("Resize",self,triggered=self.imgResize,enabled=False) self.rotateAct = QAction("Rotate", self,triggered=self.imgRotate, enabled=False) def createMenus(self): self.fileMenu = QMenu("&File", self) self.fileMenu.addAction(self.openAct) self.fileMenu.addAction(self.printAct) self.fileMenu.addSeparator() self.fileMenu.addAction(self.exitAct) self.viewMenu = QMenu("&View", self) self.viewMenu.addAction(self.zoomInAct) self.viewMenu.addAction(self.zoomOutAct) self.viewMenu.addAction(self.normalSizeAct) self.viewMenu.addSeparator() self.viewMenu.addAction(self.fitToWindowAct) self.optionsMenu = QMenu("&Options", self) self.optionsMenu.addAction(self.faceDetectAct) self.optionsMenu.addAction(self.resizeAct) self.optionsMenu.addAction(self.rotateAct) self.helpMenu = QMenu("&Help", self) self.helpMenu.addAction(self.aboutAct) self.menuBar().addMenu(self.fileMenu) self.menuBar().addMenu(self.viewMenu) self.menuBar().addMenu(self.optionsMenu) self.menuBar().addMenu(self.helpMenu) def updateActions(self): self.zoomInAct.setEnabled(not self.fitToWindowAct.isChecked()) self.zoomOutAct.setEnabled(not self.fitToWindowAct.isChecked()) self.normalSizeAct.setEnabled(not self.fitToWindowAct.isChecked()) self.resizeAct.setEnabled(not self.fitToWindowAct.isChecked()) self.faceDetectAct.setEnabled(not self.fitToWindowAct.isChecked()) self.rotateAct.setEnabled(not self.fitToWindowAct.isChecked()) def scaleImage(self, factor): self.scaleFactor *= factor self.imageLabel.resize(self.scaleFactor * self.imageLabel.pixmap().size()) self.adjustScrollBar(self.scrollArea.horizontalScrollBar(), factor) self.adjustScrollBar(self.scrollArea.verticalScrollBar(), factor) self.zoomInAct.setEnabled(self.scaleFactor < 3.0) self.zoomOutAct.setEnabled(self.scaleFactor > 0.333) def adjustScrollBar(self, scrollBar, factor): scrollBar.setValue(int(factor * scrollBar.value() + ((factor - 1) * scrollBar.pageStep()/2)))
class EditorBar(QToolBar): saveDocAsSignal = pyqtSignal() spellSignal = pyqtSignal(bool) whiteSpaceSignal = pyqtSignal(bool) boldSignal = pyqtSignal(bool) italicSignal = pyqtSignal(bool) underlineSignal = pyqtSignal(bool) strikethroughSignal = pyqtSignal(bool) subscriptSignal = pyqtSignal(bool) superscriptSignal = pyqtSignal(bool) def __init__(self, parent = None): QToolBar.__init__(self, parent) self.setWindowTitle('EditorBar') self.setIconSize(QSize(16, 16)) self.createActions() def createActions(self): self.settingsAction = QAction(self.tr("Settings"), self) self.settingsAction.setIcon(QIcon(":/icons/icons/configure.png")) self.settingsAction.triggered.connect(self.settings) self.addAction(self.settingsAction) self.saveDocAsAction = QAction(self.tr("Save As"), self) self.saveDocAsAction.triggered.connect(self.SaveDocumentAs) self.saveDocAsAction.setIcon(QIcon(":/icons/icons/filesave.png")) self.addAction(self.saveDocAsAction) self.spellAction = QAction(self.tr("Spellchecking"), self) self.spellAction.setIcon( QIcon(":/icons/icons/tools-check-spelling.png")) self.spellAction.setCheckable(True) self.spellAction.setChecked(settings.get('editor:spell')) self.spellAction.toggled.connect(self.spell) self.insertSeparator(self.spellAction) self.addAction(self.spellAction) self.whiteSpaceAction = QAction(self.tr("Show whitespace"), self) self.whiteSpaceAction.setIcon( QIcon(":/icons/icons/whiteSpace.png")) self.whiteSpaceAction.setCheckable(True) self.whiteSpaceAction.setChecked(settings.get('editor:whiteSpace')) self.whiteSpaceAction.toggled.connect(self.whiteSpace) self.addAction(self.whiteSpaceAction) self.BoldAction = QAction( QIcon(":/icons/icons/format-text-bold.png"), self.tr("&Bold"), self, shortcut=Qt.CTRL + Qt.Key_B, triggered=self.bold, checkable=True) self.addAction(self.BoldAction) self.ItalicAction = QAction(self.tr("Italic"), self) self.ItalicAction.setIcon( QIcon(":/icons/icons/format-text-italic.png")) self.ItalicAction.setShortcut(Qt.CTRL + Qt.Key_I) self.ItalicAction.setCheckable(True) self.ItalicAction.triggered.connect(self.italic) self.addAction(self.ItalicAction) self.UnderlineAction = QAction(self.tr("Underline"), self) self.UnderlineAction.setIcon( QIcon(":/icons/icons/format-text-underline.png")) self.UnderlineAction.setCheckable(True) self.UnderlineAction.setShortcut(Qt.CTRL + Qt.Key_U) self.UnderlineAction.triggered.connect(self.underline) self.addAction(self.UnderlineAction) self.StrikethroughAction = QAction(self.tr("Strikethrough"), self) self.StrikethroughAction.setIcon( QIcon(":/icons/icons/format-text-strikethrough.png")) self.StrikethroughAction.setCheckable(True) self.StrikethroughAction.triggered.connect(self.strikethrough) self.addAction(self.StrikethroughAction) self.SubscriptAction = QAction(self.tr("Subscript"), self) self.SubscriptAction.setIcon( QIcon(":/icons/icons/format-text-subscript.png")) self.SubscriptAction.setCheckable(True) self.SubscriptAction.triggered.connect(self.subscript) self.addAction(self.SubscriptAction) self.SuperscriptAction = QAction(self.tr("Superscript"), self) self.SuperscriptAction.setIcon( QIcon(":/icons/icons/format-text-superscript.png")) self.SuperscriptAction.setCheckable(True) self.SuperscriptAction.triggered.connect(self.superscript) self.addAction(self.SuperscriptAction) def settings(self): lectorSettings = Settings(self, 1) # QObject.connect(lectorSettings, SIGNAL('accepted()'), # self.updateTextEditor) lectorSettings.settingAccepted.connect(self.resetSpell) lectorSettings.show() def SaveDocumentAs(self): self.saveDocAsSignal.emit() def spell(self): state = self.spellAction.isChecked() self.spellSignal.emit(state) def resetSpell(self): ''' Turn off and on spellcheckig to use correct dictionary ''' state = self.spellAction.isChecked() if state: self.spellSignal.emit(False) self.spellSignal.emit(state) def whiteSpace(self): state = self.whiteSpaceAction.isChecked() self.whiteSpaceSignal.emit(state) def toggleFormat(self, CharFormat): font = CharFormat.font() self.BoldAction.setChecked(font.bold()) self.ItalicAction.setChecked(font.italic()) self.UnderlineAction.setChecked(font.underline()) self.StrikethroughAction.setChecked(CharFormat.fontStrikeOut()) if CharFormat.verticalAlignment() == \ QTextCharFormat.AlignSuperScript: self.SubscriptAction.setChecked(False) self.SuperscriptAction.setChecked(True) elif CharFormat.verticalAlignment() == \ QTextCharFormat.AlignSubScript: self.SubscriptAction.setChecked(True) self.SuperscriptAction.setChecked(False) else: self.SubscriptAction.setChecked(False) self.SuperscriptAction.setChecked(False) def bold(self): state = self.BoldAction.isChecked() self.boldSignal.emit(state) def italic(self): state = self.ItalicAction.isChecked() self.italicSignal.emit(state) def underline(self): state = self.UnderlineAction.isChecked() self.underlineSignal.emit(state) def strikethrough(self): state = self.StrikethroughAction.isChecked() self.strikethroughSignal.emit(state) def subscript(self): state = self.SubscriptAction.isChecked() self.subscriptSignal.emit(state) def superscript(self): state = self.SuperscriptAction.isChecked() self.superscriptSignal.emit(state)
class UserAgentMenu(QMenu): """ Class implementing a menu to select the user agent string. """ def __init__(self, title, url=None, parent=None): """ Constructor @param title title of the menu (string) @param url URL to set user agent for (QUrl) @param parent reference to the parent widget (QWidget) """ super(UserAgentMenu, self).__init__(title, parent) self.__manager = None self.__url = url if self.__url: if self.__url.isValid(): import Helpviewer.HelpWindow self.__manager = \ Helpviewer.HelpWindow.HelpWindow.userAgentsManager() else: self.__url = None self.aboutToShow.connect(self.__populateMenu) def __populateMenu(self): """ Private slot to populate the menu. """ self.aboutToShow.disconnect(self.__populateMenu) self.__actionGroup = QActionGroup(self) # add default action self.__defaultUserAgent = QAction(self) self.__defaultUserAgent.setText(self.tr("Default")) self.__defaultUserAgent.setCheckable(True) self.__defaultUserAgent.triggered.connect( self.__switchToDefaultUserAgent) if self.__url: self.__defaultUserAgent.setChecked( self.__manager.userAgentForUrl(self.__url) == "") else: from Helpviewer.HelpBrowserWV import HelpWebPage self.__defaultUserAgent.setChecked(HelpWebPage().userAgent() == "") self.addAction(self.__defaultUserAgent) self.__actionGroup.addAction(self.__defaultUserAgent) isChecked = self.__defaultUserAgent.isChecked() # add default extra user agents isChecked = self.__addDefaultActions() or isChecked # add other action self.addSeparator() self.__otherUserAgent = QAction(self) self.__otherUserAgent.setText(self.tr("Other...")) self.__otherUserAgent.setCheckable(True) self.__otherUserAgent.triggered.connect( self.__switchToOtherUserAgent) self.addAction(self.__otherUserAgent) self.__actionGroup.addAction(self.__otherUserAgent) self.__otherUserAgent.setChecked(not isChecked) def __switchToDefaultUserAgent(self): """ Private slot to set the default user agent. """ if self.__url: self.__manager.removeUserAgent(self.__url.host()) else: from Helpviewer.HelpBrowserWV import HelpWebPage HelpWebPage().setUserAgent("") def __switchToOtherUserAgent(self): """ Private slot to set a custom user agent string. """ from Helpviewer.HelpBrowserWV import HelpWebPage userAgent, ok = QInputDialog.getText( self, self.tr("Custom user agent"), self.tr("User agent:"), QLineEdit.Normal, HelpWebPage().userAgent(resolveEmpty=True)) if ok: if self.__url: self.__manager.setUserAgentForUrl(self.__url, userAgent) else: HelpWebPage().setUserAgent(userAgent) def __changeUserAgent(self): """ Private slot to change the user agent. """ act = self.sender() if self.__url: self.__manager.setUserAgentForUrl(self.__url, act.data()) else: from Helpviewer.HelpBrowserWV import HelpWebPage HelpWebPage().setUserAgent(act.data()) def __addDefaultActions(self): """ Private slot to add the default user agent entries. @return flag indicating that a user agent entry is checked (boolean) """ from . import UserAgentDefaults_rc # __IGNORE_WARNING__ defaultUserAgents = QFile(":/UserAgentDefaults.xml") defaultUserAgents.open(QIODevice.ReadOnly) menuStack = [] isChecked = False if self.__url: currentUserAgentString = self.__manager.userAgentForUrl(self.__url) else: from Helpviewer.HelpBrowserWV import HelpWebPage currentUserAgentString = HelpWebPage().userAgent() xml = QXmlStreamReader(defaultUserAgents) while not xml.atEnd(): xml.readNext() if xml.isStartElement() and xml.name() == "separator": if menuStack: menuStack[-1].addSeparator() else: self.addSeparator() continue if xml.isStartElement() and xml.name() == "useragent": attributes = xml.attributes() title = attributes.value("description") userAgent = attributes.value("useragent") act = QAction(self) act.setText(title) act.setData(userAgent) act.setToolTip(userAgent) act.setCheckable(True) act.setChecked(userAgent == currentUserAgentString) act.triggered.connect(self.__changeUserAgent) if menuStack: menuStack[-1].addAction(act) else: self.addAction(act) self.__actionGroup.addAction(act) isChecked = isChecked or act.isChecked() if xml.isStartElement() and xml.name() == "useragentmenu": attributes = xml.attributes() title = attributes.value("title") if title == "v_a_r_i_o_u_s": title = self.tr("Various") menu = QMenu(self) menu.setTitle(title) self.addMenu(menu) menuStack.append(menu) if xml.isEndElement() and xml.name() == "useragentmenu": menuStack.pop() if xml.hasError(): E5MessageBox.critical( self, self.tr("Parsing default user agents"), self.tr( """<p>Error parsing default user agents.</p><p>{0}</p>""") .format(xml.errorString())) return isChecked
class SimulationGui(QMainWindow): """ class for the graphical user interface """ # TODO enable closing plot docks by right-clicking their name runSimulation = pyqtSignal() stopSimulation = pyqtSignal() playbackTimeChanged = pyqtSignal() regimeFinished = pyqtSignal() finishedRegimeBatch = pyqtSignal(bool) def __init__(self): # constructor of the base class QMainWindow.__init__(self) QCoreApplication.setOrganizationName("RST") QCoreApplication.setOrganizationDomain("https://tu-dresden.de/rst") QCoreApplication.setApplicationVersion( pkg_resources.require("PyMoskito")[0].version) QCoreApplication.setApplicationName(globals()["__package__"]) # load settings self._settings = QSettings() self._read_settings() # initialize logger self._logger = logging.getLogger(self.__class__.__name__) # Create Simulation Backend self.guiProgress = None self.cmdProgress = None self.sim = SimulatorInteractor(self) self.runSimulation.connect(self.sim.run_simulation) self.stopSimulation.connect(self.sim.stop_simulation) self.sim.simulation_finalized.connect(self.new_simulation_data) self.currentDataset = None self.interpolator = None # sim setup viewer self.targetView = SimulatorView(self) self.targetView.setModel(self.sim.target_model) self.targetView.expanded.connect(self.target_view_changed) self.targetView.collapsed.connect(self.target_view_changed) # sim results viewer self.result_view = QTreeView() # the docking area allows to rearrange the user interface at runtime self.area = pg.dockarea.DockArea() # Window properties icon_size = QSize(25, 25) self.setCentralWidget(self.area) self.resize(1000, 700) self.setWindowTitle("PyMoskito") res_path = get_resource("mosquito.png") icon = QIcon(res_path) self.setWindowIcon(icon) # create docks self.propertyDock = pg.dockarea.Dock("Properties") self.animationDock = pg.dockarea.Dock("Animation") self.regimeDock = pg.dockarea.Dock("Regimes") self.dataDock = pg.dockarea.Dock("Data") self.logDock = pg.dockarea.Dock("Log") self.plotDockPlaceholder = pg.dockarea.Dock("Placeholder") # arrange docks self.area.addDock(self.animationDock, "right") self.area.addDock(self.regimeDock, "left", self.animationDock) self.area.addDock(self.propertyDock, "bottom", self.regimeDock) self.area.addDock(self.dataDock, "bottom", self.propertyDock) self.area.addDock(self.plotDockPlaceholder, "bottom", self.animationDock) self.area.addDock(self.logDock, "bottom", self.dataDock) self.non_plotting_docks = list(self.area.findAll()[1].keys()) # add widgets to the docks self.propertyDock.addWidget(self.targetView) if not vtk_available: self._logger.error("loading vtk failed with:{}".format(vtk_error_msg)) # check if there is a registered visualizer available_vis = get_registered_visualizers() self._logger.info("found visualizers: {}".format( [name for cls, name in available_vis])) if available_vis: # instantiate the first visualizer self._logger.info("loading visualizer '{}'".format(available_vis[0][1])) self.animationLayout = QVBoxLayout() if issubclass(available_vis[0][0], MplVisualizer): self.animationWidget = QWidget() self.visualizer = available_vis[0][0](self.animationWidget, self.animationLayout) self.animationDock.addWidget(self.animationWidget) elif issubclass(available_vis[0][0], VtkVisualizer): if vtk_available: # vtk window self.animationFrame = QFrame() self.vtkWidget = QVTKRenderWindowInteractor( self.animationFrame) self.animationLayout.addWidget(self.vtkWidget) self.animationFrame.setLayout(self.animationLayout) self.animationDock.addWidget(self.animationFrame) self.vtk_renderer = vtkRenderer() self.vtkWidget.GetRenderWindow().AddRenderer( self.vtk_renderer) self.visualizer = available_vis[0][0](self.vtk_renderer) self.vtkWidget.Initialize() else: self._logger.warning("visualizer depends on vtk which is " "not available on this system!") elif available_vis: raise NotImplementedError else: self.visualizer = None # regime window self.regime_list = QListWidget(self) self.regime_list.setSelectionMode(QAbstractItemView.ExtendedSelection) self.regimeDock.addWidget(self.regime_list) self.regime_list.itemDoubleClicked.connect(self.regime_dclicked) self._regimes = [] self.regime_file_name = "" self.actDeleteRegimes = QAction(self.regime_list) self.actDeleteRegimes.setText("&Delete Selected Regimes") # TODO shortcut works always, not only with focus on the regime list # self.actDeleteRegimes.setShortcutContext(Qt.WindowShortcut) self.actDeleteRegimes.setShortcut(QKeySequence(Qt.Key_Delete)) self.actDeleteRegimes.triggered.connect(self.remove_regime_items) self.actSave = QAction(self) self.actSave.setText('Save Results As') self.actSave.setIcon(QIcon(get_resource("save.png"))) self.actSave.setDisabled(True) self.actSave.setShortcut(QKeySequence.Save) self.actSave.triggered.connect(self.export_simulation_data) self.actLoadRegimes = QAction(self) self.actLoadRegimes.setText("Load Regimes from File") self.actLoadRegimes.setIcon(QIcon(get_resource("load.png"))) self.actLoadRegimes.setDisabled(False) self.actLoadRegimes.setShortcut(QKeySequence.Open) self.actLoadRegimes.triggered.connect(self.load_regime_dialog) self.actExitOnBatchCompletion = QAction(self) self.actExitOnBatchCompletion.setText("&Exit On Batch Completion") self.actExitOnBatchCompletion.setCheckable(True) self.actExitOnBatchCompletion.setChecked( self._settings.value("control/exit_on_batch_completion") == "True" ) self.actExitOnBatchCompletion.changed.connect( self.update_exit_on_batch_completion_setting) # regime management self.runningBatch = False self._current_regime_index = None self._current_regime_name = None self._regimes = [] self.regimeFinished.connect(self.run_next_regime) self.finishedRegimeBatch.connect(self.regime_batch_finished) # data window self.dataList = QListWidget(self) self.dataDock.addWidget(self.dataList) self.dataList.itemDoubleClicked.connect(self.create_plot) # actions for simulation control self.actSimulateCurrent = QAction(self) self.actSimulateCurrent.setText("&Simulate Current Regime") self.actSimulateCurrent.setIcon(QIcon(get_resource("simulate.png"))) self.actSimulateCurrent.setShortcut(QKeySequence("F5")) self.actSimulateCurrent.triggered.connect(self.start_simulation) self.actSimulateAll = QAction(self) self.actSimulateAll.setText("Simulate &All Regimes") self.actSimulateAll.setIcon(QIcon(get_resource("execute_regimes.png"))) self.actSimulateAll.setShortcut(QKeySequence("F6")) self.actSimulateAll.setDisabled(True) self.actSimulateAll.triggered.connect(self.start_regime_execution) # actions for animation control self.actAutoPlay = QAction(self) self.actAutoPlay.setText("&Autoplay Simulation") self.actAutoPlay.setCheckable(True) self.actAutoPlay.setChecked( self._settings.value("control/autoplay_animation") == "True" ) self.actAutoPlay.changed.connect(self.update_autoplay_setting) self.actPlayPause = QAction(self) self.actPlayPause.setText("Play Animation") self.actPlayPause.setIcon(QIcon(get_resource("play.png"))) self.actPlayPause.setDisabled(True) self.actPlayPause.setShortcut(QKeySequence(Qt.Key_Space)) self.actPlayPause.triggered.connect(self.play_animation) self.actStop = QAction(self) self.actStop.setText("Stop") self.actStop.setIcon(QIcon(get_resource("stop.png"))) self.actStop.setDisabled(True) self.actStop.triggered.connect(self.stop_animation) self.actSlow = QAction(self) self.actSlow.setText("Slowest") self.actSlow.setIcon(QIcon(get_resource("slow.png"))) self.actSlow.setDisabled(False) self.actSlow.triggered.connect(self.set_slowest_playback_speed) self.actFast = QAction(self) self.actFast.setText("Fastest") self.actFast.setIcon(QIcon(get_resource("fast.png"))) self.actFast.setDisabled(False) self.actFast.triggered.connect(self.set_fastest_playback_speed) self.speedControl = QSlider(Qt.Horizontal, self) self.speedControl.setMaximumSize(200, 25) self.speedControl.setTickPosition(QSlider.TicksBothSides) self.speedControl.setDisabled(False) self.speedControl.setMinimum(0) self.speedControl.setMaximum(12) self.speedControl.setValue(6) self.speedControl.setTickInterval(6) self.speedControl.setSingleStep(2) self.speedControl.setPageStep(3) self.speedControl.valueChanged.connect(self.update_playback_speed) self.timeSlider = QSlider(Qt.Horizontal, self) self.timeSlider.setMinimum(0) self.timeSliderRange = 1000 self.timeSlider.setMaximum(self.timeSliderRange) self.timeSlider.setTickInterval(1) self.timeSlider.setTracking(True) self.timeSlider.setDisabled(True) self.timeSlider.valueChanged.connect(self.update_playback_time) self.playbackTime = .0 self.playbackGain = 1 self.currentStepSize = .0 self.currentEndTime = .0 self.playbackTimer = QTimer() self.playbackTimer.timeout.connect(self.increment_playback_time) self.playbackTimeChanged.connect(self.update_gui) self.playbackTimeout = 33 # in [ms] -> 30 fps self.actResetCamera = QAction(self) self.actResetCamera.setText("Reset Camera") self.actResetCamera.setIcon(QIcon(get_resource("reset_camera.png"))) self.actResetCamera.setDisabled(True) if available_vis: self.actResetCamera.setEnabled(self.visualizer.can_reset_view) self.actResetCamera.triggered.connect(self.reset_camera_clicked) # postprocessing self.actPostprocessing = QAction(self) self.actPostprocessing.setText("Launch Postprocessor") self.actPostprocessing.setIcon(QIcon(get_resource("processing.png"))) self.actPostprocessing.setDisabled(False) self.actPostprocessing.triggered.connect(self.postprocessing_clicked) self.actPostprocessing.setShortcut(QKeySequence("F7")) self.postprocessor = None # toolbar self.toolbarSim = QToolBar("Simulation") self.toolbarSim.setContextMenuPolicy(Qt.PreventContextMenu) self.toolbarSim.setMovable(False) self.toolbarSim.setIconSize(icon_size) self.addToolBar(self.toolbarSim) self.toolbarSim.addAction(self.actLoadRegimes) self.toolbarSim.addAction(self.actSave) self.toolbarSim.addSeparator() self.toolbarSim.addAction(self.actSimulateCurrent) self.toolbarSim.addAction(self.actSimulateAll) self.toolbarSim.addSeparator() self.toolbarSim.addAction(self.actPlayPause) self.toolbarSim.addAction(self.actStop) self.toolbarSim.addWidget(self.timeSlider) self.toolbarSim.addSeparator() self.toolbarSim.addAction(self.actSlow) self.toolbarSim.addWidget(self.speedControl) self.toolbarSim.addAction(self.actFast) self.toolbarSim.addSeparator() self.toolbarSim.addAction(self.actPostprocessing) self.toolbarSim.addAction(self.actResetCamera) self.postprocessor = None # log dock self.logBox = QPlainTextEdit(self) self.logBox.setReadOnly(True) self.logDock.addWidget(self.logBox) # init logger for logging box self.textLogger = PlainTextLogger(logging.INFO) self.textLogger.set_target_cb(self.logBox.appendPlainText) logging.getLogger().addHandler(self.textLogger) # menu bar fileMenu = self.menuBar().addMenu("&File") fileMenu.addAction(self.actLoadRegimes) fileMenu.addAction(self.actSave) fileMenu.addAction("&Quit", self.close) editMenu = self.menuBar().addMenu("&Edit") editMenu.addAction(self.actDeleteRegimes) simMenu = self.menuBar().addMenu("&Simulation") simMenu.addAction(self.actSimulateCurrent) simMenu.addAction(self.actSimulateAll) simMenu.addAction(self.actExitOnBatchCompletion) simMenu.addAction(self.actPostprocessing) animMenu = self.menuBar().addMenu("&Animation") animMenu.addAction(self.actPlayPause) animMenu.addAction("&Increase Playback Speed", self.increment_playback_speed, QKeySequence(Qt.CTRL + Qt.Key_Plus)) animMenu.addAction("&Decrease Playback Speed", self.decrement_playback_speed, QKeySequence(Qt.CTRL + Qt.Key_Minus)) animMenu.addAction("&Reset Playback Speed", self.reset_playback_speed, QKeySequence(Qt.CTRL + Qt.Key_0)) animMenu.addAction(self.actAutoPlay) animMenu.addAction(self.actResetCamera) helpMenu = self.menuBar().addMenu("&Help") helpMenu.addAction("&Online Documentation", self.show_online_docs) helpMenu.addAction("&About", self.show_info) # status bar self.status = QStatusBar(self) self.setStatusBar(self.status) self.statusLabel = QLabel("Ready.") self.statusBar().addPermanentWidget(self.statusLabel) self.timeLabel = QLabel("current time: 0.0") self.statusBar().addPermanentWidget(self.timeLabel) self._logger.info("Simulation GUI is up and running.") def _read_settings(self): # add default settings if none are present if not self._settings.contains("path/simulation_results"): self._settings.setValue("path/simulation_results", os.path.join(os.path.curdir, "results", "simulation")) if not self._settings.contains("path/postprocessing_results"): self._settings.setValue("path/postprocessing_results", os.path.join(os.path.curdir, "results", "postprocessing")) if not self._settings.contains("path/metaprocessing_results"): self._settings.setValue("path/metaprocessing_results", os.path.join(os.path.curdir, "results", "metaprocessing")) if not self._settings.contains("control/autoplay_animation"): self._settings.setValue("control/autoplay_animation", "False") if not self._settings.contains("control/exit_on_batch_completion"): self._settings.setValue("control/exit_on_batch_completion", "False") def _write_settings(self): """ Store the application state. """ pass @pyqtSlot() def update_autoplay_setting(self): self._settings.setValue("control/autoplay_animation", str(self.actAutoPlay.isChecked())) @pyqtSlot() def update_exit_on_batch_completion_setting(self, state=None): if state is None: state = self.actExitOnBatchCompletion.isChecked() self._settings.setValue("control/exit_on_batch_completion", str(state)) def set_visualizer(self, vis): self.visualizer = vis self.vtkWidget.Initialize() @pyqtSlot() def play_animation(self): """ play the animation """ self._logger.debug("Starting Playback") # if we are at the end, start from the beginning if self.playbackTime == self.currentEndTime: self.timeSlider.setValue(0) self.actPlayPause.setText("Pause Animation") self.actPlayPause.setIcon(QIcon(get_resource("pause.png"))) self.actPlayPause.triggered.disconnect(self.play_animation) self.actPlayPause.triggered.connect(self.pause_animation) self.playbackTimer.start(self.playbackTimeout) @pyqtSlot() def pause_animation(self): """ pause the animation """ self._logger.debug("Pausing Playback") self.playbackTimer.stop() self.actPlayPause.setText("Play Animation") self.actPlayPause.setIcon(QIcon(get_resource("play.png"))) self.actPlayPause.triggered.disconnect(self.pause_animation) self.actPlayPause.triggered.connect(self.play_animation) def stop_animation(self): """ Stop the animation if it is running and reset the playback time. """ self._logger.debug("Stopping Playback") if self.actPlayPause.text() == "Pause Animation": # animation is playing -> stop it self.playbackTimer.stop() self.actPlayPause.setText("Play Animation") self.actPlayPause.setIcon(QIcon(get_resource("play.png"))) self.actPlayPause.triggered.disconnect(self.pause_animation) self.actPlayPause.triggered.connect(self.play_animation) self.timeSlider.setValue(0) @pyqtSlot() def start_simulation(self): """ start the simulation and disable start button """ if self._current_regime_index is None: regime_name = "" else: regime_name = str(self.regime_list.item( self._current_regime_index).text()) self.statusLabel.setText("simulating {}".format(regime_name)) self._logger.info("Simulating: {}".format(regime_name)) self.actSimulateCurrent.setIcon(QIcon( get_resource("stop_simulation.png"))) self.actSimulateCurrent.setText("Abort &Simulation") self.actSimulateCurrent.triggered.disconnect(self.start_simulation) self.actSimulateCurrent.triggered.connect(self.stop_simulation) if not self.runningBatch: self.actSimulateAll.setDisabled(True) self.guiProgress = QProgressBar(self) self.sim.simulationProgressChanged.connect(self.guiProgress.setValue) self.statusBar().addWidget(self.guiProgress) self.runSimulation.emit() @pyqtSlot() def stop_simulation(self): self.stopSimulation.emit() def export_simulation_data(self, ok): """ Query the user for a custom name and export the current simulation results. :param ok: unused parameter from QAction.triggered() Signal """ self._save_data() def _save_data(self, file_path=None): """ Save the current simulation results. If *fie_name* is given, the result will be saved to the specified location, making automated exporting easier. Args: file_path(str): Absolute path of the target file. If `None` the use will be asked for a storage location. """ regime_name = self._regimes[self._current_regime_index]["Name"] if file_path is None: # get default path path = self._settings.value("path/simulation_results") # create canonic file name suggestion = self._simfile_name(regime_name) else: path = os.path.dirname(file_path) suggestion = os.path.basename(file_path) # check if path exists otherwise create it if not os.path.isdir(path): box = QMessageBox() box.setText("Export Folder does not exist yet.") box.setInformativeText("Do you want to create it? \n" "{}".format(os.path.abspath(path))) box.setStandardButtons(QMessageBox.Ok | QMessageBox.No) box.setDefaultButton(QMessageBox.Ok) ret = box.exec_() if ret == QMessageBox.Ok: os.makedirs(path) else: path = os.path.abspath(os.path.curdir) file_path = None # If no path was given, present the default and let the user choose if file_path is None: dialog = QFileDialog(self) dialog.setAcceptMode(QFileDialog.AcceptSave) dialog.setFileMode(QFileDialog.AnyFile) dialog.setDirectory(path) dialog.setNameFilter("PyMoskito Results (*.pmr)") dialog.selectFile(suggestion) if dialog.exec_(): file_path = dialog.selectedFiles()[0] else: self._logger.warning("Export Aborted") return -1 # ask whether this should act as new default path = os.path.abspath(os.path.dirname(file_path)) if path != self._settings.value("path/simulation_results"): box = QMessageBox() box.setText("Use this path as new default?") box.setInformativeText("{}".format(path)) box.setStandardButtons(QMessageBox.Yes | QMessageBox.No) box.setDefaultButton(QMessageBox.Yes) ret = box.exec_() if ret == QMessageBox.Yes: self._settings.setValue("path/simulation_results", path) self.currentDataset.update({"regime name": regime_name}) with open(file_path, "wb") as f: pickle.dump(self.currentDataset, f, protocol=4) self.statusLabel.setText("results saved to {}".format(file_path)) self._logger.info("results saved to {}".format(file_path)) def _simfile_name(self, regime_name): """ Create a canonical name for a simulation result file """ suggestion = (time.strftime("%Y%m%d-%H%M%S") + "_" + regime_name + ".pmr") return suggestion def load_regime_dialog(self): regime_path = os.path.join(os.curdir) dialog = QFileDialog(self) dialog.setFileMode(QFileDialog.ExistingFile) dialog.setDirectory(regime_path) dialog.setNameFilter("Simulation Regime files (*.sreg)") if dialog.exec_(): file = dialog.selectedFiles()[0] self.load_regimes_from_file(file) def load_regimes_from_file(self, file_name): """ load simulation regime from file :param file_name: """ self.regime_file_name = os.path.split(file_name)[-1][:-5] self._logger.info("loading regime file: {0}".format(self.regime_file_name)) with open(file_name.encode(), "r") as f: self._regimes += yaml.load(f) self._update_regime_list() if self._regimes: self.actSimulateAll.setDisabled(False) self._logger.info("loaded {} regimes".format(len(self._regimes))) self.statusBar().showMessage("loaded {} regimes.".format(len(self._regimes)), 1000) return def _update_regime_list(self): self.regime_list.clear() for reg in self._regimes: self._logger.debug("adding '{}' to regime list".format(reg["Name"])) self.regime_list.addItem(reg["Name"]) def remove_regime_items(self): if self.regime_list.currentRow() >= 0: # flag all selected files as invalid items = self.regime_list.selectedItems() for item in items: del self._regimes[self.regime_list.row(item)] self.regime_list.takeItem(self.regime_list.row(item)) @pyqtSlot(QListWidgetItem) def regime_dclicked(self, item): """ Apply the selected regime to the current target. """ self.apply_regime_by_name(str(item.text())) def apply_regime_by_name(self, regime_name): """ Apply the regime given by `regime_name` und update the regime index. Returns: bool: `True` if successful, `False` if errors occurred. """ # get regime idx try: idx = list(map(itemgetter("Name"), self._regimes)).index(regime_name) except ValueError as e: self._logger.error("apply_regime_by_name(): Error no regime called " "'{0}'".format(regime_name)) return False # apply return self._apply_regime_by_idx(idx) def _apply_regime_by_idx(self, index=0): """ Apply the given regime. Args: index(int): Index of the regime in the `RegimeList` . Returns: bool: `True` if successful, `False` if errors occurred. """ if index >= len(self._regimes): self._logger.error("applyRegime: index error! ({})".format(index)) return False reg_name = self._regimes[index]["Name"] self.statusBar().showMessage("regime {} applied.".format(reg_name), 1000) self._logger.info("applying regime '{}'".format(reg_name)) self._current_regime_index = index self._current_regime_name = reg_name return self.sim.set_regime(self._regimes[index]) @pyqtSlot() def start_regime_execution(self): """ Simulate all regimes in the regime list. """ self.actSimulateAll.setText("Stop Simulating &All Regimes") self.actSimulateAll.setIcon(QIcon(get_resource("stop_batch.png"))) self.actSimulateAll.triggered.disconnect(self.start_regime_execution) self.actSimulateAll.triggered.connect(self.stop_regime_excecution) self.runningBatch = True self._current_regime_index = -1 self.regimeFinished.emit() def run_next_regime(self): """ Execute the next regime in the regime batch. """ # are we finished? if self._current_regime_index == len(self._regimes) - 1: self.finishedRegimeBatch.emit(True) return suc = self._apply_regime_by_idx(self._current_regime_index + 1) if not suc: self.finishedRegimeBatch.emit(False) return self.start_simulation() @pyqtSlot() def stop_regime_excecution(self): """ Stop the batch process. """ self.stopSimulation.emit() self.finishedRegimeBatch.emit(False) def regime_batch_finished(self, status): self.runningBatch = False self.actSimulateAll.setDisabled(False) self.actSave.setDisabled(True) self.actSimulateAll.setText("Simulate &All Regimes") self.actSimulateAll.setIcon(QIcon(get_resource("execute_regimes.png"))) self.actSimulateAll.triggered.disconnect(self.stop_regime_excecution) self.actSimulateAll.triggered.connect(self.start_regime_execution) if status: self.statusLabel.setText("All regimes have been simulated") self._logger.info("All Regimes have been simulated") else: self._logger.error("Batch simulation has been aborted") if self._settings.value("control/exit_on_batch_completion") == "True": self._logger.info("Shutting down SimulationGUI") self.close() @pyqtSlot(str, dict, name="new_simulation_data") def new_simulation_data(self, status, data): """ Slot to be called when the simulation interface has completed the current job and new data is available. Args: status (str): Status of the simulation, either - `finished` : Simulation has been finished successfully or - `failed` : Simulation has failed. data (dict): Dictionary, holding the simulation data. """ self._logger.info("Simulation {}".format(status)) self.statusLabel.setText("Simulation {}".format(status)) self.actSimulateCurrent.setText("&Simulate Current Regime") self.actSimulateCurrent.setIcon(QIcon(get_resource("simulate.png"))) self.actSimulateCurrent.triggered.disconnect(self.stop_simulation) self.actSimulateCurrent.triggered.connect(self.start_simulation) self.actPlayPause.setDisabled(False) self.actStop.setDisabled(False) self.actSave.setDisabled(False) self.speedControl.setDisabled(False) self.timeSlider.setDisabled(False) self.sim.simulationProgressChanged.disconnect(self.guiProgress.setValue) self.statusBar().removeWidget(self.guiProgress) self.stop_animation() self.currentDataset = data if data: self._read_results() self._update_data_list() self._update_plots() if self._settings.value("control/autoplay_animation") == "True": self.actPlayPause.trigger() if self.runningBatch: regime_name = self._regimes[self._current_regime_index]["Name"] file_name = self._simfile_name(regime_name) self._save_data(os.path.join( self._settings.value("path/simulation_results"), file_name)) self.regimeFinished.emit() else: self.actSimulateAll.setDisabled(False) def _read_results(self): state = self.currentDataset["results"]["Solver"] self.interpolator = interp1d(self.currentDataset["results"]["time"], state, axis=0, bounds_error=False, fill_value=(state[0], state[-1])) self.currentStepSize = 1.0/self.currentDataset["simulation"][ "measure rate"] self.currentEndTime = self.currentDataset["simulation"]["end time"] self.validData = True def increment_playback_speed(self): self.speedControl.setValue(self.speedControl.value() + self.speedControl.singleStep()) def decrement_playback_speed(self): self.speedControl.setValue(self.speedControl.value() - self.speedControl.singleStep()) def reset_playback_speed(self): self.speedControl.setValue((self.speedControl.maximum() - self.speedControl.minimum())/2) def set_slowest_playback_speed(self): self.speedControl.setValue(self.speedControl.minimum()) def set_fastest_playback_speed(self): self.speedControl.setValue(self.speedControl.maximum()) def update_playback_speed(self, val): """ adjust playback time to slider value :param val: """ maximum = self.speedControl.maximum() self.playbackGain = 10**(3.0 * (val - maximum / 2) / maximum) @pyqtSlot() def increment_playback_time(self): """ go one time step forward in playback """ if self.playbackTime == self.currentEndTime: self.pause_animation() return increment = self.playbackGain * self.playbackTimeout / 1000 self.playbackTime = min(self.currentEndTime, self.playbackTime + increment) pos = int(self.playbackTime / self.currentEndTime * self.timeSliderRange) self.timeSlider.blockSignals(True) self.timeSlider.setValue(pos) self.timeSlider.blockSignals(False) self.playbackTimeChanged.emit() def update_playback_time(self): """ adjust playback time to slider value """ self.playbackTime = self.timeSlider.value()/self.timeSliderRange*self.currentEndTime self.playbackTimeChanged.emit() return def update_gui(self): """ updates the graphical user interface, including: - timestamp - visualisation - time cursor in diagrams """ if not self.validData: return self.timeLabel.setText("current time: %4f" % self.playbackTime) # update time cursor in plots self._update_time_cursors() # update state of rendering if self.visualizer: state = self.interpolator(self.playbackTime) self.visualizer.update_scene(state) if isinstance(self.visualizer, MplVisualizer): pass elif isinstance(self.visualizer, VtkVisualizer): self.vtkWidget.GetRenderWindow().Render() def _update_data_list(self): self.dataList.clear() for module_name, results in self.currentDataset["results"].items(): if not isinstance(results, np.ndarray): continue if len(results.shape) == 1: self.dataList.insertItem(0, module_name) elif len(results.shape) == 2: for col in range(results.shape[1]): self.dataList.insertItem( 0, self._build_entry_name(module_name, (col, )) ) elif len(results.shape) == 3: for col in range(results.shape[1]): for der in range(results.shape[2]): self.dataList.insertItem( 0, self._build_entry_name(module_name, (col, der)) ) def _build_entry_name(self, module_name, idx): """ Construct an identifier for a given entry of a module. Args: module_name (str): name of the module the entry belongs to. idx (tuple): Index of the entry. Returns: str: Identifier to use for display. """ # save the user from defining 1d entries via tuples if len(idx) == 1: m_idx = idx[0] else: m_idx = idx mod_settings = self.currentDataset["modules"] info = mod_settings.get(module_name, {}).get("output_info", None) if info: if m_idx in info: return ".".join([module_name, info[m_idx]["Name"]]) return ".".join([module_name] + [str(i) for i in idx]) def _get_index_from_suffix(self, module_name, suffix): info = self.currentDataset["modules"].get(module_name, {}).get( "output_info", None) idx = next((i for i in info if info[i]["Name"] == suffix), None) return idx def _get_units(self, entry): """ Return the unit that corresponds to a given entry. If no information is available, None is returned. Args: entry (str): Name of the entry. This can either be "Model.a.b" where a and b are numbers or if information is available "Model.Signal" where signal is the name of that part. Returns: """ args = entry.split(".") module_name = args.pop(0) info = self.currentDataset["modules"].get(module_name, {}).get( "output_info", None) if info is None: return None if len(args) == 1: try: idx = int(args[0]) except ValueError: idx = next((i for i in info if info[i]["Name"] == args[0]), None) else: idx = (int(a) for a in args) return info[idx]["Unit"] def create_plot(self, item): """ Creates a plot widget based on the given item. If a plot for this item is already open no new plot is created but the existing one is raised up again. Args: item(Qt.ListItem): Item to plot. """ title = str(item.text()) if title in self.non_plotting_docks: self._logger.error("Title '{}' not allowed for a plot window since" "it would shadow on of the reserved " "names".format(title)) # check if plot has already been opened if title in self.area.findAll()[1]: self.area.docks[title].raiseDock() return # collect data data = self._get_data_by_name(title) t = self.currentDataset["results"]["time"] unit = self._get_units(title) if "." in title: name = title.split(".")[1] else: name = title # create plot widget widget = pg.PlotWidget(title=title) widget.showGrid(True, True) widget.plot(x=t, y=data) widget.getPlotItem().getAxis("bottom").setLabel(text="Time", units="s") widget.getPlotItem().getAxis("left").setLabel(text=name, units=unit) # add a time line time_line = pg.InfiniteLine(self.playbackTime, angle=90, movable=False, pen=pg.mkPen("#FF0000", width=2.0)) widget.getPlotItem().addItem(time_line) # create dock container and add it to dock area dock = pg.dockarea.Dock(title, closable=True) dock.addWidget(widget) self.area.addDock(dock, "above", self.plotDockPlaceholder) def _get_data_by_name(self, name): tmp = name.split(".") module_name = tmp[0] if len(tmp) == 1: data = np.array(self.currentDataset["results"][module_name]) elif len(tmp) == 2: try: idx = int(tmp[1]) except ValueError: idx = self._get_index_from_suffix(module_name, tmp[1]) finally: data = self.currentDataset["results"][module_name][..., idx] elif len(tmp) == 3: idx = int(tmp[1]) der = int(tmp[2]) data = self.currentDataset["results"][module_name][..., idx, der] else: raise ValueError("Format not supported") return data def _update_time_cursors(self): """ Update the time lines of all plot windows """ for title, dock in self.area.findAll()[1].items(): if title in self.non_plotting_docks: continue for widget in dock.widgets: for item in widget.getPlotItem().items: if isinstance(item, pg.InfiniteLine): item.setValue(self.playbackTime) def _update_plots(self): """ Update the data in all plot windows """ for title, dock in self.area.findAll()[1].items(): if title in self.non_plotting_docks: continue if not self.dataList.findItems(dock.name(), Qt.MatchExactly): # no data for this plot -> remove it dock.close() continue for widget in dock.widgets: for item in widget.getPlotItem().items: if isinstance(item, pg.PlotDataItem): x_data = self.currentDataset["results"]["time"] y_data = self._get_data_by_name(dock.name()) item.setData(x=x_data, y=y_data) @pyqtSlot(QModelIndex) def target_view_changed(self, index): self.targetView.resizeColumnToContents(0) def postprocessing_clicked(self): """ starts the post- and metaprocessing application """ self._logger.info("launching postprocessor") self.statusBar().showMessage("launching postprocessor", 1000) if self.postprocessor is None: self.postprocessor = PostProcessor() self.postprocessor.show() def reset_camera_clicked(self): """ reset camera in vtk window """ self.visualizer.reset_camera() self.vtkWidget.GetRenderWindow().Render() def show_info(self): icon_lic = open(get_resource("license.txt"), "r").read() text = "This application was build using PyMoskito ver. {} .<br />" \ "PyMoskito is free software distributed under GPLv3. <br />" \ "It is developed by members of the " \ "<a href=\'https://tu-dresden.de/ing/elektrotechnik/rst'>" \ "Institute of Control Theory</a>" \ " at the <a href=\'https://tu-dresden.de'>" \ "Dresden University of Technology</a>. <br />" \ "".format(pkg_resources.require("PyMoskito")[0].version) \ + "<br />" + icon_lic box = QMessageBox.about(self, "PyMoskito", text) def show_online_docs(self): webbrowser.open("https://pymoskito.readthedocs.org") def closeEvent(self, QCloseEvent): self._logger.info("Close Event received, shutting down.") logging.getLogger().removeHandler(self.textLogger) super().closeEvent(QCloseEvent)
def __addDefaultActions(self): """ Private slot to add the default user agent entries. @return flag indicating that a user agent entry is checked (boolean) """ from . import UserAgentDefaults_rc # __IGNORE_WARNING__ defaultUserAgents = QFile(":/UserAgentDefaults.xml") defaultUserAgents.open(QIODevice.ReadOnly) menuStack = [] isChecked = False if self.__url: currentUserAgentString = self.__manager.userAgentForUrl(self.__url) else: from Helpviewer.HelpBrowserWV import HelpWebPage currentUserAgentString = HelpWebPage().userAgent() xml = QXmlStreamReader(defaultUserAgents) while not xml.atEnd(): xml.readNext() if xml.isStartElement() and xml.name() == "separator": if menuStack: menuStack[-1].addSeparator() else: self.addSeparator() continue if xml.isStartElement() and xml.name() == "useragent": attributes = xml.attributes() title = attributes.value("description") userAgent = attributes.value("useragent") act = QAction(self) act.setText(title) act.setData(userAgent) act.setToolTip(userAgent) act.setCheckable(True) act.setChecked(userAgent == currentUserAgentString) act.triggered.connect(self.__changeUserAgent) if menuStack: menuStack[-1].addAction(act) else: self.addAction(act) self.__actionGroup.addAction(act) isChecked = isChecked or act.isChecked() if xml.isStartElement() and xml.name() == "useragentmenu": attributes = xml.attributes() title = attributes.value("title") if title == "v_a_r_i_o_u_s": title = self.tr("Various") menu = QMenu(self) menu.setTitle(title) self.addMenu(menu) menuStack.append(menu) if xml.isEndElement() and xml.name() == "useragentmenu": menuStack.pop() if xml.hasError(): E5MessageBox.critical( self, self.tr("Parsing default user agents"), self.tr( """<p>Error parsing default user agents.</p><p>{0}</p>""") .format(xml.errorString())) return isChecked
class storylineView(QWidget, Ui_storylineView): def __init__(self, parent=None): QWidget.__init__(self, parent) self.setupUi(self) self._mdlPlots = None self.scene = QGraphicsScene() self.view.setScene(self.scene) self.reloadTimer = QTimer() self.reloadTimer.timeout.connect(self.refresh) self.reloadTimer.setSingleShot(True) self.reloadTimer.setInterval(500) self.btnRefresh.clicked.connect(self.refresh) self.sldTxtSize.sliderMoved.connect(self.reloadTimer.start) self.generateMenu() def generateMenu(self): m = QMenu() self.actPlots = QAction(self.tr("Show Plots"), m) self.actPlots.setCheckable(True) self.actPlots.setChecked(True) self.actPlots.toggled.connect(self.reloadTimer.start) m.addAction(self.actPlots) self.actCharacters = QAction(self.tr("Show Characters"), m) self.actCharacters.setCheckable(True) self.actCharacters.setChecked(False) self.actCharacters.toggled.connect(self.reloadTimer.start) m.addAction(self.actCharacters) self.btnSettings.setMenu(m) def setModels(self, mdlOutline, mdlCharacter, mdlPlots): self._mdlPlots = mdlPlots # self._mdlPlots.dataChanged.connect(self.refresh) # self._mdlPlots.rowsInserted.connect(self.refresh) self._mdlOutline = mdlOutline self._mdlOutline.dataChanged.connect(self.updateMaybe) self._mdlCharacter = mdlCharacter self._mdlCharacter.dataChanged.connect(self.reloadTimer.start) def updateMaybe(self, topLeft, bottomRight): if topLeft.column() <= Outline.notes.value <= bottomRight.column(): self.reloadTimer.start def plotReferences(self): "Returns a list of plot references" if not self._mdlPlots: pass plotsID = self._mdlPlots.getPlotsByImportance() r = [] for importance in plotsID: for ID in importance: ref = references.plotReference(ID) r.append(ref) return r def charactersReferences(self): "Returns a list of character references" if not self._mdlCharacter: pass chars = self._mdlCharacter.getCharactersByImportance() r = [] for importance in chars: for c in importance: ref = references.characterReference(c.ID()) r.append(ref) return r def refresh(self): if not self._mdlPlots or not self._mdlOutline or not self._mdlCharacter: return if not self.isVisible(): return LINE_HEIGHT = 18 SPACING = 3 TEXT_WIDTH = self.sldTxtSize.value() CIRCLE_WIDTH = 10 LEVEL_HEIGHT = 12 s = self.scene s.clear() # Get Max Level (max depth) root = self._mdlOutline.rootItem def maxLevel(item, level=0, max=0): if level > max: max = level for c in item.children(): m = maxLevel(c, level + 1) if m > max: max = m return max MAX_LEVEL = maxLevel(root) # Get the list of tracked items (array of references) trackedItems = [] if self.actPlots.isChecked(): trackedItems += self.plotReferences() if self.actCharacters.isChecked(): trackedItems += self.charactersReferences() ROWS_HEIGHT = len(trackedItems) * (LINE_HEIGHT + SPACING ) fm = QFontMetrics(s.font()) max_name = 0 for ref in trackedItems: name = references.title(ref) max_name = max(fm.width(name), max_name) TITLE_WIDTH = max_name + 2 * SPACING # Add Folders and Texts outline = OutlineRect(0, 0, 0, ROWS_HEIGHT + SPACING + MAX_LEVEL * LEVEL_HEIGHT) s.addItem(outline) outline.setPos(TITLE_WIDTH + SPACING, 0) refCircles = [] # a list of all references, to be added later on the lines # A Function to add a rect with centered elided text def addRectText(x, w, parent, text="", level=0, tooltip=""): deltaH = LEVEL_HEIGHT if level else 0 r = OutlineRect(0, 0, w, parent.rect().height()-deltaH, parent, title=text) r.setPos(x, deltaH) txt = QGraphicsSimpleTextItem(text, r) f = txt.font() f.setPointSize(8) fm = QFontMetricsF(f) elidedText = fm.elidedText(text, Qt.ElideMiddle, w) txt.setFont(f) txt.setText(elidedText) txt.setPos(r.boundingRect().center() - txt.boundingRect().center()) txt.setY(0) return r # A function to returns an item's width, by counting its children def itemWidth(item): if item.isFolder(): r = 0 for c in item.children(): r += itemWidth(c) return r or TEXT_WIDTH else: return TEXT_WIDTH def listItems(item, rect, level=0): delta = 0 for child in item.children(): w = itemWidth(child) if child.isFolder(): parent = addRectText(delta, w, rect, child.title(), level, tooltip=child.title()) parent.setToolTip(references.tooltip(references.textReference(child.ID()))) listItems(child, parent, level + 1) else: rectChild = addRectText(delta, TEXT_WIDTH, rect, "", level, tooltip=child.title()) rectChild.setToolTip(references.tooltip(references.textReference(child.ID()))) # Find tracked references in that scene (or parent folders) for ref in trackedItems: result = [] # Tests if POV scenePOV = False # Will hold true of character is POV of the current text, not containing folder if references.type(ref) == references.CharacterLetter: ID = references.ID(ref) c = child while c: if c.POV() == ID: result.append(c.ID()) if c == child: scenePOV = True c = c.parent() # Search in notes/references c = child while c: result += references.findReferencesTo(ref, c, recursive=False) c = c.parent() if result: ref2 = result[0] # Create a RefCircle with the reference c = RefCircle(TEXT_WIDTH / 2, - CIRCLE_WIDTH / 2, CIRCLE_WIDTH, ID=ref2, important=scenePOV) # Store it, with the position of that item, to display it on the line later on refCircles.append((ref, c, rect.mapToItem(outline, rectChild.pos()))) delta += w listItems(root, outline) OUTLINE_WIDTH = itemWidth(root) # Add Tracked items i = 0 itemsRect = s.addRect(0, 0, 0, 0) itemsRect.setPos(0, MAX_LEVEL * LEVEL_HEIGHT + SPACING) # Set of colors for plots (as long as they don't have their own colors) colors = [ "#D97777", "#AE5F8C", "#D9A377", "#FFC2C2", "#FFDEC2", "#D2A0BC", "#7B0F0F", "#7B400F", "#620C3D", "#AA3939", "#AA6C39", "#882D61", "#4C0000", "#4C2200", "#3D0022", ] for ref in trackedItems: if references.type(ref) == references.CharacterLetter: color = self._mdlCharacter.getCharacterByID(references.ID(ref)).color() else: color = QColor(colors[i % len(colors)]) # Rect r = QGraphicsRectItem(0, 0, TITLE_WIDTH, LINE_HEIGHT, itemsRect) r.setPen(QPen(Qt.NoPen)) r.setBrush(QBrush(color)) r.setPos(0, i * LINE_HEIGHT + i * SPACING) r.setToolTip(references.tooltip(ref)) i += 1 # Text name = references.title(ref) txt = QGraphicsSimpleTextItem(name, r) txt.setPos(r.boundingRect().center() - txt.boundingRect().center()) # Line line = PlotLine(0, 0, OUTLINE_WIDTH + SPACING, 0) line.setPos(TITLE_WIDTH, r.mapToScene(r.rect().center()).y()) s.addItem(line) line.setPen(QPen(color, 5)) line.setToolTip(references.tooltip(ref)) # We add the circles / references to text, on the line for ref2, circle, pos in refCircles: if ref2 == ref: circle.setParentItem(line) circle.setPos(pos.x(), 0) # self.view.fitInView(0, 0, TOTAL_WIDTH, i * LINE_HEIGHT, Qt.KeepAspectRatioByExpanding) # KeepAspectRatio self.view.setSceneRect(0, 0, 0, 0)
class MainWindow(QMainWindow): def __init__(self, parent=None): super(MainWindow, self).__init__(parent) self.settingsTree = SettingsTree() self.setCentralWidget(self.settingsTree) self.locationDialog = None self.createActions() self.createMenus() self.autoRefreshAct.setChecked(True) self.fallbacksAct.setChecked(True) self.setWindowTitle("Settings Editor") self.resize(500, 600) def openSettings(self): if self.locationDialog is None: self.locationDialog = LocationDialog(self) if self.locationDialog.exec_(): settings = QSettings(self.locationDialog.format(), self.locationDialog.scope(), self.locationDialog.organization(), self.locationDialog.application()) self.setSettingsObject(settings) self.fallbacksAct.setEnabled(True) def openIniFile(self): fileName, _ = QFileDialog.getOpenFileName(self, "Open INI File", '', "INI Files (*.ini *.conf)") if fileName: settings = QSettings(fileName, QSettings.IniFormat) self.setSettingsObject(settings) self.fallbacksAct.setEnabled(False) def openPropertyList(self): fileName, _ = QFileDialog.getOpenFileName(self, "Open Property List", '', "Property List Files (*.plist)") if fileName: settings = QSettings(fileName, QSettings.NativeFormat) self.setSettingsObject(settings) self.fallbacksAct.setEnabled(False) def openRegistryPath(self): path, ok = QInputDialog.getText(self, "Open Registry Path", "Enter the path in the Windows registry:", QLineEdit.Normal, 'HKEY_CURRENT_USER\\') if ok and path != '': settings = QSettings(path, QSettings.NativeFormat) self.setSettingsObject(settings) self.fallbacksAct.setEnabled(False) def about(self): QMessageBox.about(self, "About Settings Editor", "The <b>Settings Editor</b> example shows how to access " "application settings using Qt.") def createActions(self): self.openSettingsAct = QAction("&Open Application Settings...", self, shortcut="Ctrl+O", triggered=self.openSettings) self.openIniFileAct = QAction("Open I&NI File...", self, shortcut="Ctrl+N", triggered=self.openIniFile) self.openPropertyListAct = QAction("Open Mac &Property List...", self, shortcut="Ctrl+P", triggered=self.openPropertyList) if sys.platform != 'darwin': self.openPropertyListAct.setEnabled(False) self.openRegistryPathAct = QAction("Open Windows &Registry Path...", self, shortcut="Ctrl+G", triggered=self.openRegistryPath) if sys.platform != 'win32': self.openRegistryPathAct.setEnabled(False) self.refreshAct = QAction("&Refresh", self, shortcut="Ctrl+R", enabled=False, triggered=self.settingsTree.refresh) self.exitAct = QAction("E&xit", self, shortcut="Ctrl+Q", triggered=self.close) self.autoRefreshAct = QAction("&Auto-Refresh", self, shortcut="Ctrl+A", checkable=True, enabled=False) self.autoRefreshAct.triggered.connect(self.settingsTree.setAutoRefresh) self.autoRefreshAct.triggered.connect(self.refreshAct.setDisabled) self.fallbacksAct = QAction("&Fallbacks", self, shortcut="Ctrl+F", checkable=True, enabled=False, triggered=self.settingsTree.setFallbacksEnabled) self.aboutAct = QAction("&About", self, triggered=self.about) self.aboutQtAct = QAction("About &Qt", self, triggered=QApplication.instance().aboutQt) def createMenus(self): self.fileMenu = self.menuBar().addMenu("&File") self.fileMenu.addAction(self.openSettingsAct) self.fileMenu.addAction(self.openIniFileAct) self.fileMenu.addAction(self.openPropertyListAct) self.fileMenu.addAction(self.openRegistryPathAct) self.fileMenu.addSeparator() self.fileMenu.addAction(self.refreshAct) self.fileMenu.addSeparator() self.fileMenu.addAction(self.exitAct) self.optionsMenu = self.menuBar().addMenu("&Options") self.optionsMenu.addAction(self.autoRefreshAct) self.optionsMenu.addAction(self.fallbacksAct) self.menuBar().addSeparator() self.helpMenu = self.menuBar().addMenu("&Help") self.helpMenu.addAction(self.aboutAct) self.helpMenu.addAction(self.aboutQtAct) def setSettingsObject(self, settings): settings.setFallbacksEnabled(self.fallbacksAct.isChecked()) self.settingsTree.setSettingsObject(settings) self.refreshAct.setEnabled(True) self.autoRefreshAct.setEnabled(True) niceName = settings.fileName() niceName.replace('\\', '/') niceName = niceName.split('/')[-1] if not settings.isWritable(): niceName += " (read only)" self.setWindowTitle("%s - Settings Editor" % niceName)
class MainWindow(QMainWindow): def __init__(self, parent): QMainWindow.__init__(self) self.printer = QPrinter() self.load_img = self.load_img_fit self.reload_img = self.reload_auto self.open_new = parent.open_win self.exit = parent.closeAllWindows self.scene = QGraphicsScene() self.img_view = ImageView(self) self.img_view.setScene(self.scene) self.setCentralWidget(self.img_view) self.create_actions() self.create_menu() self.create_dict() self.slides_next = True self.setContextMenuPolicy(Qt.CustomContextMenu) self.customContextMenuRequested.connect(self.showMenu) self.read_prefs() self.read_list = parent.read_list self.write_list = parent.write_list self.pics_dir = os.path.expanduser('~/Pictures') or QDir.currentPath() self.resize(700, 500) def create_actions(self): self.open_act = QAction('&Open', self, shortcut='Ctrl+O') self.open_act.triggered.connect(self.open) self.open_new_act = QAction('Open new window', self, shortcut='Ctrl+Shift+O') self.open_new_act.triggered.connect(partial(self.open, True)) self.reload_act = QAction('&Reload image', self, shortcut='Ctrl+R') self.reload_act.triggered.connect(self.reload_img) self.print_act = QAction('&Print', self, shortcut='Ctrl+P') self.print_act.triggered.connect(self.print_img) self.save_act = QAction('&Save image', self, shortcut='Ctrl+S') self.save_act.triggered.connect(self.save_img) self.close_act = QAction('Close window', self, shortcut='Ctrl+W') self.close_act.triggered.connect(self.close) self.exit_act = QAction('E&xit', self, shortcut='Ctrl+Q') self.exit_act.triggered.connect(self.exit) self.fulls_act = QAction('Fullscreen', self, shortcut='F11', checkable=True) self.fulls_act.triggered.connect(self.toggle_fs) self.ss_act = QAction('Slideshow', self, shortcut='F5', checkable=True) self.ss_act.triggered.connect(self.toggle_slideshow) self.ss_next_act = QAction('Next / Random image', self, checkable=True) self.ss_next_act.triggered.connect(self.set_slide_type) self.ss_next_act.setChecked(True) self.next_act = QAction('Next image', self, shortcut='Right') self.next_act.triggered.connect(self.go_next_img) self.prev_act = QAction('Previous image', self, shortcut='Left') self.prev_act.triggered.connect(self.go_prev_img) self.rotleft_act = QAction('Rotate left', self, shortcut='Ctrl+Left') self.rotleft_act.triggered.connect(partial(self.img_rotate, 270)) self.rotright_act = QAction('Rotate right', self, shortcut='Ctrl+Right') self.rotright_act.triggered.connect(partial(self.img_rotate, 90)) self.fliph_act = QAction('Flip image horizontally', self, shortcut='Ctrl+H') self.fliph_act.triggered.connect(partial(self.img_flip, -1, 1)) self.flipv_act = QAction('Flip image vertically', self, shortcut='Ctrl+V') self.flipv_act.triggered.connect(partial(self.img_flip, 1, -1)) self.resize_act = QAction('Resize image', self, triggered=self.resize_img) self.crop_act = QAction('Crop image', self, triggered=self.crop_img) self.zin_act = QAction('Zoom &In', self, shortcut='Up') self.zin_act.triggered.connect(partial(self.img_view.zoom, 1.1)) self.zout_act = QAction('Zoom &Out', self, shortcut='Down') self.zout_act.triggered.connect(partial(self.img_view.zoom, 1 / 1.1)) self.fit_win_act = QAction('Best &fit', self, checkable=True, shortcut='F', triggered=self.zoom_default) self.fit_win_act.setChecked(True) self.prefs_act = QAction('Preferences', self, triggered=self.set_prefs) self.props_act = QAction('Properties', self, triggered=self.get_props) self.help_act = QAction('&Help', self, shortcut='F1', triggered=self.help_page) self.about_act = QAction('&About', self, triggered=self.about_cm) self.aboutQt_act = QAction('About &Qt', self, triggered=qApp.aboutQt) def create_menu(self): self.popup = QMenu(self) main_acts = [self.open_act, self.open_new_act, self.reload_act, self.print_act, self.save_act] edit_acts1 = [self.rotleft_act, self.rotright_act, self.fliph_act, self.flipv_act] edit_acts2 = [self.resize_act, self.crop_act] view_acts = [self.next_act, self.prev_act, self.zin_act, self.zout_act, self.fit_win_act, self.fulls_act, self.ss_act, self.ss_next_act] help_acts = [self.help_act, self.about_act, self.aboutQt_act] end_acts = [self.prefs_act, self.props_act, self.close_act, self.exit_act] for act in main_acts: self.popup.addAction(act) edit_menu = QMenu(self.popup) edit_menu.setTitle('&Edit') for act in edit_acts1: edit_menu.addAction(act) edit_menu.addSeparator() for act in edit_acts2: edit_menu.addAction(act) self.popup.addMenu(edit_menu) view_menu = QMenu(self.popup) view_menu.setTitle('&View') for act in view_acts: view_menu.addAction(act) self.popup.addMenu(view_menu) help_menu = QMenu(self.popup) help_menu.setTitle('&Help') for act in help_acts: help_menu.addAction(act) self.popup.addMenu(help_menu) for act in end_acts: self.popup.addAction(act) self.action_list = main_acts + edit_acts1 + edit_acts2 + view_acts + help_acts + end_acts for act in self.action_list: self.addAction(act) def showMenu(self, pos): self.popup.popup(self.mapToGlobal(pos)) def create_dict(self): """Create a dictionary to handle auto-orientation.""" self.orient_dict = {None: self.load_img, '1': self.load_img, '2': partial(self.img_flip, -1, 1), '3': partial(self.img_rotate, 180), '4': partial(self.img_flip, -1, 1), '5': self.img_rotate_fliph, '6': partial(self.img_rotate, 90), '7': self.img_rotate_flipv, '8': partial(self.img_rotate, 270)} def read_prefs(self): """Parse the preferences from the config file, or set default values.""" try: conf = preferences.Config() values = conf.read_config() self.auto_orient = values[0] self.slide_delay = values[1] self.quality = values[2] except: self.auto_orient = True self.slide_delay = 5 self.quality = 90 self.reload_img = self.reload_auto if self.auto_orient else self.reload_nonauto def set_prefs(self): """Write preferences to the config file.""" dialog = preferences.PrefsDialog(self) if dialog.exec_() == QDialog.Accepted: self.auto_orient = dialog.auto_orient self.slide_delay = dialog.delay_spinb.value() self.quality = dialog.qual_spinb.value() conf = preferences.Config() conf.write_config(self.auto_orient, self.slide_delay, self.quality) self.reload_img = self.reload_auto if self.auto_orient else self.reload_nonauto def open(self, new_win=False): fname = QFileDialog.getOpenFileName(self, 'Open File', self.pics_dir)[0] if fname: if fname.lower().endswith(self.read_list): if new_win: self.open_new(fname) else: self.open_img(fname) else: QMessageBox.information(self, 'Error', 'Cannot load {} images.'.format(fname.rsplit('.', 1)[1])) def open_img(self, fname): self.fname = fname self.reload_img() dirname = os.path.dirname(self.fname) self.set_img_list(dirname) self.img_index = self.filelist.index(self.fname) def set_img_list(self, dirname): """Create a list of readable images from the current directory.""" filelist = os.listdir(dirname) self.filelist = [os.path.join(dirname, fname) for fname in filelist if fname.lower().endswith(self.read_list)] self.filelist.sort() self.last_file = len(self.filelist) - 1 def get_img(self): """Get image from fname and create pixmap.""" image = QImage(self.fname) self.pixmap = QPixmap.fromImage(image) self.setWindowTitle(self.fname.rsplit('/', 1)[1]) def reload_auto(self): """Load a new image with auto-orientation.""" self.get_img() try: orient = GExiv2.Metadata(self.fname)['Exif.Image.Orientation'] self.orient_dict[orient]() except: self.load_img() def reload_nonauto(self): """Load a new image without auto-orientation.""" self.get_img() self.load_img() def load_img_fit(self): """Load the image to fit the window.""" self.scene.clear() self.scene.addPixmap(self.pixmap) self.scene.setSceneRect(0, 0, self.pixmap.width(), self.pixmap.height()) self.img_view.fitInView(self.scene.sceneRect(), Qt.KeepAspectRatio) def load_img_1to1(self): """Load the image at its original size.""" self.scene.clear() self.img_view.resetTransform() self.scene.addPixmap(self.pixmap) self.scene.setSceneRect(0, 0, self.pixmap.width(), self.pixmap.height()) pixitem = QGraphicsPixmapItem(self.pixmap) self.img_view.centerOn(pixitem) def go_next_img(self): self.img_index = self.img_index + 1 if self.img_index < self.last_file else 0 self.fname = self.filelist[self.img_index] self.reload_img() def go_prev_img(self): self.img_index = self.img_index - 1 if self.img_index else self.last_file self.fname = self.filelist[self.img_index] self.reload_img() def zoom_default(self): """Toggle best fit / original size loading.""" if self.fit_win_act.isChecked(): self.load_img = self.load_img_fit self.create_dict() self.load_img() else: self.load_img = self.load_img_1to1 self.create_dict() self.load_img() def img_rotate(self, angle): self.pixmap = self.pixmap.transformed(QTransform().rotate(angle)) self.load_img() def img_flip(self, x, y): self.pixmap = self.pixmap.transformed(QTransform().scale(x, y)) self.load_img() def img_rotate_fliph(self): self.img_rotate(90) self.img_flip(-1, 1) def img_rotate_flipv(self): self.img_rotate(90) self.img_flip(1, -1) def resize_img(self): dialog = editimage.ResizeDialog(self, self.pixmap.width(), self.pixmap.height()) if dialog.exec_() == QDialog.Accepted: width = dialog.get_width.value() height = dialog.get_height.value() self.pixmap = self.pixmap.scaled(width, height, Qt.IgnoreAspectRatio, Qt.SmoothTransformation) self.save_img() def crop_img(self): self.img_view.setup_crop(self.pixmap.width(), self.pixmap.height()) dialog = editimage.CropDialog(self, self.pixmap.width(), self.pixmap.height()) if dialog.exec_() == QDialog.Accepted: coords = self.img_view.get_coords() self.pixmap = self.pixmap.copy(*coords) self.load_img() self.img_view.rband.hide() def toggle_fs(self): if self.fulls_act.isChecked(): self.showFullScreen() else: self.showNormal() def toggle_slideshow(self): if self.ss_act.isChecked(): self.showFullScreen() self.start_ss() else: self.toggle_fs() self.timer.stop() self.ss_timer.stop() def start_ss(self): self.timer = QTimer() self.timer.timeout.connect(self.update_img) self.timer.start(self.slide_delay * 1000) self.ss_timer = QTimer() self.ss_timer.timeout.connect(self.update_img) self.ss_timer.start(60000) def update_img(self): if self.slides_next: self.go_next_img() else: self.fname = random.choice(self.filelist) self.reload_img() def set_slide_type(self): self.slides_next = self.ss_next_act.isChecked() def save_img(self): fname = QFileDialog.getSaveFileName(self, 'Save your image', self.fname)[0] if fname: if fname.lower().endswith(self.write_list): keep_exif = QMessageBox.question(self, 'Save exif data', 'Do you want to save the picture metadata?', QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes) if keep_exif == QMessageBox.Yes: self.pixmap.save(fname, None, self.quality) exif = GExiv2.Metadata(self.fname) if exif: saved_exif = GExiv2.Metadata(fname) for tag in exif.get_exif_tags(): saved_exif[tag] = exif[tag] saved_exif.set_orientation(GExiv2.Orientation.NORMAL) saved_exif.save_file() else: QMessageBox.information(self, 'Error', 'Cannot save {} images.'.format(fname.rsplit('.', 1)[1])) def print_img(self): dialog = QPrintDialog(self.printer, self) if dialog.exec_(): painter = QPainter(self.printer) rect = painter.viewport() if self.pixmap.width() > self.pixmap.height(): self.pixmap = self.pixmap.transformed(QTransform().rotate(90)) size = self.pixmap.size() size.scale(rect.size(), Qt.KeepAspectRatio) painter.setViewport(rect.x(), rect.y(), size.width(), size.height()) painter.setWindow(self.pixmap.rect()) painter.drawPixmap(0, 0, self.pixmap) def resizeEvent(self, event=None): if self.fit_win_act.isChecked(): try: self.load_img() except: pass def get_props(self): """Get the properties of the current image.""" image = QImage(self.fname) preferences.PropsDialog(self, self.fname.rsplit('/', 1)[1], image.width(), image.height()) def help_page(self): preferences.HelpDialog(self) def about_cm(self): about_message = 'Version: 0.3.9\nAuthor: David Whitlock\nLicense: GPLv3' QMessageBox.about(self, 'About Cheesemaker', about_message)
class revisions(QWidget, Ui_revisions): def __init__(self, parent=None): QWidget.__init__(self, parent) self.setupUi(self) self.splitter.setStretchFactor(0, 5) self.splitter.setStretchFactor(1, 70) self.listDelegate = listCompleterDelegate(self) self.list.setItemDelegate(self.listDelegate) self.list.itemClicked.connect(self.showDiff) self.list.setContextMenuPolicy(Qt.CustomContextMenu) self.list.customContextMenuRequested.connect(self.popupMenu) self.btnDelete.setEnabled(False) self.btnDelete.clicked.connect(self.delete) self.btnRestore.clicked.connect(self.restore) self.btnRestore.setEnabled(False) # self.list.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.updateTimer = QTimer() self.updateTimer.setSingleShot(True) self.updateTimer.setInterval(500) self.updateTimer.timeout.connect(self.update) self.updateTimer.stop() self.menu = QMenu(self) self.actGroup = QActionGroup(self) self.actShowDiff = QAction(self.tr("Show modifications"), self.menu) self.actShowDiff.setCheckable(True) self.actShowDiff.setChecked(True) self.actShowDiff.triggered.connect(self.showDiff) self.menu.addAction(self.actShowDiff) self.actGroup.addAction(self.actShowDiff) self.actShowVersion = QAction(self.tr("Show ancient version"), self.menu) self.actShowVersion.setCheckable(True) self.actShowVersion.setChecked(False) self.actShowVersion.triggered.connect(self.showDiff) self.menu.addAction(self.actShowVersion) self.actGroup.addAction(self.actShowVersion) self.menu.addSeparator() self.actShowSpaces = QAction(self.tr("Show spaces"), self.menu) self.actShowSpaces.setCheckable(True) self.actShowSpaces.setChecked(False) self.actShowSpaces.triggered.connect(self.showDiff) self.menu.addAction(self.actShowSpaces) self.actDiffOnly = QAction(self.tr("Show modifications only"), self.menu) self.actDiffOnly.setCheckable(True) self.actDiffOnly.setChecked(True) self.actDiffOnly.triggered.connect(self.showDiff) self.menu.addAction(self.actDiffOnly) self.btnOptions.setMenu(self.menu) self._model = None self._index = None def setModel(self, model): self._model = model self._model.dataChanged.connect(self.updateMaybe) def setCurrentModelIndex(self, index): self._index = index self.view.setText("") self.update() def updateMaybe(self, topLeft, bottomRight): if self._index and \ topLeft.column() <= Outline.revisions.value <= bottomRight.column() and \ topLeft.row() <= self._index.row() <= bottomRight.row(): # self.update() self.updateTimer.start() def update(self): self.list.clear() item = self._index.internalPointer() rev = item.revisions() # Sort revisions rev = sorted(rev, key=lambda x: x[0], reverse=True) for r in rev: timestamp = datetime.datetime.fromtimestamp( r[0]).strftime('%Y-%m-%d %H:%M:%S') readable = self.readableDelta(r[0]) i = QListWidgetItem(readable) i.setData(Qt.UserRole, r[0]) i.setData(Qt.UserRole + 1, timestamp) self.list.addItem(i) def readableDelta(self, timestamp): now = datetime.datetime.now() delta = now - datetime.datetime.fromtimestamp(timestamp) if delta.days > 365: return self.tr("{} years ago").format(str(int(delta.days / 365))) elif delta.days > 30: return self.tr("{} months ago").format(str(int(delta.days / 30.5))) elif delta.days > 0: return self.tr("{} days ago").format(str(delta.days)) if delta.days == 1: return self.tr("1 day ago") elif delta.seconds > 60 * 60: return self.tr("{} hours ago").format( str(int(delta.seconds / 60 / 60))) elif delta.seconds > 60: return self.tr("{} minutes ago").format( str(int(delta.seconds / 60))) else: return self.tr("{} seconds ago").format(str(delta.seconds)) def showDiff(self): # UI stuff self.actShowSpaces.setEnabled(self.actShowDiff.isChecked()) self.actDiffOnly.setEnabled(self.actShowDiff.isChecked()) # FIXME: Errors in line number i = self.list.currentItem() if not i: self.btnDelete.setEnabled(False) self.btnRestore.setEnabled(False) return self.btnDelete.setEnabled(True) self.btnRestore.setEnabled(True) ts = i.data(Qt.UserRole) item = self._index.internalPointer() textNow = item.text() textBefore = [r[1] for r in item.revisions() if r[0] == ts][0] if self.actShowVersion.isChecked(): self.view.setText(textBefore) return textNow = textNow.splitlines() textBefore = textBefore.splitlines() d = difflib.Differ() diff = list(d.compare(textBefore, textNow)) if self.actShowSpaces.isChecked(): _format = lambda x: x.replace(" ", "␣ ") else: _format = lambda x: x extra = "<br>" diff = [d for d in diff if d and not d[:2] == "? "] mydiff = "" skip = False for n, l in enumerate(diff): l = diff[n] op = l[:2] txt = l[2:] op2 = diff[n + 1][:2] if n + 1 < len(diff) else None txt2 = diff[n + 1][2:] if n + 1 < len(diff) else None if skip: skip = False continue # Same line if op == " " and not self.actDiffOnly.isChecked(): mydiff += "{}{}".format(txt, extra) elif op == "- " and op2 == "+ ": if self.actDiffOnly.isChecked(): mydiff += "<br><span style='color: blue;'>{}</span><br>".format( self.tr("Line {}:").format(str(n))) s = difflib.SequenceMatcher(None, txt, txt2, autojunk=True) newline = "" for tag, i1, i2, j1, j2 in s.get_opcodes(): if tag == "equal": newline += txt[i1:i2] elif tag == "delete": newline += "<span style='color:red; background:yellow;'>{}</span>".format( _format(txt[i1:i2])) elif tag == "insert": newline += "<span style='color:green; background:yellow;'>{}</span>".format( _format(txt2[j1:j2])) elif tag == "replace": newline += "<span style='color:red; background:yellow;'>{}</span>".format( _format(txt[i1:i2])) newline += "<span style='color:green; background:yellow;'>{}</span>".format( _format(txt2[j1:j2])) # Few ugly tweaks for html diffs newline = re.sub( r"(<span style='color.*?><span.*?>)</span>(.*)<span style='color:.*?>(</span></span>)", "\\1\\2\\3", newline) newline = re.sub( r"<p align=\"<span style='color:red; background:yellow;'>cen</span><span style='color:green; background:yellow;'>righ</span>t<span style='color:red; background:yellow;'>er</span>\" style=\" -qt-block-indent:0; -qt-user-state:0; \">(.*?)</p>", "<p align=\"right\"><span style='color:green; background:yellow;'>\\1</span></p>", newline) newline = re.sub( r"<p align=\"<span style='color:green; background:yellow;'>cente</span>r<span style='color:red; background:yellow;'>ight</span>\" style=\" -qt-block-indent:0; -qt-user-state:0; \">(.*)</p>", "<p align=\"center\"><span style='color:green; background:yellow;'>\\1</span></p>", newline) newline = re.sub(r"<p(<span.*?>)(.*?)(</span>)(.*?)>(.*?)</p>", "<p\\2\\4>\\1\\5\\3</p>", newline) mydiff += newline + extra skip = True elif op == "- ": if self.actDiffOnly.isChecked(): mydiff += "<br>{}:<br>".format(str(n)) mydiff += "<span style='color:red;'>{}</span>{}".format( txt, extra) elif op == "+ ": if self.actDiffOnly.isChecked(): mydiff += "<br>{}:<br>".format(str(n)) mydiff += "<span style='color:green;'>{}</span>{}".format( txt, extra) self.view.setText(mydiff) def restore(self): i = self.list.currentItem() if not i: return ts = i.data(Qt.UserRole) item = self._index.internalPointer() textBefore = [r[1] for r in item.revisions() if r[0] == ts][0] index = self._index.sibling(self._index.row(), Outline.text.value) self._index.model().setData(index, textBefore) # item.setData(Outline.text.value, textBefore) def delete(self): i = self.list.currentItem() if not i: return ts = i.data(Qt.UserRole) self._index.internalPointer().deleteRevision(ts) def clearAll(self): self._index.internalPointer().clearAllRevisions() def saveState(self): return [ self.actShowDiff.isChecked(), self.actShowVersion.isChecked(), self.actShowSpaces.isChecked(), self.actDiffOnly.isChecked(), ] def popupMenu(self, pos): i = self.list.itemAt(pos) m = QMenu(self) if i: m.addAction(self.tr("Restore")).triggered.connect(self.restore) m.addAction(self.tr("Delete")).triggered.connect(self.delete) m.addSeparator() if self.list.count(): m.addAction(self.tr("Clear all")).triggered.connect(self.clearAll) m.popup(self.list.mapToGlobal(pos)) def restoreState(self, state): self.actShowDiff.setChecked(state[0]) self.actShowVersion.setChecked(state[1]) self.actShowSpaces.setChecked(state[2]) self.actDiffOnly.setChecked(state[3]) self.actShowSpaces.setEnabled(self.actShowDiff.isChecked()) self.actDiffOnly.setEnabled(self.actShowDiff.isChecked())
class PDFAreaSelectorDlg(QDialog): def __init__(self, parent=None): super().__init__(parent) self._parent = parent self.setWindowTitle("Area selection") self.scaleFactor = 1 self.setModal(True) self.controlBarWgt = QWidget(self) self.nextPageBtn = QPushButton("Next page (Ctrl+right arrow)") self.previousPageBtn = QPushButton("Previous page (Ctrl+left arrow)") self.noPageTxt = QLabel("1") self.noPageTxt.setStyleSheet("border: 1px solid grey") self.noPageTxt.setFixedWidth(40) self.controlBarWgt.setLayout(QHBoxLayout()) self.nextPageBtn.clicked.connect(self.nextPage) self.previousPageBtn.clicked.connect(self.previousPage) self.controlBarWgt.layout().addWidget(self.previousPageBtn) self.controlBarWgt.layout().addWidget(self.noPageTxt) self.controlBarWgt.layout().addWidget(self.nextPageBtn) self.imageLabel = ImageWidget() self.imageLabel.setBackgroundRole(QPalette.Base) self.imageLabel.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored) self.imageLabel.setScaledContents(True) self.scrollArea = QScrollArea() self.scrollArea.setBackgroundRole(QPalette.Dark) self.scrollArea.setWidget(self.imageLabel) self.createActions() self.createMenus() self.setLayout(QVBoxLayout()) self.layout().addWidget(self.menuBar) self.layout().addWidget(self.controlBarWgt) self.layout().addWidget(self.scrollArea) self.imageLabel.areaSelected.connect(self.resendSelectedEvent) self.loadImage() @pyqtSlot(float, float, float, float, QPixmap) def resendSelectedEvent(self, x, y, width, height, image): self._parent.x = x self._parent.y = y self._parent.width = width self._parent.height = height self._parent.image = image self._parent.areaSelected.emit() self.close() def loadImage(self): image = QImage.fromData(self._parent.pages[self._parent.currentPageInd],"PNG") self.imageLabel.setPixmap(QPixmap.fromImage(image)) self.fitToWindowAct.setEnabled(True) self.updateActions() if not self.fitToWindowAct.isChecked(): self.imageLabel.adjustSize() self.setWindowFilePath(self._parent.fileName) return True def zoomIn(self): self.scaleImage(1.25) def zoomOut(self): self.scaleImage(0.8) def normalSize(self): self.imageLabel.adjustSize() self.scaleFactor = 1.0 def fitToWindow(self): fitToWindow = self.fitToWindowAct.isChecked() self.scrollArea.setWidgetResizable(fitToWindow) if not fitToWindow : self.normalSize() self.updateActions() def createActions(self): self.exitAct = QAction("E&xit", self) self.exitAct.setShortcut("Ctrl+Q") self.exitAct.triggered.connect(self.close) self.zoomInAct = QAction("Zoom &In (25%)", self) self.zoomInAct.setShortcut("Ctrl++") self.zoomInAct.setEnabled(False) self.zoomInAct.triggered.connect(self.zoomIn) self.zoomOutAct = QAction("Zoom &Out (25%)", self) self.zoomOutAct.setShortcut("Ctrl+-") self.zoomOutAct.setEnabled(False) self.zoomOutAct.triggered.connect(self.zoomOut) self.normalSizeAct = QAction("&Normal Size", self) self.normalSizeAct.setShortcut("Ctrl+S") self.normalSizeAct.setEnabled(False) self.normalSizeAct.triggered.connect(self.normalSize) self.fitToWindowAct = QAction("&Fit to Window", self) self.fitToWindowAct.setEnabled(False) self.fitToWindowAct.setCheckable(True) self.fitToWindowAct.setShortcut("Ctrl+F") self.fitToWindowAct.triggered.connect(self.fitToWindow) self.nextPageAct = QAction("&Next page", self) self.nextPageAct.setEnabled(True) self.nextPageAct.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_Right)) self.nextPageAct.triggered.connect(self.nextPage) self.previousPageAct = QAction("&Previous page", self) self.previousPageAct.setEnabled(True) self.previousPageAct.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_Left)) self.previousPageAct.triggered.connect(self.previousPage) def createMenus(self): self.menuBar = QMenuBar(self) self.fileMenu = QMenu("&File", self) self.fileMenu.addAction(self.exitAct) self.viewMenu = QMenu("&View", self) self.viewMenu.addAction(self.zoomInAct) self.viewMenu.addAction(self.zoomOutAct) self.viewMenu.addAction(self.normalSizeAct) self.viewMenu.addSeparator() self.viewMenu.addAction(self.fitToWindowAct) self.viewMenu.addSeparator() self.viewMenu.addAction(self.nextPageAct) self.viewMenu.addAction(self.previousPageAct) self.menuBar.addMenu(self.fileMenu) self.menuBar.addMenu(self.viewMenu) def updateActions(self): self.zoomInAct.setEnabled(not self.fitToWindowAct.isChecked()) self.zoomOutAct.setEnabled(not self.fitToWindowAct.isChecked()) self.normalSizeAct.setEnabled(not self.fitToWindowAct.isChecked()) def scaleImage(self, factor): assert(self.imageLabel.pixmap()) self.scaleFactor *= factor self.imageLabel.resize(self.scaleFactor * self.imageLabel.pixmap().size()) self.adjustScrollBar(self.scrollArea.horizontalScrollBar(), factor) self.adjustScrollBar(self.scrollArea.verticalScrollBar(), factor) self.zoomInAct.setEnabled(self.scaleFactor < 3.0) self.zoomOutAct.setEnabled(self.scaleFactor > 0.333) def adjustScrollBar(self, scrollBar, factor): scrollBar.setValue(int(factor * scrollBar.value() + ((factor - 1) * scrollBar.pageStep()/2))) def nextPage(self): if self._parent.currentPageInd < len(self._parent.pages)-1: self._parent.currentPageInd += 1 self.loadImage() self.noPageTxt.setText(str(self._parent.currentPageInd+1)) def previousPage(self): if self._parent.currentPageInd > 0: self._parent.currentPageInd -= 1 self.loadImage() self.noPageTxt.setText(str(self._parent.currentPageInd+1))
class UserAgentMenu(QMenu): """ Class implementing a menu to select the user agent string. """ def __init__(self, title, url=None, parent=None): """ Constructor @param title title of the menu (string) @param url URL to set user agent for (QUrl) @param parent reference to the parent widget (QWidget) """ super(UserAgentMenu, self).__init__(title, parent) self.__manager = None self.__url = url if self.__url: if self.__url.isValid(): import Helpviewer.HelpWindow self.__manager = \ Helpviewer.HelpWindow.HelpWindow.userAgentsManager() else: self.__url = None self.aboutToShow.connect(self.__populateMenu) def __populateMenu(self): """ Private slot to populate the menu. """ self.aboutToShow.disconnect(self.__populateMenu) self.__actionGroup = QActionGroup(self) # add default action self.__defaultUserAgent = QAction(self) self.__defaultUserAgent.setText(self.tr("Default")) self.__defaultUserAgent.setCheckable(True) self.__defaultUserAgent.triggered.connect( self.__switchToDefaultUserAgent) if self.__url: self.__defaultUserAgent.setChecked( self.__manager.userAgentForUrl(self.__url) == "") else: from Helpviewer.HelpBrowserWV import HelpWebPage self.__defaultUserAgent.setChecked(HelpWebPage().userAgent() == "") self.addAction(self.__defaultUserAgent) self.__actionGroup.addAction(self.__defaultUserAgent) isChecked = self.__defaultUserAgent.isChecked() # add default extra user agents isChecked = self.__addDefaultActions() or isChecked # add other action self.addSeparator() self.__otherUserAgent = QAction(self) self.__otherUserAgent.setText(self.tr("Other...")) self.__otherUserAgent.setCheckable(True) self.__otherUserAgent.triggered.connect(self.__switchToOtherUserAgent) self.addAction(self.__otherUserAgent) self.__actionGroup.addAction(self.__otherUserAgent) self.__otherUserAgent.setChecked(not isChecked) def __switchToDefaultUserAgent(self): """ Private slot to set the default user agent. """ if self.__url: self.__manager.removeUserAgent(self.__url.host()) else: from Helpviewer.HelpBrowserWV import HelpWebPage HelpWebPage().setUserAgent("") def __switchToOtherUserAgent(self): """ Private slot to set a custom user agent string. """ from Helpviewer.HelpBrowserWV import HelpWebPage userAgent, ok = QInputDialog.getText( self, self.tr("Custom user agent"), self.tr("User agent:"), QLineEdit.Normal, HelpWebPage().userAgent(resolveEmpty=True)) if ok: if self.__url: self.__manager.setUserAgentForUrl(self.__url, userAgent) else: HelpWebPage().setUserAgent(userAgent) def __changeUserAgent(self): """ Private slot to change the user agent. """ act = self.sender() if self.__url: self.__manager.setUserAgentForUrl(self.__url, act.data()) else: from Helpviewer.HelpBrowserWV import HelpWebPage HelpWebPage().setUserAgent(act.data()) def __addDefaultActions(self): """ Private slot to add the default user agent entries. @return flag indicating that a user agent entry is checked (boolean) """ from . import UserAgentDefaults_rc # __IGNORE_WARNING__ defaultUserAgents = QFile(":/UserAgentDefaults.xml") defaultUserAgents.open(QIODevice.ReadOnly) menuStack = [] isChecked = False if self.__url: currentUserAgentString = self.__manager.userAgentForUrl(self.__url) else: from Helpviewer.HelpBrowserWV import HelpWebPage currentUserAgentString = HelpWebPage().userAgent() xml = QXmlStreamReader(defaultUserAgents) while not xml.atEnd(): xml.readNext() if xml.isStartElement() and xml.name() == "separator": if menuStack: menuStack[-1].addSeparator() else: self.addSeparator() continue if xml.isStartElement() and xml.name() == "useragent": attributes = xml.attributes() title = attributes.value("description") userAgent = attributes.value("useragent") act = QAction(self) act.setText(title) act.setData(userAgent) act.setToolTip(userAgent) act.setCheckable(True) act.setChecked(userAgent == currentUserAgentString) act.triggered.connect(self.__changeUserAgent) if menuStack: menuStack[-1].addAction(act) else: self.addAction(act) self.__actionGroup.addAction(act) isChecked = isChecked or act.isChecked() if xml.isStartElement() and xml.name() == "useragentmenu": attributes = xml.attributes() title = attributes.value("title") if title == "v_a_r_i_o_u_s": title = self.tr("Various") menu = QMenu(self) menu.setTitle(title) self.addMenu(menu) menuStack.append(menu) if xml.isEndElement() and xml.name() == "useragentmenu": menuStack.pop() if xml.hasError(): E5MessageBox.critical( self, self.tr("Parsing default user agents"), self.tr( """<p>Error parsing default user agents.</p><p>{0}</p>"""). format(xml.errorString())) return isChecked
class revisions(QWidget, Ui_revisions): def __init__(self, parent=None): QWidget.__init__(self, parent) self.setupUi(self) self.splitter.setStretchFactor(0, 5) self.splitter.setStretchFactor(1, 70) self.listDelegate = listCompleterDelegate(self) self.list.setItemDelegate(self.listDelegate) self.list.itemClicked.connect(self.showDiff) self.list.setContextMenuPolicy(Qt.CustomContextMenu) self.list.customContextMenuRequested.connect(self.popupMenu) self.btnDelete.setEnabled(False) self.btnDelete.clicked.connect(self.delete) self.btnRestore.clicked.connect(self.restore) self.btnRestore.setEnabled(False) # self.list.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.updateTimer = QTimer() self.updateTimer.setSingleShot(True) self.updateTimer.setInterval(500) self.updateTimer.timeout.connect(self.update) self.updateTimer.stop() self.menu = QMenu(self) self.actGroup = QActionGroup(self) self.actShowDiff = QAction(self.tr("Show modifications"), self.menu) self.actShowDiff.setCheckable(True) self.actShowDiff.setChecked(True) self.actShowDiff.triggered.connect(self.showDiff) self.menu.addAction(self.actShowDiff) self.actGroup.addAction(self.actShowDiff) self.actShowVersion = QAction(self.tr("Show ancient version"), self.menu) self.actShowVersion.setCheckable(True) self.actShowVersion.setChecked(False) self.actShowVersion.triggered.connect(self.showDiff) self.menu.addAction(self.actShowVersion) self.actGroup.addAction(self.actShowVersion) self.menu.addSeparator() self.actShowSpaces = QAction(self.tr("Show spaces"), self.menu) self.actShowSpaces.setCheckable(True) self.actShowSpaces.setChecked(False) self.actShowSpaces.triggered.connect(self.showDiff) self.menu.addAction(self.actShowSpaces) self.actDiffOnly = QAction(self.tr("Show modifications only"), self.menu) self.actDiffOnly.setCheckable(True) self.actDiffOnly.setChecked(True) self.actDiffOnly.triggered.connect(self.showDiff) self.menu.addAction(self.actDiffOnly) self.btnOptions.setMenu(self.menu) self._model = None self._index = None def setModel(self, model): self._model = model self._model.dataChanged.connect(self.updateMaybe) def setCurrentModelIndex(self, index): self._index = index self.view.setText("") self.update() def updateMaybe(self, topLeft, bottomRight): if self._index and \ topLeft.column() <= Outline.revisions <= bottomRight.column() and \ topLeft.row() <= self._index.row() <= bottomRight.row(): # self.update() self.updateTimer.start() def update(self): self.list.clear() item = self._index.internalPointer() rev = item.revisions() # Sort revisions rev = sorted(rev, key=lambda x: x[0], reverse=True) for r in rev: timestamp = datetime.datetime.fromtimestamp(r[0]).strftime('%Y-%m-%d %H:%M:%S') readable = self.readableDelta(r[0]) i = QListWidgetItem(readable) i.setData(Qt.UserRole, r[0]) i.setData(Qt.UserRole + 1, timestamp) self.list.addItem(i) def readableDelta(self, timestamp): now = datetime.datetime.now() delta = now - datetime.datetime.fromtimestamp(timestamp) if delta.days > 365: return self.tr("{} years ago").format(str(int(delta.days / 365))) elif delta.days > 30: return self.tr("{} months ago").format(str(int(delta.days / 30.5))) elif delta.days > 0: return self.tr("{} days ago").format(str(delta.days)) if delta.days == 1: return self.tr("1 day ago") elif delta.seconds > 60 * 60: return self.tr("{} hours ago").format(str(int(delta.seconds / 60 / 60))) elif delta.seconds > 60: return self.tr("{} minutes ago").format(str(int(delta.seconds / 60))) else: return self.tr("{} seconds ago").format(str(delta.seconds)) def showDiff(self): # UI stuff self.actShowSpaces.setEnabled(self.actShowDiff.isChecked()) self.actDiffOnly.setEnabled(self.actShowDiff.isChecked()) # FIXME: Errors in line number i = self.list.currentItem() if not i: self.btnDelete.setEnabled(False) self.btnRestore.setEnabled(False) return self.btnDelete.setEnabled(True) self.btnRestore.setEnabled(True) ts = i.data(Qt.UserRole) item = self._index.internalPointer() textNow = item.text() textBefore = [r[1] for r in item.revisions() if r[0] == ts][0] if self.actShowVersion.isChecked(): self.view.setText(textBefore) return textNow = textNow.splitlines() textBefore = textBefore.splitlines() d = difflib.Differ() diff = list(d.compare(textBefore, textNow)) if self.actShowSpaces.isChecked(): _format = lambda x: x.replace(" ", "␣ ") else: _format = lambda x: x extra = "<br>" diff = [d for d in diff if d and not d[:2] == "? "] mydiff = "" skip = False for n, l in enumerate(diff): l = diff[n] op = l[:2] txt = l[2:] op2 = diff[n + 1][:2] if n + 1 < len(diff) else None txt2 = diff[n + 1][2:] if n + 1 < len(diff) else None if skip: skip = False continue # Same line if op == " " and not self.actDiffOnly.isChecked(): mydiff += "{}{}".format(txt, extra) elif op == "- " and op2 == "+ ": if self.actDiffOnly.isChecked(): mydiff += "<br><span style='color: blue;'>{}</span><br>".format( self.tr("Line {}:").format(str(n))) s = difflib.SequenceMatcher(None, txt, txt2, autojunk=True) newline = "" for tag, i1, i2, j1, j2 in s.get_opcodes(): if tag == "equal": newline += txt[i1:i2] elif tag == "delete": newline += "<span style='color:red; background:yellow;'>{}</span>".format(_format(txt[i1:i2])) elif tag == "insert": newline += "<span style='color:green; background:yellow;'>{}</span>".format( _format(txt2[j1:j2])) elif tag == "replace": newline += "<span style='color:red; background:yellow;'>{}</span>".format(_format(txt[i1:i2])) newline += "<span style='color:green; background:yellow;'>{}</span>".format( _format(txt2[j1:j2])) # Few ugly tweaks for html diffs newline = re.sub(r"(<span style='color.*?><span.*?>)</span>(.*)<span style='color:.*?>(</span></span>)", "\\1\\2\\3", newline) newline = re.sub( r"<p align=\"<span style='color:red; background:yellow;'>cen</span><span style='color:green; background:yellow;'>righ</span>t<span style='color:red; background:yellow;'>er</span>\" style=\" -qt-block-indent:0; -qt-user-state:0; \">(.*?)</p>", "<p align=\"right\"><span style='color:green; background:yellow;'>\\1</span></p>", newline) newline = re.sub( r"<p align=\"<span style='color:green; background:yellow;'>cente</span>r<span style='color:red; background:yellow;'>ight</span>\" style=\" -qt-block-indent:0; -qt-user-state:0; \">(.*)</p>", "<p align=\"center\"><span style='color:green; background:yellow;'>\\1</span></p>", newline) newline = re.sub(r"<p(<span.*?>)(.*?)(</span>)(.*?)>(.*?)</p>", "<p\\2\\4>\\1\\5\\3</p>", newline) mydiff += newline + extra skip = True elif op == "- ": if self.actDiffOnly.isChecked(): mydiff += "<br>{}:<br>".format(str(n)) mydiff += "<span style='color:red;'>{}</span>{}".format(txt, extra) elif op == "+ ": if self.actDiffOnly.isChecked(): mydiff += "<br>{}:<br>".format(str(n)) mydiff += "<span style='color:green;'>{}</span>{}".format(txt, extra) self.view.setText(mydiff) def restore(self): i = self.list.currentItem() if not i: return ts = i.data(Qt.UserRole) item = self._index.internalPointer() textBefore = [r[1] for r in item.revisions() if r[0] == ts][0] index = self._index.sibling(self._index.row(), Outline.text) self._index.model().setData(index, textBefore) # item.setData(Outline.text, textBefore) def delete(self): i = self.list.currentItem() if not i: return ts = i.data(Qt.UserRole) self._index.internalPointer().deleteRevision(ts) def clearAll(self): self._index.internalPointer().clearAllRevisions() def saveState(self): return [ self.actShowDiff.isChecked(), self.actShowVersion.isChecked(), self.actShowSpaces.isChecked(), self.actDiffOnly.isChecked(), ] def popupMenu(self, pos): i = self.list.itemAt(pos) m = QMenu(self) if i: m.addAction(self.tr("Restore")).triggered.connect(self.restore) m.addAction(self.tr("Delete")).triggered.connect(self.delete) m.addSeparator() if self.list.count(): m.addAction(self.tr("Clear all")).triggered.connect(self.clearAll) m.popup(self.list.mapToGlobal(pos)) def restoreState(self, state): self.actShowDiff.setChecked(state[0]) self.actShowVersion.setChecked(state[1]) self.actShowSpaces.setChecked(state[2]) self.actDiffOnly.setChecked(state[3]) self.actShowSpaces.setEnabled(self.actShowDiff.isChecked()) self.actDiffOnly.setEnabled(self.actShowDiff.isChecked())
class GUI(Ui_mainWindow): def __init__(self, window): super().__init__() self.setupUi(window) self.window = window self.settings = Settings() self.window.closeEvent = self.closeEvent self.server = Server() self.downman = DownloadManager() self.browser = Browser() # snapshot updater is to be started on exchange connect self.xchgClient = ExchangeClient() self.lastKnownDir = "/tmp" self.destPrefix = '' self.userlist = None self.di_list = [] self.addEventListeners() self.browserTable.setColumnHidden(0, True) self.userListTable.setColumnHidden(0, True) self.tabWidget.setCurrentIndex(0) self.urlFrame.setVisible(False) self.window.setWindowIcon(QIcon(":/images/favicon.ico")) self.window.setWindowTitle("21Lane") self.makeMenuBar() self.setupSystemTray() self.loadSettings() self.window.show() def makeMenuBar(self): self.menuBar = QMenuBar(self.window) self.fileMenu = QMenu("File") self.menuBar.addMenu(self.fileMenu) self.exitAction = QAction("Exit", self.window) self.exitAction.triggered.connect(self.closeEvent) self.minimizeToTrayAction = QAction("Minimize to Tray", self.window) self.minimizeToTrayAction.setCheckable(True) self.minimizeToTrayAction.setChecked(True) self.fileMenu.addAction(self.minimizeToTrayAction) self.fileMenu.addAction(self.exitAction) self.window.layout().setMenuBar(self.menuBar) def loadSettings(self): success = self.settings.load() self.publicNameInput.setText(self.settings.configDic["publicName"]) self.port.setValue(self.settings.configDic["port"]) self.sharedLocationInput.setText(self.settings.configDic["sharedDir"]) self.downloadLocationInput.setText( self.settings.configDic["downloadDir"]) self.speedLimitSlider.setValue(self.settings.configDic["speedLimit"]) self.speedLimitSpin.setValue(self.settings.configDic["speedLimit"]) self.exchangeURLInput.setText(self.settings.configDic["exchangeURL"]) if success: self.toggleShare() self.tabWidget.setCurrentIndex(1) self.reloadUsersBtn.click() def keyPressedEvent(self, event): if event.key() == Qt.Key_Escape: event.ignore() def closeEvent(self, event): if (self.window.sender() == None) and (self.minimizeToTrayAction.isChecked()): self.showWindow(False) self.activateAction.setChecked(False) event.ignore() return if self.server.isRunning(): self.server.stopServer() if self.xchgClient.isRunning(): print('attempting to shut down exchange client') self.xchgClient.quit() print('asked to end xchgclient politely') if not self.xchgClient.wait(1): print('forced closure of xchgclient required') self.xchgClient.terminate() print('xchgclient forcefully closed') if self.downman.running: self.downman.stopDownloader() qApp.exit() def showMessage(self, maintext=None, subtext=None): QMessageBox.information(self.window, maintext, subtext, QMessageBox.Ok, QMessageBox.Ok) def getPathFromDialog(self): return QFileDialog.getExistingDirectory(self.window, "Select folder", self.lastKnownDir, QFileDialog.ShowDirsOnly) def addEventListeners(self): self.speedLimitSlider.valueChanged[int].connect(self.updateSpeedLimit) self.speedLimitSpin.valueChanged[int].connect(self.updateSpeedLimit) self.sharedLocationBtn.clicked.connect(self.showDirectorySelector) self.downloadLocationBtn.clicked.connect(self.showDirectorySelector) self.toggleShareBtn.setText("Start Sharing") self.toggleShareBtn.clicked.connect(self.toggleShare) self.server.ftp_handler.stats.clientConnect.connect( self.statClientConnected) self.server.ftp_handler.stats.clientDisconnect.connect( self.statClientDisconnected) self.server.ftp_handler.stats.fileTransfer[int].connect( self.statFileTransferred) self.reloadUsersBtn.clicked.connect(self.loadUsers) self.browserInput.returnPressed.connect(self.browserGoBtn.click) self.browserGoBtn.clicked.connect(self.loadBrowserTable) self.browserHomeBtn.clicked.connect(self.loadBrowserTable) self.browserPrevBtn.clicked.connect(self.handleBackBtnClick) self.userListTable.doubleClicked.connect(self.showBrowser) self.browserTable.doubleClicked.connect(self.handleFileSelection) self.developerLink.linkActivated.connect(xdg_open) self.projectLink.linkActivated.connect(xdg_open) def showDirectorySelector(self, event): if self.window.sender() is self.downloadLocationBtn: self.downloadLocationInput.setText(self.getPathFromDialog()) self.destPrefix = self.downloadLocationInput.text() elif self.window.sender() is self.sharedLocationBtn: self.sharedLocationInput.setText(self.getPathFromDialog()) def updateSpeedLimit(self, value): self.speedLimitSlider.setValue(value) self.speedLimitSpin.setValue(value) def statClientConnected(self): self.server.connected += 1 self.stats_connected.setText(str(self.server.connected)) def statClientDisconnected(self): self.server.connected -= 1 self.stats_connected.setText(str(self.server.connected)) def statFileTransferred(self, filesize): self.server.bytesTransferred += filesize self.server.filesTransferred += 1 self.stats_files.setText(str(self.server.filesTransferred)) self.stats_bytes.setText(toHumanReadable(self.server.bytesTransferred)) def toggleShare(self): try: if (not self.publicNameInput.text()) or \ (not self.port.value()) or \ (not self.sharedLocationInput.text()): raise FormIncompleteError if self.xchgClient.isRunning(): self.xchgClient.quit() if not self.xchgClient.wait(1): self.xchgClient.terminate() if self.server.isRunning(): self.server.stopServer() self.toggleShareBtn.setText("Start Sharing") self.toggleShareBtn.setIcon(QIcon(":/images/failed.svg")) self.urlFrame.setVisible(False) else: self.server.setPort(self.port.value()) self.server.setSharedDirectory(self.sharedLocationInput.text()) self.server.start() if not self.exchangeURLInput.text(): self.xchgClient.updateInfo(self.publicNameInput.text(), None, self.port.value()) else: self.xchgClient.updateInfo(self.publicNameInput.text(), self.exchangeURLInput.text(), self.port.value()) self.xchgClient.updateDir(self.sharedLocationInput.text()) self.settings.update(self.publicNameInput.text(), self.port.value(), \ self.sharedLocationInput.text(), self.downloadLocationInput.text(), self.speedLimitSlider.value(), self.exchangeURLInput.text()) self.toggleShareBtn.setText("Stop Sharing") self.toggleShareBtn.setIcon(QIcon(":/images/complete.svg")) addresses = getAllAddresses() print(addresses) if len(addresses) != 0: lblstr = "<html><body>" current = 0 end = len(addresses) - 1 for addr in addresses: hyperlink = 'ftp://' + addr + ':' + str( self.server.port) lblstr += "<a href=\'" + hyperlink + "\'>" + hyperlink + "</a>" if current != (end - 1): lblstr += '<br>' current += 1 lblstr += "</body></html>" self.urlLabel.setText(lblstr) self.urlFrame.setVisible(True) print(self.urlFrame.isVisible(), 'url frame is visivle') except FileNotFoundError: self.showMessage("Don't fool me", "Shared location doesn't exist") except PortUnavailableError: self.showMessage("Port unavailable", "Please select some other port number") except FormIncompleteError: self.showMessage("Form incomplete", "Please fill in proper values!") def loadUsers(self): userlist = self.xchgClient.getUserList() self.userlist = userlist if not userlist: self.showMessage("Sorry", "Cannot retrieve list of users") return table = self.userListTable table.clearContents() table.setRowCount(len(userlist)) for i, entry in enumerate(userlist): table.setItem(i, 0, QTableWidgetItem(str(i))) table.setItem( i, 1, QTableWidgetItem(toHumanReadable(int(entry["sharedSize"])))) table.setItem(i, 2, QTableWidgetItem(entry["publicName"])) def showBrowser(self): if not self.userlist: return current = self.userListTable.selectedItems()[0] index = int(self.userListTable.item(current.row(), 0).text()) user = self.userlist[index] self.browser.update(user["ip"], int(user["port"])) self.tabWidget.setCurrentIndex(2) self.browserInput.setText("/") self.browserGoBtn.click() def loadBrowserTable(self): pwd = self.browserInput.text() filelist = [] try: if not self.browser.pathExists(self.browser.host, self.browser.port, pwd): self.showMessage("Error", "The path does not exist!") return self.browser.historyStack.append(pwd) filelist = self.browser.getFileList(self.browser.host, self.browser.port, pwd) self.browser.filelist = filelist except ConnectionRefusedError: self.showMessage( "Offline", "The remote machine cannot be contacted!\nBetter luck next time." ) self.tabWidget.setCurrentIndex(1) table = self.browserTable table.clearContents() table.setRowCount(len(filelist)) for i, file in enumerate(filelist): table.setItem(i, 0, QTableWidgetItem(str(i))) table.setItem(i, 1, QTableWidgetItem(QIcon(":images/download.png"), "")) table.setItem(i, 2, QTableWidgetItem(toHumanReadable(file["filesize"]))) if file["isDir"]: table.setItem( i, 3, QTableWidgetItem(QIcon(":/images/folder.png"), "")) else: table.setItem( i, 3, QTableWidgetItem(guess_mime(file["filename"])[0])) table.setItem(i, 4, QTableWidgetItem(file["filename"])) def handleBackBtnClick(self): if not self.userlist: self.showMessage( "Confused", "What should I load? \nFind someone from list of connected users." ) return if len(self.browser.historyStack) < 2: self.showMessage("Sorry", "Hey, there's no looking back!") return self.browser.historyStack.pop() prev = self.browser.historyStack.pop() self.browserInput.setText(prev) self.loadBrowserTable() def handleFileSelection(self): if not self.browser.filelist: return current = self.browserTable.selectedItems()[0] index = int(self.browserTable.item(current.row(), 0).text()) file = self.browser.filelist[index] print("index", index, file["pathname"], current.text()) if file["isDir"] and current.column() is not 1: pwd = join_path(self.browserInput.text(), file["pathname"]) self.browserInput.setText(pwd) self.browserGoBtn.click() return # a download is to be handled print("downloading directory", file["filename"]) # decide it is a file or directory if not self.destPrefix: destDir = join_path(self.getPathFromDialog()) else: destDir = self.destPrefix meta = None if file["isDir"]: meta = self.browser.getRecursiveFileList(self.browser.host, self.browser.port, file["pathname"]) filelist = self.browser.recfilelist else: meta = {"totalFiles": 1, "totalSize": file["filesize"]} filelist = [file] signal = DownloadItemUpdater() diui = self.createDownloadItemBox(file["filename"], meta["totalSize"]) dilist = [] for item in filelist: di = DownloadItem(item["filename"], self.browser.host, self.browser.port, item["pathname"], join_path(destDir, item["filename"]), item["filesize"], signal) di.updateGuiComponents(diui) dilist.append(di) # create callbacks for gui events def cancelCallback(): for di in dilist: di.cancel() diui["cancelBtn"].setIcon(QIcon(":/images/reload.png")) diui["cancelBtn"].clicked.disconnect() diui["cancelBtn"].clicked.connect(retryCallback) def updateProgressCallback(progress): sum = 0 for di in dilist: sum += di.completed diui["progress"].setValue(sum) diui["completion"].setText(toHumanReadable(sum)) def retryCallback(): print("retrying") di.completed = 0 self.downman.addItem(di) diui["cancelBtn"].clicked.disconnect() diui["cancelBtn"].clicked.connect(cancelCallback) diui["cancelBtn"].setIcon(QIcon(":/images/cancel.png")) def errorCallback(): diui["completion"].setText("Failed") cancelCallback() def completeCallback(): diui["completion"].setText("Completed") diui["cancelBtn"].clicked.disconnect() diui["cancelBtn"].setToolTip("Open") diui["cancelBtn"].setIcon(QIcon(":/images/open.png")) diui["cancelBtn"].clicked.connect(openFile) def openFile(): xdg_open(join_path(destDir, file["filename"])) def openDir(): xdg_open(destDir) diui["cancelBtn"].clicked.connect(cancelCallback) signal.progress[int].connect(updateProgressCallback) signal.error.connect(errorCallback) signal.complete.connect(completeCallback) diui["openDestBtn"].clicked.connect(openDir) self.downloadsLayout.insertWidget(0, diui["widget"]) for entry in dilist: self.downman.addItem(entry) # print ("downloading", di.filename, di.filesize, "to") def createDownloadItemBox(self, filename, filesize): diui = {} frame = QFrame(self.window) frame.setFrameStyle(QFrame.StyledPanel) layout = QHBoxLayout() filesize = filesize if not filesize is 0 else 1 diui["layout"] = layout diui["widget"] = frame diui["filename"] = QLabel(filename) diui["filename"].setToolTip(filename) diui["filename"].setMaximumWidth(30) diui["filesize"] = QLabel(toHumanReadable(filesize)) diui["filesize"].setAlignment(Qt.AlignCenter) diui["progress"] = QProgressBar() diui["progress"].setRange(0, filesize) diui["completion"] = QLabel("Waiting...") diui["completion"].setAlignment(Qt.AlignCenter) diui["cancelBtn"] = QPushButton(QIcon(":/images/cancel.png"), '') diui["openDestBtn"] = QPushButton(QIcon(":/images/folder.png"), '') diui["cancelBtn"].setToolTip("Cancel download") diui["openDestBtn"].setToolTip("Open folder") diui["layout"].addWidget(diui["filename"]) diui["layout"].addWidget(diui["filesize"]) diui["layout"].addWidget(diui["progress"]) diui["layout"].addWidget(diui["completion"]) diui["layout"].addWidget(diui["openDestBtn"]) diui["layout"].addWidget(diui["cancelBtn"]) diui["layout"].setSpacing(0) layout.setContentsMargins(5, 2, 5, 2) layout.setSpacing(6) layout.setStretch(0, 3) layout.setStretch(1, 2) layout.setStretch(2, 5) layout.setStretch(3, 2) layout.setStretch(4, 1) layout.setStretch(5, 1) frame.setLayout(layout) return diui def showWindow(self, checked): self.window.setVisible(checked) def setupSystemTray(self): self.activateAction = QAction("Show", self.window) self.activateAction.setCheckable(True) self.activateAction.setChecked(True) self.activateAction.triggered.connect(self.showWindow) self.quitAction = QAction("Quit", self.window) self.quitAction.triggered.connect(self.closeEvent) self.trayIconMenu = QMenu(self.window) self.trayIconMenu.addAction(self.activateAction) self.trayIconMenu.addAction(self.quitAction) self.trayIcon = QSystemTrayIcon(QIcon(":/images/icon.ico"), self.window) self.trayIcon.setContextMenu(self.trayIconMenu) self.trayIcon.show()
class PackTreeView(TreeView): EMPTY_VIEW_MSG = "Drop AAS files here" EMPTY_VIEW_ICON = OPEN_DRAG_ICON def __init__(self, parent=None): super(PackTreeView, self).__init__(parent, emptyViewMsg=self.EMPTY_VIEW_MSG, emptyViewIcon=self.EMPTY_VIEW_ICON) PackTreeView.__instance = self self.recentFilesSeparator = None self.setAcceptDrops(True) self.setExpandsOnDoubleClick(False) # noinspection PyArgumentList def initActions(self): super(PackTreeView, self).initActions() self.addAct.setText("Add package") self.addAct.setEnabled(True) self.newPackAct = QAction(NEW_PACK_ICON, "&New AAS file", self, statusTip="Create new AAS file", triggered=self.newPackWithDialog, enabled=True) self.defNewFileTypeFilter = "" self.defNewFileTypeActs = [] for typ in FILE_TYPE_FILTERS.keys(): act = QAction( typ, self, checkable=True, statusTip=f"Choose {typ} as standard initialisation file type", triggered=self.toggleDefNewFileType) self.defNewFileTypeActs.append(act) self.openPackAct = QAction(OPEN_ICON, "&Open AAS file", self, shortcut=SC_OPEN, statusTip="Open AAS file", triggered=self.openPackWithDialog, enabled=True) # Recent files actions self.recentFileActs = [] for i in range(MAX_RECENT_FILES): recentFileAct = QAction("", self, statusTip=f"Open recent file", triggered=self.openRecentSlot, visible=False) self.recentFileActs.append(recentFileAct) self.saveAct = QAction(SAVE_ICON, "Save", self, statusTip="Save current file", triggered=lambda: self.savePack(), enabled=False) self.saveAsAct = QAction("Save As...", self, statusTip="Save current file as..", triggered=lambda: self.savePackAsWithDialog(), enabled=False) self.saveAllAct = QAction(SAVE_ALL_ICON, "&Save All", self, shortcut=SC_SAVE_ALL, statusTip="Save all files", triggered=self.saveAll, enabled=True) self.closeAct = QAction("Close AAS file", self, statusTip="Close current file", triggered=self.closeFileWithDialog, enabled=False) self.closeAllAct = QAction("Close all", self, statusTip="Close all files", triggered=self.closeAllFilesWithDialog, enabled=False) self.autoScrollToSrcAct = QAction( "Autoscroll to source", self, # icon=AUTOSCROLL_TO_SRC_ICON, toolTip="Autoscroll to source", statusTip="Autoscroll to source", checkable=True) self.autoScrollToSrcAct.toggle() self.autoScrollFromSrcAct = QAction( "Autoscroll from source", self, # icon=AUTOSCROLL_FROM_SRC_ICON, toolTip="Autoscroll from source", statusTip="Autoscroll from source", checkable=True) self.autoScrollFromSrcAct.toggle() self.shellViewAct = QAction(VIEW_ICON, "Shell view", self, toolTip="Shell view", statusTip="Change to shell view", triggered=self.onShellViewPushed, checkable=True) def toggleDefNewFileType(self): #FIXME refactor action = self.sender() if action: typ = action.text() self.defNewFileTypeFilter = FILE_TYPE_FILTERS[typ] def onShellViewPushed(self): checked = self.shellViewAct.isChecked() packs = self.sourceModel().match(QModelIndex(), TYPE_ROLE, Package) if checked: for pack in packs: CLASSES_INFO[AssetAdministrationShell][PACKVIEW_ATTRS_INFO] = { "asset": {}, "submodel": {} } self.sourceModel().update(pack) rowsToHide = [] for attr in ("submodels", "assets", "concept_descriptions", "others"): rowsToHide.extend(self.model().match(QModelIndex(), Qt.DisplayRole, attr)) for row in rowsToHide: self.setRowHidden(row.row(), row.parent(), True) else: CLASSES_INFO[AssetAdministrationShell][PACKVIEW_ATTRS_INFO] = {} for pack in packs: self.sourceModel().update(pack) # noinspection PyUnresolvedReferences def initMenu(self): super(PackTreeView, self).initMenu() self.attrsMenu.addSeparator() self.attrsMenu.addAction(self.openPackAct) self.attrsMenu.addAction(self.saveAct) self.attrsMenu.addAction(self.saveAsAct) self.attrsMenu.addAction(self.saveAllAct) self.attrsMenu.addAction(self.closeAct) self.attrsMenu.addAction(self.closeAllAct) self.openInCurrTabAct.triggered.connect( lambda: self.openInCurrTabClicked.emit(self.currentIndex())) self.openInNewTabAct.triggered.connect( lambda: self.openInNewTabClicked.emit(self.currentIndex())) self.openInBackgroundAct.triggered.connect( lambda: self.openInBgTabClicked.emit(self.currentIndex())) self.openInNewWindowAct.triggered.connect( lambda: self.openInNewWindowClicked.emit(self.currentIndex())) def updateActions(self, index: QModelIndex): super(PackTreeView, self).updateActions(index) if index.isValid(): self.openInCurrTabAct.setEnabled(True) self.openInNewTabAct.setEnabled(True) self.openInBackgroundAct.setEnabled(True) self.openInNewWindowAct.setEnabled(True) # update save and close actions self.saveAct.setEnabled(self.isSaveOk()) self.saveAct.setText(f"Save {index.data(PACKAGE_ROLE)}") self.saveAct.setToolTip(f"Save {index.data(PACKAGE_ROLE)}") self.saveAsAct.setEnabled(self.isSaveOk()) self.saveAllAct.setEnabled(self.isSaveAllOk()) self.closeAct.setEnabled(self.isCloseOk()) self.closeAllAct.setEnabled(self.isCloseAllOk()) # update add action if not index.isValid(): self.addAct.setEnabled(True) self.addAct.setText("Add package") def onDelClear(self): index = self.currentIndex() if isinstance(index.data(OBJECT_ROLE), Package): self.closeAct.trigger() else: super(PackTreeView, self).onDelClear() def isPasteOk(self, index: QModelIndex) -> bool: if not self.treeObjClipboard or not index.isValid(): return False if super(PackTreeView, self).isPasteOk(index): return True obj2paste = self.treeObjClipboard[0] currObj = index.data(OBJECT_ROLE) if ClassesInfo.addType(type(currObj)) and isinstance( obj2paste, ClassesInfo.addType(type(currObj))): return True return False def isSaveOk(self) -> bool: pack = self.currentIndex().data(PACKAGE_ROLE) return True if pack else False def isCloseOk(self) -> bool: pack = self.currentIndex().data(PACKAGE_ROLE) return True if pack else False def isSaveAllOk(self) -> bool: return True if self.model().data(QModelIndex(), OPENED_PACKS_ROLE) else False def isCloseAllOk(self) -> bool: return True if self.model().data(QModelIndex(), OPENED_PACKS_ROLE) else False def onAddAct(self, objVal=None, parent: QModelIndex = None): parent = parent if parent else self.currentIndex() name = parent.data(NAME_ROLE) parentObj = parent.data(OBJECT_ROLE) if objVal: kwargs = {"parent": parent, "rmDefParams": False, "objVal": objVal} else: kwargs = {"parent": parent, "rmDefParams": True} try: if not parent.isValid(): self.newPackWithDialog() elif name in Package.addableAttrs(): self.addItemWithDialog(objType=ClassesInfo.addType( Package, name), **kwargs) elif ClassesInfo.addType(type(parentObj)): self.addItemWithDialog(objType=ClassesInfo.addType( type(parentObj)), **kwargs) else: raise TypeError("Parent type is not extendable:", type(parent.data(OBJECT_ROLE))) except Exception as e: print(e) QMessageBox.critical(self, "Error", str(e)) def addItemWithDialog(self, parent: QModelIndex, objType, objVal=None, title="", rmDefParams=False): if objType is Package: self.newPackWithDialog() return elif objType is StoredFile: self.addFileWithDialog(parent) return super(PackTreeView, self).addItemWithDialog(parent, objType, objVal, title, rmDefParams) def newPackWithDialog(self): saved = False file = 'new_aas_file.aasx' while not saved: file = QFileDialog.getSaveFileName( self, 'Create new AAS File', file, filter=FILTER_AAS_FILES, initialFilter=self.defNewFileTypeFilter, options=FILE_DIALOG_OPTIONS)[0] if file: pack = Package() saved = self.savePack(pack, file) if saved: self.model().setData(QModelIndex(), pack, ADD_ITEM_ROLE) else: # cancel pressed return def openPackWithDialog(self): opened = False file = "" while not opened: file = QFileDialog.getOpenFileName(self, "Open AAS file", file, filter=FILTER_AAS_FILES, options=FILE_DIALOG_OPTIONS)[0] if file: opened = self.openPack(file) else: # cancel pressed return def addFileWithDialog(self, parent: QModelIndex): opened = False file = "" while not opened: file = QFileDialog.getOpenFileName(self, "Add file", file, options=FILE_DIALOG_OPTIONS)[0] if file: storedFile = StoredFile(filePath=file) opened = self.model().setData(parent, storedFile, ADD_ITEM_ROLE) else: # cancel pressed return def openPack(self, file: str) -> bool: try: pack = Package(file) absFile = pack.file.absolute().as_posix() self.updateRecentFiles(absFile) except Exception as e: self.removeFromRecentFiles(file) QMessageBox.critical(self, "Error", f"Package {file} couldn't be opened: {e}") else: openedPacks = self.model().data(QModelIndex(), OPENED_FILES_ROLE) if Path(file).absolute() in openedPacks: QMessageBox.critical(self, "Error", f"Package {file} is already opened") else: self.model().setData(QModelIndex(), pack, ADD_ITEM_ROLE) return True return False def savePack(self, pack: Package = None, file: str = None) -> bool: pack = self.currentIndex().data(PACKAGE_ROLE) if pack is None else pack try: pack.write(file) self.updateRecentFiles(pack.file.absolute().as_posix()) return True except (TypeError, ValueError) as e: QMessageBox.critical(self, "Error", f"Package couldn't be saved: {file}: {e}") except AttributeError as e: QMessageBox.critical(self, "Error", f"No chosen package to save: {e}") return False def savePackAsWithDialog(self, pack: Package = None) -> bool: pack = self.currentIndex().data(PACKAGE_ROLE) if pack is None else pack saved = False file = pack.file.as_posix() while not saved: try: file = QFileDialog.getSaveFileName( self, 'Save AAS File', file, filter=FILTER_AAS_FILES, options=FILE_DIALOG_OPTIONS)[0] except AttributeError as e: QMessageBox.critical(self, "Error", f"No chosen package to save: {e}") else: if file: saved = self.savePack(pack, file) else: # cancel pressed return def saveAll(self): for pack in self.model().data(QModelIndex(), OPENED_PACKS_ROLE): self.savePack(pack) def closeFileWithDialog(self): pack = self.currentIndex().data(PACKAGE_ROLE) try: packItem, = self.model().match(QModelIndex(), OBJECT_ROLE, pack, hits=1) except ValueError: QMessageBox.critical(self, "Not found error", f"The file to close is not found: {pack}") if packItem.isValid(): try: dialog = QMessageBox( QMessageBox.NoIcon, f"Close {pack}", f"Do you want to save your changes in {pack} before closing?", standardButtons=QMessageBox.Save | QMessageBox.Cancel | QMessageBox.Discard) dialog.setDefaultButton = QMessageBox.Save dialog.button(QMessageBox.Save).setText("&Save&Close") res = dialog.exec() if res == QMessageBox.Save: self.savePack() self.closeFile(packItem) elif res == QMessageBox.Discard: self.closeFile(packItem) except AttributeError as e: QMessageBox.critical(self, "Error", f"No chosen package to close: {e}") def closeAllFilesWithDialog(self): dialog = QMessageBox( QMessageBox.NoIcon, f"Close all AAS files", f"Do you want to save your changes before closing? ", standardButtons=QMessageBox.Save | QMessageBox.Cancel | QMessageBox.Discard) dialog.setDefaultButton = QMessageBox.Save dialog.button(QMessageBox.Save).setText("&Save and Close All") res = dialog.exec() if res == QMessageBox.Save: for pack in self.model().data(QModelIndex(), OPENED_PACKS_ROLE): self.savePack(pack) packItem, = self.model().match(QModelIndex(), OBJECT_ROLE, pack, hits=1) self.closeFile(packItem) elif res == QMessageBox.Discard: for pack in self.model().data(QModelIndex(), OPENED_PACKS_ROLE): packItem, = self.model().match(QModelIndex(), OBJECT_ROLE, pack, hits=1) self.closeFile(packItem) def closeFile(self, packItem: QModelIndex): self.model().setData(packItem, NOT_GIVEN, CLEAR_ROW_ROLE) def openRecentSlot(self): action = self.sender() if action: self.openPack(action.data()) def updateRecentFiles(self, file: str): self.removeFromRecentFiles(file) settings = QSettings(ACPLT, APPLICATION_NAME) files = settings.value('recentFiles', []) files.insert(0, file) del files[MAX_RECENT_FILES:] settings.setValue('recentFiles', files) def removeFromRecentFiles(self, file: str): settings = QSettings(ACPLT, APPLICATION_NAME) files = settings.value('recentFiles', []) try: files.remove(file) except ValueError: pass except AttributeError: files = [] settings.setValue('recentFiles', files) def updateRecentFileActs(self): settings = QSettings(ACPLT, APPLICATION_NAME) files = settings.value('recentFiles', []) try: files = files[:MAX_RECENT_FILES] except TypeError: files = [] for i, file in enumerate(files): if len(file) < 30: self.recentFileActs[i].setText(file) else: self.recentFileActs[i].setText(f"..{file[len(file)-30:]}") self.recentFileActs[i].setData(file) self.recentFileActs[i].setVisible(True) for i in range(len(files), MAX_RECENT_FILES): self.recentFileActs[i].setVisible(False) self.recentFilesSeparator.setVisible(bool(files)) def dragEnterEvent(self, event: QDragEnterEvent): if event.mimeData().hasUrls: event.accept() def dragMoveEvent(self, event): if event.mimeData().hasUrls: event.accept() def dropEvent(self, e: QDropEvent) -> None: for url in e.mimeData().urls(): file = str(url.toLocalFile()) self.openPack(file)
class WebBrowser(QMainWindow): adjTitle = pyqtSignal(str) setProg = pyqtSignal(int) finishLoad = pyqtSignal(str) def __init__(self, url, parent=None): super(WebBrowser, self).__init__(parent) self.progress = 0 fd = QFile(":/jquery.min.js") if fd.open(QIODevice.ReadOnly | QFile.Text): self.jQuery = QTextStream(fd).readAll() fd.close() else: self.jQuery = '' QNetworkProxyFactory.setUseSystemConfiguration(True) self.view = QWebEngineView(self) self.view.load(QUrl(url)) self.view.loadFinished.connect(self.adjustLocation) self.view.titleChanged.connect(self.adjustTitle) self.view.loadProgress.connect(self.setProgress) self.view.loadFinished.connect(self.finishLoading) self.locationEdit = QLineEdit(self) self.locationEdit.setSizePolicy( QSizePolicy.Expanding, self.locationEdit.sizePolicy().verticalPolicy()) self.locationEdit.returnPressed.connect(self.changeLocation) toolBar = self.addToolBar("Navigation") toolBar.addAction(self.view.pageAction(QWebEnginePage.Back)) toolBar.addAction(self.view.pageAction(QWebEnginePage.Forward)) toolBar.addAction(self.view.pageAction(QWebEnginePage.Reload)) toolBar.addAction(self.view.pageAction(QWebEnginePage.Stop)) toolBar.addWidget(self.locationEdit) viewMenu = self.menuBar().addMenu("&View") viewSourceAction = QAction("Page Source", self) viewSourceAction.triggered.connect(self.viewSource) viewMenu.addAction(viewSourceAction) effectMenu = self.menuBar().addMenu("&Effect") effectMenu.addAction("Highlight all links", self.highlightAllLinks) self.rotateAction = QAction(self.style().standardIcon( QStyle.SP_FileDialogDetailedView), "Turn images upside down", self, checkable=True, toggled=self.rotateImages) effectMenu.addAction(self.rotateAction) toolsMenu = self.menuBar().addMenu("&Tools") toolsMenu.addAction("Remove GIF images", self.removeGifImages) toolsMenu.addAction("Remove all inline frames", self.removeInlineFrames) toolsMenu.addAction("Remove all object elements", self.removeObjectElements) toolsMenu.addAction("Remove all embedded elements", self.removeEmbeddedElements) self.setCentralWidget(self.view) def viewSource(self): accessManager = self.view.page().networkAccessManager() request = QNetworkRequest(self.view.url()) reply = accessManager.get(request) reply.finished.connect(self.slotSourceDownloaded) def slotSourceDownloaded(self): reply = self.sender() self.textEdit = QTextEdit() self.textEdit.setAttribute(Qt.WA_DeleteOnClose) self.textEdit.show() self.textEdit.setPlainText(QTextStream(reply).readAll()) self.textEdit.resize(600, 400) reply.deleteLater() def adjustLocation(self): self.locationEdit.setText(self.view.url().toString()) def changeLocation(self): url = QUrl.fromUserInput(self.locationEdit.text()) self.view.load(url) self.view.setFocus() def adjustTitle(self): if 0 < self.progress < 100: self.setWindowTitle("{0} ({1}%%)".format(self.view.title(), self.progress)) else: self.setWindowTitle(self.view.title()) def setProgress(self, p): self.progress = p self.setProg.emit(self.progress) self.adjustTitle() def finishLoading(self): self.progress = 100 self.adjustTitle() # self.view.page().mainFrame().evaluateJavaScript(self.jQuery) self.rotateImages(self.rotateAction.isChecked()) def highlightAllLinks(self): code = """$('a').each( function () { $(this).css('background-color', 'yellow') } )""" self.view.page().javaScriptPrompt(code) def rotateImages(self, invert): if invert: code = """ $('img').each( function () { $(this).css('-webkit-transition', '-webkit-transform 2s'); $(this).css('-webkit-transform', 'rotate(180deg)') } )""" else: code = """ $('img').each( function () { $(this).css('-webkit-transition', '-webkit-transform 2s'); $(this).css('-webkit-transform', 'rotate(0deg)') } )""" # self.view.page().mainFrame().evaluateJavaScript(code) def removeGifImages(self): code = "$('[src*=gif]').remove()" self.view.page().runJavaScript(code) def removeInlineFrames(self): code = "$('iframe').remove()" self.view.page().runJavaScript(code) def removeObjectElements(self): code = "$('object').remove()" self.view.page().runJavaScript(code) def removeEmbeddedElements(self): code = "$('embed').remove()" self.view.page().runJavaScript(code)
class SimpleRichText(QTextEdit): # pylint: disable=method-hidden def __init__(self, focusin, focusout): QTextEdit.__init__(self) self.focusin = focusin self.focusout = focusout self.createActions() #self.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu) def focusOutEvent ( self, event ): self.focusout() def focusInEvent ( self, event ): self.focusin() def closeEvent(self, event): event.accept() def createActions(self): self.boldAct = QAction(self.tr("&Bold"), self) self.boldAct.setCheckable(True) self.boldAct.setShortcut(self.tr("Ctrl+B")) self.boldAct.setStatusTip(self.tr("Make the text bold")) self.boldAct.triggered.connect(self.setBold) ### self.connect(self.boldAct, SIGNAL("triggered()"), self.setBold) self.addAction(self.boldAct) boldFont = self.boldAct.font() boldFont.setBold(True) self.boldAct.setFont(boldFont) self.italicAct = QAction(self.tr("&Italic"), self) self.italicAct.setCheckable(True) self.italicAct.setShortcut(self.tr("Ctrl+I")) self.italicAct.setStatusTip(self.tr("Make the text italic")) self.italicAct.triggered.connect(self.setItalic) ### self.connect(self.italicAct, SIGNAL("triggered()"), self.setItalic) self.addAction(self.italicAct) def setBold(self): format = QTextCharFormat() if self.boldAct.isChecked(): weight = QFont.Bold else: weight = QFont.Normal format.setFontWeight(weight) self.setFormat(format) def setItalic(self): format = QTextCharFormat() #format.setFontItalic(self.__italic.isChecked()) format.setFontItalic(self.italicAct.isChecked()) self.setFormat(format) def setUnderline(self): format = QTextCharFormat() format.setFontUnderline(self.__underline.isChecked()) self.setFormat(format) def setFormat(self, format): self.textCursor().mergeCharFormat(format) self.mergeCurrentCharFormat(format) def bold(self): print("bold") def italic(self): print("italic")
class QImageViewer(QMainWindow): def __init__(self): super().__init__() # gets all the images from given url self.url = "/home/emsee/Documents/Manga/Terror Man/chapter_1" image_list = sorted_nicely( [os.path.join(self.url, file) for file in os.listdir(self.url)]) self.files_it = iter(image_list) pprint.pprint(image_list) self.printer = QPrinter() self.scaleFactor = 0.0 self.imageLabel = [QLabel() for i in image_list] pprint.pprint(self.imageLabel) if len(image_list) == len(self.imageLabel): print("THESE TWO ARE EQUAL") map(lambda obj: obj.setBackgroundRole(QPalette.Base), self.imageLabel) map( lambda obj: obj.setSizePolicy(QSizePolicy.Ignored, QSizePolicy. Ignored), self.imageLabel) map(lambda obj: obj.setScaledContents(True), self.imageLabel) # self.imageLabel.setBackgroundRole(QPalette.Base) # self.imageLabel.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored) # self.imageLabel.setScaledContents(True) self.scrollArea = QScrollArea() self.scrollArea.setBackgroundRole(QPalette.Dark) # for i in self.imageLabel: # self.scrollArea.setWidget(i) self.scrollArea.setVisible(False) self.setCentralWidget(self.scrollArea) #test2 shit content_widget = QWidget() self.scrollArea.setWidget(content_widget) self._lay = QVBoxLayout(content_widget) # print(type(self.url)) # print(type(self.url[0])) self.count = 0 for url in image_list: print("I am here: " + str(self.count)) self.open(url) self.createActions() self.createMenus() self.setWindowTitle("Image Viewer") self.resize(800, 600) def open(self, url): print("IN THE OPEN FUNCTION") # options = QFileDialog.Options() # fileName = QFileDialog.getOpenFileName(self, "Open File", QDir.currentPath()) # fileName, _ = QFileDialog.getOpenFileName(self, 'QFileDialog.getOpenFileName()', '', # 'Images (*.png *.jpeg *.jpg *.bmp *.gif)', options=options) fileName = url if fileName: image = QImage(fileName) if image.isNull(): QMessageBox.information(self, "Image Viewer", "Cannot load %s." % fileName) return self.imageLabel[self.count].setPixmap(QPixmap.fromImage(image)) # map(lambda obj: obj.setPixmap(QPixmap.fromImage(image)), self.imageLabel) # map(lambda obj: self._lay.addWidget(obj), self.imageLabel) self._lay.addWidget(self.imageLabel[self.count]) self.count += 1 self.scaleFactor = 1.0 self.scrollArea.setVisible(True) self.printAct.setEnabled(True) self.fitToWindowAct.setEnabled(True) self.updateActions() if not self.fitToWindowAct.isChecked(): self.imageLabel[self.count].adjustSize() # map(lambda obj: obj.imageLabel.adjustSize(), self.imageLabel) def print_(self): dialog = QPrintDialog(self.printer, self) if dialog.exec_(): painter = QPainter(self.printer) rect = painter.viewport() size = self.imageLabel.pixmap().size() size.scale(rect.size(), Qt.KeepAspectRatio) painter.setViewport(rect.x(), rect.y(), size.width(), size.height()) painter.setWindow(self.imageLabel.pixmap().rect()) painter.drawPixmap(0, 0, self.imageLabel.pixmap()) def zoomIn(self): self.scaleImage(1.25) def zoomOut(self): self.scaleImage(0.8) def normalSize(self): # self.imageLabel.adjustSize() map(lambda obj: obj.imageLabel.adjustSize(), self.imageLabel) self.scaleFactor = 1.0 def fitToWindow(self): fitToWindow = self.fitToWindowAct.isChecked() self.scrollArea.setWidgetResizable(fitToWindow) if not fitToWindow: self.normalSize() self.updateActions() def about(self): QMessageBox.about( self, "About Image Viewer", "<p>The <b>Image Viewer</b> example shows how to combine " "QLabel and QScrollArea to display an image. QLabel is " "typically used for displaying text, but it can also display " "an image. QScrollArea provides a scrolling view around " "another widget. If the child widget exceeds the size of the " "frame, QScrollArea automatically provides scroll bars.</p>" "<p>The example demonstrates how QLabel's ability to scale " "its contents (QLabel.scaledContents), and QScrollArea's " "ability to automatically resize its contents " "(QScrollArea.widgetResizable), can be used to implement " "zooming and scaling features.</p>" "<p>In addition the example shows how to use QPainter to " "print an image.</p>") def createActions(self): self.openAct = QAction("&Open...", self, shortcut="Ctrl+O", triggered=self.open) self.printAct = QAction("&Print...", self, shortcut="Ctrl+P", enabled=False, triggered=self.print_) self.exitAct = QAction("E&xit", self, shortcut="Ctrl+Q", triggered=self.close) self.zoomInAct = QAction("Zoom &In (25%)", self, shortcut="Ctrl++", enabled=False, triggered=self.zoomIn) self.zoomOutAct = QAction("Zoom &Out (25%)", self, shortcut="Ctrl+-", enabled=False, triggered=self.zoomOut) self.normalSizeAct = QAction("&Normal Size", self, shortcut="Ctrl+S", enabled=False, triggered=self.normalSize) self.fitToWindowAct = QAction("&Fit to Window", self, enabled=False, checkable=True, shortcut="Ctrl+F", triggered=self.fitToWindow) self.aboutAct = QAction("&About", self, triggered=self.about) self.aboutQtAct = QAction("About &Qt", self, triggered=qApp.aboutQt) def createMenus(self): self.fileMenu = QMenu("&File", self) self.fileMenu.addAction(self.openAct) # self.fileMenu.addAction(self.printAct) self.fileMenu.addSeparator() self.fileMenu.addAction(self.exitAct) self.viewMenu = QMenu("&View", self) self.viewMenu.addAction(self.zoomInAct) self.viewMenu.addAction(self.zoomOutAct) self.viewMenu.addAction(self.normalSizeAct) self.viewMenu.addSeparator() self.viewMenu.addAction(self.fitToWindowAct) self.helpMenu = QMenu("&Help", self) self.helpMenu.addAction(self.aboutAct) self.helpMenu.addAction(self.aboutQtAct) self.menuBar().addMenu(self.fileMenu) self.menuBar().addMenu(self.viewMenu) self.menuBar().addMenu(self.helpMenu) def updateActions(self): self.zoomInAct.setEnabled(not self.fitToWindowAct.isChecked()) self.zoomOutAct.setEnabled(not self.fitToWindowAct.isChecked()) self.normalSizeAct.setEnabled(not self.fitToWindowAct.isChecked()) def scaleImage(self, factor): self.scaleFactor *= factor x = [map(lambda obj: obj.imageLabel.pixmap().size(), self.imageLabel)] for i in x: map(lambda obj: obj.imageLabel.resize(self.scaleFactor * x[i]), self.imageLabel) # self.imageLabel.resize(self.scaleFactor * x) self.adjustScrollBar(self.scrollArea.horizontalScrollBar(), factor) self.adjustScrollBar(self.scrollArea.verticalScrollBar(), factor) self.zoomInAct.setEnabled(self.scaleFactor < 3.0) self.zoomOutAct.setEnabled(self.scaleFactor > 0.333) def adjustScrollBar(self, scrollBar, factor): scrollBar.setValue( int(factor * scrollBar.value() + ((factor - 1) * scrollBar.pageStep() / 2)))
def toggle_column(self, action: QtWidgets.QAction) -> None: column: AssEventsModelColumn = action.data() self.horizontalHeader().setSectionHidden( column.value, not action.isChecked() )
class TextEdit(QMainWindow): def __init__(self, fileName=None, parent=None): super(TextEdit, self).__init__(parent) self.setWindowIcon(QIcon(':/images/logo.png')) self.setToolButtonStyle(Qt.ToolButtonFollowStyle) self.setupFileActions() self.setupEditActions() self.setupTextActions() helpMenu = QMenu("Help", self) self.menuBar().addMenu(helpMenu) helpMenu.addAction("About", self.about) helpMenu.addAction("About &Qt", QApplication.instance().aboutQt) self.textEdit = QTextEdit(self) self.textEdit.currentCharFormatChanged.connect( self.currentCharFormatChanged) self.textEdit.cursorPositionChanged.connect(self.cursorPositionChanged) self.setCentralWidget(self.textEdit) self.textEdit.setFocus() self.setCurrentFileName() self.fontChanged(self.textEdit.font()) self.colorChanged(self.textEdit.textColor()) self.alignmentChanged(self.textEdit.alignment()) self.textEdit.document().modificationChanged.connect( self.actionSave.setEnabled) self.textEdit.document().modificationChanged.connect( self.setWindowModified) self.textEdit.document().undoAvailable.connect( self.actionUndo.setEnabled) self.textEdit.document().redoAvailable.connect( self.actionRedo.setEnabled) self.setWindowModified(self.textEdit.document().isModified()) self.actionSave.setEnabled(self.textEdit.document().isModified()) self.actionUndo.setEnabled(self.textEdit.document().isUndoAvailable()) self.actionRedo.setEnabled(self.textEdit.document().isRedoAvailable()) self.actionUndo.triggered.connect(self.textEdit.undo) self.actionRedo.triggered.connect(self.textEdit.redo) self.actionCut.setEnabled(False) self.actionCopy.setEnabled(False) self.actionCut.triggered.connect(self.textEdit.cut) self.actionCopy.triggered.connect(self.textEdit.copy) self.actionPaste.triggered.connect(self.textEdit.paste) self.textEdit.copyAvailable.connect(self.actionCut.setEnabled) self.textEdit.copyAvailable.connect(self.actionCopy.setEnabled) QApplication.clipboard().dataChanged.connect(self.clipboardDataChanged) if fileName is None: fileName = ':/example.html' if not self.load(fileName): self.fileNew() def closeEvent(self, e): if self.maybeSave(): e.accept() else: e.ignore() def setupFileActions(self): tb = QToolBar(self) tb.setWindowTitle("File Actions") self.addToolBar(tb) menu = QMenu("&File", self) self.menuBar().addMenu(menu) self.actionNew = QAction( QIcon.fromTheme('document-new', QIcon(rsrcPath + '/filenew.png')), "&New", self, priority=QAction.LowPriority, shortcut=QKeySequence.New, triggered=self.fileNew) tb.addAction(self.actionNew) menu.addAction(self.actionNew) self.actionOpen = QAction( QIcon.fromTheme('document-open', QIcon(rsrcPath + '/fileopen.png')), "&Open...", self, shortcut=QKeySequence.Open, triggered=self.fileOpen) tb.addAction(self.actionOpen) menu.addAction(self.actionOpen) menu.addSeparator() self.actionSave = QAction( QIcon.fromTheme('document-save', QIcon(rsrcPath + '/filesave.png')), "&Save", self, shortcut=QKeySequence.Save, triggered=self.fileSave, enabled=False) tb.addAction(self.actionSave) menu.addAction(self.actionSave) self.actionSaveAs = QAction("Save &As...", self, priority=QAction.LowPriority, shortcut=Qt.CTRL + Qt.SHIFT + Qt.Key_S, triggered=self.fileSaveAs) menu.addAction(self.actionSaveAs) menu.addSeparator() self.actionPrint = QAction( QIcon.fromTheme('document-print', QIcon(rsrcPath + '/fileprint.png')), "&Print...", self, priority=QAction.LowPriority, shortcut=QKeySequence.Print, triggered=self.filePrint) tb.addAction(self.actionPrint) menu.addAction(self.actionPrint) self.actionPrintPreview = QAction( QIcon.fromTheme('fileprint', QIcon(rsrcPath + '/fileprint.png')), "Print Preview...", self, shortcut=Qt.CTRL + Qt.SHIFT + Qt.Key_P, triggered=self.filePrintPreview) menu.addAction(self.actionPrintPreview) self.actionPrintPdf = QAction( QIcon.fromTheme('exportpdf', QIcon(rsrcPath + '/exportpdf.png')), "&Export PDF...", self, priority=QAction.LowPriority, shortcut=Qt.CTRL + Qt.Key_D, triggered=self.filePrintPdf) tb.addAction(self.actionPrintPdf) menu.addAction(self.actionPrintPdf) menu.addSeparator() self.actionQuit = QAction("&Quit", self, shortcut=QKeySequence.Quit, triggered=self.close) menu.addAction(self.actionQuit) def setupEditActions(self): tb = QToolBar(self) tb.setWindowTitle("Edit Actions") self.addToolBar(tb) menu = QMenu("&Edit", self) self.menuBar().addMenu(menu) self.actionUndo = QAction( QIcon.fromTheme('edit-undo', QIcon(rsrcPath + '/editundo.png')), "&Undo", self, shortcut=QKeySequence.Undo) tb.addAction(self.actionUndo) menu.addAction(self.actionUndo) self.actionRedo = QAction( QIcon.fromTheme('edit-redo', QIcon(rsrcPath + '/editredo.png')), "&Redo", self, priority=QAction.LowPriority, shortcut=QKeySequence.Redo) tb.addAction(self.actionRedo) menu.addAction(self.actionRedo) menu.addSeparator() self.actionCut = QAction( QIcon.fromTheme('edit-cut', QIcon(rsrcPath + '/editcut.png')), "Cu&t", self, priority=QAction.LowPriority, shortcut=QKeySequence.Cut) tb.addAction(self.actionCut) menu.addAction(self.actionCut) self.actionCopy = QAction( QIcon.fromTheme('edit-copy', QIcon(rsrcPath + '/editcopy.png')), "&Copy", self, priority=QAction.LowPriority, shortcut=QKeySequence.Copy) tb.addAction(self.actionCopy) menu.addAction(self.actionCopy) self.actionPaste = QAction( QIcon.fromTheme('edit-paste', QIcon(rsrcPath + '/editpaste.png')), "&Paste", self, priority=QAction.LowPriority, shortcut=QKeySequence.Paste, enabled=(len(QApplication.clipboard().text()) != 0)) tb.addAction(self.actionPaste) menu.addAction(self.actionPaste) def setupTextActions(self): tb = QToolBar(self) tb.setWindowTitle("Format Actions") self.addToolBar(tb) menu = QMenu("F&ormat", self) self.menuBar().addMenu(menu) self.actionTextBold = QAction( QIcon.fromTheme('format-text-bold', QIcon(rsrcPath + '/textbold.png')), "&Bold", self, priority=QAction.LowPriority, shortcut=Qt.CTRL + Qt.Key_B, triggered=self.textBold, checkable=True) bold = QFont() bold.setBold(True) self.actionTextBold.setFont(bold) tb.addAction(self.actionTextBold) menu.addAction(self.actionTextBold) self.actionTextItalic = QAction( QIcon.fromTheme('format-text-italic', QIcon(rsrcPath + '/textitalic.png')), "&Italic", self, priority=QAction.LowPriority, shortcut=Qt.CTRL + Qt.Key_I, triggered=self.textItalic, checkable=True) italic = QFont() italic.setItalic(True) self.actionTextItalic.setFont(italic) tb.addAction(self.actionTextItalic) menu.addAction(self.actionTextItalic) self.actionTextUnderline = QAction( QIcon.fromTheme('format-text-underline', QIcon(rsrcPath + '/textunder.png')), "&Underline", self, priority=QAction.LowPriority, shortcut=Qt.CTRL + Qt.Key_U, triggered=self.textUnderline, checkable=True) underline = QFont() underline.setUnderline(True) self.actionTextUnderline.setFont(underline) tb.addAction(self.actionTextUnderline) menu.addAction(self.actionTextUnderline) menu.addSeparator() grp = QActionGroup(self, triggered=self.textAlign) # Make sure the alignLeft is always left of the alignRight. if QApplication.isLeftToRight(): self.actionAlignLeft = QAction( QIcon.fromTheme('format-justify-left', QIcon(rsrcPath + '/textleft.png')), "&Left", grp) self.actionAlignCenter = QAction( QIcon.fromTheme('format-justify-center', QIcon(rsrcPath + '/textcenter.png')), "C&enter", grp) self.actionAlignRight = QAction( QIcon.fromTheme('format-justify-right', QIcon(rsrcPath + '/textright.png')), "&Right", grp) else: self.actionAlignRight = QAction( QIcon.fromTheme('format-justify-right', QIcon(rsrcPath + '/textright.png')), "&Right", grp) self.actionAlignCenter = QAction( QIcon.fromTheme('format-justify-center', QIcon(rsrcPath + '/textcenter.png')), "C&enter", grp) self.actionAlignLeft = QAction( QIcon.fromTheme('format-justify-left', QIcon(rsrcPath + '/textleft.png')), "&Left", grp) self.actionAlignJustify = QAction( QIcon.fromTheme('format-justify-fill', QIcon(rsrcPath + '/textjustify.png')), "&Justify", grp) self.actionAlignLeft.setShortcut(Qt.CTRL + Qt.Key_L) self.actionAlignLeft.setCheckable(True) self.actionAlignLeft.setPriority(QAction.LowPriority) self.actionAlignCenter.setShortcut(Qt.CTRL + Qt.Key_E) self.actionAlignCenter.setCheckable(True) self.actionAlignCenter.setPriority(QAction.LowPriority) self.actionAlignRight.setShortcut(Qt.CTRL + Qt.Key_R) self.actionAlignRight.setCheckable(True) self.actionAlignRight.setPriority(QAction.LowPriority) self.actionAlignJustify.setShortcut(Qt.CTRL + Qt.Key_J) self.actionAlignJustify.setCheckable(True) self.actionAlignJustify.setPriority(QAction.LowPriority) tb.addActions(grp.actions()) menu.addActions(grp.actions()) menu.addSeparator() pix = QPixmap(16, 16) pix.fill(Qt.black) self.actionTextColor = QAction(QIcon(pix), "&Color...", self, triggered=self.textColor) tb.addAction(self.actionTextColor) menu.addAction(self.actionTextColor) tb = QToolBar(self) tb.setAllowedAreas(Qt.TopToolBarArea | Qt.BottomToolBarArea) tb.setWindowTitle("Format Actions") self.addToolBarBreak(Qt.TopToolBarArea) self.addToolBar(tb) comboStyle = QComboBox(tb) tb.addWidget(comboStyle) comboStyle.addItem("Standard") comboStyle.addItem("Bullet List (Disc)") comboStyle.addItem("Bullet List (Circle)") comboStyle.addItem("Bullet List (Square)") comboStyle.addItem("Ordered List (Decimal)") comboStyle.addItem("Ordered List (Alpha lower)") comboStyle.addItem("Ordered List (Alpha upper)") comboStyle.addItem("Ordered List (Roman lower)") comboStyle.addItem("Ordered List (Roman upper)") comboStyle.activated.connect(self.textStyle) self.comboFont = QFontComboBox(tb) tb.addWidget(self.comboFont) self.comboFont.activated[str].connect(self.textFamily) self.comboSize = QComboBox(tb) self.comboSize.setObjectName("comboSize") tb.addWidget(self.comboSize) self.comboSize.setEditable(True) db = QFontDatabase() for size in db.standardSizes(): self.comboSize.addItem("%s" % (size)) self.comboSize.activated[str].connect(self.textSize) self.comboSize.setCurrentIndex( self.comboSize.findText( "%s" % (QApplication.font().pointSize()))) def load(self, f): if not QFile.exists(f): return False fh = QFile(f) if not fh.open(QFile.ReadOnly): return False data = fh.readAll() codec = QTextCodec.codecForHtml(data) unistr = codec.toUnicode(data) if Qt.mightBeRichText(unistr): self.textEdit.setHtml(unistr) else: self.textEdit.setPlainText(unistr) self.setCurrentFileName(f) return True def maybeSave(self): if not self.textEdit.document().isModified(): return True if self.fileName.startswith(':/'): return True ret = QMessageBox.warning(self, "Application", "The document has been modified.\n" "Do you want to save your changes?", QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel) if ret == QMessageBox.Save: return self.fileSave() if ret == QMessageBox.Cancel: return False return True def setCurrentFileName(self, fileName=''): self.fileName = fileName self.textEdit.document().setModified(False) if not fileName: shownName = 'untitled.txt' else: shownName = QFileInfo(fileName).fileName() self.setWindowTitle(self.tr("%s[*] - %s" % (shownName, "Rich Text"))) self.setWindowModified(False) def fileNew(self): if self.maybeSave(): self.textEdit.clear() self.setCurrentFileName() def fileOpen(self): fn, _ = QFileDialog.getOpenFileName(self, "Open File...", None, "HTML-Files (*.htm *.html);;All Files (*)") if fn: self.load(fn) def fileSave(self): if not self.fileName: return self.fileSaveAs() writer = QTextDocumentWriter(self.fileName) success = writer.write(self.textEdit.document()) if success: self.textEdit.document().setModified(False) return success def fileSaveAs(self): fn, _ = QFileDialog.getSaveFileName(self, "Save as...", None, "ODF files (*.odt);;HTML-Files (*.htm *.html);;All Files (*)") if not fn: return False lfn = fn.lower() if not lfn.endswith(('.odt', '.htm', '.html')): # The default. fn += '.odt' self.setCurrentFileName(fn) return self.fileSave() def filePrint(self): printer = QPrinter(QPrinter.HighResolution) dlg = QPrintDialog(printer, self) if self.textEdit.textCursor().hasSelection(): dlg.addEnabledOption(QPrintDialog.PrintSelection) dlg.setWindowTitle("Print Document") if dlg.exec_() == QPrintDialog.Accepted: self.textEdit.print_(printer) del dlg def filePrintPreview(self): printer = QPrinter(QPrinter.HighResolution) preview = QPrintPreviewDialog(printer, self) preview.paintRequested.connect(self.printPreview) preview.exec_() def printPreview(self, printer): self.textEdit.print_(printer) def filePrintPdf(self): fn, _ = QFileDialog.getSaveFileName(self, "Export PDF", None, "PDF files (*.pdf);;All Files (*)") if fn: if QFileInfo(fn).suffix().isEmpty(): fn += '.pdf' printer = QPrinter(QPrinter.HighResolution) printer.setOutputFormat(QPrinter.PdfFormat) printer.setOutputFileName(fn) self.textEdit.document().print_(printer) def textBold(self): fmt = QTextCharFormat() fmt.setFontWeight(self.actionTextBold.isChecked() and QFont.Bold or QFont.Normal) self.mergeFormatOnWordOrSelection(fmt) def textUnderline(self): fmt = QTextCharFormat() fmt.setFontUnderline(self.actionTextUnderline.isChecked()) self.mergeFormatOnWordOrSelection(fmt) def textItalic(self): fmt = QTextCharFormat() fmt.setFontItalic(self.actionTextItalic.isChecked()) self.mergeFormatOnWordOrSelection(fmt) def textFamily(self, family): fmt = QTextCharFormat() fmt.setFontFamily(family) self.mergeFormatOnWordOrSelection(fmt) def textSize(self, pointSize): pointSize = float(pointSize) if pointSize > 0: fmt = QTextCharFormat() fmt.setFontPointSize(pointSize) self.mergeFormatOnWordOrSelection(fmt) def textStyle(self, styleIndex): cursor = self.textEdit.textCursor() if styleIndex: styleDict = { 1: QTextListFormat.ListDisc, 2: QTextListFormat.ListCircle, 3: QTextListFormat.ListSquare, 4: QTextListFormat.ListDecimal, 5: QTextListFormat.ListLowerAlpha, 6: QTextListFormat.ListUpperAlpha, 7: QTextListFormat.ListLowerRoman, 8: QTextListFormat.ListUpperRoman, } style = styleDict.get(styleIndex, QTextListFormat.ListDisc) cursor.beginEditBlock() blockFmt = cursor.blockFormat() listFmt = QTextListFormat() if cursor.currentList(): listFmt = cursor.currentList().format() else: listFmt.setIndent(blockFmt.indent() + 1) blockFmt.setIndent(0) cursor.setBlockFormat(blockFmt) listFmt.setStyle(style) cursor.createList(listFmt) cursor.endEditBlock() else: bfmt = QTextBlockFormat() bfmt.setObjectIndex(-1) cursor.mergeBlockFormat(bfmt) def textColor(self): col = QColorDialog.getColor(self.textEdit.textColor(), self) if not col.isValid(): return fmt = QTextCharFormat() fmt.setForeground(col) self.mergeFormatOnWordOrSelection(fmt) self.colorChanged(col) def textAlign(self, action): if action == self.actionAlignLeft: self.textEdit.setAlignment(Qt.AlignLeft | Qt.AlignAbsolute) elif action == self.actionAlignCenter: self.textEdit.setAlignment(Qt.AlignHCenter) elif action == self.actionAlignRight: self.textEdit.setAlignment(Qt.AlignRight | Qt.AlignAbsolute) elif action == self.actionAlignJustify: self.textEdit.setAlignment(Qt.AlignJustify) def currentCharFormatChanged(self, format): self.fontChanged(format.font()) self.colorChanged(format.foreground().color()) def cursorPositionChanged(self): self.alignmentChanged(self.textEdit.alignment()) def clipboardDataChanged(self): self.actionPaste.setEnabled(len(QApplication.clipboard().text()) != 0) def about(self): QMessageBox.about(self, "About", "This example demonstrates Qt's rich text editing facilities " "in action, providing an example document for you to " "experiment with.") def mergeFormatOnWordOrSelection(self, format): cursor = self.textEdit.textCursor() if not cursor.hasSelection(): cursor.select(QTextCursor.WordUnderCursor) cursor.mergeCharFormat(format) self.textEdit.mergeCurrentCharFormat(format) def fontChanged(self, font): self.comboFont.setCurrentIndex( self.comboFont.findText(QFontInfo(font).family())) self.comboSize.setCurrentIndex( self.comboSize.findText("%s" % font.pointSize())) self.actionTextBold.setChecked(font.bold()) self.actionTextItalic.setChecked(font.italic()) self.actionTextUnderline.setChecked(font.underline()) def colorChanged(self, color): pix = QPixmap(16, 16) pix.fill(color) self.actionTextColor.setIcon(QIcon(pix)) def alignmentChanged(self, alignment): if alignment & Qt.AlignLeft: self.actionAlignLeft.setChecked(True) elif alignment & Qt.AlignHCenter: self.actionAlignCenter.setChecked(True) elif alignment & Qt.AlignRight: self.actionAlignRight.setChecked(True) elif alignment & Qt.AlignJustify: self.actionAlignJustify.setChecked(True)
class MainWindow(QMainWindow): """Главное окно программы. Управляет интерфейсом и событиями.""" def __init__(self, core): """Инициализация главного окна. core - ядро программы, содержащее логику.""" super().__init__() self.core = core self.core.logged.connect(self.onLogged) self.initUI() self.command() def initUI(self): """Инициализировать графический интерфейс.""" self.textEdit = QTextEdit() self.actionFileStart = QAction('&Start', self) self.actionFileStart.triggered.connect(self.onActionFileStartTriggered) self.actionFileStop = QAction('&Stop', self) self.actionFileStop.triggered.connect(self.onActionFileStopTriggered) self.actionFileStop.setDisabled(True) actionFileSave = QAction('&Save config', self) actionFileSave.triggered.connect(self.core.save) actionFileDraw = QAction('&Save chart', self) actionFileDraw.triggered.connect(self.core.draw) actionFileLogClear = QAction('&Clear', self) actionFileLogClear.triggered.connect(self.textEdit.clear) actionFileExit = QAction('&Exit', self) actionFileExit.triggered.connect(self.close) self.actionViewInfo = QAction('&Extended', self) self.actionViewInfo.setCheckable(True) self.actionViewInfo.setChecked(True) self.actionViewInfo.changed.connect(self.changeRow) actionConfigPeriod = QAction('&Period...', self) actionConfigPeriod.triggered.connect(self.actionConfigPeriodTriggered) actionConfigSensorsAdd = QAction('&Add...', self) actionConfigSensorsAdd.triggered.connect( self.actionConfigSensorsAddTriggered) actionConfigSensorsDel = QAction('&Delete...', self) actionConfigSensorsDel.triggered.connect( self.actionConfigSensorsDelTriggered) actionConfigPathAddresses = QAction('&Adressess...', self) actionConfigPathAddresses.triggered.connect( self.actionConfigPathAddressesTriggered) actionConfigPathSensors = QAction('&Sensors...', self) actionConfigPathSensors.triggered.connect( self.actionConfigPathSensorsTriggered) actionConfigPathData = QAction('&Data...', self) actionConfigPathData.triggered.connect( self.actionConfigPathDataTriggered) actionHelpHelp = QAction('&Help...', self) actionHelpHelp.triggered.connect(self.actionHelpHelpTriggered) actionHelpAbout = QAction('&About...', self) actionHelpAbout.triggered.connect(self.onAbout) menuFile = self.menuBar().addMenu('&File') menuFile.addAction(self.actionFileStart) menuFile.addAction(self.actionFileStop) menuFile.addSeparator() menuFileOpen = menuFile.addMenu('&Open') menuFileSave = menuFile.addMenu('&Save') menuFileSave.addAction(actionFileSave) menuFileSave.addAction(actionFileDraw) menuFile.addSeparator() menuFile.addAction(actionFileLogClear) menuFile.addSeparator() menuFile.addAction(actionFileExit) menuView = self.menuBar().addMenu('&View') menuView.addAction(self.actionViewInfo) menuConfig = self.menuBar().addMenu('&Settings') menuConfig.addAction(actionConfigPeriod) menuConfigSensors = menuConfig.addMenu('&Sensors') menuConfigSensors.addAction(actionConfigSensorsAdd) menuConfigSensors.addAction(actionConfigSensorsDel) menuConfigPath = menuConfig.addMenu('&Path') menuConfigPath.addAction(actionConfigPathAddresses) menuConfigPath.addAction(actionConfigPathSensors) menuConfigPath.addAction(actionConfigPathData) menuHelp = self.menuBar().addMenu('&Help') menuHelp.addAction(actionHelpHelp) menuHelp.addAction(actionHelpAbout) self.textEditList = deque(maxlen=100) self.textEdit.setVerticalScrollBarPolicy(2) self.textEdit.setToolTip("Action log.") self.textEdit.setReadOnly(True) self.isItemSave = False self.table = QTableWidget(0, 3) self.table.setToolTip("Temperature sensors.") self.table.setFixedWidth(342) self.table.setVerticalScrollBarPolicy(1) self.table.setHorizontalScrollBarPolicy(1) self.table.setColumnWidth(0, 50) self.table.setColumnWidth(1, 150) self.table.setColumnWidth(2, 120) self.table.setAlternatingRowColors(True) self.table.setSelectionMode(QTableWidget.SingleSelection) self.table.setSelectionBehavior(QTableWidget.SelectRows) self.table.setHorizontalHeaderLabels(['Val', 'Name', 'Address']) self.table.verticalHeader().setFixedWidth(20) self.table.verticalHeader().setDefaultAlignment(Qt.AlignRight) self.table.setEditTriggers(self.table.NoEditTriggers) grid = QGridLayout() grid.setSpacing(3) grid.setContentsMargins(3, 3, 3, 3) grid.addWidget(self.textEdit, 0, 0, 1, 1) grid.addWidget(self.table, 0, 1, 1, 1) widget = QWidget() widget.setLayout(grid) self.setCentralWidget(widget) self.setGeometry(300, 200, 600, 400) self.setWindowFlags(Qt.MSWindowsFixedSizeDialogHint) self.statusBar().setSizeGripEnabled(False) self.setWindowTitle('Observer') self.show() self.core.dataAdded.connect(self.onDataAdded) self.statusBar().showMessage('Application is runnig.') def command(self): """Обработка командой строки при запуске.""" if len(sys.argv) > 1: if sys.argv[1] == '-s': if len(sys.argv) > 2: if sys.argv[2].isdigit(): self.core.period = sys.argv[2] self.actionFileStart.trigger() def closeEvent(self, event): """Событие закрытия программы.""" self.core.stop() super().closeEvent(event) def onActionFileStartTriggered(self): """Действие нажатия Файл -> Старт. Запускает мониторинг.""" self.actionFileStop.setEnabled(True) self.actionFileStart.setDisabled(True) self.core.start() def onActionFileStopTriggered(self): """Действие нажатия Файл -> Стоп. Остановить мониторинг.""" self.actionFileStart.setEnabled(True) self.actionFileStop.setDisabled(True) self.core.stop() def actionConfigSensorsAddTriggered(self): """Действие нажатия Настройки -> Сенсоры -> Добавить. Открыть файл с настройками датчиков.""" if os.path.exists(self.core.pathSensors): answer = QMessageBox.question( self, 'Add sensors', 'Open file "{}/{}" to add sensors in external editor?'.format( self.core.pathSensors, 'temperature')) if answer == QMessageBox.Yes: try: os.startfile('{}\\{}'.format(self.core.pathSensors, 'temperature')) except Exception: QMessageBox.warning( self, 'Add sensors', 'File "{}/{}" don`t open!'.format( self.core.pathSensors, 'temperature')) else: QMessageBox.warning( self, 'Add sensors', '"{}" path does not exist'.format(self.core.pathSensors)) def actionConfigSensorsDelTriggered(self): """Действие нажатия Настройки -> Сенсоры -> Удалить. Открыть файл с настройками датчиков.""" if os.path.exists(self.core.pathSensors): answer = QMessageBox.question( self, 'Delete sensors', 'Open "{}/{}" to delete sensors in external editor?'.format( self.core.pathSensors, 'temperature')) if answer == QMessageBox.Yes: try: os.startfile('{}\\{}'.format(self.core.pathSensors, 'temperature')) except Exception: QMessageBox.warning( self, 'Delete sensors', 'File "{}/{}" don`t open!'.format( self.core.pathSensors, 'temperature')) else: QMessageBox.warning( self, 'Delete sensors', '"{}" path does not exist'.format(self.core.pathSensors)) def actionConfigPeriodTriggered(self): """Действие нажатия Настройки -> Период. Открыть окно настройки периода опроса датчиков.""" period, ok = QInputDialog.getInt(self, 'Request period', 'Enter request period:', int(self.core.period), 10, 3600) if ok: self.core.period = str(period) self.core.configSave() def actionConfigPathAddressesTriggered(self): """Действие нажатия Настройки -> Путь -> Адреса. Открывает окно выбора пути к файлу настройки адресов.""" path = QFileDialog.getOpenFileName(self, 'Addresses path', self.core.pathAddresses, '') if path[0] != '': self.core.pathAddresses = path[0] self.core.configSave() def actionConfigPathSensorsTriggered(self): """Действие нажатия Настройки -> Путь -> Датчики. Открывает окно выбора пути к файлу настройки датчиков.""" path = QFileDialog.getExistingDirectory(self, 'Sensors directory', self.core.pathSensors) if path != '': self.core.pathSensors = path self.core.configSave() def actionConfigPathDataTriggered(self): """Действие нажатия Настройки -> Путь -> Данные. Открывает окно выбора пути к папке, содержащей данные.""" path = QFileDialog.getExistingDirectory(self, 'Data directory', self.core.pathData) if path != '': self.core.pathData = path self.core.configSave() def changeRow(self): """Изменение отображения информации в колонках.""" if self.actionViewInfo.isChecked(): self.table.setColumnHidden(1, False) self.table.setColumnHidden(2, False) self.table.setFixedWidth(self.table.width() + 270) self.setFixedWidth(self.width() + 270) else: self.table.setColumnHidden(1, True) self.table.setColumnHidden(2, True) self.table.setFixedWidth(self.table.width() - 270) self.setFixedWidth(self.width() - 270) def checkItemSave(self, item): """Проверка изменения ячейки имени датчика.""" if item.column() == 1: self.isItemSave = True else: self.isItemSave = False def itemSave(self, item): """Сохранение имени в списке.""" if self.isItemSave: if item.column() == 1: if item.tableWidget() == self.table: self.isItemSave = False self.statusBar().showMessage(item.text() + " save") temp = self.table.item(item.row(), 2) key = temp.text() self.core.sensors[key].name = item.text() self.table.clearSelection() def onLogged(self, log, modes): """Событие логирования. log - передаваемое сообщение; modes - содержит один или несколько режимов отображения лога: l - вывод в текстовое поле; s - вывод в статус бар; f - запись в файл.""" if 'l' in modes: self.textEditList.appendleft(log) self.textEdit.clear() self.textEdit.setText('\n'.join(self.textEditList)) if 's' in modes: self.statusBar().showMessage(log) if 'f' in modes: directory = '{0}\\{1}\\{2}\\{3}'.format( self.core.pathData, self.core.currentDate.strftime('%Y'), self.core.currentDate.strftime('%m'), self.core.currentDate.strftime('%d')) os.makedirs(directory, 0o777, True) with open( '{0}\\{1}.log'.format( directory, self.core.currentDate.strftime('%Y.%m.%d')), 'a') as file: file.write(log + '\n') def onDataAdded(self): """Событие добавления данных.""" self.table.setRowCount(len(self.core.sensors)) if self.table.rowCount() <= 20: self.table.setMinimumHeight(25 + self.table.rowCount() * 23) i = 0 for key in self.core.sensors.keys(): if self.core.sensors[key].value is not None: self.table.setRowHeight(i, 23) self.table.setItem( i, 0, QTableWidgetItem(self.core.sensors[key].value)) self.table.setItem( i, 1, QTableWidgetItem(self.core.sensors[key].name)) self.table.setItem(i, 2, QTableWidgetItem(key)) i += 1 self.table.setRowCount(i) self.table.sortItems(1) def onAbout(self): """Действие нажатия Помощь -> О программе.""" text = '<h2>{0}</h2>'\ '<p>Client program for monitoring the condition<br>'\ 'of the premises of the Kharkov Radar.<br>'\ 'Source code is available on '\ '<a href="https://github.com/StanislavMain/ObserverClient">'\ 'GitHub</a>.</p><p><b>Stanislav Hnatiuk</b><br>'\ 'Institute of Ionosphere\n<br>Ukraine, Kharkiv.<br><br>'\ 'Contacts with me:<br>'\ '<a href="https://t.me/stanmain">Telegram</a><br>'\ '<a href="https://github.com/StanislavMain">GitHub</a><br>'\ '<br><b>All rights reserved.</b></p>'.format(self.windowTitle()) QMessageBox.about(self, 'About the program', text) def actionHelpHelpTriggered(self): """Действие нажатия Помощь -> Помощь.""" text = '<p><b>Console parametrs</b><br>'\ 'usage: main [options]<br>'\ 'where options have next key:<br>'\ '-s [seconds]: start monitoring with a period of [seconds]</p>'\ '<p><b>Code and name</b><br>'\ 'Write your address and sensors in the appropriate files:<br>'\ '{}<br>{}/*</p>'\ '<p><b>Data</b><br>'\ 'Data is located in "{}"<br>'\ '"/Year/Month/Day/Y.M.D.csv" : data file<br>'\ '"/Year/Month/Day/Y.M.D.png" : chart file<br>'\ '"/Year/Month/Day/Y.M.D.log" : log file<br>'\ '</p>'.format( self.core.pathAddresses, self.core.pathSensors, self.core.pathData ) QMessageBox.information(self, 'Help', text)
class ImageViewer(QMainWindow): def __init__(self): super(ImageViewer, self).__init__() self.printer = QPrinter() self.scaleFactor = 0.0 self.imageLabel = QLabel() self.imageLabel.setBackgroundRole(QPalette.Base) self.imageLabel.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored) self.imageLabel.setScaledContents(True) self.scrollArea = QScrollArea() self.scrollArea.setBackgroundRole(QPalette.Dark) self.scrollArea.setWidget(self.imageLabel) self.setCentralWidget(self.scrollArea) self.createActions() self.createMenus() self.setWindowTitle("Image Viewer") self.resize(500, 400) def open(self): fileName, _ = QFileDialog.getOpenFileName(self, "Open File", QDir.currentPath()) if fileName: image = QImage(fileName) if image.isNull(): QMessageBox.information(self, "Image Viewer", "Cannot load %s." % fileName) return self.imageLabel.setPixmap(QPixmap.fromImage(image)) self.scaleFactor = 1.0 self.printAct.setEnabled(True) self.fitToWindowAct.setEnabled(True) self.updateActions() if not self.fitToWindowAct.isChecked(): self.imageLabel.adjustSize() def print_(self): dialog = QPrintDialog(self.printer, self) if dialog.exec_(): painter = QPainter(self.printer) rect = painter.viewport() size = self.imageLabel.pixmap().size() size.scale(rect.size(), Qt.KeepAspectRatio) painter.setViewport(rect.x(), rect.y(), size.width(), size.height()) painter.setWindow(self.imageLabel.pixmap().rect()) painter.drawPixmap(0, 0, self.imageLabel.pixmap()) def zoomIn(self): self.scaleImage(1.25) def zoomOut(self): self.scaleImage(0.8) def normalSize(self): self.imageLabel.adjustSize() self.scaleFactor = 1.0 def fitToWindow(self): fitToWindow = self.fitToWindowAct.isChecked() self.scrollArea.setWidgetResizable(fitToWindow) if not fitToWindow: self.normalSize() self.updateActions() def about(self): QMessageBox.about(self, "About Image Viewer", "<p>The <b>Image Viewer</b> example shows how to combine " "QLabel and QScrollArea to display an image. QLabel is " "typically used for displaying text, but it can also display " "an image. QScrollArea provides a scrolling view around " "another widget. If the child widget exceeds the size of the " "frame, QScrollArea automatically provides scroll bars.</p>" "<p>The example demonstrates how QLabel's ability to scale " "its contents (QLabel.scaledContents), and QScrollArea's " "ability to automatically resize its contents " "(QScrollArea.widgetResizable), can be used to implement " "zooming and scaling features.</p>" "<p>In addition the example shows how to use QPainter to " "print an image.</p>") def createActions(self): self.openAct = QAction("&Open...", self, shortcut="Ctrl+O", triggered=self.open) self.printAct = QAction("&Print...", self, shortcut="Ctrl+P", enabled=False, triggered=self.print_) self.exitAct = QAction("E&xit", self, shortcut="Ctrl+Q", triggered=self.close) self.zoomInAct = QAction("Zoom &In (25%)", self, shortcut="Ctrl++", enabled=False, triggered=self.zoomIn) self.zoomOutAct = QAction("Zoom &Out (25%)", self, shortcut="Ctrl+-", enabled=False, triggered=self.zoomOut) self.normalSizeAct = QAction("&Normal Size", self, shortcut="Ctrl+S", enabled=False, triggered=self.normalSize) self.fitToWindowAct = QAction("&Fit to Window", self, enabled=False, checkable=True, shortcut="Ctrl+F", triggered=self.fitToWindow) self.aboutAct = QAction("&About", self, triggered=self.about) self.aboutQtAct = QAction("About &Qt", self, triggered=QApplication.instance().aboutQt) def createMenus(self): self.fileMenu = QMenu("&File", self) self.fileMenu.addAction(self.openAct) self.fileMenu.addAction(self.printAct) self.fileMenu.addSeparator() self.fileMenu.addAction(self.exitAct) self.viewMenu = QMenu("&View", self) self.viewMenu.addAction(self.zoomInAct) self.viewMenu.addAction(self.zoomOutAct) self.viewMenu.addAction(self.normalSizeAct) self.viewMenu.addSeparator() self.viewMenu.addAction(self.fitToWindowAct) self.helpMenu = QMenu("&Help", self) self.helpMenu.addAction(self.aboutAct) self.helpMenu.addAction(self.aboutQtAct) self.menuBar().addMenu(self.fileMenu) self.menuBar().addMenu(self.viewMenu) self.menuBar().addMenu(self.helpMenu) def updateActions(self): self.zoomInAct.setEnabled(not self.fitToWindowAct.isChecked()) self.zoomOutAct.setEnabled(not self.fitToWindowAct.isChecked()) self.normalSizeAct.setEnabled(not self.fitToWindowAct.isChecked()) def scaleImage(self, factor): self.scaleFactor *= factor self.imageLabel.resize(self.scaleFactor * self.imageLabel.pixmap().size()) self.adjustScrollBar(self.scrollArea.horizontalScrollBar(), factor) self.adjustScrollBar(self.scrollArea.verticalScrollBar(), factor) self.zoomInAct.setEnabled(self.scaleFactor < 3.0) self.zoomOutAct.setEnabled(self.scaleFactor > 0.333) def adjustScrollBar(self, scrollBar, factor): scrollBar.setValue(int(factor * scrollBar.value() + ((factor - 1) * scrollBar.pageStep()/2)))
def __addDefaultActions(self): """ Private slot to add the default user agent entries. @return flag indicating that a user agent entry is checked (boolean) """ from . import UserAgentDefaults_rc # __IGNORE_WARNING__ defaultUserAgents = QFile(":/UserAgentDefaults.xml") defaultUserAgents.open(QIODevice.ReadOnly) menuStack = [] isChecked = False if self.__url: currentUserAgentString = self.__manager.userAgentForUrl(self.__url) else: from Helpviewer.HelpBrowserWV import HelpWebPage currentUserAgentString = HelpWebPage().userAgent() xml = QXmlStreamReader(defaultUserAgents) while not xml.atEnd(): xml.readNext() if xml.isStartElement() and xml.name() == "separator": if menuStack: menuStack[-1].addSeparator() else: self.addSeparator() continue if xml.isStartElement() and xml.name() == "useragent": attributes = xml.attributes() title = attributes.value("description") userAgent = attributes.value("useragent") act = QAction(self) act.setText(title) act.setData(userAgent) act.setToolTip(userAgent) act.setCheckable(True) act.setChecked(userAgent == currentUserAgentString) act.triggered.connect(self.__changeUserAgent) if menuStack: menuStack[-1].addAction(act) else: self.addAction(act) self.__actionGroup.addAction(act) isChecked = isChecked or act.isChecked() if xml.isStartElement() and xml.name() == "useragentmenu": attributes = xml.attributes() title = attributes.value("title") if title == "v_a_r_i_o_u_s": title = self.tr("Various") menu = QMenu(self) menu.setTitle(title) self.addMenu(menu) menuStack.append(menu) if xml.isEndElement() and xml.name() == "useragentmenu": menuStack.pop() if xml.hasError(): E5MessageBox.critical( self, self.tr("Parsing default user agents"), self.tr( """<p>Error parsing default user agents.</p><p>{0}</p>"""). format(xml.errorString())) return isChecked
class MusicPlayer(QMainWindow): """MusicPlayer houses all of elements that directly interact with the main window.""" def __init__(self, parent=None): """Initialize the QMainWindow widget. The window title, window icon, and window size are initialized here as well as the following widgets: QMediaPlayer, QMediaPlaylist, QMediaContent, QMenuBar, QToolBar, QLabel, QPixmap, QSlider, QDockWidget, QListWidget, QWidget, and QVBoxLayout. The connect signals for relavant widgets are also initialized. """ super(MusicPlayer, self).__init__(parent) self.setWindowTitle('Mosaic') window_icon = utilities.resource_filename('mosaic.images', 'icon.png') self.setWindowIcon(QIcon(window_icon)) self.resize(defaults.Settings().window_size, defaults.Settings().window_size + 63) # Initiates Qt objects to be used by MusicPlayer self.player = QMediaPlayer() self.playlist = QMediaPlaylist() self.playlist_location = defaults.Settings().playlist_path self.content = QMediaContent() self.menu = self.menuBar() self.toolbar = QToolBar() self.art = QLabel() self.pixmap = QPixmap() self.slider = QSlider(Qt.Horizontal) self.duration_label = QLabel() self.playlist_dock = QDockWidget('Playlist', self) self.library_dock = QDockWidget('Media Library', self) self.playlist_view = QListWidget() self.library_view = library.MediaLibraryView() self.library_model = library.MediaLibraryModel() self.preferences = configuration.PreferencesDialog() self.widget = QWidget() self.layout = QVBoxLayout(self.widget) self.duration = 0 self.playlist_dock_state = None self.library_dock_state = None # Sets QWidget() as the central widget of the main window self.setCentralWidget(self.widget) self.layout.setContentsMargins(0, 0, 0, 0) self.art.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored) # Initiates the playlist dock widget and the library dock widget self.addDockWidget(defaults.Settings().dock_position, self.playlist_dock) self.playlist_dock.setWidget(self.playlist_view) self.playlist_dock.setVisible(defaults.Settings().playlist_on_start) self.playlist_dock.setFeatures(QDockWidget.DockWidgetClosable) self.addDockWidget(defaults.Settings().dock_position, self.library_dock) self.library_dock.setWidget(self.library_view) self.library_dock.setVisible(defaults.Settings().media_library_on_start) self.library_dock.setFeatures(QDockWidget.DockWidgetClosable) self.tabifyDockWidget(self.playlist_dock, self.library_dock) # Sets the range of the playback slider and sets the playback mode as looping self.slider.setRange(0, self.player.duration() / 1000) self.playlist.setPlaybackMode(QMediaPlaylist.Sequential) # OSX system menu bar causes conflicts with PyQt5 menu bar if sys.platform == 'darwin': self.menu.setNativeMenuBar(False) # Initiates Settings in the defaults module to give access to settings.toml defaults.Settings() # Signals that connect to other methods when they're called self.player.metaDataChanged.connect(self.display_meta_data) self.slider.sliderMoved.connect(self.seek) self.player.durationChanged.connect(self.song_duration) self.player.positionChanged.connect(self.song_position) self.player.stateChanged.connect(self.set_state) self.playlist_view.itemActivated.connect(self.activate_playlist_item) self.library_view.activated.connect(self.open_media_library) self.playlist.currentIndexChanged.connect(self.change_index) self.playlist.mediaInserted.connect(self.initialize_playlist) self.playlist_dock.visibilityChanged.connect(self.dock_visiblity_change) self.library_dock.visibilityChanged.connect(self.dock_visiblity_change) self.preferences.dialog_media_library.media_library_line.textChanged.connect(self.change_media_library_path) self.preferences.dialog_view_options.dropdown_box.currentIndexChanged.connect(self.change_window_size) self.art.mousePressEvent = self.press_playback # Creating the menu controls, media controls, and window size of the music player self.menu_controls() self.media_controls() self.load_saved_playlist() def menu_controls(self): """Initiate the menu bar and add it to the QMainWindow widget.""" self.file = self.menu.addMenu('File') self.edit = self.menu.addMenu('Edit') self.playback = self.menu.addMenu('Playback') self.view = self.menu.addMenu('View') self.help_ = self.menu.addMenu('Help') self.file_menu() self.edit_menu() self.playback_menu() self.view_menu() self.help_menu() def media_controls(self): """Create the bottom toolbar and controls used for media playback.""" self.addToolBar(Qt.BottomToolBarArea, self.toolbar) self.toolbar.setMovable(False) play_icon = utilities.resource_filename('mosaic.images', 'md_play.png') self.play_action = QAction(QIcon(play_icon), 'Play', self) self.play_action.triggered.connect(self.player.play) stop_icon = utilities.resource_filename('mosaic.images', 'md_stop.png') self.stop_action = QAction(QIcon(stop_icon), 'Stop', self) self.stop_action.triggered.connect(self.player.stop) previous_icon = utilities.resource_filename('mosaic.images', 'md_previous.png') self.previous_action = QAction(QIcon(previous_icon), 'Previous', self) self.previous_action.triggered.connect(self.previous) next_icon = utilities.resource_filename('mosaic.images', 'md_next.png') self.next_action = QAction(QIcon(next_icon), 'Next', self) self.next_action.triggered.connect(self.playlist.next) repeat_icon = utilities.resource_filename('mosaic.images', 'md_repeat_none.png') self.repeat_action = QAction(QIcon(repeat_icon), 'Repeat', self) self.repeat_action.setShortcut('R') self.repeat_action.triggered.connect(self.repeat_song) self.toolbar.addAction(self.play_action) self.toolbar.addAction(self.stop_action) self.toolbar.addAction(self.previous_action) self.toolbar.addAction(self.next_action) self.toolbar.addAction(self.repeat_action) self.toolbar.addWidget(self.slider) self.toolbar.addWidget(self.duration_label) def file_menu(self): """Add a file menu to the menu bar. The file menu houses the Open File, Open Multiple Files, Open Playlist, Open Directory, and Exit Application menu items. """ self.open_action = QAction('Open File', self) self.open_action.setShortcut('O') self.open_action.triggered.connect(self.open_file) self.open_multiple_files_action = QAction('Open Multiple Files', self) self.open_multiple_files_action.setShortcut('M') self.open_multiple_files_action.triggered.connect(self.open_multiple_files) self.open_playlist_action = QAction('Open Playlist', self) self.open_playlist_action.setShortcut('CTRL+P') self.open_playlist_action.triggered.connect(self.open_playlist) self.open_directory_action = QAction('Open Directory', self) self.open_directory_action.setShortcut('D') self.open_directory_action.triggered.connect(self.open_directory) self.save_playlist_action = QAction('Save Playlist', self) self.save_playlist_action.setShortcut('CTRL+S') self.save_playlist_action.triggered.connect(self.save_playlist) self.exit_action = QAction('Quit', self) self.exit_action.setShortcut('CTRL+Q') self.exit_action.triggered.connect(self.closeEvent) self.file.addAction(self.open_action) self.file.addAction(self.open_multiple_files_action) self.file.addAction(self.open_playlist_action) self.file.addAction(self.open_directory_action) self.file.addSeparator() self.file.addAction(self.save_playlist_action) self.file.addSeparator() self.file.addAction(self.exit_action) def edit_menu(self): """Add an edit menu to the menu bar. The edit menu houses the preferences item that opens a preferences dialog that allows the user to customize features of the music player. """ self.preferences_action = QAction('Preferences', self) self.preferences_action.setShortcut('CTRL+SHIFT+P') self.preferences_action.triggered.connect(lambda: self.preferences.exec_()) self.edit.addAction(self.preferences_action) def playback_menu(self): """Add a playback menu to the menu bar. The playback menu houses """ self.play_playback_action = QAction('Play', self) self.play_playback_action.setShortcut('P') self.play_playback_action.triggered.connect(self.player.play) self.stop_playback_action = QAction('Stop', self) self.stop_playback_action.setShortcut('S') self.stop_playback_action.triggered.connect(self.player.stop) self.previous_playback_action = QAction('Previous', self) self.previous_playback_action.setShortcut('B') self.previous_playback_action.triggered.connect(self.previous) self.next_playback_action = QAction('Next', self) self.next_playback_action.setShortcut('N') self.next_playback_action.triggered.connect(self.playlist.next) self.playback.addAction(self.play_playback_action) self.playback.addAction(self.stop_playback_action) self.playback.addAction(self.previous_playback_action) self.playback.addAction(self.next_playback_action) def view_menu(self): """Add a view menu to the menu bar. The view menu houses the Playlist, Media Library, Minimalist View, and Media Information menu items. The Playlist item toggles the playlist dock into and out of view. The Media Library items toggles the media library dock into and out of view. The Minimalist View item resizes the window and shows only the menu bar and player controls. The Media Information item opens a dialog that shows information relevant to the currently playing song. """ self.dock_action = self.playlist_dock.toggleViewAction() self.dock_action.setShortcut('CTRL+ALT+P') self.library_dock_action = self.library_dock.toggleViewAction() self.library_dock_action.setShortcut('CTRL+ALT+L') self.minimalist_view_action = QAction('Minimalist View', self) self.minimalist_view_action.setShortcut('CTRL+ALT+M') self.minimalist_view_action.setCheckable(True) self.minimalist_view_action.triggered.connect(self.minimalist_view) self.view_media_info_action = QAction('Media Information', self) self.view_media_info_action.setShortcut('CTRL+SHIFT+M') self.view_media_info_action.triggered.connect(self.media_information_dialog) self.view.addAction(self.dock_action) self.view.addAction(self.library_dock_action) self.view.addSeparator() self.view.addAction(self.minimalist_view_action) self.view.addSeparator() self.view.addAction(self.view_media_info_action) def help_menu(self): """Add a help menu to the menu bar. The help menu houses the about dialog that shows the user information related to the application. """ self.about_action = QAction('About', self) self.about_action.setShortcut('H') self.about_action.triggered.connect(lambda: about.AboutDialog().exec_()) self.help_.addAction(self.about_action) def open_file(self): """Open the selected file and add it to a new playlist.""" filename, success = QFileDialog.getOpenFileName(self, 'Open File', '', 'Audio (*.mp3 *.flac)', '', QFileDialog.ReadOnly) if success: file_info = QFileInfo(filename).fileName() playlist_item = QListWidgetItem(file_info) self.playlist.clear() self.playlist_view.clear() self.playlist.addMedia(QMediaContent(QUrl().fromLocalFile(filename))) self.player.setPlaylist(self.playlist) playlist_item.setToolTip(file_info) self.playlist_view.addItem(playlist_item) self.playlist_view.setCurrentRow(0) self.player.play() def open_multiple_files(self): """Open the selected files and add them to a new playlist.""" filenames, success = QFileDialog.getOpenFileNames(self, 'Open Multiple Files', '', 'Audio (*.mp3 *.flac)', '', QFileDialog.ReadOnly) if success: self.playlist.clear() self.playlist_view.clear() for file in natsort.natsorted(filenames, alg=natsort.ns.PATH): file_info = QFileInfo(file).fileName() playlist_item = QListWidgetItem(file_info) self.playlist.addMedia(QMediaContent(QUrl().fromLocalFile(file))) self.player.setPlaylist(self.playlist) playlist_item.setToolTip(file_info) self.playlist_view.addItem(playlist_item) self.playlist_view.setCurrentRow(0) self.player.play() def open_playlist(self): """Load an M3U or PLS file into a new playlist.""" playlist, success = QFileDialog.getOpenFileName(self, 'Open Playlist', '', 'Playlist (*.m3u *.pls)', '', QFileDialog.ReadOnly) if success: playlist = QUrl.fromLocalFile(playlist) self.playlist.clear() self.playlist_view.clear() self.playlist.load(playlist) self.player.setPlaylist(self.playlist) for song_index in range(self.playlist.mediaCount()): file_info = self.playlist.media(song_index).canonicalUrl().fileName() playlist_item = QListWidgetItem(file_info) playlist_item.setToolTip(file_info) self.playlist_view.addItem(playlist_item) self.playlist_view.setCurrentRow(0) self.player.play() def save_playlist(self): """Save the media in the playlist dock as a new M3U playlist.""" playlist, success = QFileDialog.getSaveFileName(self, 'Save Playlist', '', 'Playlist (*.m3u)', '') if success: saved_playlist = "{}.m3u" .format(playlist) self.playlist.save(QUrl().fromLocalFile(saved_playlist), "m3u") def load_saved_playlist(self): """Load the saved playlist if user setting permits.""" saved_playlist = "{}/.m3u" .format(self.playlist_location) if os.path.exists(saved_playlist): playlist = QUrl().fromLocalFile(saved_playlist) self.playlist.load(playlist) self.player.setPlaylist(self.playlist) for song_index in range(self.playlist.mediaCount()): file_info = self.playlist.media(song_index).canonicalUrl().fileName() playlist_item = QListWidgetItem(file_info) playlist_item.setToolTip(file_info) self.playlist_view.addItem(playlist_item) self.playlist_view.setCurrentRow(0) def open_directory(self): """Open the selected directory and add the files within to an empty playlist.""" directory = QFileDialog.getExistingDirectory(self, 'Open Directory', '', QFileDialog.ReadOnly) if directory: self.playlist.clear() self.playlist_view.clear() for dirpath, __, files in os.walk(directory): for filename in natsort.natsorted(files, alg=natsort.ns.PATH): file = os.path.join(dirpath, filename) if filename.endswith(('mp3', 'flac')): self.playlist.addMedia(QMediaContent(QUrl().fromLocalFile(file))) playlist_item = QListWidgetItem(filename) playlist_item.setToolTip(filename) self.playlist_view.addItem(playlist_item) self.player.setPlaylist(self.playlist) self.playlist_view.setCurrentRow(0) self.player.play() def open_media_library(self, index): """Open a directory or file from the media library into an empty playlist.""" self.playlist.clear() self.playlist_view.clear() if self.library_model.fileName(index).endswith(('mp3', 'flac')): self.playlist.addMedia(QMediaContent(QUrl().fromLocalFile(self.library_model.filePath(index)))) self.playlist_view.addItem(self.library_model.fileName(index)) elif self.library_model.isDir(index): directory = self.library_model.filePath(index) for dirpath, __, files in os.walk(directory): for filename in natsort.natsorted(files, alg=natsort.ns.PATH): file = os.path.join(dirpath, filename) if filename.endswith(('mp3', 'flac')): self.playlist.addMedia(QMediaContent(QUrl().fromLocalFile(file))) playlist_item = QListWidgetItem(filename) playlist_item.setToolTip(filename) self.playlist_view.addItem(playlist_item) self.player.setPlaylist(self.playlist) self.player.play() def display_meta_data(self): """Display the current song's metadata in the main window. If the current song contains metadata, its cover art is extracted and shown in the main window while the track number, artist, album, and track title are shown in the window title. """ if self.player.isMetaDataAvailable(): file_path = self.player.currentMedia().canonicalUrl().toLocalFile() (album, artist, title, track_number, *__, artwork) = metadata.metadata(file_path) try: self.pixmap.loadFromData(artwork) except TypeError: self.pixmap = QPixmap(artwork) meta_data = '{} - {} - {} - {}' .format(track_number, artist, album, title) self.setWindowTitle(meta_data) self.art.setScaledContents(True) self.art.setPixmap(self.pixmap) self.layout.addWidget(self.art) def initialize_playlist(self, start): """Display playlist and reset playback mode when media inserted into playlist.""" if start == 0: if self.library_dock.isVisible(): self.playlist_dock.setVisible(True) self.playlist_dock.show() self.playlist_dock.raise_() if self.playlist.playbackMode() != QMediaPlaylist.Sequential: self.playlist.setPlaybackMode(QMediaPlaylist.Sequential) repeat_icon = utilities.resource_filename('mosaic.images', 'md_repeat_none.png') self.repeat_action.setIcon(QIcon(repeat_icon)) def press_playback(self, event): """Change the playback of the player on cover art mouse event. When the cover art is clicked, the player will play the media if the player is either paused or stopped. If the media is playing, the media is set to pause. """ if event.button() == 1 and configuration.Playback().cover_art_playback.isChecked(): if (self.player.state() == QMediaPlayer.StoppedState or self.player.state() == QMediaPlayer.PausedState): self.player.play() elif self.player.state() == QMediaPlayer.PlayingState: self.player.pause() def seek(self, seconds): """Set the position of the song to the position dragged to by the user.""" self.player.setPosition(seconds * 1000) def song_duration(self, duration): """Set the slider to the duration of the currently played media.""" duration /= 1000 self.duration = duration self.slider.setMaximum(duration) def song_position(self, progress): """Move the horizontal slider in sync with the duration of the song. The progress is relayed to update_duration() in order to display the time label next to the slider. """ progress /= 1000 if not self.slider.isSliderDown(): self.slider.setValue(progress) self.update_duration(progress) def update_duration(self, current_duration): """Calculate the time played and the length of the song. Both of these times are sent to duration_label() in order to display the times on the toolbar. """ duration = self.duration if current_duration or duration: time_played = QTime((current_duration / 3600) % 60, (current_duration / 60) % 60, (current_duration % 60), (current_duration * 1000) % 1000) song_length = QTime((duration / 3600) % 60, (duration / 60) % 60, (duration % 60), (duration * 1000) % 1000) if duration > 3600: time_format = "hh:mm:ss" else: time_format = "mm:ss" time_display = "{} / {}" .format(time_played.toString(time_format), song_length.toString(time_format)) else: time_display = "" self.duration_label.setText(time_display) def set_state(self, state): """Change the icon in the toolbar in relation to the state of the player. The play icon changes to the pause icon when a song is playing and the pause icon changes back to the play icon when either paused or stopped. """ if self.player.state() == QMediaPlayer.PlayingState: pause_icon = utilities.resource_filename('mosaic.images', 'md_pause.png') self.play_action.setIcon(QIcon(pause_icon)) self.play_action.triggered.connect(self.player.pause) elif (self.player.state() == QMediaPlayer.PausedState or self.player.state() == QMediaPlayer.StoppedState): self.play_action.triggered.connect(self.player.play) play_icon = utilities.resource_filename('mosaic.images', 'md_play.png') self.play_action.setIcon(QIcon(play_icon)) def previous(self): """Move to the previous song in the playlist. Moves to the previous song in the playlist if the current song is less than five seconds in. Otherwise, restarts the current song. """ if self.player.position() <= 5000: self.playlist.previous() else: self.player.setPosition(0) def repeat_song(self): """Set the current media to repeat and change the repeat icon accordingly. There are four playback modes: repeat none, repeat all, repeat once, and shuffle. Clicking the repeat button cycles through each playback mode. """ if self.playlist.playbackMode() == QMediaPlaylist.Sequential: self.playlist.setPlaybackMode(QMediaPlaylist.Loop) repeat_on_icon = utilities.resource_filename('mosaic.images', 'md_repeat_all.png') self.repeat_action.setIcon(QIcon(repeat_on_icon)) elif self.playlist.playbackMode() == QMediaPlaylist.Loop: self.playlist.setPlaybackMode(QMediaPlaylist.CurrentItemInLoop) repeat_on_icon = utilities.resource_filename('mosaic.images', 'md_repeat_once.png') self.repeat_action.setIcon(QIcon(repeat_on_icon)) elif self.playlist.playbackMode() == QMediaPlaylist.CurrentItemInLoop: self.playlist.setPlaybackMode(QMediaPlaylist.Random) repeat_icon = utilities.resource_filename('mosaic.images', 'md_shuffle.png') self.repeat_action.setIcon(QIcon(repeat_icon)) elif self.playlist.playbackMode() == QMediaPlaylist.Random: self.playlist.setPlaybackMode(QMediaPlaylist.Sequential) repeat_icon = utilities.resource_filename('mosaic.images', 'md_repeat_none.png') self.repeat_action.setIcon(QIcon(repeat_icon)) def activate_playlist_item(self, item): """Set the active media to the playlist item dobule-clicked on by the user.""" current_index = self.playlist_view.row(item) if self.playlist.currentIndex() != current_index: self.playlist.setCurrentIndex(current_index) if self.player.state() != QMediaPlayer.PlayingState: self.player.play() def change_index(self, row): """Highlight the row in the playlist of the active media.""" self.playlist_view.setCurrentRow(row) def minimalist_view(self): """Resize the window to only show the menu bar and audio controls.""" if self.minimalist_view_action.isChecked(): if self.playlist_dock.isVisible(): self.playlist_dock_state = True if self.library_dock.isVisible(): self.library_dock_state = True self.library_dock.close() self.playlist_dock.close() QTimer.singleShot(10, lambda: self.resize(500, 0)) else: self.resize(defaults.Settings().window_size, defaults.Settings().window_size + 63) if self.library_dock_state: self.library_dock.setVisible(True) if self.playlist_dock_state: self.playlist_dock.setVisible(True) def dock_visiblity_change(self, visible): """Change the size of the main window when the docks are toggled.""" if visible and self.playlist_dock.isVisible() and not self.library_dock.isVisible(): self.resize(defaults.Settings().window_size + self.playlist_dock.width() + 6, self.height()) elif visible and not self.playlist_dock.isVisible() and self.library_dock.isVisible(): self.resize(defaults.Settings().window_size + self.library_dock.width() + 6, self.height()) elif visible and self.playlist_dock.isVisible() and self.library_dock.isVisible(): self.resize(defaults.Settings().window_size + self.library_dock.width() + 6, self.height()) elif (not visible and not self.playlist_dock.isVisible() and not self.library_dock.isVisible()): self.resize(defaults.Settings().window_size, defaults.Settings().window_size + 63) def media_information_dialog(self): """Show a dialog of the current song's metadata.""" if self.player.isMetaDataAvailable(): file_path = self.player.currentMedia().canonicalUrl().toLocalFile() else: file_path = None dialog = information.InformationDialog(file_path) dialog.exec_() def change_window_size(self): """Change the window size of the music player.""" self.playlist_dock.close() self.library_dock.close() self.resize(defaults.Settings().window_size, defaults.Settings().window_size + 63) def change_media_library_path(self, path): """Change the media library path to the new path selected in the preferences dialog.""" self.library_model.setRootPath(path) self.library_view.setModel(self.library_model) self.library_view.setRootIndex(self.library_model.index(path)) def closeEvent(self, event): """Override the PyQt close event in order to handle save playlist on close.""" playlist = "{}/.m3u" .format(self.playlist_location) if defaults.Settings().save_playlist_on_close: self.playlist.save(QUrl().fromLocalFile(playlist), "m3u") else: if os.path.exists(playlist): os.remove(playlist) QApplication.quit()
class ImageViewer(QMainWindow): def __init__(self): super(ImageViewer, self).__init__() self.printer = QPrinter() self.scaleFactor = 0.0 self.imageLabel = QLabel() self.imageLabel.setBackgroundRole(QPalette.Base) self.imageLabel.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored) self.imageLabel.setScaledContents(True) self.imageLabel.setAlignment(Qt.AlignCenter) self.scrollArea = QScrollArea() self.scrollArea.setBackgroundRole(QPalette.Dark) self.scrollArea.setWidget(self.imageLabel) self.setCentralWidget(self.scrollArea) self.scrollArea.setAlignment(Qt.AlignCenter) self.createActions() self.createMenus() self.setWindowTitle("Image Viewer") self.resize(500, 400) self.setWindowIcon(QIcon('./res/Sketch-Icon.png')) def open(self): # 打开图片后根据weight重新调整大小 fileName, _ = QFileDialog.getOpenFileName(self, "Open File", QDir.currentPath()) if fileName: # 如果是webp格式用PIL # 如果是psd用PSDtool # gif格式? fileType = fileName.split('.')[-1] if fileType == 'gif': #QMessageBox.information(self, "file type", "type:%s"%(fileType)) self.movie = QMovie(fileName) self.movie.setCacheMode(QMovie.CacheAll) self.movie.setSpeed(100) self.imageLabel.setMovie(self.movie) self.movie.start() if not self.fitToWindowAct.isChecked(): self.imageLabel.adjustSize() elif fileType == 'psd': img = PSDImage.load(fileName) img = img.as_PIL() img.save('./res/temp.jpg') image = QImage('./res/temp.jpg') if image.isNull(): QMessageBox.information(self, "Image Viewer", "Cannot load %s." % (fileName)) return self.imageLabel.setPixmap(QPixmap.fromImage(image)) self.scaleFactor = 1.0 self.printAct.setEnabled(True) self.fitToWindowAct.setEnabled(True) self.updateActions() if not self.fitToWindowAct.isChecked(): self.imageLabel.adjustSize() elif fileType == 'svg': svg = QtSvg.QSvgRenderer(fileName) img = QImage(svg.defaultSize().width(), svg.defaultSize().height(), QImage.Format_ARGB32) p = QPainter(img) svg.render(p) img.save('./res/temp.png') p.end() image = QImage('./res/temp.png') if image.isNull(): QMessageBox.information(self, "Image Viewer", "Cannot load %s." % (fileName)) return self.imageLabel.setPixmap(QPixmap.fromImage(image)) self.scaleFactor = 1.0 self.printAct.setEnabled(True) self.fitToWindowAct.setEnabled(True) self.updateActions() if not self.fitToWindowAct.isChecked(): self.imageLabel.adjustSize() else: image = QImage(fileName) if image.isNull(): QMessageBox.information(self, "Image Viewer", "Cannot load %s." % (fileName)) return self.imageLabel.setPixmap(QPixmap.fromImage(image)) self.scaleFactor = 1.0 self.printAct.setEnabled(True) self.fitToWindowAct.setEnabled(True) self.updateActions() if not self.fitToWindowAct.isChecked(): self.imageLabel.adjustSize() def print_(self): dialog = QPrintDialog(self.printer, self) if dialog.exec_(): painter = QPainter(self.printer) rect = painter.viewport() size = self.imageLabel.pixmap().size() size.scale(rect.size(), Qt.KeepAspectRatio) painter.setViewport(rect.x(), rect.y(), size.width(), size.height()) painter.setWindow(self.imageLabel.pixmap().rect()) painter.drawPixmap(0, 0, self.imageLabel.pixmap()) def zoomIn(self): self.scaleImage(1.25) def zoomOut(self): self.scaleImage(0.8) def normalSize(self): self.imageLabel.adjustSize() self.scaleFactor = 1.0 def fitToWindow(self): fitToWindow = self.fitToWindowAct.isChecked() self.scrollArea.setWidgetResizable(fitToWindow) if not fitToWindow: self.normalSize() self.updateActions() def about(self): QMessageBox.about(self, "About Image Viewer", "<p>The <b>Image Viewer</b> example shows how to combine " "QLabel and QScrollArea to display an image. QLabel is " "typically used for displaying text, but it can also display " "an image. QScrollArea provides a scrolling view around " "another widget. If the child widget exceeds the size of the " "frame, QScrollArea automatically provides scroll bars.</p>" "<p>The example demonstrates how QLabel's ability to scale " "its contents (QLabel.scaledContents), and QScrollArea's " "ability to automatically resize its contents " "(QScrollArea.widgetResizable), can be used to implement " "zooming and scaling features.</p>" "<p>In addition the example shows how to use QPainter to " "print an image.</p>") def rgbColor(self): pass def hexColor(self): pass def createActions(self): self.openAct = QAction("&Open...", self, shortcut="Ctrl+O", triggered=self.open) self.printAct = QAction("&Print...", self, shortcut="Ctrl+P", enabled=False, triggered=self.print_) self.exitAct = QAction("E&xit", self, shortcut="Ctrl+Q", triggered=self.close) self.zoomInAct = QAction("Zoom &In (25%)", self, shortcut="Ctrl++", enabled=False, triggered=self.zoomIn) self.zoomOutAct = QAction("Zoom &Out (25%)", self, shortcut="Ctrl+-", enabled=False, triggered=self.zoomOut) self.normalSizeAct = QAction("&Normal Size", self, shortcut="Ctrl+S", enabled=False, triggered=self.normalSize) self.fitToWindowAct = QAction("&Fit to Window", self, enabled=False, checkable=True, shortcut="Ctrl+F", triggered=self.fitToWindow) self.rgbColor = QAction("&RGB Color", self, enabled=False, checkable=True, triggered=self.rgbColor) self.hexColor = QAction("&HEX Color", self, enabled=False, checkable=True, triggered=self.hexColor) self.aboutAct = QAction("&About", self, triggered=self.about) self.aboutQtAct = QAction("About &Qt", self, triggered=QApplication.instance().aboutQt) def createMenus(self): self.fileMenu = QMenu("&File", self) self.fileMenu.addAction(self.openAct) self.fileMenu.addAction(self.printAct) self.fileMenu.addSeparator() self.fileMenu.addAction(self.exitAct) self.viewMenu = QMenu("&View", self) self.viewMenu.addAction(self.zoomInAct) self.viewMenu.addAction(self.zoomOutAct) self.viewMenu.addAction(self.normalSizeAct) self.viewMenu.addSeparator() self.viewMenu.addAction(self.fitToWindowAct) self.pickColor = QMenu("&PickColor", self) self.pickColor.addAction(self.rgbColor) self.pickColor.addAction(self.hexColor) self.helpMenu = QMenu("&Help", self) self.helpMenu.addAction(self.aboutAct) self.helpMenu.addAction(self.aboutQtAct) self.menuBar().addMenu(self.fileMenu) self.menuBar().addMenu(self.viewMenu) self.menuBar().addMenu(self.pickColor) self.menuBar().addMenu(self.helpMenu) def updateActions(self): self.zoomInAct.setEnabled(not self.fitToWindowAct.isChecked()) self.zoomOutAct.setEnabled(not self.fitToWindowAct.isChecked()) self.normalSizeAct.setEnabled(not self.fitToWindowAct.isChecked()) def scaleImage(self, factor): self.scaleFactor *= factor self.imageLabel.resize(self.scaleFactor * self.imageLabel.pixmap().size()) self.adjustScrollBar(self.scrollArea.horizontalScrollBar(), factor) self.adjustScrollBar(self.scrollArea.verticalScrollBar(), factor) self.zoomInAct.setEnabled(self.scaleFactor < 3.0) self.zoomOutAct.setEnabled(self.scaleFactor > 0.333) def adjustScrollBar(self, scrollBar, factor): scrollBar.setValue(int(factor * scrollBar.value() + ((factor - 1) * scrollBar.pageStep()/2)))
class Actions(QObject): def __init__(self, controller: 'Controller') -> None: super().__init__() self.controller = controller self.make_actions() def make_actions(self) -> None: self.save_as = QAction(QIcon.fromTheme('save-as'), 'Save Filelist As', self) self.save_as.setShortcut('Ctrl+s') self.save_as.setStatusTip('Save the current file selection') self.save_as.triggered.connect(self.controller.save_as) def on_debug(enabled): if enabled: logging.getLogger().setLevel(logging.DEBUG) else: logging.getLogger().setLevel(logging.ERROR) self.debug = QAction(QIcon.fromTheme('media-record'), '&Debug', self, checkable=True) self.debug.setStatusTip('Debug application') self.debug.setChecked(logging.getLogger().getEffectiveLevel() == logging.DEBUG) self.debug.triggered.connect(lambda: on_debug(self.debug.isChecked())) self.exit = QAction(QIcon.fromTheme('window-close'), '&Exit', self) self.exit.setShortcut('Ctrl+W') self.exit.setStatusTip('Close Window') self.exit.triggered.connect(self.controller.close_window) self.home = QAction(QIcon.fromTheme('go-home'), '&Go to Home', self) self.home.setStatusTip('Go to the Home directory') self.home.triggered.connect(self.controller.go_home) self.undo = QAction(QIcon.fromTheme('undo'), '&Undo', self) self.undo.setShortcut('Ctrl+Z') self.undo.setStatusTip('Undo the last action') self.redo = QAction(QIcon.fromTheme('redo'), '&Redo', self) self.redo.setShortcut('Ctrl+Y') self.redo.setStatusTip('Redo the last action') self.edit_copy = QAction(QIcon.fromTheme('edit-copy'), '&Copy', self) self.edit_copy.setShortcut('Ctrl+C') self.edit_copy.setStatusTip('Copy Selected Files') self.edit_copy.triggered.connect(self.controller.on_edit_copy) self.edit_cut = QAction(QIcon.fromTheme('edit-cut'), 'Cu&t', self) self.edit_cut.setShortcut('Ctrl+X') self.edit_cut.setStatusTip('Cut Selected Files') self.edit_cut.triggered.connect(self.controller.on_edit_cut) self.edit_paste = QAction(QIcon.fromTheme('edit-paste'), '&Paste', self) self.edit_paste.setShortcut('Ctrl+V') self.edit_paste.setStatusTip('Paste Files') self.edit_paste.triggered.connect(self.controller.on_edit_paste) self.edit_delete = QAction(QIcon.fromTheme('edit-delete'), '&Delete', self) self.edit_delete.setStatusTip('Delete Selected Files') self.edit_select_all = QAction(QIcon.fromTheme('edit-select-all'), '&Select All', self) self.edit_select_all.setShortcut('Ctrl+A') self.edit_select_all.setStatusTip('Select All') self.edit_select_all.triggered.connect(self.controller.select_all) self.zoom_in = QAction(QIcon.fromTheme('zoom-in'), "Zoom &In", self) self.zoom_in.triggered.connect(self.controller.zoom_in) self.zoom_in.setShortcut('Ctrl+=') self.zoom_out = QAction(QIcon.fromTheme('zoom-out'), "Zoom &Out", self) self.zoom_out.triggered.connect(self.controller.zoom_out) self.zoom_out.setShortcut('Ctrl+-') self.lod_in = QAction(QIcon.fromTheme('zoom-in'), "Level of Detail &In", self) self.lod_in.triggered.connect(self.controller.more_details) self.lod_in.setShortcut('Alt+=') self.lod_out = QAction(QIcon.fromTheme('zoom-out'), "Level of Detail &Out", self) self.lod_out.triggered.connect(self.controller.less_details) self.lod_out.setShortcut('Alt+-') self.crop_thumbnails = QAction(QIcon.fromTheme('zoom-fit-best'), "Crop Thumbnails", self, checkable=True) self.crop_thumbnails.triggered.connect( lambda: self.controller.set_crop_thumbnails(self.crop_thumbnails.isChecked())) self.new_window = QAction(QIcon.fromTheme('window-new'), "New Window", self) self.new_window.triggered.connect(lambda x: self.controller.new_controller(clone=True)) self.new_window.setShortcut('Ctrl+N') self.parent_directory = QAction(self.controller.app.qapp.style().standardIcon(QStyle.SP_FileDialogToParent), "Parent Directory") self.parent_directory.triggered.connect(self.controller.parent_directory) self.back = QAction(QIcon.fromTheme('back'), 'Go &back', self) self.back.setShortcut('Alt+Left') self.back.setStatusTip('Go back in history') self.back.setEnabled(False) self.back.triggered.connect(self.controller.go_back) self.forward = QAction(QIcon.fromTheme('forward'), 'Go &forward', self) self.forward.setShortcut('Alt+Right') self.forward.setStatusTip('Go forward in history') self.forward.setEnabled(False) self.forward.triggered.connect(self.controller.go_forward) self.search = QAction(QIcon.fromTheme('system-search'), 'Search', self) self.search.setShortcut('F3') self.search.setStatusTip('Search for files') self.search.triggered.connect(self.controller.show_search) self.reload = QAction(QIcon.fromTheme('reload'), 'Reload', self) self.reload.setShortcut('F5') self.reload.setStatusTip('Reload the View') self.reload.triggered.connect(self.controller.reload) self.rename = QAction(QIcon.fromTheme('rename'), 'Rename', self) self.rename.setShortcut('F2') self.rename.setStatusTip('Rename the current file') self.rename.triggered.connect(lambda checked: self.controller.show_rename_dialog()) self.reload_thumbnails = QAction(QIcon.fromTheme('edit-delete'), 'Reload Thumbnails', self) self.reload_thumbnails.setStatusTip('Reload Thumbnails') self.reload_thumbnails.triggered.connect(self.controller.reload_thumbnails) self.reload_metadata = QAction(QIcon.fromTheme('edit-delete'), 'Reload MetaData', self) self.reload_metadata.setStatusTip('Reload MetaData') self.reload_metadata.triggered.connect(self.controller.reload_metadata) self.make_directory_thumbnails = QAction(QIcon.fromTheme('folder'), 'Make Directory Thumbnails', self) self.make_directory_thumbnails.setStatusTip('Make Directory Thumbnails') self.make_directory_thumbnails.triggered.connect(self.controller.make_directory_thumbnails) self.prepare = QAction(QIcon.fromTheme('media-playback-start'), 'Load Thumbnails', self) self.prepare.setShortcut('F6') self.prepare.setStatusTip('Load Thumbnails') self.prepare.triggered.connect(self.controller.prepare) self.view_detail_view = QAction("Detail View", self, checkable=True) self.view_icon_view = QAction("Icon View", self, checkable=True) self.view_small_icon_view = QAction("Small Icon View", self, checkable=True) self.view_icon_view = QAction(QIcon.fromTheme("view-grid-symbolic"), "Icon View") self.view_icon_view.triggered.connect(self.controller.view_icon_view) self.view_small_icon_view = QAction(QIcon.fromTheme("view-list-symbolic"), "Small Icon View") self.view_small_icon_view.triggered.connect(self.controller.view_small_icon_view) self.view_detail_view = QAction(QIcon.fromTheme("view-more-horizontal-symbolic"), "Detail View") self.view_detail_view.triggered.connect(self.controller.view_detail_view) self.view_group = QActionGroup(self) self.view_group.addAction(self.view_detail_view) self.view_group.addAction(self.view_icon_view) self.view_group.addAction(self.view_small_icon_view) self.show_hidden = QAction(QIcon.fromTheme('camera-photo'), "Show Hidden", self, checkable=True) self.show_hidden.triggered.connect(self.controller.show_hidden) self.show_hidden.setShortcut('Ctrl+H') self.show_hidden.setChecked(settings.value("globals/show_hidden", False, bool)) self.show_filtered = QAction(QIcon.fromTheme('camera-photo'), "Show Filtered", self, checkable=True) self.show_filtered.triggered.connect(self.controller.show_filtered) self.show_abspath = QAction("Show AbsPath", self, checkable=True) self.show_abspath.triggered.connect(self.controller.show_abspath) self.show_basename = QAction("Show Basename", self, checkable=True) self.show_basename.triggered.connect(self.controller.show_basename) self.path_options_group = QActionGroup(self) self.path_options_group.addAction(self.show_abspath) self.path_options_group.addAction(self.show_basename) self.toggle_timegaps = QAction("Show Time Gaps", self, checkable=True) self.toggle_timegaps.triggered.connect(self.controller.toggle_timegaps) # Sorting Options self.sort_directories_first = QAction("Directories First", checkable=True) self.sort_directories_first.triggered.connect( lambda: self.controller._sorter.set_directories_first(self.sort_directories_first.isChecked())) self.sort_directories_first.setChecked(True) self.sort_reversed = QAction("Reverse Sort", checkable=True) self.sort_reversed.triggered.connect( lambda: self.controller.set_sort_reversed(self.sort_reversed.isChecked())) self.sort_by_name = QAction("Sort by Name", checkable=True) self.sort_by_name.triggered.connect(lambda: self.controller.set_sort_key_func( lambda x: numeric_sort_key(x.basename().lower()))) self.sort_by_name.setChecked(True) self.sort_by_size = QAction("Sort by Size", checkable=True) self.sort_by_size.triggered.connect(lambda: self.controller.set_sort_key_func(FileInfo.size)) self.sort_by_ext = QAction("Sort by Extension", checkable=True) self.sort_by_ext.triggered.connect(lambda: self.controller.set_sort_key_func(FileInfo.ext)) self.sort_by_date = QAction("Sort by Date", checkable=True) self.sort_by_date.triggered.connect(lambda: self.controller.set_sort_key_func(FileInfo.mtime)) def framerate_key(fileinfo): metadata = fileinfo.metadata() return metadata.get('framerate', 0) self.sort_by_framerate = QAction("Sort by Framerate", checkable=True) self.sort_by_framerate.triggered.connect(lambda: self.controller.set_sort_key_func(framerate_key)) def aspect_ratio_key(fileinfo): metadata = fileinfo.metadata() if 'width' in metadata and 'height' in metadata and metadata['height'] != 0: return metadata['width'] / metadata['height'] else: return 0 self.sort_by_aspect_ratio = QAction("Sort by Aspect Ratio", checkable=True) self.sort_by_aspect_ratio.triggered.connect(lambda: self.controller.set_sort_key_func(aspect_ratio_key)) def area_key(fileinfo): metadata = fileinfo.metadata() if 'width' in metadata and 'height' in metadata: return metadata['width'] * metadata['height'] else: return 0 self.sort_by_area = QAction("Sort by Area", checkable=True) self.sort_by_area.triggered.connect(lambda: self.controller.set_sort_key_func(area_key)) def duration_key(fileinfo): metadata = fileinfo.metadata() if 'duration' in metadata: return metadata['duration'] else: return 0 self.sort_by_duration = QAction("Sort by Duration", checkable=True) self.sort_by_duration.triggered.connect(lambda: self.controller.set_sort_key_func(duration_key)) self.sort_by_user = QAction("Sort by User", checkable=True) self.sort_by_group = QAction("Sort by Group", checkable=True) self.sort_by_permission = QAction("Sort by Permission", checkable=True) self.sort_by_random = QAction("Random Shuffle", checkable=True) # self.sort_by_random.triggered.connect(lambda: self.controller.set_sort_key_func(None)) self.sort_by_random.triggered.connect(lambda: print("sort_by_random: not implemented")) self.sort_group = QActionGroup(self) self.sort_group.addAction(self.sort_by_name) self.sort_group.addAction(self.sort_by_size) self.sort_group.addAction(self.sort_by_ext) self.sort_group.addAction(self.sort_by_date) self.sort_group.addAction(self.sort_by_area) self.sort_group.addAction(self.sort_by_duration) self.sort_group.addAction(self.sort_by_aspect_ratio) self.sort_group.addAction(self.sort_by_framerate) self.sort_group.addAction(self.sort_by_user) self.sort_group.addAction(self.sort_by_group) self.sort_group.addAction(self.sort_by_permission) self.sort_group.addAction(self.sort_by_random) self.group_by_none = QAction("Don't Group", checkable=True) self.group_by_none.triggered.connect(self.controller.set_grouper_by_none) self.group_by_none.setChecked(True) self.group_by_day = QAction("Group by Day", checkable=True) self.group_by_day.triggered.connect(self.controller.set_grouper_by_day) self.group_by_directory = QAction("Group by Directory", checkable=True) self.group_by_directory.triggered.connect(self.controller.set_grouper_by_directory) self.group_by_duration = QAction("Group by Duration", checkable=True) self.group_by_duration.triggered.connect(self.controller.set_grouper_by_duration) self.group_group = QActionGroup(self) self.group_group.addAction(self.group_by_none) self.group_group.addAction(self.group_by_day) self.group_group.addAction(self.group_by_directory) self.group_group.addAction(self.group_by_duration) self.about = QAction(QIcon.fromTheme('help-about'), 'About dt-fileview', self) self.about.setStatusTip('Show About dialog') self.about.triggered.connect(self.controller.show_about_dialog) self.show_preferences = QAction(QIcon.fromTheme("preferences-system"), "Preferencs...") self.show_preferences.triggered.connect(self.controller.show_preferences) def on_filter_pin(checked) -> None: # FIXME: Could use icon state for this if checked: self.filter_pin.setIcon(QIcon.fromTheme("remmina-pin-down")) else: self.filter_pin.setIcon(QIcon.fromTheme("remmina-pin-up")) self.filter_pin = QAction(QIcon.fromTheme("remmina-pin-up"), "Pin the filter", checkable=True) self.filter_pin.triggered.connect(self.controller.set_filter_pin) # self.filter_pin.setToolTip("Pin the filter") self.filter_pin.triggered.connect(on_filter_pin)
class MainWindow(QMainWindow): def __init__(self): super().__init__() #constructs an object from parent class self.data_list = [] self.initUI() #calls for initializing method #initializes titles that will be plotted later self.title = "Plot title" self.xtitle = "x axis" self.ytitle = "y axis" def initUI(self): #initializing method for main window self.setGeometry( 300, 300, 1050, 1000 ) #first numbers are coordinates on the window, latter ones size self.setWindowTitle('Data Visualization programme') self.center() #calls for method center self.statusBar() self.create_menubar() self.grid_button() #creates checkable grid button to menubar self.fitline_button() #creates button for fitting a line to points self.givetitle_button( ) #creates button for user to change the title of the plot self.giveaxis_button( ) #creates button for user to change the names of the axis self.show() #METHODS: #centers the window on user's screen def center(self): qr = self.frameGeometry() print(qr) cp = QDesktopWidget().availableGeometry().center() qr.moveCenter(cp) self.move(qr.topLeft()) #creates menubar and adds file menu to it: def create_menubar(self): self.menubar = self.menuBar() #creates top menubar fileMenu = self.menubar.addMenu( '&File') #creates filemenu and adds it to menubar openfileact = QAction("Open file", self) #creates "Open file" action to file menu openfileact.triggered.connect( self.showDialog ) #goes to method showDialog(), when user selects "Open file fileMenu.addAction(openfileact) #Opens file selection window to user and calls for method create_data_objects() of the ReadFile class to read data from the file: def showDialog(self): dialog = QFileDialog() dialog.setNameFilter('Text files (*.txt *.csv) ') path = dialog.getOpenFileName(self, 'Open file', '/home', 'Text files (*.txt *.csv) ') try: with open(path[0]) as fname: self.data_list = ReadFile.create_data_objects( fname) #reads data from file to data_list #randomly chooces plotting colours that each data set self.colour_list = [] for i in range(len(self.data_list)): self.colour_list.append(self.get_color()) except OSError: print("Could not open {}".format(path), file=sys.stderr) #randomly selects colours for all the data sets plotted def get_color(self): red = random.randrange(0, 255) green = random.randrange(0, 255) blue = random.randrange(0, 255) return QColor(red, green, blue) #Draws the data points: def drawPoints(self, qp): for i in range(self.how_many_datasets): pen = QPen(self.colour_list[i], 14, Qt.SolidLine, Qt.RoundCap) qp.setPen(pen) for b in range(self.data_list[i].data_length): x0 = self.data_list[i].x[b] y0 = self.data_list[i].y[b] x0 = ( x0 + self.normalizer_x ) * self.scaling_x + self.margin_x #here we normalize and scale the x coordinate to be able to plot it y0 = (self.height() - 2 * self.margin_y) - ( y0 + self.normalizer_y) * self.scaling_y + self.margin_y #for y coordinate we also have to substract y from the (heigth - margins) in order to start the drawing from lower left corner instead of upper left corner. #On Qt, (0,0) coordinate is on upper left corner and we change it to be in lower left corner for the data. qp.drawPoint(x0, y0) # scales the data for nice plotting according to the min and max values in data sets: def scale(self): """ idea: 1.find min and max values in all the data sets that will be plottet at the same time 2.calculate the distance between the min and max values 3.compare the distance of the min and max data values to the plotting window's length minus some margins --> Like this we find scaling number that the data needs to be multiplied with to get nice full screen plot. --> Scaling keeps the relative distances between datapoints but makes them spread to entire alvailable plotting area. """ #initialize values max_values_x = [] min_values_x = [] max_values_y = [] min_values_y = [] how_many_datasets = len(self.data_list) #find min and max values on data for i in range(how_many_datasets): max_values_x.append(max(self.data_list[i].x)) min_values_x.append(min(self.data_list[i].x)) max_values_y.append(max(self.data_list[i].y)) min_values_y.append(min(self.data_list[i].y)) self.max_y = max(max_values_y) self.max_x = max(max_values_x) self.min_y = min(min_values_y) self.min_x = min(min_values_x) #calculate max distances on data self.width_x = abs(self.max_x - self.min_x) self.heigth_y = abs(self.max_y - self.min_y) #chooce margin sizes self.margin_x = 0.13 * self.width() self.margin_y = 0.13 * self.height() #determine scaling factor to get nice plot self.scaling_x = (self.width() - 2 * self.margin_x) / self.width_x self.scaling_y = (self.height() - 2 * self.margin_y) / self.heigth_y #we will normalize the data to start from zero self.normalizer_x = 0 self.normalizer_y = 0 if (self.min_x < 0): self.normalizer_x = abs(self.min_x) if (self.min_y < 0): self.normalizer_y = abs(self.min_y) if (self.min_x > 0): self.normalizer_x = -1 * self.min_x if (self.min_y > 0): self.normalizer_y = -1 * self.min_y #creates figure settings menu and checkable grid button to it: def grid_button(self): self.plotmenu = self.menubar.addMenu( '&Figure settings') #adds Figure settings menu to menubar self.gridact = QAction('Grid', self, checkable=True) self.gridact.setStatusTip('Set grid on the figure') self.gridact.setChecked(True) #first put grid on self.gridact.triggered.connect( self.repaint) #repaints window if user wants grid on/off self.plotmenu.addAction(self.gridact) #creates figure settings menu and checkable fitting line button to it: def fitline_button(self): self.fitact = QAction('Fit line', self, checkable=True) self.fitact.setStatusTip('Draw line between points on the figure') self.fitact.setChecked(True) self.fitact.triggered.connect( self.repaint) #repaints window if user wants line on/off self.plotmenu.addAction(self.fitact) #when paintEvent is generated, the window is repainted: def paintEvent(self, e): qp = QPainter() #Creates a QPainter object that does the painting qp.begin(self) self.how_many_datasets = len(self.data_list) if (self.how_many_datasets != 0): #repaints only if we have data on the list self.scale() self.drawPoints(qp) self.drawGrid(qp) if (self.fitact.isChecked()): self.fitline(qp) qp.end() #Draws the grid and number axis: def drawGrid(self, qp): """ idea: 1.divides the plot into n blocs depending how thick grid we want. 2. starts plotting the horizontal and vertical grid lines from up to down. Coordinates of the lines are again skaled and for y coordinate we have to substract y from the (heigth - margins) in order to get the maximun value up and lower value down. """ #select how many grids we want in horizontal and vertical directions number_of_blocs = 5 #calculates the width and height of one grid square dist_between_xlines = self.width_x * self.scaling_x / number_of_blocs dist_between_ylines = self.heigth_y * self.scaling_y / number_of_blocs for i in range(number_of_blocs + 1): #sets color for grid lines color = QColor(Qt.black) pen = QPen(color, 1, Qt.SolidLine) qp.setPen(pen) # calculates the coordinates for horizontal lines xmin = (self.min_x + self.normalizer_x) * self.scaling_x + self.margin_x xmax = (self.max_x + self.normalizer_x) * self.scaling_x + self.margin_x yline = ((self.height() - 2 * self.margin_y) - (self.max_y + self.normalizer_y) * self.scaling_y + self.margin_y + dist_between_ylines * i) #calculates the coordinates for vertical lines ymin = (self.height() - 2 * self.margin_y) - (self.max_y + self.normalizer_y ) * self.scaling_y + self.margin_y ymax = (self.height() - 2 * self.margin_y) - (self.min_y + self.normalizer_y ) * self.scaling_y + self.margin_y xline = ((self.min_x + self.normalizer_x) * self.scaling_x + self.margin_x + dist_between_xlines * i) #creates plottable QLineF objects from coordinates lineHorizontal = QLineF(xmin, yline, xmax, yline) lineVertical = QLineF(xline, ymin, xline, ymax) #draws grid only if user has checked it from the menu if (self.gridact.isChecked()): qp.drawLine(lineHorizontal) qp.drawLine(lineVertical) #draws the axis numbers: #to find out the real data number we need to remove all the scaling factors. textpointx = QPointF(xline, (ymax + 0.02 * ymax)) textpointy = QPointF((xmin - 0.3 * xmin), yline) #finds the x number on data (strips all the scaling) xnumbertowrite = xline - self.margin_x xnumbertowrite /= self.scaling_x xnumbertowrite -= self.normalizer_x #finds y number on data (strips all the scaling) ynumbertowrite = yline - self.margin_y ynumbertowrite /= self.scaling_y ynumbertowrite -= self.normalizer_y #removes more scaling numbers if there is negative data to get the real y number if (self.min_y < 0): if (ynumbertowrite < 0): ynumbertowrite = self.heigth_y - abs(ynumbertowrite) else: ynumbertowrite = self.max_y - abs( self.min_y) - ynumbertowrite else: ynumbertowrite = self.max_y - abs(self.min_y) - ynumbertowrite #draws the real x and y values qp.drawText(textpointx, str(round(xnumbertowrite, 2))) qp.drawText(textpointy, str(round(ynumbertowrite, 2))) #makes some of the values global for later use when we draw titles and axis names. self.xmin = xmin self.ymin = ymin self.xmax = xmax self.ymax = ymax self.dist_between_xlines = dist_between_xlines self.dist_between_ylines = dist_between_ylines self.dist_between_xlines = dist_between_xlines #calls for method to draw the title of the plot self.draw_title(qp, self.title, self.xtitle, self.ytitle) #calls for method to draw the info box of the plot self.create_plot_info_box(qp) #fits a line between plotted points when user checks "Fit line" from figure settings menu. def fitline(self, qp): """ idea of this method: The x and y data is not sorted from min to max value. However, in order to fit the line we need to now the indexes of data points that have x values next to each others. (To combine these points to form the line.) To do this we copy the datasets and modify the copied datas in following way: 1. copy x and y data sets for local variables to be able to modify them only for this line fitting purpose 2. find the index of min x value. (The corresponding y coordinate has the same index.) 3. delete the current min x value and its corresponding y value to find out the next smallest value. (The next smallest value is the new min x value after deletation) 4. draw line between these adjacent points. 5.repeat until the end of data. """ #draws lines between all data sets for i in range(self.how_many_datasets): #set pen for each line with rigth colour pen = QPen(self.colour_list[i], 1, Qt.SolidLine) qp.setPen(pen) #copies data sets so that they can be edited xdata = self.data_list[i].x.copy() ydata = self.data_list[i].y.copy() for b in range(self.data_list[i].data_length - 1): #find min x value and index corresponding to it ind0 = xdata.index(min(xdata)) x0 = xdata[ind0] y0 = ydata[ind0] x0 = ( x0 + self.normalizer_x ) * self.scaling_x + self.margin_x #here we normalize and scale the x coordinate to be able to plot it y0 = (self.height() - 2 * self.margin_y) - ( y0 + self.normalizer_y) * self.scaling_y + self.margin_y #for y coordinate we also have to substract y from the (heigth - margins) in order to start the drawing from lower left corner instead of upper left corner. #(On Qt, (0,0) coordinate is on upper left corner and we change it to be in lower left corner for the data. #delete min x value and y value corresponding to it to be able to find the second smallest del xdata[ind0] del ydata[ind0] ind1 = xdata.index(min(xdata)) x1 = xdata[ind1] y1 = ydata[ind1] #scale datavalues for plotting x1 = (x1 + self.normalizer_x) * self.scaling_x + self.margin_x y1 = (self.height() - 2 * self.margin_y) - ( y1 + self.normalizer_y) * self.scaling_y + self.margin_y #draw line line = QLineF(x0, y0, x1, y1) qp.drawLine(line) #creates button for changing the title of the plot and adds it to plotmenu def givetitle_button(self): self.titleact = QAction('Edit title', self) self.titleact.setStatusTip('Give a title to the figure') self.titleact.triggered.connect(self.edit_title) self.plotmenu.addAction(self.titleact) #reads new title name from the user and calls for repaint. def edit_title(self): text, ok = QInputDialog.getText(self, 'Enter new title', 'Title:') if ok: self.title = str(text) self.xtitle = self.xtitle self.ytitle = self.ytitle self.repaint() #creates button for changing the names of the axis of the plot and adds it to plotmenu def giveaxis_button(self): self.axisact = QAction('Edit axis names', self) self.axisact.setStatusTip('Give names to the axis of the figure') self.axisact.triggered.connect(self.edit_axis) self.plotmenu.addAction(self.axisact) #reads axis names from the user and calls for repaint def edit_axis(self): text, ok = QInputDialog.getText( self, 'Enter new axis names', 'Enter new axis names in following format: x-name , y.name') if ok: xname, yname = text.split(",") self.xtitle = str(xname) self.ytitle = str(yname) self.title = self.title self.repaint() #draws axis names and title name. def draw_title(self, qp, plot_title, x_title, y_title): #sets pen for title color = QColor(Qt.black) pen = QPen(color, 1, Qt.SolidLine) qp.setFont(QFont('Decorative', 20)) qp.setPen(pen) #sets coordinates for x axis name: textx = QPointF(self.width() / 1.4, (self.ymax + 0.06 * self.ymax)) textpointxaxis = QPointF(self.width() / 1.35, (self.ymax + 0.06 * self.ymax)) #sets coordinates for y axis name: texty = QPointF((self.xmin - 0.3 * self.xmin), self.ymin - 0.4 * self.ymin) textpointyaxis = QPointF((self.xmin - 0.15 * self.xmin), self.ymin - 0.4 * self.ymin) #sets coordinates for title: textpointitle = QPointF((self.width() / 2.3), self.ymin - 0.2 * self.ymin) #draws title qp.drawText(textpointitle, plot_title) #sets pen smaller for axis names and draws them qp.setFont(QFont('Decorative', 10)) qp.setPen(pen) qp.drawText(textx, "x:") qp.drawText(texty, "y:") qp.drawText(textpointxaxis, x_title) qp.drawText(textpointyaxis, y_title) #creates the info box that includes the name and colour of all the datasets plotted def create_plot_info_box(self, qp): #selects the colour of the box and its outer lines color = QColor(Qt.black) pen = QPen(color, 1, Qt.SolidLine) qp.setBrush(Qt.white) qp.setPen(pen) #selects the height and width reserved for the name of one data set text_height = self.dist_between_ylines / 6 text_width = self.dist_between_xlines #draws the info box. First two parameters are coordinates and two latter are height and width of the box. #The height is determined by how many datasets there are and how many names needs to be written. qp.drawRect((self.xmax - 0.2 * self.xmax), (self.ymax - 0.2 * self.ymax), text_width, text_height * len(self.data_list) + self.dist_between_ylines * 0.14) for i in range(len(self.data_list)): #draw the name of every dataset color = QColor(Qt.black) pen = QPen(color, 1, Qt.SolidLine) qp.setFont(QFont('Decorative', 10)) qp.setPen(pen) textpoint = QPointF( (self.xmax - 0.16 * self.xmax), (self.ymax - 0.2 * self.ymax) + i * text_height + self.dist_between_ylines * 0.14) qp.drawText(textpoint, self.data_list[i].data_title) #draw matching point of every dataset pen = QPen(self.colour_list[i], 14, Qt.SolidLine, Qt.RoundCap) qp.setPen(pen) pointcoordinates = QPointF( (self.xmax - 0.18 * self.xmax), (self.ymax - 0.205 * self.ymax) + i * text_height + self.dist_between_ylines * 0.14) qp.drawPoint(pointcoordinates) #Verifies closing of the window: def closeEvent(self, event): #here we modify closeEvent -method's event handler to verify exit. reply = QMessageBox.question(self, 'Exit programme', "Are you sure you want to exit?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if reply == QMessageBox.Yes: event.accept() else: event.ignore()
def init_menubar(self): """Generates the main window menu bar.""" # Creates the actions for the main menu ### 'File' menu exit_action = QAction('E&xit', self) exit_action.setShortcut('Ctrl+Q') exit_action.setStatusTip('Exit smtracker') exit_action.triggered.connect(qApp.exit) export_action = QAction('&Export...', self) export_action.setShortcut('Ctrl+E') export_action.setStatusTip('Export table as HTML file') export_action.triggered.connect(self.export_html) open_action = QAction('&Open...', self) open_action.setShortcut('Ctrl+O') open_action.setStatusTip('Open a Stats.xml file') open_action.triggered.connect(self.open_file) ### 'Options' menu icons_action = QAction('Enable &icons', self) icons_action.setCheckable(True) icons_action.setChecked(self.icons_enabled) icons_action.triggered.connect(lambda: self.toggle_icons(icons_action.isChecked())) ### 'About' menu about_action = QAction('&About smtracker...', self) about_action.triggered.connect(self.about_box) qt_action = QAction('About &Qt...', self) qt_action.triggered.connect(QApplication.aboutQt) # Creates the menu bar and starts adding items to it menubar = self.menuBar() file_menu = menubar.addMenu('&File') file_menu.addAction(open_action) # Create the profile submenu and add the machine profile item profile_menu = file_menu.addMenu('Open &profile') mp_action = profile_menu.addAction('Machine Profile') # Define the location for profiles profile_folder, mp_folder = parse.get_profile_location() # Check if the machine profile exists if os.path.isfile(mp_folder + "Stats.xml") is True: no_mp = False mp_action.setStatusTip('Open this machine\'s profile') machine_profile = etree.parse(mp_folder + "Stats.xml").getroot() mp_action.triggered.connect(lambda: self.set_stats(machine_profile)) else: no_mp = True mp_action.setEnabled(False) # Check if there's any local profiles if os.path.isdir(profile_folder) is True: no_lp = False profile_menu.addSeparator() for profile in os.listdir(profile_folder): tempstats = etree.parse(profile_folder + profile + "/Stats.xml").getroot() tempname = parse.get_profile_name(tempstats) action = profile_menu.addAction(tempname) function = functools.partial(self.set_stats, tempstats) action.triggered.connect(function) else: no_lp = True # If there are no profiles at all, disable profile menu if no_mp is True and no_lp is True: profile_menu.setEnabled(False) # Add the rest of the actions to the menubar file_menu.addAction(export_action) file_menu.addAction(exit_action) options_menu = menubar.addMenu('&Options') options_menu.addAction(icons_action) about_menu = menubar.addMenu('&About') about_menu.addAction(about_action) about_menu.addAction(qt_action)
class MainWindow(QMainWindow): def __init__(self, parent): QMainWindow.__init__(self) self.printer = QPrinter() self.load_img = self.load_img_fit self.reload_img = self.reload_auto self.open_new = parent.open_win self.scene = QGraphicsScene() self.img_view = ImageView(self) self.img_view.setScene(self.scene) self.setCentralWidget(self.img_view) self.create_actions() self.create_menu() self.create_dict() self.create_toolbar() self.slides_next = True self.setContextMenuPolicy(Qt.CustomContextMenu) self.customContextMenuRequested.connect(self.showMenu) self.read_prefs() self.read_list = parent.read_list self.write_list = parent.write_list self.pics_dir = os.path.expanduser('~/Pictures') or QDir.currentPath() self.resize(700, 500) def create_actions(self): self.open_act = QAction('&Open', self, shortcut='Ctrl+O') self.open_act.triggered.connect(self.open) self.open_new_act = QAction('Open new window', self, shortcut='Ctrl+Shift+O') self.open_new_act.triggered.connect(partial(self.open, True)) self.reload_act = QAction('&Reload image', self, shortcut='Ctrl+R') self.reload_act.triggered.connect(self.reload_img) self.print_act = QAction('&Print', self, shortcut='Ctrl+P') self.print_act.triggered.connect(self.print_img) self.save_act = QAction('&Save image', self, shortcut='Ctrl+S') self.save_act.triggered.connect(self.save_img_as) self.close_act = QAction('Close window', self, shortcut='Ctrl+W') self.close_act.triggered.connect(self.close) self.exit_act = QAction('E&xit', self, shortcut='Ctrl+Q') self.exit_act.triggered.connect(self.exit) self.fulls_act = QAction('Fullscreen', self, shortcut='F11', checkable=True) self.fulls_act.triggered.connect(self.toggle_fs) self.ss_act = QAction('Slideshow', self, shortcut='F5', checkable=True) self.ss_act.triggered.connect(self.toggle_slideshow) self.ss_next_act = QAction('Next / Random image', self, checkable=True) self.ss_next_act.triggered.connect(self.set_slide_type) self.ss_next_act.setChecked(True) self.next_act = QAction('Next image', self, shortcut='Right') self.next_act.triggered.connect(self.go_next_img) self.prev_act = QAction('Previous image', self, shortcut='Left') self.prev_act.triggered.connect(self.go_prev_img) self.rotleft_act = QAction('Rotate left', self, shortcut='Ctrl+Left') self.rotleft_act.triggered.connect(partial(self.img_rotate, 270)) self.rotright_act = QAction('Rotate right', self, shortcut='Ctrl+Right') self.rotright_act.triggered.connect(partial(self.img_rotate, 90)) self.fliph_act = QAction('Flip image horizontally', self, shortcut='Ctrl+H') self.fliph_act.triggered.connect(partial(self.img_flip, -1, 1)) self.flipv_act = QAction('Flip image vertically', self, shortcut='Ctrl+V') self.flipv_act.triggered.connect(partial(self.img_flip, 1, -1)) self.resize_act = QAction('Resize image', self, triggered=self.resize_img) self.crop_act = QAction('Crop image', self, triggered=self.crop_img) self.zin_act = QAction('Zoom &In', self, shortcut='Up') self.zin_act.triggered.connect(partial(self.img_view.zoom, 1.1)) self.zout_act = QAction('Zoom &Out', self, shortcut='Down') self.zout_act.triggered.connect(partial(self.img_view.zoom, 1 / 1.1)) self.fit_win_act = QAction('Best &fit', self, checkable=True, shortcut='F', triggered=self.zoom_default) self.fit_win_act.setChecked(True) self.prefs_act = QAction('Preferences', self, triggered=self.set_prefs) self.props_act = QAction('Properties', self, triggered=self.get_props) self.help_act = QAction('&Help', self, shortcut='F1', triggered=self.help_page) self.about_act = QAction('&About', self, triggered=self.about_cm) self.aboutQt_act = QAction('About &Qt', self, triggered=qApp.aboutQt) def create_menu(self): self.popup = QMenu(self) main_acts = [ self.open_act, self.open_new_act, self.reload_act, self.print_act, self.save_act ] edit_acts1 = [ self.rotleft_act, self.rotright_act, self.fliph_act, self.flipv_act ] edit_acts2 = [self.resize_act, self.crop_act] view_acts = [ self.next_act, self.prev_act, self.zin_act, self.zout_act, self.fit_win_act, self.fulls_act, self.ss_act, self.ss_next_act ] help_acts = [self.help_act, self.about_act, self.aboutQt_act] end_acts = [ self.prefs_act, self.props_act, self.close_act, self.exit_act ] for act in main_acts: self.popup.addAction(act) edit_menu = QMenu(self.popup) edit_menu.setTitle('&Edit') for act in edit_acts1: edit_menu.addAction(act) edit_menu.addSeparator() for act in edit_acts2: edit_menu.addAction(act) self.popup.addMenu(edit_menu) view_menu = QMenu(self.popup) view_menu.setTitle('&View') for act in view_acts: view_menu.addAction(act) self.popup.addMenu(view_menu) help_menu = QMenu(self.popup) help_menu.setTitle('&Help') for act in help_acts: help_menu.addAction(act) self.popup.addMenu(help_menu) for act in end_acts: self.popup.addAction(act) self.action_list = main_acts + edit_acts1 + edit_acts2 + view_acts + help_acts + end_acts for act in self.action_list: self.addAction(act) def showMenu(self, pos): self.popup.popup(self.mapToGlobal(pos)) def create_dict(self): """Create a dictionary to handle auto-orientation.""" self.orient_dict = { None: self.load_img, '1': self.load_img, '2': partial(self.img_flip, -1, 1), '3': partial(self.img_rotate, 180), '4': partial(self.img_flip, -1, 1), '5': self.img_rotate_fliph, '6': partial(self.img_rotate, 90), '7': self.img_rotate_flipv, '8': partial(self.img_rotate, 270) } def create_toolbar(self): script_dir = os.path.dirname(os.path.realpath(__file__)) def icon(icon_name): return QIcon(os.path.join(script_dir, "assets", icon_name)) def add_action(description, icon_file, function): action = QAction(icon(icon_file), description, self) action.triggered.connect(function) self.toolbar.addAction(action) self.toolbar = self.addToolBar("File") add_action("save", "save.png", self.save_img) add_action("crop", "crop.png", self.crop_img) add_action("resize", "resize.png", self.resize_img) add_action("save important", "star.png", lambda: self.save_img(rating=100)) add_action("save non important", "hollow_star.png", lambda: self.save_img(rating=0)) def read_prefs(self): """Parse the preferences from the config file, or set default values.""" try: conf = preferences.Config() values = conf.read_config() self.auto_orient = values[0] self.slide_delay = values[1] self.quality = values[2] except: self.auto_orient = True self.slide_delay = 5 self.quality = 90 self.reload_img = self.reload_auto if self.auto_orient else self.reload_nonauto def set_prefs(self): """Write preferences to the config file.""" dialog = preferences.PrefsDialog(self) if dialog.exec_() == QDialog.Accepted: self.auto_orient = dialog.auto_orient self.slide_delay = dialog.delay_spinb.value() self.quality = dialog.qual_spinb.value() conf = preferences.Config() conf.write_config(self.auto_orient, self.slide_delay, self.quality) self.reload_img = self.reload_auto if self.auto_orient else self.reload_nonauto def open(self, new_win=False): fname = QFileDialog.getOpenFileName(self, 'Open File', self.pics_dir)[0] if fname: if fname.lower().endswith(self.read_list): if new_win: self.open_new(fname) else: self.open_img(fname) else: QMessageBox.information( self, 'Error', 'Cannot load {} images.'.format(fname.rsplit('.', 1)[1])) def open_img(self, fname): self.fname = fname self.reload_img() dirname = os.path.dirname(self.fname) self.set_img_list(dirname) self.img_index = self.filelist.index(self.fname) def set_img_list(self, dirname): """Create a list of readable images from the current directory.""" filelist = os.listdir(dirname) self.filelist = [ os.path.join(dirname, fname) for fname in filelist if fname.lower().endswith(self.read_list) ] self.filelist.sort() self.last_file = len(self.filelist) - 1 def set_title(self): file_name = self.fname.rsplit('/', 1)[1] size = " [%d x %d]" % (self.pixmap.width(), self.pixmap.height()) self.setWindowTitle(file_name + size) def get_img(self): """Get image from fname and create pixmap.""" image = QImage(self.fname) self.pixmap = QPixmap.fromImage(image) def reload_auto(self): """Load a new image with auto-orientation.""" self.get_img() try: orient = GExiv2.Metadata(self.fname)['Exif.Image.Orientation'] self.orient_dict[orient]() except: self.load_img() def reload_nonauto(self): """Load a new image without auto-orientation.""" self.get_img() self.load_img() def load_img_fit(self): """Load the image to fit the window.""" self.scene.clear() self.scene.addPixmap(self.pixmap) self.scene.setSceneRect(0, 0, self.pixmap.width(), self.pixmap.height()) self.img_view.fitInView(self.scene.sceneRect(), Qt.KeepAspectRatio) self.set_title() def load_img_1to1(self): """Load the image at its original size.""" self.scene.clear() self.img_view.resetTransform() self.scene.addPixmap(self.pixmap) self.scene.setSceneRect(0, 0, self.pixmap.width(), self.pixmap.height()) pixitem = QGraphicsPixmapItem(self.pixmap) self.img_view.centerOn(pixitem) self.set_title() def go_next_img(self): self.img_index = self.img_index + 1 if self.img_index < self.last_file else 0 self.fname = self.filelist[self.img_index] self.reload_img() def go_prev_img(self): self.img_index = self.img_index - 1 if self.img_index else self.last_file self.fname = self.filelist[self.img_index] self.reload_img() def zoom_default(self): """Toggle best fit / original size loading.""" if self.fit_win_act.isChecked(): self.load_img = self.load_img_fit self.create_dict() self.load_img() else: self.load_img = self.load_img_1to1 self.create_dict() self.load_img() def img_rotate(self, angle): self.pixmap = self.pixmap.transformed(QTransform().rotate(angle)) self.load_img() def img_flip(self, x, y): self.pixmap = self.pixmap.transformed(QTransform().scale(x, y)) self.load_img() def img_rotate_fliph(self): self.img_rotate(90) self.img_flip(-1, 1) def img_rotate_flipv(self): self.img_rotate(90) self.img_flip(1, -1) def resize_img(self): dialog = editimage.ResizeDialog(self, self.pixmap.width(), self.pixmap.height()) if dialog.exec_() == QDialog.Accepted: width = dialog.get_width.value() height = dialog.get_height.value() self.pixmap = self.pixmap.scaled(width, height, Qt.IgnoreAspectRatio, Qt.SmoothTransformation) self.load_img() def crop_img(self): def callback(coords): self.pixmap = self.pixmap.copy(*coords) self.load_img() self.img_view.crop(callback) def toggle_fs(self): if self.fulls_act.isChecked(): self.showFullScreen() else: self.showNormal() def toggle_slideshow(self): if self.ss_act.isChecked(): self.showFullScreen() self.start_ss() else: self.toggle_fs() self.timer.stop() self.ss_timer.stop() def start_ss(self): self.timer = QTimer() self.timer.timeout.connect(self.update_img) self.timer.start(self.slide_delay * 1000) self.ss_timer = QTimer() self.ss_timer.timeout.connect(self.update_img) self.ss_timer.start(60000) def update_img(self): if self.slides_next: self.go_next_img() else: self.fname = random.choice(self.filelist) self.reload_img() def set_slide_type(self): self.slides_next = self.ss_next_act.isChecked() def save_img_as(self): fname = QFileDialog.getSaveFileName(self, 'Save your image', self.fname)[0] if fname: if fname.lower().endswith(self.write_list): keep_exif = QMessageBox.question( self, 'Save exif data', 'Do you want to save the picture metadata?', QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes) if keep_exif == QMessageBox.Yes: exif = GExiv2.Metadata(self.fname) self.pixmap.save(fname, None, self.quality) if exif: saved_exif = GExiv2.Metadata(fname) for tag in exif.get_exif_tags(): saved_exif[tag] = exif[tag] saved_exif.set_orientation(GExiv2.Orientation.NORMAL) saved_exif.save_file() else: QMessageBox.information( self, 'Error', 'Cannot save {} images.'.format(fname.rsplit('.', 1)[1])) def save_img(self, rating=None): exif = GExiv2.Metadata(self.fname) self.pixmap.save(self.fname, None, self.quality) if rating is not None: exif["Exif.Image.Rating"] = str(rating) if exif: exif.save_file() def print_img(self): dialog = QPrintDialog(self.printer, self) if dialog.exec_(): painter = QPainter(self.printer) rect = painter.viewport() if self.pixmap.width() > self.pixmap.height(): self.pixmap = self.pixmap.transformed(QTransform().rotate(90)) size = self.pixmap.size() size.scale(rect.size(), Qt.KeepAspectRatio) painter.setViewport(rect.x(), rect.y(), size.width(), size.height()) painter.setWindow(self.pixmap.rect()) painter.drawPixmap(0, 0, self.pixmap) def resizeEvent(self, event=None): if self.fit_win_act.isChecked(): try: self.load_img() except: pass def get_props(self): """Get the properties of the current image.""" image = QImage(self.fname) preferences.PropsDialog(self, self.fname.rsplit('/', 1)[1], image.width(), image.height()) def help_page(self): preferences.HelpDialog(self) def about_cm(self): about_message = 'Version: 0.3.9\nAuthor: David Whitlock\nLicense: GPLv3' QMessageBox.about(self, 'About Cheesemaker', about_message) def exit(self): QCoreApplication.quit()
class ImageViewer(QMainWindow): def __init__(self): super(ImageViewer, self).__init__() self.printer = QPrinter() self.scaleFactor = 0.0 self.imageLabel = QLabel() self.imageLabel.setBackgroundRole(QPalette.Base) self.imageLabel.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored) self.imageLabel.setScaledContents(True) self.scrollArea = QScrollArea() self.scrollArea.setBackgroundRole(QPalette.Dark) self.scrollArea.setWidget(self.imageLabel) self.setCentralWidget(self.scrollArea) self.createActions() self.createMenus() self.initUI() self.setWindowTitle("Encrypter Pictures") self.resize(500, 400) def initUI(self): encrypt = QAction(QIcon(CURRENT_DIR + '/encrypt.png'), 'Encrypt', self) encrypt.setShortcut('Ctrl+D') encrypt.triggered.connect(self.buttonClicked) open_file= QAction(QIcon(CURRENT_DIR + '/open.png'), 'Exit', self) open_file.triggered.connect(self.open) exitAction = QAction(QIcon(CURRENT_DIR + '/exit24.png'), 'Exit', self) exitAction.triggered.connect(qApp.quit) self.toolbar = self.addToolBar('Exit') self.toolbar.addAction(open_file) self.toolbar.addAction(encrypt) self.toolbar.addAction(exitAction) self.statusBar() def buttonClicked(self): try: self.statusBar().showMessage("Encrypting: " + self.fileName) self.output_path = cript(self.fileName) self.show_image() self.statusBar().showMessage("Salve on: " + self.output_path) except AttributeError: self.statusBar().showMessage("Select an image") def show_image(self): """docstring for show_image""" if self.output_path: image = QImage(self.output_path) if image.isNull(): QMessageBox.information(self, "Image Viewer", "Cannot load %s." % self.output_path) return self.imageLabel.setPixmap(QPixmap.fromImage(image)) self.scaleFactor = 1.0 self.printAct.setEnabled(True) self.fitToWindowAct.setEnabled(True) self.updateActions() if not self.fitToWindowAct.isChecked(): self.imageLabel.adjustSize() def open(self): self.fileName, _ = QFileDialog.getOpenFileName(self, "Open File", QDir.currentPath()) if self.fileName: image = QImage(self.fileName) if image.isNull(): QMessageBox.information(self, "Image Viewer", "Cannot load %s." % self.fileName) return self.imageLabel.setPixmap(QPixmap.fromImage(image)) self.scaleFactor = 1.0 self.printAct.setEnabled(True) self.fitToWindowAct.setEnabled(True) self.updateActions() if not self.fitToWindowAct.isChecked(): self.imageLabel.adjustSize() def print_(self): dialog = QPrintDialog(self.printer, self) if dialog.exec_(): painter = QPainter(self.printer) rect = painter.viewport() size = self.imageLabel.pixmap().size() size.scale(rect.size(), Qt.KeepAspectRatio) painter.setViewport( rect.x(), rect.y(), size.width(), size.height() ) painter.setWindow(self.imageLabel.pixmap().rect()) painter.drawPixmap(0, 0, self.imageLabel.pixmap()) def zoomIn(self): self.scaleImage(1.25) def zoomOut(self): self.scaleImage(0.8) def normalSize(self): self.imageLabel.adjustSize() self.scaleFactor = 1.0 def fitToWindow(self): fitToWindow = self.fitToWindowAct.isChecked() self.scrollArea.setWidgetResizable(fitToWindow) if not fitToWindow: self.normalSize() self.updateActions() def about(self): QMessageBox.about(self, "About Encrypter Pictures", "<p>The <b>Encrypter Pictures</b> is a software to encrypt and" "decrypting any given image") def createActions(self): self.openAct = QAction( QIcon(CURRENT_DIR + '/open.png'), "&Open...", self, shortcut="Ctrl+O", triggered=self.open ) self.printAct = QAction( QIcon(CURRENT_DIR + '/print.png'), "&Print...", self, shortcut="Ctrl+P", enabled=False, triggered=self.print_ ) self.exitAct = QAction( QIcon(CURRENT_DIR + '/exit24.png'), "E&xit", self, shortcut="Ctrl+Q", triggered=self.close ) self.zoomInAct = QAction( QIcon(CURRENT_DIR + '/zoom_in.png'), "Zoom &In (25%)", self, shortcut="Ctrl++", enabled=False, triggered=self.zoomIn ) self.zoomOutAct = QAction( QIcon(CURRENT_DIR + '/zoom_out.png'), "Zoom &Out (25%)", self, shortcut="Ctrl+-", enabled=False, triggered=self.zoomOut ) self.normalSizeAct = QAction( QIcon(CURRENT_DIR + '/zoom.png'), "&Normal Size", self, shortcut="Ctrl+S", enabled=False, triggered=self.normalSize ) self.fitToWindowAct = QAction( QIcon(CURRENT_DIR + '/expand.png'), "&Fit to Window", self, enabled=False, checkable=True, shortcut="Ctrl+F", triggered=self.fitToWindow ) self.aboutAct = QAction( QIcon(CURRENT_DIR + '/info.png'), "&About", self, triggered=self.about ) self.aboutQtAct = QAction( QIcon(CURRENT_DIR + '/pyqt.png'), "About &Qt", self, triggered=QApplication.instance().aboutQt ) def createMenus(self): self.fileMenu = QMenu("&File", self) self.fileMenu.addAction(self.openAct) self.fileMenu.addAction(self.printAct) self.fileMenu.addSeparator() self.fileMenu.addAction(self.exitAct) self.viewMenu = QMenu("&View", self) self.viewMenu.addAction(self.zoomInAct) self.viewMenu.addAction(self.zoomOutAct) self.viewMenu.addAction(self.normalSizeAct) self.viewMenu.addSeparator() self.viewMenu.addAction(self.fitToWindowAct) self.helpMenu = QMenu("&Help", self) self.helpMenu.addAction(self.aboutAct) self.helpMenu.addAction(self.aboutQtAct) self.menuBar().addMenu(self.fileMenu) self.menuBar().addMenu(self.viewMenu) self.menuBar().addMenu(self.helpMenu) def updateActions(self): self.zoomInAct.setEnabled(not self.fitToWindowAct.isChecked()) self.zoomOutAct.setEnabled(not self.fitToWindowAct.isChecked()) self.normalSizeAct.setEnabled(not self.fitToWindowAct.isChecked()) def scaleImage(self, factor): self.scaleFactor *= factor self.imageLabel.resize(self.scaleFactor * self.imageLabel.pixmap().size()) self.adjustScrollBar(self.scrollArea.horizontalScrollBar(), factor) self.adjustScrollBar(self.scrollArea.verticalScrollBar(), factor) self.zoomInAct.setEnabled(self.scaleFactor < 3.0) self.zoomOutAct.setEnabled(self.scaleFactor > 0.333) def adjustScrollBar(self, scrollBar, factor): scrollBar.setValue(int(factor * scrollBar.value() + ((factor - 1) * scrollBar.pageStep()/2)))
class ImageViewer(QWidget): def __init__(self): super(ImageViewer, self).__init__() pal = QPalette() pal.setColor(QPalette.Background, Qt.lightGray) self.factor = 3.0 self.config = Config() self.currentRep = "" self.createActions() self.createToolbarMenus() #self.createMenus() self.browserFile() self.imgqLabel() self.boxSliders() self.verticalLayout = QVBoxLayout(self) self.horizontalLayout = QHBoxLayout(self) self.textInfo = QTextEdit() self.textInfoTop = QTextEdit() self.textInfoTop.setEnabled(True) self.textInfoTop.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Ignored) self.textInfoTop.setFontPointSize(11) self.textInfoTop.setStyleSheet("background-color: lightgray") #self.textInfoTop.adjustSize() self.textInfoTop.setText('Welcome to IRMaGe') self.tableJson = QTableWidget() self.tableJson.setColumnCount(2) self.tableJson.setColumnWidth(0, 150) self.tableJson.setColumnWidth(1, 400) self.tableJson.setSizeAdjustPolicy( QAbstractScrollArea.AdjustToContents) self.tableJson.setHorizontalHeaderLabels(['Keys', 'Values']) #self.tableJson.setBackgroundRole(QPalette.Light) self.scrollText = QScrollArea() self.scrollText.setBackgroundRole(QPalette.Dark) self.scrollText.setWidget(self.textInfoTop) self.scrollText.setWidgetResizable(True) #======================================================================= # self.adjustScrollBar(self.scrollText.horizontalScrollBar(), 1.0) # self.adjustScrollBar(self.scrollText.verticalScrollBar(), 2.0) #======================================================================= self.scrollTable = QScrollArea() self.scrollTable.setBackgroundRole(QPalette.Dark) self.scrollTable.setWidget(self.tableJson) self.scrollTable.setWidgetResizable(True) #======================================================================= # self.adjustScrollBar(self.scrollTable.horizontalScrollBar(), 2.0) # self.adjustScrollBar(self.scrollTable.verticalScrollBar(), 2.0) #======================================================================= self.headerTabData = [ 'Data', 'PatientName', 'StudyName', 'DateCreation', 'PatientSex', 'PatientWeight', 'ProtocolName', 'SequenceName' ] self.tableData = TableDataBrower(self) self.tableData.setColumnCount(8) self.tableData.setRowCount(10) self.tableData.setColumnWidth(0, 200) self.tableData.setHorizontalHeaderLabels(self.headerTabData) self.tableData.setBackgroundRole(QPalette.Light) self.tableData.setSizeAdjustPolicy( QAbstractScrollArea.AdjustToContents) self.tableData.verticalHeader().hide() self.scrollBrowser = QScrollArea() self.scrollBrowser.setBackgroundRole(QPalette.Dark) self.scrollBrowser.setWidget(self.tableData) self.scrollBrowser.setWidgetResizable(True) self.splitter0 = QSplitter(Qt.Vertical) self.splitter0.addWidget(self.scrollText) self.splitter0.addWidget(self.scrollTable) self.scrollArea = QScrollArea() self.scrollArea.setBackgroundRole(QPalette.Dark) self.scrollArea.setWidget(self.imageLabel) self.scrollArea.setWidgetResizable(False) self.scrollArea.setAlignment(Qt.AlignCenter) self.adjustScrollBar(self.scrollArea.horizontalScrollBar(), 0.8) self.adjustScrollBar(self.scrollArea.verticalScrollBar(), 1.0) self.splitter1 = QSplitter(Qt.Horizontal) self.splitter1.addWidget(self.splitter0) self.splitter1.addWidget(self.scrollArea) self.splitter1.addWidget(self.layoutSlide) self.splitter3 = QSplitter(Qt.Horizontal) self.splitter3.addWidget(self.browser) self.splitter3.addWidget(self.scrollBrowser) self.splitter2 = QSplitter(Qt.Vertical) self.splitter2.addWidget(self.splitter1) self.splitter2.addWidget(self.splitter3) self.splitter2.setHandleWidth(15) #======================================================================= # self.splitter2. #======================================================================= self.verticalLayout.addWidget(self.menuToolBar) self.verticalLayout.addWidget(self.splitter2) self.setWindowTitle("MRImage Viewer (IRMaGe)") self.resize(800, 600) self.setAutoFillBackground(True) self.setPalette(pal) def changeSel(self): print('Tab changed') def adjustScrollBar(self, scrollBar, factor): scrollBar.setValue( int(factor * scrollBar.value() + ((factor - 1) * scrollBar.pageStep() / 2))) def imgqLabel(self): QLabel.__init__(self) image = QImage('sources_images/LogoIRMaGe.png') self.scaleFactor = 1.0 self.imageLabel = QLabel() self.imageLabel.setBackgroundRole(QPalette.Base) self.imageLabel.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored) self.imageLabel.setScaledContents(True) self.imageLabel.setPixmap(QPixmap.fromImage(image)) self.scaleFactor *= self.factor self.imageLabel.adjustSize() self.imageLabel.resize(self.scaleFactor * self.imageLabel.pixmap().size()) def open(self, filePath): self.img = nib.load(filePath) self.textInfoTop.setText('File : ' + filePath + '\n') self.textInfoTop.append('Dim : ' + str(self.img.shape) + '\n') self.enableSliders() self.a1.setValue(0) self.a2.setValue(0) self.a3.setValue(0) self.c2.setMaximum(self.img.shape[0]) self.c2.setMinimum(-self.img.shape[0]) self.c3.setMaximum(self.img.shape[1]) self.c3.setMinimum(-self.img.shape[1]) self.navigImage() self.fitToWindowAct.setEnabled(True) self.fitToWindow() def openJson(self, pathJson, fileName): with open(pathJson, 'r') as stream: try: json_object = json.load(stream) data = json.dumps(json_object, indent=0, sort_keys=True) data = json.loads(data) rowPosition = 0 self.tableJson.setRowCount(0) i = 0 for keyd in self.headerTabData: try: val = str(data[keyd]) val = val.replace('[', '') val = val.replace(']', '') except: val = '' #=========================================================== # self.tableData.insertRow(i) # self.tableData.setItem(0,i,QTableWidgetItem(val)) # i+=1 #=========================================================== #=============================================================== # self.tableData.setItem(0,0,QTableWidgetItem(fileName)) # self.tableData.selectRow(0) #=============================================================== for keys in data: stringValue = str(data[keys]) stringValue = stringValue.replace('[', '') stringValue = stringValue.replace(']', '') self.tableJson.insertRow(rowPosition) self.tableJson.setItem(rowPosition, 0, QTableWidgetItem(keys)) self.tableJson.setItem(rowPosition, 1, QTableWidgetItem(stringValue)) rowPosition += 1 self.tableJson.resizeColumnsToContents() except json.JSONDecodeError as exc: itemError = 'Error Json format' self.tableJson.setRowCount(0) self.tableJson.insertRow(0) self.tableJson.setItem(0, 0, QTableWidgetItem(itemError)) print(exc) def jsonParser(self, pathJson): with open(pathJson, 'r') as stream: try: json_object = json.load(stream) listTag = json.dumps(json_object, indent=0, sort_keys=True) listTag = json.loads(listTag) except json.JSONDecodeError as exc: itemError = 'Error Json format' return listTag def tableDataFill(self, pathRepertory): files = [f for f in fnmatch.filter(os.listdir(pathRepertory), '*.nii')] self.tableData.setRowCount(0) j = 0 for f in files: base = os.path.splitext(f)[0] g = os.path.join(pathRepertory, base + ".json") self.tableData.insertRow(j) if os.path.isfile(g): data = self.jsonParser(g) i = 0 for keyw in self.headerTabData: try: val = str(data[keyw]) val = val.replace('[', '') val = val.replace(']', '') except: val = '' self.tableData.setItem(j, i, QTableWidgetItem(val)) i += 1 else: self.tableData.setItem(j, 1, QTableWidgetItem('No json file found')) self.tableData.setItem(j, 0, QTableWidgetItem(f)) self.tableData.resizeColumnsToContents() j += 1 def indexImage(self): sl1 = self.a1.value() sl2 = self.a2.value() sl3 = self.a3.value() if len(self.img.shape) == 3: x = self.img.get_data()[:, :, sl1].copy() self.a1.setMaximum(self.img.shape[2] - 1) self.a2.setMaximum(0) self.a3.setMaximum(0) if len(self.img.shape) == 4: x = self.img.get_data()[:, :, sl1, sl2].copy() self.a1.setMaximum(self.img.shape[2] - 1) self.a2.setMaximum(self.img.shape[3] - 1) self.a3.setMaximum(0) if len(self.img.shape) == 5: x = self.img.get_data()[:, :, sl1, sl2, sl3].copy() self.a1.setMaximum(self.img.shape[2] - 1) self.a2.setMaximum(self.img.shape[3] - 1) self.a3.setMaximum(self.img.shape[4] - 1) x = rotate(x, -90, reshape=False) x = np.uint8((x - x.min()) / x.ptp() * 255.0) self.x = x ############################ Slice controls ######################################### def boxSliders(self): self.k1 = QLabel('Slider 1 ') self.k2 = QLabel('Slider 2') self.k3 = QLabel('Slider 3') self.a1 = self.createSlider(0, 0, 0) self.a2 = self.createSlider(0, 0, 0) self.a3 = self.createSlider(0, 0, 0) self.a1.valueChanged.connect(self.changePosValue) self.a2.valueChanged.connect(self.changePosValue) self.a3.valueChanged.connect(self.changePosValue) self.txta1 = self.createFieldValue() self.txta2 = self.createFieldValue() self.txta3 = self.createFieldValue() self.controlsGroup = QGroupBox('Slice Controls') gridCtrl = QGridLayout() gridCtrl.addWidget(self.k1, 0, 0) gridCtrl.addWidget(self.a1, 0, 1) gridCtrl.addWidget(self.txta1, 0, 2) gridCtrl.addWidget(self.k2, 1, 0) gridCtrl.addWidget(self.a2, 1, 1) gridCtrl.addWidget(self.txta2, 1, 2) gridCtrl.addWidget(self.k3, 2, 0) gridCtrl.addWidget(self.a3, 2, 1) gridCtrl.addWidget(self.txta3, 2, 2) self.controlsGroup.setLayout(gridCtrl) ############################ brightness and contrast ################################ self.txtb1 = self.createFieldValue() self.txtb2 = self.createFieldValue() self.txtb3 = self.createFieldValue() self.txtb4 = self.createFieldValue() self.l1 = QLabel('Brightness ') self.b1 = self.createSlider(101, 0, 50) self.l2 = QLabel('Contrast') self.b2 = self.createSlider(101, 0, 50) self.l3 = QLabel('Sharpness') self.b3 = self.createSlider(101, 0, 50) self.l4 = QLabel('Color') self.b4 = self.createSlider(101, 0, 50) self.b1.valueChanged.connect(self.changeContValue) self.b2.valueChanged.connect(self.changeContValue) self.b3.valueChanged.connect(self.changeContValue) self.b4.valueChanged.connect(self.changeContValue) self.txtb1.setText(str(0)) self.txtb2.setText(str(0)) self.txtb3.setText(str(0)) self.txtb4.setText(str(0)) self.buttonResetContrast = QPushButton('reset', self) self.buttonResetContrast.setToolTip('Reset all values') self.buttonResetContrast.setEnabled(False) self.buttonResetContrast.clicked.connect(self.resetValuesContrast) self.contrastGroup = QGroupBox('Brightness and Contrast') gridCont = QGridLayout() gridCont.addWidget(self.l1, 0, 0) gridCont.addWidget(self.b1, 0, 1) gridCont.addWidget(self.txtb1, 0, 2) gridCont.addWidget(self.l2, 1, 0) gridCont.addWidget(self.b2, 1, 1) gridCont.addWidget(self.txtb2, 1, 2) gridCont.addWidget(self.l3, 2, 0) gridCont.addWidget(self.b3, 2, 1) gridCont.addWidget(self.txtb3, 2, 2) gridCont.addWidget(self.l4, 3, 0) gridCont.addWidget(self.b4, 3, 1) gridCont.addWidget(self.txtb4, 3, 2) gridCont.addWidget(self.buttonResetContrast, 4, 2) self.contrastGroup.setLayout(gridCont) ############################ Transformation ######################################### self.txtc1 = self.createFieldValue() self.txtc2 = self.createFieldValue() self.txtc3 = self.createFieldValue() self.txtc4 = self.createFieldValue() self.m1 = QLabel('Rotation') self.c1 = self.createSlider(180, -180, 0) self.m2 = QLabel('Translate X ') self.c2 = self.createSlider(1, -1, 0) self.m3 = QLabel('Translate Y ') self.c3 = self.createSlider(1, -1, 0) self.m4 = QLabel('Resize') self.c4 = self.createSlider(10, 0, 0) self.c1.valueChanged.connect(self.changeTransValue) self.c2.valueChanged.connect(self.changeTransValue) self.c3.valueChanged.connect(self.changeTransValue) self.c4.valueChanged.connect(self.changeTransValue) self.txtc1.setText(str(0)) self.txtc2.setText(str(0)) self.txtc3.setText(str(0)) self.txtc4.setText(str(0)) self.buttonResetTransform = QPushButton('reset', self) self.buttonResetTransform.setToolTip('Reset all values') self.buttonResetTransform.setEnabled(False) self.buttonResetTransform.clicked.connect(self.resetValuesTransform) self.transformationGroup = QGroupBox('Transformations') gridTransf = QGridLayout() gridTransf.addWidget(self.m1, 0, 0) gridTransf.addWidget(self.c1, 0, 1) gridTransf.addWidget(self.txtc1, 0, 2) gridTransf.addWidget(self.m2, 1, 0) gridTransf.addWidget(self.c2, 1, 1) gridTransf.addWidget(self.txtc2, 1, 2) gridTransf.addWidget(self.m3, 2, 0) gridTransf.addWidget(self.c3, 2, 1) gridTransf.addWidget(self.txtc3, 2, 2) gridTransf.addWidget(self.m4, 3, 0) gridTransf.addWidget(self.c4, 3, 1) gridTransf.addWidget(self.txtc4, 3, 2) gridTransf.addWidget(self.buttonResetTransform, 4, 2) self.transformationGroup.setLayout(gridTransf) #################################################################################### self.layoutSliders = QVBoxLayout() self.layoutSliders.addWidget(self.controlsGroup) self.layoutSliders.addWidget(self.contrastGroup) self.layoutSliders.addWidget(self.transformationGroup) self.layoutSlide = QWidget() self.layoutSlide.setLayout(self.layoutSliders) def createSlider(self, maxm=0, minm=0, pos=0): slider = QSlider(Qt.Horizontal) slider.setFocusPolicy(Qt.StrongFocus) #slider.setTickPosition(QSlider.TicksBothSides) slider.setTickInterval(1) #slider.setSingleStep(1) slider.setMaximum(maxm) slider.setMinimum(minm) slider.setValue(pos) slider.setEnabled(False) return slider def createFieldValue(self): fieldValue = QLineEdit() fieldValue.setEnabled(False) fieldValue.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) return fieldValue def displayPosValue(self): self.txta1.setText( str(self.a1.value() + 1) + ' / ' + str(self.a1.maximum() + 1)) self.txta2.setText( str(self.a2.value() + 1) + ' / ' + str(self.a2.maximum() + 1)) self.txta3.setText( str(self.a3.value() + 1) + ' / ' + str(self.a3.maximum() + 1)) def changePosValue(self): self.navigImage() def navigImage(self): self.indexImage() self.displayPosValue() w, h = self.x.shape image = QImage(self.x.data, w, h, QImage.Format_Indexed8) self.pixm = QPixmap.fromImage(image) self.imageLabel.setPixmap(self.pixm) self.imageLabel.adjustSize() self.imageLabel.resize(self.scaleFactor * self.imageLabel.pixmap().size()) self.filter() def changeContValue(self): self.txtb1.setText(str(self.b1.value() - 50)) self.txtb2.setText(str(self.b2.value() - 50)) self.txtb3.setText(str(self.b3.value() - 50)) self.txtb4.setText(str(self.b4.value() - 50)) self.filter() def changeTransValue(self): self.txtc1.setText(str(self.c1.value())) self.txtc2.setText(str(self.c2.value())) self.txtc3.setText(str(self.c3.value())) self.txtc4.setText(str(self.c4.value())) self.filter() def filter(self): img = Image.fromarray(self.x, 'L') brightness = ImageEnhance.Brightness(img) newImg = brightness.enhance(1.2 * (self.b1.value() + 1) / 50.0) contrast = ImageEnhance.Contrast(newImg) newImg = contrast.enhance((self.b2.value() + 1) / 50.0) sharpness = ImageEnhance.Sharpness(newImg) newImg = sharpness.enhance(2.0 * (self.b3.value() + 1) / 50.0) color = ImageEnhance.Color(newImg) newImg = color.enhance((self.b4.value() + 1) / 50.0) newImg = newImg.rotate(self.c1.value()) newImg = newImg.transform( img.size, Image.AFFINE, (1, 0, self.c2.value(), 0, 1, self.c3.value())) size1 = int(img.size[0] * (self.c4.value() + 1)) size2 = int(img.size[1] * (self.c4.value() + 1)) newImg = newImg.resize((size1, size2), Image.ANTIALIAS) self.pixm = QPixmap.fromImage(newImg.toqimage()) self.imageLabel.setPixmap(self.pixm) self.imageLabel.adjustSize() self.imageLabel.resize(self.scaleFactor * self.imageLabel.pixmap().size()) def resetValuesContrast(self): self.b1.setSliderPosition(50) self.b2.setSliderPosition(50) self.b3.setSliderPosition(50) self.b4.setSliderPosition(50) self.changeContValue() def resetValuesTransform(self): self.c1.setSliderPosition(0) self.c2.setSliderPosition(0) self.c3.setSliderPosition(0) self.c4.setSliderPosition(0) self.changeTransValue() def enableSliders(self): self.a1.setEnabled(True) self.a2.setEnabled(True) self.a3.setEnabled(True) self.b1.setEnabled(True) self.b2.setEnabled(True) self.b3.setEnabled(True) self.b4.setEnabled(True) self.c1.setEnabled(True) self.c2.setEnabled(True) self.c3.setEnabled(True) self.c4.setEnabled(True) self.buttonResetContrast.setEnabled(True) self.buttonResetTransform.setEnabled(True) #################################################################################### def browserFile(self): global Browser, Model self.browser = QTreeView() model = QFileSystemModel() model.setNameFilters(['*.nii']) model.setNameFilterDisables(False) model.setReadOnly(True) self.browser.setModel(model) self.browser.expandAll() self.browser.setColumnWidth(0, 400) self.browser.selectionModel().selectionChanged.connect(self.select) Browser = self.browser Model = model #======================================================================= # self.browser.doubleClicked.connect(self.selection) #self.browser.clicked.connect(self.selection) #======================================================================= def select(self, signal): file_path = self.browser.model().filePath(signal.indexes()[0]) shortName, fileExt = os.path.splitext(file_path) filePath, fileName = os.path.split(file_path) self.textInfo.setText(filePath) blackColor = QColor(0, 0, 0) if os.path.isfile(file_path): if fileExt == ".nii": if self.currentRep != filePath: self.tableDataFill(filePath) self.currentRep = filePath self.open(file_path) self.tableData.selectRow( self.tableData.findItems(fileName, Qt.MatchExactly)[0].row()) if os.path.isfile(shortName + '.json'): greenColor = QColor(50, 150, 100) self.textInfoTop.setTextColor(greenColor) self.textInfoTop.append('Json file exists ' + '\n') self.openJson(shortName + '.json', fileName) else: redColor = QColor(255, 0, 0) self.textInfoTop.setTextColor(redColor) self.textInfoTop.append('Json file doesn\'t exist' + '\n') self.tableJson.setRowCount(0) else: self.tableData.setRowCount(0) self.currentRep = filePath self.textInfoTop.setTextColor(blackColor) self.scrollText.setWidgetResizable(True) #################################################################################### def createMenus(self): self.fileMenu = QMenu("&File", self) self.fileMenu.addAction(self.exitAct) self.viewMenu = QMenu("&View", self) self.viewMenu.addAction(self.zoomInAct) self.viewMenu.addAction(self.zoomOutAct) self.viewMenu.addAction(self.normalSizeAct) self.viewMenu.addSeparator() self.viewMenu.addAction(self.fitToWindowAct) self.viewMenu.addSeparator() self.helpMenu = QMenu("&Help", self) self.helpMenu.addAction(self.aboutAct) self.menuBar = QMenuBar() self.menuBar.addMenu(self.fileMenu) self.menuBar.addMenu(self.viewMenu) self.menuBar.addMenu(self.helpMenu) def createToolbarMenus(self): self.menuToolBar = QToolBar() viewMenu = QToolButton() viewMenu.setText('View') viewMenu.setPopupMode(QToolButton.MenuButtonPopup) aMenu = QMenu() aMenu.addAction(self.zoomInAct) aMenu.addAction(self.zoomOutAct) aMenu.addAction(self.normalSizeAct) aMenu.addSeparator() aMenu.addAction(self.fitToWindowAct) viewMenu.setMenu(aMenu) helpMenu = QToolButton() helpMenu.setText('Help') helpMenu.setPopupMode(QToolButton.MenuButtonPopup) bMenu = QMenu() helpMenu.setMenu(bMenu) self.menuToolBar.addWidget(viewMenu) self.menuToolBar.addWidget(helpMenu) def createActions(self): self.exitAct = QAction("Exit", self, shortcut="Ctrl+Q", triggered=self.close) self.zoomInAct = QAction("Zoom In (25%)", self, shortcut="Ctrl++", enabled=False, triggered=self.zoomIn) self.zoomOutAct = QAction("Zoom Out (25%)", self, shortcut="Ctrl+-", enabled=False, triggered=self.zoomOut) self.normalSizeAct = QAction("Normal Size", self, shortcut="Ctrl+S", enabled=False, triggered=self.normalSize) self.fitToWindowAct = QAction("Fit to Window", self, enabled=False, checkable=True, shortcut="Ctrl+F", triggered=self.fitToWindow) def zoomIn(self): self.factor = 1.25 self.scaleImage(self.factor) def zoomOut(self): self.factor = 0.8 self.scaleImage(self.factor) def normalSize(self): self.imageLabel.adjustSize() self.scaleFactor = 1.0 def fitToWindow(self): fitToWindow = self.fitToWindowAct.isChecked() self.scrollArea.setWidgetResizable(fitToWindow) self.scrollText.setWidgetResizable(fitToWindow) if not fitToWindow: self.normalSize() self.updateActions() def updateActions(self): self.zoomInAct.setEnabled(not self.fitToWindowAct.isChecked()) self.zoomOutAct.setEnabled(not self.fitToWindowAct.isChecked()) self.normalSizeAct.setEnabled(not self.fitToWindowAct.isChecked()) def scaleImage(self, factor): self.scaleFactor *= factor self.imageLabel.resize(self.scaleFactor * self.imageLabel.pixmap().size()) self.adjustScrollBar(self.scrollArea.horizontalScrollBar(), factor) self.adjustScrollBar(self.scrollArea.verticalScrollBar(), factor) self.zoomInAct.setEnabled(self.scaleFactor < 5.0) self.zoomOutAct.setEnabled(self.scaleFactor > 0.333) def close(self): self.close()
class ObjectView(QListView): """Shows cropped object images either in a grid or expanded """ def __init__(self, parent=None): super(ObjectView, self).__init__(parent) # Items are shown either in a grid or with a single item expanded # When more than one item is selected, view changes to grid. self.setItemDelegate(CropDelegate(self)) self.setFlow(self.LeftToRight) self.setWrapping(True) self.setResizeMode(self.Adjust) self.setSelectionMode(QAbstractItemView.ExtendedSelection) # Activating an item toggles the expanded state self.activated.connect(self.toggle_expanded) colour_scheme_choice().colour_scheme_changed.connect(self.colour_scheme_changed) self._create_actions() def _create_actions(self): group = QActionGroup(self) self.grid_action = QAction( '&Grid', self, shortcut='ctrl+G', triggered=self.show_grid, checkable=True, icon=load_icon(':/icons/show_grid.png') ) self.grid_action.setChecked(True) group.addAction(self.grid_action) self.expanded_action = QAction( '&Expanded', self, shortcut='ctrl+E', triggered=self.show_expanded, checkable=True, icon=load_icon(':/icons/show_expanded.png') ) group.addAction(self.expanded_action) def colour_scheme_changed(self): """Slot for colour_scheme_changed signal """ self.update() def selectionChanged(self, selected, deselected): """QAbstractItemView slot """ debug_print('ObjectView.selectionChanged') # Grid view unless exactly one item selected if (self.expanded_action.isChecked() and 1 != len(self.selectionModel().selectedIndexes())): self.grid_action.trigger() super(ObjectView, self).selectionChanged(selected, deselected) def show_grid(self, checked=False): """Shows the list as a grid of squares """ debug_print('ObjectView.show_grid') self._refresh() def show_expanded(self, checked=False): """Shows the first item of the selection expanded to fill the viewport. If the selection is empty, the first item in the list is selected. """ debug_print('ObjectView.show_expanded') # Select a single item sm = self.selectionModel() selected = sm.selectedIndexes() if len(selected) > 1: sm.select(selected[0], QItemSelectionModel.ClearAndSelect) elif not selected: sm.select(self.model().index(0, 0), QItemSelectionModel.Select) self._refresh() def toggle_expanded(self, index): """Selects 'index' and toggles the expanded state """ debug_print('ObjectView.toggle_expanded') self.selectionModel().select(index, QItemSelectionModel.Select) if self.expanded_action.isChecked(): self.grid_action.trigger() else: self.expanded_action.trigger() def _refresh(self): debug_print('ObjectView._refresh') self.scheduleDelayedItemsLayout() selected = self.selectionModel().selectedIndexes() if selected: self.scrollTo(selected[0]) def keyPressEvent(self, event): """QAbstractItemView virtual """ if event.key() in (Qt.Key_Return, Qt.Key_Enter): # This logic reimplemented from QAbstractItemView::keyPressEvent, # in src/gui/itemviews/qabstractitemview.cpp - make 'Enter' and # 'Return' keys toggle the 'Expanded' / 'Grid' state on Mac OS X if self.state() != QListView.EditingState or self.hasFocus(): if self.currentIndex().isValid(): self.activated.emit(self.currentIndex()) event.ignore() else: super(ObjectView, self).keyPressEvent(event)
class SimpleRichText(QTextEdit): # pylint: disable=method-hidden def __init__(self, focusin, focusout): QTextEdit.__init__(self) self.focusin = focusin self.focusout = focusout self.createActions() #self.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu) def focusOutEvent(self, event): self.focusout() def focusInEvent(self, event): self.focusin() def closeEvent(self, event): event.accept() def createActions(self): self.boldAct = QAction(self.tr("&Bold"), self) self.boldAct.setCheckable(True) self.boldAct.setShortcut(self.tr("Ctrl+B")) self.boldAct.setStatusTip(self.tr("Make the text bold")) self.boldAct.triggered.connect(self.setBold) ### self.connect(self.boldAct, SIGNAL("triggered()"), self.setBold) self.addAction(self.boldAct) boldFont = self.boldAct.font() boldFont.setBold(True) self.boldAct.setFont(boldFont) self.italicAct = QAction(self.tr("&Italic"), self) self.italicAct.setCheckable(True) self.italicAct.setShortcut(self.tr("Ctrl+I")) self.italicAct.setStatusTip(self.tr("Make the text italic")) self.italicAct.triggered.connect(self.setItalic) ### self.connect(self.italicAct, SIGNAL("triggered()"), self.setItalic) self.addAction(self.italicAct) def setBold(self): format = QTextCharFormat() if self.boldAct.isChecked(): weight = QFont.Bold else: weight = QFont.Normal format.setFontWeight(weight) self.setFormat(format) def setItalic(self): format = QTextCharFormat() #format.setFontItalic(self.__italic.isChecked()) format.setFontItalic(self.italicAct.isChecked()) self.setFormat(format) def setUnderline(self): format = QTextCharFormat() format.setFontUnderline(self.__underline.isChecked()) self.setFormat(format) def setFormat(self, format): self.textCursor().mergeCharFormat(format) self.mergeCurrentCharFormat(format) def bold(self): print("bold") def italic(self): print("italic")
class MainWindow(QMainWindow): InsertTextButton = 10 items = {-2: "source", -3: "channel", -4: "sink"} def __init__(self): import _diagramscene_rc super(MainWindow, self).__init__() self.config_manipulations = FlumeConfig(self) properties_generator.dump_props() self.create_actions() self.create_menus() self.create_tool_box() self.clicked_button_id = 0 self.scene = DiagramScene(self.item_menu) self.scene.setSceneRect(QRectF(0, 0, 5000, 5000)) self.scene.itemInserted.connect(self.item_inserted) self.scene.textInserted.connect(self.text_inserted) self.scene.itemSelected.connect(self.item_selected) self.create_tool_bars() # self.scene.enable_grid() layout = QHBoxLayout() layout.addWidget(self.tool_box) self.view = QGraphicsView(self.scene) self.view.centerOn(0, 0) layout.addWidget(self.view) self.widget = QWidget() self.widget.setLayout(layout) self.setCentralWidget(self.widget) self.setWindowTitle("The Flume Illustrator") # noinspection PyAttributeOutsideInit,PyArgumentList def create_actions(self): self.to_front_action = QAction(QIcon(':/images/bringtofront.png'), "Bring to &Front", self, shortcut="Ctrl+F", statusTip="Bring item to front", triggered=self.bring_to_front) self.send_back_action = QAction(QIcon(':/images/sendtoback.png'), "Send to &Back", self, shortcut="Ctrl+B", statusTip="Send item to back", triggered=self.send_to_back) self.bold_action = QAction(QIcon(':/images/bold.png'), "Bold", self, checkable=True, shortcut="Ctrl+B", triggered=self.handle_font_change) self.italic_action = QAction(QIcon(':/images/italic.png'), "Italic", self, checkable=True, shortcut="Ctrl+I", triggered=self.handle_font_change) self.underline_action = QAction(QIcon(':/images/underline.png'), "Underline", self, checkable=True, shortcut="Ctrl+U", triggered=self.handle_font_change) self.delete_action = QAction(QIcon(':/images/delete.png'), "Delete", self, shortcut="Delete", statusTip='Delete item from diagram', triggered=self.delete_item) self.exit_action = QAction("Exit", self, shortcut="Ctrl+X", statusTip="Quit program", triggered=self.close) self.about_action = QAction("About", self, shortcut="Ctrl+B", triggered=self.about) self.load_config_action = QAction("Load", self, shortcut="Ctrl+O", statusTip="Load config file", triggered=self.config_manipulations.load_config) self.enable_grid_action = QAction("Enable grid", self, checkable=True, triggered=self.enable_grid) # noinspection PyAttributeOutsideInit def create_menus(self): self.file_menu = self.menuBar().addMenu("File") self.file_menu.addAction(self.load_config_action) self.file_menu.addAction(self.exit_action) self.item_menu = self.menuBar().addMenu("Item") self.item_menu.addAction(self.delete_action) self.item_menu.addSeparator() self.item_menu.addAction(self.to_front_action) self.item_menu.addAction(self.send_back_action) self.about_menu = self.menuBar().addMenu("Help") self.about_menu.addAction(self.about_action) # noinspection PyAttributeOutsideInit,PyUnresolvedReferences def create_tool_box(self): self.button_group = QButtonGroup() self.button_group.setExclusive(False) self.button_group.buttonClicked[int].connect(self.button_group_clicked) layout = QGridLayout() layout.addWidget(self.create_cell_widget("Source", "source"), 0, 0) layout.addWidget(self.create_cell_widget("Channel", "channel"), 0, 1) layout.addWidget(self.create_cell_widget("Sink", "sink"), 1, 0) text_button = QToolButton() text_button.setCheckable(True) self.button_group.addButton(text_button, self.InsertTextButton) text_button.setIcon(QIcon(QPixmap(':/images/textpointer.png').scaled(30, 30))) text_button.setIconSize(QSize(50, 50)) text_layout = QGridLayout() text_layout.addWidget(text_button, 0, 0, Qt.AlignHCenter) text_layout.addWidget(QLabel("Text"), 1, 0, Qt.AlignCenter) text_widget = QWidget() text_widget.setLayout(text_layout) layout.addWidget(text_widget, 1, 1) layout.setRowStretch(3, 10) layout.setColumnStretch(2, 10) item_widget = QWidget() item_widget.setLayout(layout) self.tool_box = QToolBox() self.tool_box.setSizePolicy(QSizePolicy(QSizePolicy.Maximum, QSizePolicy.Ignored)) self.tool_box.setMinimumWidth(item_widget.sizeHint().width()) self.tool_box.addItem(item_widget, "Basic Flume Items") # noinspection PyAttributeOutsideInit,PyUnresolvedReferences def create_tool_bars(self): self.edit_tool_bar = self.addToolBar("Edit") self.edit_tool_bar.addAction(self.delete_action) self.edit_tool_bar.addAction(self.to_front_action) self.edit_tool_bar.addAction(self.send_back_action) self.edit_tool_bar.addAction(self.enable_grid_action) self.font_combo = QFontComboBox() self.font_combo.currentFontChanged.connect(self.current_font_changed) self.font_size_combo = QComboBox() self.font_size_combo.setEditable(True) for i in range(8, 30, 2): self.font_size_combo.addItem(str(i)) validator = QIntValidator(2, 64, self) self.font_size_combo.setValidator(validator) self.font_size_combo.currentIndexChanged.connect(self.font_size_changed) self.font_color_tool_button = QToolButton() self.font_color_tool_button.setPopupMode(QToolButton.MenuButtonPopup) self.font_color_tool_button.setMenu( self.create_color_menu(self.text_color_changed, Qt.black)) self.text_action = self.font_color_tool_button.menu().defaultAction() self.font_color_tool_button.setIcon( self.create_color_tool_button_icon(':/images/textpointer.png', Qt.black)) self.font_color_tool_button.setAutoFillBackground(True) self.font_color_tool_button.clicked.connect(self.text_button_triggered) self.fill_color_tool_button = QToolButton() self.fill_color_tool_button.setPopupMode(QToolButton.MenuButtonPopup) self.fill_color_tool_button.setMenu( self.create_color_menu(self.item_color_changed, Qt.white)) self.fillAction = self.fill_color_tool_button.menu().defaultAction() self.fill_color_tool_button.setIcon( self.create_color_tool_button_icon(':/images/floodfill.png', Qt.white)) self.fill_color_tool_button.clicked.connect(self.fill_button_triggered) self.line_color_tool_button = QToolButton() self.line_color_tool_button.setPopupMode(QToolButton.MenuButtonPopup) self.line_color_tool_button.setMenu( self.create_color_menu(self.line_color_changed, Qt.black)) self.lineAction = self.line_color_tool_button.menu().defaultAction() self.line_color_tool_button.setIcon( self.create_color_tool_button_icon(':/images/linecolor.png', Qt.black)) self.line_color_tool_button.clicked.connect(self.line_button_triggered) self.text_tool_bar = self.addToolBar("Font") self.text_tool_bar.addWidget(self.font_combo) self.text_tool_bar.addWidget(self.font_size_combo) self.text_tool_bar.addAction(self.bold_action) self.text_tool_bar.addAction(self.italic_action) self.text_tool_bar.addAction(self.underline_action) self.color_tool_bar = self.addToolBar("Color") self.color_tool_bar.addWidget(self.font_color_tool_button) self.color_tool_bar.addWidget(self.fill_color_tool_button) self.color_tool_bar.addWidget(self.line_color_tool_button) self.loading_tool_bar = self.addToolBar("Load") self.loading_tool_bar.addAction(self.load_config_action) pointer_button = QToolButton() pointer_button.setCheckable(True) pointer_button.setChecked(True) pointer_button.setIcon(QIcon(":/images/pointer.png")) line_pointer_button = QToolButton() line_pointer_button.setCheckable(True) line_pointer_button.setIcon(QIcon(":/images/linepointer.png")) self.pointer_type_group = QButtonGroup() self.pointer_type_group.addButton(pointer_button, DiagramScene.MoveItem) self.pointer_type_group.addButton(line_pointer_button, DiagramScene.InsertLine) self.pointer_type_group.buttonClicked[int].connect(self.pointer_group_clicked) self.scene_scale_combo = QComboBox() self.scene_scale_combo.addItems(["50%", "75%", "100%", "125%", "150%"]) self.scene_scale_combo.setCurrentIndex(2) self.scene_scale_combo.currentIndexChanged[str].connect(self.scene_scale_changed) self.pointer_tool_bar = self.addToolBar("Pointer type") self.pointer_tool_bar.addWidget(pointer_button) self.pointer_tool_bar.addWidget(line_pointer_button) self.pointer_tool_bar.addWidget(self.scene_scale_combo) def button_group_clicked(self, button_id): buttons = self.button_group.buttons() self.clicked_button_id = button_id for button in buttons: if self.button_group.button(button_id) != button: button.setChecked(False) if button_id == self.InsertTextButton: self.scene.set_mode(DiagramScene.InsertText) else: self.scene.set_item_type(self.items[button_id]) self.scene.set_mode(DiagramScene.InsertItem) def delete_item(self): for item in self.scene.selectedItems(): if isinstance(item, FlumeDiagramItem): item.remove_arrows() self.scene.removeItem(item) # noinspection PyTypeChecker,PyCallByClass def about(self): # noinspection PyArgumentList QMessageBox.about(self, "About Flume Illustrator", "The Flume illustrator shows config-file details") def pointer_group_clicked(self): self.scene.set_mode(self.pointer_type_group.checkedId()) def bring_to_front(self): if not self.scene.selectedItems(): return selected_item = self.scene.selectedItems()[0] overlap_items = selected_item.collidingItems() z_value = 0 for item in overlap_items: if item.zValue() >= z_value and isinstance(item, FlumeDiagramItem): z_value = item.zValue() + 0.1 selected_item.setZValue(z_value) def send_to_back(self): if not self.scene.selectedItems(): return selected_item = self.scene.selectedItems()[0] overlap_items = selected_item.collidingItems() z_value = 0 for item in overlap_items: if item.zValue() <= z_value and isinstance(item, FlumeDiagramItem): z_value = item.zValue() - 0.1 selected_item.setZValue(z_value) def scene_scale_changed(self, scale): new_scale = float(scale[:scale.index("%")]) / 100 old_transform = self.view.transform() self.view.resetTransform() self.view.translate(old_transform.dx(), old_transform.dy()) self.view.scale(new_scale, new_scale) def item_inserted(self, diagram_type): self.pointer_type_group.button(DiagramScene.MoveItem).setChecked(True) self.scene.set_mode(self.scene.DefaultMode) self.button_group.button(self.clicked_button_id).setChecked(False) def text_inserted(self, item): self.button_group.button(self.InsertTextButton).setChecked(False) self.scene.set_mode(self.pointer_type_group.checkedId()) def current_font_changed(self, font): self.handle_font_change() def font_size_changed(self, font=None): self.handle_font_change() def text_color_changed(self): self.text_action = self.sender() self.font_color_tool_button.setIcon( self.create_color_tool_button_icon(':/images/textpointer.png', QColor(self.text_action.data()))) self.text_button_triggered() def item_color_changed(self): self.fillAction = self.sender() self.fill_color_tool_button.setIcon( self.create_color_tool_button_icon(':/images/floodfill.png', QColor(self.fillAction.data()))) self.fill_button_triggered() def line_color_changed(self): self.lineAction = self.sender() self.line_color_tool_button.setIcon( self.create_color_tool_button_icon(':/images/linecolor.png', QColor(self.lineAction.data()))) self.line_button_triggered() def text_button_triggered(self): self.scene.set_text_color(QColor(self.text_action.data())) def fill_button_triggered(self): self.scene.set_item_color(QColor(self.fillAction.data())) def line_button_triggered(self): self.scene.set_line_color(QColor(self.lineAction.data())) def handle_font_change(self): font = self.font_combo.currentFont() font.setPointSize(int(self.font_size_combo.currentText())) if self.bold_action.isChecked(): font.setWeight(QFont.Bold) else: font.setWeight(QFont.Normal) font.setItalic(self.italic_action.isChecked()) font.setUnderline(self.underline_action.isChecked()) self.scene.setFont(font) def item_selected(self, item): font = item.font() self.font_combo.setCurrentFont(font) self.font_size_combo.setEditText(str(font.pointSize())) self.bold_action.setChecked(font.weight() == QFont.Bold) self.italic_action.setChecked(font.italic()) self.underline_action.setChecked(font.underline()) def create_cell_widget(self, text, diagram_type): item = FlumeObject(diagram_type, "") icon = QIcon(item.pictogram.image()) button = QToolButton() button.setIcon(icon) button.setIconSize(QSize(50, 50)) button.setCheckable(True) self.button_group.addButton(button) # , diagram_type layout = QGridLayout() layout.addWidget(button, 0, 0, Qt.AlignHCenter) layout.addWidget(QLabel(text), 1, 0, Qt.AlignHCenter) widget = QWidget() widget.setLayout(layout) return widget # noinspection PyArgumentList def create_color_menu(self, slot, default_color): colors = [Qt.black, Qt.white, Qt.red, Qt.blue, Qt.yellow] names = ["black", "white", "red", "blue", "yellow"] color_menu = QMenu(self) for color, name in zip(colors, names): action = QAction(self.create_color_icon(color), name, self, triggered=slot) action.setData(QColor(color)) color_menu.addAction(action) if color == default_color: color_menu.setDefaultAction(action) return color_menu @staticmethod def create_color_tool_button_icon(image_file, color): pixmap = QPixmap(50, 80) pixmap.fill(Qt.transparent) painter = QPainter(pixmap) image = QPixmap(image_file) target = QRect(0, 0, 50, 60) source = QRect(0, 0, 42, 42) painter.fillRect(QRect(0, 60, 50, 80), color) painter.drawPixmap(target, image, source) painter.end() return QIcon(pixmap) @staticmethod def create_color_icon(color): pixmap = QPixmap(20, 20) painter = QPainter(pixmap) painter.setPen(Qt.NoPen) painter.fillRect(QRect(0, 0, 20, 20), color) painter.end() return QIcon(pixmap) def enable_grid(self): if self.enable_grid_action.isChecked(): color = Qt.black else: color = Qt.white for i in range(50): for j in range(50): self.scene.addEllipse(i * 100, j * 100, 2, 2, QPen(color))