class BufferSelectToolbar(object): def __init__(self, selectTool): super(BufferSelectToolbar, self).__init__() # references self.selectTool = selectTool self.result = None self.debug = self.selectTool.debug self.id = id self.config = self.selectTool.config self.info = Info(self) try: self.gtomain = self.selectTool.gtomain self.helper = self.gtomain.helper self.metadata = self.gtomain.metadata self.iface = self.gtomain.iface self.canvas = self.iface.mapCanvas() # tool data self.toolbar_dock = self.config.get("toolbar_dock", 4) self.toolbar_height = self.gtomain.toolbar_height # widget self.toolbar = None # load toolbar objName = "gtoTB_" + __name__ + str(id) self.toolbar = self.gtomain.helper.findToolbar(self.iface, objName) if self.toolbar is None: if self.debug: self.info.log("load", objName) self.toolbar = QToolBar() self.toolbar.setObjectName(objName) self.toolbar.setWindowTitle(u'GTO Buffer Selection') self.toolbar.setAllowedAreas(Qt.BottomToolBarArea | Qt.TopToolBarArea) self.iface.mainWindow().addToolBarBreak(self.toolbar_dock) self.iface.addToolBar(self.toolbar, self.toolbar_dock) # set the iconsize=> changed when self.iface.addToolBar :S if self.toolbar_height is not None: self.toolbar.setMaximumHeight(self.gtomain.toolbar_height) self.toolbar.setMinimumHeight(self.gtomain.toolbar_height) else: self.toolbar.clear() self.wid = Widget(self) self.toolbar.addWidget(self.wid) self.wid.setIconSizes(self.iface.iconSize(False)) self.wid.geometry_changed.connect(self.getGeometry) self.toolbar.setHidden(False) except Exception as e: self.info.err(e) # from mActionbufferselectxy def setHidden(self, a0): self.toolbar.setHidden(a0) def setGeometry(self, geo, isValid, isCircle=False, isRectangle=False): self.toolbar.setHidden(False) if self.debug: self.info.log("setGeometry", geo.isEmpty(), isValid, isCircle, isRectangle) self.wid.setOriginalGeometry(geo, isValid, isCircle, isRectangle) def getGeometry(self, geo): self.selectTool.setGeometryToMapTool(geo)
class run(QObject): # gtoAction def __init__(self, id, gtoTool, config, debug): super(run, self).__init__() # references self.debug = debug self.id = id self.config = config self.info = gtoTool.info try: self.action = gtoTool.action self.action.setCheckable(True) self.gtomain = gtoTool.gtomain self.helper = self.gtomain.helper self.metadata = self.gtomain.metadata self.iface = self.gtomain.iface self.canvas = self.iface.mapCanvas() if not self.config.get("is_widgetaction", False): # tool data self.toolbar_dock = self.config.get("toolbar_dock", 4) # widget self.toolbar = None # load toolbar self.objName = "gtoTB_" + gtoTool.action.objectName() + str(id) self.toolbar = self.gtomain.helper.findToolbar(self.iface, self.objName) if self.toolbar is None: if self.debug: self.info.log("load", self.objName) self.toolbar = QToolBar() self.toolbar.setObjectName(self.objName) self.toolbar.setWindowTitle(u'GTO Coordinate') self.toolbar.setAllowedAreas(Qt.BottomToolBarArea | Qt.TopToolBarArea) self.iface.mainWindow().addToolBarBreak(self.toolbar_dock) self.iface.addToolBar(self.toolbar, self.toolbar_dock) else: self.toolbar.clear() self.wid = GTOPointWidget(self.gtomain, self.toolbar) self.toolbar.addWidget(self.wid) if self.config.get("spacer", False): spacer = QWidget() spacer.setObjectName('spacer') spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) spacer.setStyleSheet("QWidget{background: transparent;}") self.toolbar.addWidget(spacer) self.wid.set_parent_widget(self) self.wid.isActive.connect(self.set_status) # not always(?) working? self.wid.setConfig(self.config) self.wid.added() self.wid.setMapTool() self.toolbar.show() except Exception as e: self.info.err(e) def set_status(self, isActive): try: self.action.setChecked(isActive) self.toolbar.setHidden(not isActive) except Exception as e: self.info.err(e)
class run(object): def __init__(self, id, gtoTool, config, debug): super(run, self).__init__() try: # references self.result = None self.debug = debug self.id = id self.config = config self.info = gtoTool.info self.gtomain = gtoTool.gtomain self.helper = self.gtomain.helper self.metadata = self.gtomain.metadata self.iface = self.gtomain.iface # tool data widegt self.toolbar_dock = self.config.get("toolbar_dock", None) # widget self.toolbar = None self.wid = Widget(self, self.iface.mainWindow()) if self.toolbar_dock is not None: # load toolbar objName = "gtoTB_" + gtoTool.action.objectName() self.toolbar = self.gtomain.helper.findToolbar( self.iface, objName) if self.toolbar is None: if self.debug: self.info.log("load", objName) self.toolbar = QToolBar() self.toolbar.setObjectName(objName) self.toolbar.setWindowTitle(u'GTO Suche') self.toolbar.setAllowedAreas(Qt.BottomToolBarArea | Qt.TopToolBarArea) self.iface.mainWindow().addToolBarBreak(self.toolbar_dock) self.iface.addToolBar(self.toolbar, self.toolbar_dock) else: self.toolbar.clear() self.toolbar.setHidden(False) else: self.toolbar = self.gtomain.gtotb self.toolbar.addWidget(self.wid) if self.config.get("show_hide_button", False): self.helper.addToolbarClose(self.toolbar) self.toolbar.visibilityChanged.connect(self.reset) except Exception as e: self.info.err(e) def reset(self, *args): # from (gto)toolbar hidden/shown try: if self.debug: self.info.log("gtoAction reset") self.wid.reset() except Exception as e: self.info.err(e)
class MainWindow(QMainWindow): def __init__(self, *args, **kwargs): super().__init__() # Parse incoming command line arguments: parser = argparse.ArgumentParser() parser.add_argument( "--config", type=str, help= "Local path to a YAML file (.yml, .yaml) containing configuration settings", required=False, default="", ) args = parser.parse_args() if args.config: self.config_file = args.config # Logger box: logTextBox = QTextEditLogger(self) logTextBox.setFormatter( logging.Formatter( "%(asctime)s - %(module)s - %(levelname)s - %(message)s")) logging.getLogger().addHandler(logTextBox) # Logging level control: logging.getLogger().setLevel(logging.DEBUG) loglabel = QLabel() loglabel.setText("Log output:") loglabel.setBuddy(logTextBox.widget) # Main info area container (top of main window). self.task_panel = QWidget() self.taskLayout = QHBoxLayout() # Main control panel container (center of main window). self.controls = QWidget() self.controlsLayout = QVBoxLayout() # Main container. container = QWidget() containerLayout = QVBoxLayout() containerLayout.addWidget(self.task_panel) containerLayout.addWidget(self.controls) # containerLayout.addWidget(loglabel) # containerLayout.addWidget(logTextBox.widget) container.setLayout(containerLayout) self.setCentralWidget(container) # Window title: self.setWindowTitle("Sense Record") self.setWindowIcon( QIcon( qta.icon("mdi.circle-slice-8", options=[{ "color": "#4CAF50" }]))) # Construct status bar (bottom of MainWindow): self.statusBar() # Construct Menu bar on MainWindow: menubar = self.menuBar() # File menu fileMenu = menubar.addMenu("&File") # File > Load config: loadAct = QAction(qta.icon("mdi.folder-cog-outline"), "&Load config file", self) loadAct.setShortcut("Ctrl+O") loadAct.setStatusTip("Load configuration data from a .yml file") loadAct.triggered.connect(self.get_config_file) fileMenu.addAction(loadAct) # Tasks menu self.tasksMenu = menubar.addMenu("&Tasks") # View menu self.viewMenu = menubar.addMenu("&View") self.refreshAct = QAction(qta.icon("mdi.refresh"), "Refresh", self) self.refreshAct.setStatusTip("Refresh the controls.") self.refreshAct.triggered.connect(self.load_config) self.refreshAct.setDisabled(True) self.viewMenu.addAction(self.refreshAct) # Toolbar self.toolbar = QToolBar("Main toolbar") self.addToolBar(self.toolbar) # Load configuration if present if hasattr(self, "config_file"): self.load_config() else: # Config file button and file dialog. btn_config = QPushButton(qta.icon("mdi.folder-cog-outline"), "Select config file ...") btn_config.clicked.connect(self.get_config_file) self.toolbar.addWidget( QLabel("You must select a config file to load controls.")) self.controlsLayout.addWidget(btn_config) # Set the controls (for the heart of the sun): self.controls.setLayout(self.controlsLayout) def set_controls(self, task_name: str): """Generates the labels and controls that appear in the controls area.""" # Clear the current toolbar: self.toolbar.clear() # Clear the current task panel: self.clear_layout(self.taskLayout) # Clear the current controls: self.clear_layout(self.controlsLayout) # Enable the refresh menu action in the View menu: self.refreshAct.setDisabled(False) # Load our task from global config: task = self.config["tasks"][task_name] # Make the toolbar items: if "label" in task: label_text = task["label"] else: label_text = task_name taskLabel = QLabel( chr(0xF10D5) + " " + "<strong>Task: </strong>" + label_text) taskLabel.setFont(qta.font("fa", 26)) self.toolbar.addWidget(taskLabel) toolbarSpacer = QWidget(self) # right-aligns the refresh button toolbarSpacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) refreshButton = QAction(qta.icon("mdi.refresh"), "Refresh", self) refreshButton.setStatusTip("Refresh the controls.") refreshButton.triggered.connect(self.load_config) self.toolbar.addWidget(toolbarSpacer) self.toolbar.addAction(refreshButton) # Make the task info panel: if "description" in task: taskLabel.setToolTip(task["description"]) taskDescription = QWidget() taskDescription.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Expanding) descriptionLayout = QVBoxLayout() descriptionLayout.setAlignment(Qt.AlignTop) descriptionLabel = QLabel(task["description"]) descriptionLabel.setWordWrap(True) descriptionHeading = QLabel( chr(0xF0EA7) + " " + "<strong>Description: </strong>") descriptionHeading.setFont(qta.font("fa", 26)) descriptionLayout.addWidget(descriptionHeading) descriptionLayout.addWidget(descriptionLabel) taskDescription.setLayout(descriptionLayout) self.taskLayout.addWidget(taskDescription, 6) if "sessions" in task: taskSessions = QWidget() sessionLayout = QVBoxLayout() sessionLayout.setAlignment(Qt.AlignTop) sessionsHeading = QLabel( chr(0xF0ED8) + " " + "<strong>Sessions: </strong>") sessionsHeading.setFont(qta.font("fa", 26)) sessionLayout.addWidget(sessionsHeading) for k, v in task["sessions"].items(): sessionLayout.addWidget(QLabel(v)) taskSessions.setLayout(sessionLayout) self.taskLayout.addWidget(taskSessions, 6) else: spacer = QWidget(self) spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) self.taskLayout.addWidget(spacer, 6) # Refresh the task info panel layout: self.task_panel.setLayout(self.taskLayout) # Make controls for this task: task["key"] = task_name # Insert task id key to the task dict # Construct on/off button controls for each board in our task: for key, board in task["boards"].items(): self.controlsLayout.addWidget(OnOffWidget(board, task)) self.controls.setLayout(self.controlsLayout) def set_tasks_menu(self): """Generates the items for the Tasks menu.""" self.tasksMenu.clear() for name, settings in self.config["tasks"].items(): taskAct = QAction(qta.icon("mdi.clipboard-list-outline"), "&" + name, self) taskAct.setStatusTip("Load controls for " + name) # https://stackoverflow.com/questions/6784084/how-to-pass-arguments-to-functions-by-the-click-of-button-in-pyqt taskAct.triggered.connect( lambda state, x=name: self.set_controls(x)) self.tasksMenu.addAction(taskAct) def get_config_file(self): """Shows a file browser dialog to set active config file.""" config_file = QFileDialog.getOpenFileName( self, ("Open Config File"), "./", ("YAML files (*.yml *.yaml)")) if config_file[0]: # Path to the yml: self.config_file = config_file[0] # Ingest the config from yml: self.load_config() def load_config(self): """Loads data from config file and calls functions to update window using new data from config file.""" if hasattr(self, "config_file"): try: self.config = process_yaml(self.config_file) first_taskname = list(self.config["tasks"].keys())[0] self.set_controls( first_taskname ) # default to first task when loading config self.set_tasks_menu() logging.info("Using configuration loaded from " + self.config_file) self.statusBar().showMessage( "Active config file: " + os.path.split(self.config_file)[1]) except Exception as e: QMessageBox.critical(self, "Invalid config file!", str(e), QMessageBox.Ok) logging.error(str(e)) def clear_layout(self, layout): """Recursively deletes all widgets in given layout.""" if layout is not None: while layout.count(): item = layout.takeAt(0) widget = item.widget() if widget is not None: widget.deleteLater() else: self.clear_layout(item.layout()) def closeEvent(self, event): """ Overrides inherited closeEvent method to inject a confirmation dialog when MainWindow is closed. """ quit_msg = "Are you sure you want to exit?" reply = QMessageBox.question(self, "Quit Application", quit_msg, QMessageBox.Yes, QMessageBox.No) if reply == QMessageBox.Yes: event.accept() else: event.ignore()
class QImageGridViewer(QScrollArea): def __init__(self): super().__init__() self.imageGrids = QImageGrids() self.imageGrids.focusChanged.connect(self.focusChangedSlot) self.setBackgroundRole(QPalette.Dark) self.setWidget(self.imageGrids) self.setWidgetResizable(True) self._appContext = None self.stylesheetPath = 'QImageGridStyle.qss' # self.readStyleSheet() self.toolbar = QToolBar() self.initToolbar() self.initMenu() # makes self.menu def count(self): return self.imageGrids.count() def readStyleSheet(self): f = QFile(self.appContext.get_resource(self.stylesheetPath)) f.open(QFile.ReadOnly | QFile.Text) stream = QTextStream(f) self.setStyleSheet(stream.readAll()) def open(self): options = QFileDialog.Options() fileNames, _ = QFileDialog.getOpenFileNames( self, 'QFileDialog.getOpenFileNames()', '', 'Images (*.png *.jpeg *.jpg *.bmp *.gif)', options=options) if fileNames: filePaths = [Path(name) for name in fileNames] self.openFiles(filePaths) def openFiles(self, filePaths): for filePath in filePaths: # don't open the file if a version with "inked" exists if inkPath(filePath) in filePaths: pass # print(f'skipped {filePath}') elif isInked(filePath): self.openFile(filePath, removePathInk(filePath)) else: self.openFile(filePath) def openFile(self, fileName, baseFileName=None): if baseFileName is None: self.imageGrids.add(Path(fileName)) else: self.imageGrids.add(Path(fileName), Path(baseFileName)) def removeFocusedGrid(self): if not self.imageGrids.count() == 0: self.imageGrids.removeFocusedGrid() def reloadFocusedImage(self): if not self.imageGrids.count() == 0: self.imageGrids.reloadFocusedGrid() def focusLastGrid(self): self.focusGridAtIndex(self.imageGrids.VBoxLayout.count() - 2) def focusFirstGrid(self): self.focusGridAtIndex(0) def focusGridAtIndex(self, index): oldGrid = self.imageGrids.getFocusedGrid() if oldGrid is None: return oldGrid.clearFocusItem() self.imageGrids._focusItemIndex = index newGrid = self.imageGrids.getFocusedGrid() newGrid.setFocusItem(0, 0) self.imageGrids.emitFocusChanged() def keyPressEvent(self, event: QKeyEvent): QMainWindow().keyPressEvent(event) def ensureFocusedItemVisible(self): self.ensureWidgetVisible( self.imageGrids.getFocusedGrid().getFocusWidget()) @pyqtSlot() def focusChangedSlot(self): self.readStyleSheet() self.ensureFocusedItemVisible() @pyqtSlot(QImage) def changeFocusedImageData(self, newImage): grid = self.imageGrids.getFocusedGrid() if grid is not None: widget = grid.getFocusWidget() widget.setImage(newImage) self.imageGrids.getFocusedGrid().writeImage() def moveFocusDown(self): if not self.imageGrids.count() == 0: self.imageGrids.moveItemFocusDown() def moveFocusUp(self): if not self.imageGrids.count() == 0: self.imageGrids.moveItemFocusUp() def moveFocusLeft(self): if not self.imageGrids.count() == 0: self.imageGrids.moveItemFocusLeft() def moveFocusRight(self): if not self.imageGrids.count() == 0: self.imageGrids.moveItemFocusRight() def moveFocusNext(self): if not self.imageGrids.count() == 0: self.imageGrids.moveFocusNext() def moveFocusPrevious(self): if not self.imageGrids.count() == 0: self.imageGrids.moveFocusPrevious() def sizeHint(self): return QSize(150, 400) @property def appContext(self): return self._appContext @appContext.setter def appContext(self, context): self._appContext = context self.toolbar.clear() self.initToolbar() @property def rows(self): return QImageGrid.clsRows @rows.setter def rows(self, value): QImageGrid.clsRows = value @property def cols(self): return QImageGrid.clsCols @cols.setter def cols(self, value): QImageGrid.clsCols = value def createActions(self): if self.appContext is None: refreshIconFp = './icons/refreshIcon.png' else: refreshIconFp = self.appContext.get_resource('refreshIcon.png') self.itemFocusDownAct = QAction('Down Item', self, shortcut=Qt.CTRL + Qt.Key_Down, triggered=self.moveFocusDown) self.itemFocusUpAct = QAction('Up Item', self, shortcut=Qt.CTRL + Qt.Key_Up, triggered=self.moveFocusUp) self.itemFocusLeftAct = QAction('Left Item', self, shortcut=Qt.CTRL + Qt.Key_Left, triggered=self.moveFocusLeft) self.itemFocusRightAct = QAction('Right Item', self, shortcut=Qt.CTRL + Qt.Key_Right, triggered=self.moveFocusRight) self.itemFocusNextAct = QAction('Next Item', self, shortcut=Qt.CTRL + Qt.Key_N, triggered=self.moveFocusNext) self.itemFocusPreviousAct = QAction('Previous Item', self, shortcut=Qt.CTRL + Qt.Key_P, triggered=self.moveFocusPrevious) self.resetImageAct = QAction(QIcon(refreshIconFp), 'Reset Image', self, shortcut=Qt.CTRL + Qt.Key_R, triggered=self.reloadFocusedImage) self.promptGridRowsAct = QAction('Set grid rows', self, triggered=self.promptForGridRows) self.promptGridColumnsAct = QAction( 'Set grid columns', self, triggered=self.promptForGridColumns) self.removeFocusedGridAct = QAction('Remove current image', self, shortcut=Qt.CTRL + Qt.Key_W, triggered=self.removeFocusedGrid) def initMenu(self): self.menu = QMenu('&Grids', self) self.menu.addAction(self.promptGridRowsAct) self.menu.addAction(self.promptGridColumnsAct) self.menu.addSeparator() self.menu.addAction(self.itemFocusDownAct) self.menu.addAction(self.itemFocusUpAct) self.menu.addAction(self.itemFocusLeftAct) self.menu.addAction(self.itemFocusRightAct) self.menu.addSeparator() self.menu.addAction(self.itemFocusNextAct) self.menu.addAction(self.itemFocusPreviousAct) self.menu.addSeparator() self.menu.addAction(self.removeFocusedGridAct) def initToolbar(self): self.createActions() self.toolbar.addAction(self.resetImageAct) def promptForGridRows(self): rows, okPressed = QInputDialog.getInt(self, 'Grid Rows', 'Number of grid rows:', QImageGrid.clsRows, 1, 20, 1) if okPressed: QImageGrid.clsRows = rows def promptForGridColumns(self): cols, okPressed = QInputDialog.getInt(self, 'Grid Columns', 'Number of grid columns:', QImageGrid.clsCols, 1, 20, 1) if okPressed: QImageGrid.clsCols = cols
class QGameCountTracker(QListWidget): def __init__(self): super().__init__() self._appContext = None self.addAnimalForm = QGameCountInputForm() self.addAnimalForm.animalAdded.connect(self.addAnimalData) self.addAnimalForm.animalAdded.connect(self.dump) self.currentImageFile = '' self.JSONDumpFile = None self.summaryFile = None self.counts = MultiGameCountTracker() self.createActions() self.toolbar = QToolBar() self.initToolbar() self.menu = QMenu('&Tracker', self) self.initMenu() @property def appContext(self): return self._appContext @appContext.setter def appContext(self, context): self._appContext = context self.toolbar.clear() self.menu.clear() self.createActions() self.initToolbar() self.initMenu() @pyqtSlot(GameCountData) def addAnimalData(self, data): self.counts.addData(self.currentImageFile, data) self.render() def keyReleaseEvent(self, event): key = event.key() if key == Qt.Key_Delete: self.clearCurrentSelectionCountData() else: super().keyPressEvent(event) def load(self, fileName): self.JSONDumpFile = fileName with open(self.JSONDumpFile, 'r') as f: try: d = json.load(f) except json.decoder.JSONDecodeError: print('invalid json file') else: self.counts = self._decodeJSON(d) finally: self.render() def _decodeJSON(self, d: dict): counts = MultiGameCountTracker() # first is a dict of filenames paired with a list of animals for fileName, countList in d.items(): # decode the list of animals for data in countList: gameData = GameCountData(data['Species'], data['Count'], data['Repeats']) counts.addData(fileName, gameData) return counts def dump(self): if self.JSONDumpFile is None: self.JSONDumpFile = Path().cwd() / Path('counts.json') if self.summaryFile is None: self.summaryFile = self.JSONDumpFile.parent / Path( 'count summary.txt') self.JSONDumpFile.touch() self.summaryFile.touch() with open(self.JSONDumpFile, 'w') as f: f.write(self.serialize()) with open(self.summaryFile, 'w') as f: f.write(self.summarize()) def summarize(self): s = self.counts.totalsSummary() s += '\n-------------------------\n' s += str(self.counts) return s def displaySummary(self): if len(self.counts) == 0: summary = ( 'No counts recorded yet!' '\nTry recording an animal count with the "Add New Animals" panel' ) else: summary = (self.counts.totalsSummaryHTML() + '\n-------------------------\n' + self.counts.toHTML()) QMessageBox.about(self, 'Count Summary', summary) def serialize(self): return json.dumps(self.counts, cls=ObjectEncoder, indent=2, sort_keys=True) def clearData(self): # clears the internal data structure. # NOTE: Does NOT automatically rewrite the json file self.counts.clear() self.render() def clearCurrentSelectionCountData(self): # TODO clear the data for just the current file AND species fileName = self.currentImageFile item = self.currentItem() if item is not None: species = item.species self.counts[fileName].removeSpecies(species) self.render() def render(self): self.clear() try: tracker = self.counts[self.currentImageFile] except KeyError: pass else: for track in tracker: # add item and save the species for easier access later item = QListWidgetItem(str(track), self) item.species = track.species def createActions(self): if self.appContext is None: clearFp = './resources/eraserIcon.png' infoFp = './resources/infoIcon.png' else: clearFp = self.appContext.get_resource('eraserIcon.png') infoFp = self.appContext.get_resource('infoIcon.png') self.clearDataAct = QAction(QIcon(clearFp), '&Delete All Animal Counts', self, triggered=self.clearData) self.summarizeAct = QAction(QIcon(infoFp), '&Summarize', self, shortcut=Qt.Key_S, triggered=self.displaySummary) def initToolbar(self): self.toolbar.addAction(self.summarizeAct) def initMenu(self): self.menu.addAction(self.summarizeAct) self.menu.addAction(self.clearDataAct)
class QImagePainter(QSmoothGraphicsView): # signals imageFlattened = pyqtSignal(QImage) def __init__(self): super().__init__() self.scene = QGraphicsScene(self) self.setScene(self.scene) self.setRenderHint(QPainter.Antialiasing) self.mainPixmapItem = self.scene.addPixmap(QPixmap()) self._appContext = None # policies # self.setTransformationAnchor(QGraphicsView.AnchorUnderMouse) # self.setResizeAnchor(QGraphicsView.AnchorUnderMouse) self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.toolbar = QToolBar() self.initToolbar() self._pen = QPen() self._pen.setWidth(50) self.setDefaultPenColor() self._drawStartPos = None self._dynamicOval = None self._drawnItems = [] self.updateDragMode() @property def appContext(self): return self._appContext @appContext.setter def appContext(self, context): self._appContext = context self.toolbar.clear() self.initToolbar() def setMainPixmapFromPath(self, imgPath): # set image image = QImage(str(imgPath)) pixmap = self.mainPixmapItem.pixmap() pixmap.convertFromImage(image) self.setMainPixmap(pixmap) def setMainPixmap(self, pixmap): self.mainPixmapItem.setPixmap(pixmap) # set scene rect boundingRect = self.mainPixmapItem.boundingRect() margin = 0 boundingRect += QMarginsF(margin, margin, margin, margin) self.scene.setSceneRect(boundingRect) def saveImage(self, fileName): image = self.flattenImage() image.save(fileName) def flattenImageIfDrawnOn(self): if not len(self._drawnItems) == 0: self.flattenImage() def flattenImage(self): # get region of scene area = self.mainPixmapItem.boundingRect() # create a QImage to render to and fix up a QPainter for it image = QImage(area.width(), area.height(), QImage.Format_ARGB32_Premultiplied) painter = QPainter(image) # render the region of interest to the QImage self.scene.render(painter, QRectF(image.rect()), area) painter.end() # set this flattened image to this view pixmap = self.mainPixmapItem.pixmap() pixmap.convertFromImage(image) self.setMainPixmap(pixmap) # clear the drawings from the view self.clearDrawnItems() # emit flattened image signal self.imageFlattened.emit(image) # return the flattened image return image def clearDrawnItems(self): for item in self._drawnItems: self.scene.removeItem(item) self._drawnItems.clear() def removeLastDrawnItem(self): try: item = self._drawnItems.pop() except IndexError: pass else: self.scene.removeItem(item) def scaleView(self, scaleFactor): # print(f'self.width: {self.width()}') # print(f'pixmap.width(): {self.scene.map.mainPixmapItem.boundingRect().width()}') self.scale(scaleFactor, scaleFactor) def centerImage(self): self.centerOn(self.mainPixmapItem) def bestFitImage(self): self.fitInView(self.mainPixmapItem, Qt.KeepAspectRatio) def keyPressEvent(self, event: QKeyEvent): key = event.key() if key == Qt.Key_Space: self.bestFitImage() else: super().keyPressEvent(event) def mousePressEvent(self, event): self._drawStartPos = None if self.ovalModeAct.isChecked(): if self.mainPixmapItem.isUnderMouse(): self._drawStartPos = self.mapToScene(event.pos()) self._dynamicOval = self.scene.addEllipse( QRectF(self._drawStartPos.x(), self._drawStartPos.y(), 1, 1), self._pen) else: super().mousePressEvent(event) def mouseMoveEvent(self, event): if self._dynamicOval: pos = self.mapToScene(event.pos()) self._dynamicOval.setRect( QRectF(self._drawStartPos.x(), self._drawStartPos.y(), pos.x() - self._drawStartPos.x(), pos.y() - self._drawStartPos.y())) else: super().mouseMoveEvent(event) def mouseReleaseEvent(self, event): if self._dynamicOval: self._drawnItems.append(self._dynamicOval) self._dynamicOval = None else: super().mouseReleaseEvent(event) def toggleSelectionMode(self): if self.selectionModeAct.isChecked(): self.ovalModeAct.setChecked(False) else: self.selectionModeAct.setChecked(True) self.updateDragMode() def toggleOvalMode(self): if self.ovalModeAct.isChecked(): self.selectionModeAct.setChecked(False) else: self.ovalModeAct.setChecked(True) self.updateDragMode() def updateDragMode(self): if self.selectionModeAct.isChecked(): self.setDragMode(QGraphicsView.ScrollHandDrag) else: self.setDragMode(QGraphicsView.NoDrag) @property def penWidth(self): return self._pen.width() @penWidth.setter def penWidth(self, value): self._pen.setWidth(value) @property def penColor(self): return self._pen.color() @penColor.setter def penColor(self, value): self._pen.setColor(QColor(value)) def setDefaultPenColor(self): self.setPenColor(COLORS['Teleric Blue']) def promptForPenWidth(self): width, okPressed = QInputDialog.getInt(self, 'Pen Width', 'Pen width (px):', self.penWidth, 1, 100, 1) if okPressed: self.penWidth = width def setResourcePaths(self): if self.appContext is None: self.selectionModeFp = './icons/selectIcon.png' self.ovalModeFp = './icons/ovalIcon.png' self.flattenFp = './icons/saveIcon.png' self.undoFp = './icons/undoIcon.png' self.penFp = './icons/pen.png' self.penWidthFp = './icons/penWidth.png' else: self.selectionModeFp = self.appContext.get_resource( 'selectIcon.png') self.ovalModeFp = self.appContext.get_resource('ovalIcon.png') self.flattenFp = self.appContext.get_resource('saveIcon.png') self.undoFp = self.appContext.get_resource('undoIcon.png') self.penFp = self.appContext.get_resource('pen.png') self.penWidthFp = self.appContext.get_resource('penWidth.png') def createActions(self): self.setResourcePaths() self.selectionModeAct = QAction(QIcon(self.selectionModeFp), 'Select (v)', self, checkable=True, checked=True, shortcut=Qt.Key_V, triggered=self.toggleSelectionMode) self.ovalModeAct = QAction(QIcon(self.ovalModeFp), 'Draw &Oval (o)', self, checkable=True, checked=False, shortcut=Qt.Key_O, triggered=self.toggleOvalMode) self.flattenAct = QAction(QIcon(self.flattenFp), 'Save', self, shortcut=QKeySequence.Save, triggered=self.flattenImage) self.undoAct = QAction(QIcon(self.undoFp), 'Undo', self, shortcut=QKeySequence.Undo, triggered=self.removeLastDrawnItem) self.setPenWidthAct = QAction(QIcon(self.penWidthFp), 'Set Pen Width', self, triggered=self.promptForPenWidth) def addPenToolMenu(self): penButton = QToolButton(self) penButton.setText('Pen') penButton.setIcon(QIcon(self.penFp)) penButton.setPopupMode(QToolButton.InstantPopup) self.penMenu = QMenu(penButton) self.penMenu.addAction(self.setPenWidthAct) self.addPaletteToMenu(self.penMenu) penButton.setMenu(self.penMenu) self.toolbar.addWidget(penButton) def setPenColor(self, color): qColor = QColor(color) for a in self.penMenu.actions(): a.setChecked(False) try: actionColor = QColor(a.color) except AttributeError: pass else: if actionColor == qColor: a.setChecked(True) self.penColor = actionColor def addPaletteToMenu(self, menu): for name, color in COLORS.items(): paletteIcon = QPaletteIcon(color) action = QAction(paletteIcon, name, self, checkable=True) action.color = color action.triggered.connect( lambda checked, color=color: self.setPenColor(color)) menu.addAction(action) def initToolbar(self): self.createActions() # self.toolbar.addAction(self.flattenAct) self.toolbar.addAction(self.undoAct) # self.toolbar.addSeparator() self.toolbar.addAction(self.selectionModeAct) self.toolbar.addAction(self.ovalModeAct) self.addPenToolMenu()
def info(*args): print(args) try: tb_name = "mytb_test" tb = iface.mainWindow().findChild(QToolBar, tb_name) if tb is None: #create the toolbar tb = QToolBar(iface.mainWindow()) tb.setObjectName(tb_name) tb.setWindowTitle("My Toolbar") iface.mainWindow().addToolBarBreak() #toolbar in new line iface.addToolBar(tb) else: tb.clear() #add actions to tb actionsList = [ "MyAPp:", "mActionSaveProject", "|", "mActionZoomIn", "mActionZoomOut", ["mActionMeasure", "mActionMeasureAngle", "mActionMeasureArea"] ] for a in actionsList: addAction(tb, a) #add a hide button btnObjectName = 'mClose_' + tb_name btn = tb.findChild(QPushButton, btnObjectName) if btn is None: btnHide = QPushButton() btnHide.setObjectName(btnObjectName) btnHide.setMaximumWidth(15) btnHide.setToolTip('close')
class _s_IDE(QMainWindow): ############################################################################### # SIGNALS # # goingDown() ############################################################################### goingDown = pyqtSignal() def __init__(self, start_server=False): super(_s_IDE, self).__init__() self.setWindowTitle('NINJA-IDE {Ninja-IDE Is Not Just Another IDE}') self.setMinimumSize(700, 500) #Load the size and the position of the main window self.load_window_geometry() self.__project_to_open = 0 #Start server if needed self.s_listener = None if start_server: self.s_listener = QLocalServer() self.s_listener.listen("ninja_ide") self.s_listener.newConnection.connect(self._process_connection) #Profile handler self.profile = None #Opacity self.opacity = settings.MAX_OPACITY #Define Actions object before the UI self.actions = actions.Actions() #StatusBar self.status = status_bar.StatusBar(self) self.status.hide() self.setStatusBar(self.status) #Main Widget - Create first than everything else self.central = central_widget.CentralWidget(self) self.load_ui(self.central) self.setCentralWidget(self.central) #ToolBar self.toolbar = QToolBar(self) self.toolbar.setToolTip(_translate("_s_IDE", "Press and Drag to Move")) self.toolbar.setToolButtonStyle(Qt.ToolButtonIconOnly) self.addToolBar(settings.TOOLBAR_AREA, self.toolbar) if settings.HIDE_TOOLBAR: self.toolbar.hide() #Install Shortcuts after the UI has been initialized self.actions.install_shortcuts(self) self.mainContainer.currentTabChanged[str].connect(self.actions.update_explorer) #Menu menubar = self.menuBar() file_ = menubar.addMenu(_translate("_s_IDE", "&File")) edit = menubar.addMenu(_translate("_s_IDE", "&Edit")) view = menubar.addMenu(_translate("_s_IDE", "&View")) source = menubar.addMenu(_translate("_s_IDE", "&Source")) project = menubar.addMenu(_translate("_s_IDE", "&Project")) self.pluginsMenu = menubar.addMenu(_translate("_s_IDE", "&Addins")) about = menubar.addMenu(_translate("_s_IDE", "Abou&t")) #The order of the icons in the toolbar is defined by this calls self._menuFile = menu_file.MenuFile(file_, self.toolbar, self) self._menuView = menu_view.MenuView(view, self.toolbar, self) self._menuEdit = menu_edit.MenuEdit(edit, self.toolbar) self._menuSource = menu_source.MenuSource(source) self._menuProject = menu_project.MenuProject(project, self.toolbar) self._menuPlugins = menu_plugins.MenuPlugins(self.pluginsMenu) self._menuAbout = menu_about.MenuAbout(about) self.load_toolbar() #Plugin Manager services = { 'editor': plugin_services.MainService(), 'toolbar': plugin_services.ToolbarService(self.toolbar), 'menuApp': plugin_services.MenuAppService(self.pluginsMenu), 'explorer': plugin_services.ExplorerService(), 'misc': plugin_services.MiscContainerService(self.misc)} serviceLocator = plugin_manager.ServiceLocator(services) self.plugin_manager = plugin_manager.PluginManager(resources.PLUGINS, serviceLocator) self.plugin_manager.discover() #load all plugins! self.plugin_manager.load_all() #Tray Icon self.trayIcon = updates.TrayIconUpdates(self) self.trayIcon.show() self._menuFile.openFile[str].connect(self.mainContainer.open_file) self.mainContainer.fileSaved[str].connect(self.show_status_message) self.mainContainer.recentTabsModified[list].connect(self._menuFile.update_recent_files)#'QStringList' self.mainContainer.currentTabChanged[str].connect(self.actions.update_migration_tips) self.mainContainer.updateFileMetadata.connect(self.actions.update_migration_tips) self.mainContainer.migrationAnalyzed.connect(self.actions.update_migration_tips) def _process_connection(self): connection = self.s_listener.nextPendingConnection() connection.waitForReadyRead() data = connection.readAll() connection.close() if data: files, projects = str(data).split(ipc.project_delimiter, 1) files = [(x.split(':')[0], int(x.split(':')[1])) for x in files.split(ipc.file_delimiter)] projects = projects.split(ipc.project_delimiter) self.load_session_files_projects(files, [], projects, None) def load_toolbar(self): self.toolbar.clear() toolbar_items = {} toolbar_items.update(self._menuFile.toolbar_items) toolbar_items.update(self._menuView.toolbar_items) toolbar_items.update(self._menuEdit.toolbar_items) toolbar_items.update(self._menuSource.toolbar_items) toolbar_items.update(self._menuProject.toolbar_items) for item in settings.TOOLBAR_ITEMS: if item == 'separator': self.toolbar.addSeparator() else: tool_item = toolbar_items.get(item, None) if tool_item is not None: self.toolbar.addAction(tool_item) #load action added by plugins, This is a special case when reload #the toolbar after save the preferences widget for toolbar_action in settings.get_toolbar_item_for_plugins(): self.toolbar.addAction(toolbar_action) def load_external_plugins(self, paths): for path in paths: self.plugin_manager.add_plugin_dir(path) #load all plugins! self.plugin_manager.discover() self.plugin_manager.load_all() def show_status_message(self, message): self.status.showMessage(message, 2000) def load_ui(self, centralWidget): #Set Application Font for ToolTips QToolTip.setFont(QFont(settings.FONT_FAMILY, 10)) #Create Main Container to manage Tabs self.mainContainer = main_container.MainContainer(self) self.mainContainer.currentTabChanged[str].connect(self.change_window_title) self.mainContainer.locateFunction[str, str, bool].connect(self.actions.locate_function) self.mainContainer.navigateCode[bool, int].connect(self.actions.navigate_code_history) self.mainContainer.addBackItemNavigation.connect(self.actions.add_back_item_navigation) self.mainContainer.updateFileMetadata.connect(self.actions.update_explorer) self.mainContainer.updateLocator[str].connect(self.actions.update_explorer) self.mainContainer.openPreferences.connect(self._show_preferences) self.mainContainer.dontOpenStartPage.connect(self._dont_show_start_page_again) self.mainContainer.currentTabChanged[str].connect(self.status.handle_tab_changed) # When close the last tab cleanup self.mainContainer.allTabsClosed.connect(self._last_tab_closed) # Update symbols self.mainContainer.updateLocator[str].connect(self.status.explore_file_code) #Create Explorer Panel self.explorer = explorer_container.ExplorerContainer(self) self.central.splitterCentralRotated.connect(self.explorer.rotate_tab_position) self.explorer.updateLocator.connect(self.status.explore_code) self.explorer.goToDefinition[int].connect(self.actions.editor_go_to_line) self.explorer.projectClosed[str].connect(self.actions.close_files_from_project) #Create Misc Bottom Container self.misc = misc_container.MiscContainer(self) self.mainContainer.findOcurrences[str].connect(self.misc.show_find_occurrences) centralWidget.insert_central_container(self.mainContainer) centralWidget.insert_lateral_container(self.explorer) centralWidget.insert_bottom_container(self.misc) if self.explorer.count() == 0: centralWidget.change_explorer_visibility(force_hide=True) self.mainContainer.cursorPositionChange[int, int].connect(self.central.lateralPanel.update_line_col) # TODO: Change current symbol on move #self.connect(self.mainContainer, #SIGNAL("cursorPositionChange(int, int)"), #self.explorer.update_current_symbol) self.mainContainer.enabledFollowMode[bool].connect(self.central.enable_follow_mode_scrollbar) if settings.SHOW_START_PAGE: self.mainContainer.show_start_page() def _last_tab_closed(self): """ Called when the last tasb is closed """ self.explorer.cleanup_tabs() def _show_preferences(self): pref = preferences.PreferencesWidget(self.mainContainer) pref.show() def _dont_show_start_page_again(self): settings.SHOW_START_PAGE = False qsettings = QSettings(resources.SETTINGS_PATH, QSettings.IniFormat) qsettings.beginGroup('preferences') qsettings.beginGroup('general') qsettings.setValue('showStartPage', settings.SHOW_START_PAGE) qsettings.endGroup() qsettings.endGroup() self.mainContainer.actualTab.close_tab() def load_session_files_projects(self, filesTab1, filesTab2, projects, current_file, recent_files=None): self.__project_to_open = len(projects) self.explorer.projectOpened[str].connect(self._set_editors_project_data) self.explorer.open_session_projects(projects, notIDEStart=False) self.mainContainer.open_files(filesTab1, notIDEStart=False) self.mainContainer.open_files(filesTab2, mainTab=False, notIDEStart=False) if current_file: self.mainContainer.open_file(current_file, notStart=False) if recent_files is not None: self._menuFile.update_recent_files(recent_files) def _set_editors_project_data(self): self.__project_to_open -= 1 if self.__project_to_open == 0: self.explorer.projectOpened[str].disconnect(self._set_editors_project_data) self.mainContainer.update_editor_project() def open_file(self, filename): if filename: self.mainContainer.open_file(filename) def open_project(self, project): if project: self.actions.open_project(project) def __get_profile(self): return self.profile def __set_profile(self, profileName): self.profile = profileName if self.profile is not None: self.setWindowTitle('NINJA-IDE (PROFILE: %s)' % self.profile) else: self.setWindowTitle( 'NINJA-IDE {Ninja-IDE Is Not Just Another IDE}') Profile = property(__get_profile, __set_profile) def change_window_title(self, title): if self.profile is None: self.setWindowTitle('NINJA-IDE - %s' % title) else: self.setWindowTitle('NINJA-IDE (PROFILE: %s) - %s' % ( self.profile, title)) currentEditor = self.mainContainer.get_actual_editor() if currentEditor is not None: line = currentEditor.textCursor().blockNumber() + 1 col = currentEditor.textCursor().columnNumber() self.central.lateralPanel.update_line_col(line, col) def wheelEvent(self, event): if event.modifiers() == Qt.ShiftModifier: if event.delta() == 120 and self.opacity < settings.MAX_OPACITY: self.opacity += 0.1 elif event.delta() == -120 and self.opacity > settings.MIN_OPACITY: self.opacity -= 0.1 self.setWindowOpacity(self.opacity) event.ignore() else: QMainWindow.wheelEvent(self, event) def save_settings(self): """Save the settings before the application is closed with QSettings. Info saved: Tabs and projects opened, windows state(size and position). """ qsettings = QSettings(resources.SETTINGS_PATH, QSettings.IniFormat) editor_widget = self.mainContainer.get_actual_editor() current_file = '' if editor_widget is not None: current_file = editor_widget.ID if qsettings.value('preferences/general/loadFiles', True, type=bool): openedFiles = self.mainContainer.get_opened_documents() projects_obj = self.explorer.get_opened_projects() projects = [p.path for p in projects_obj] qsettings.setValue('openFiles/projects', projects) if len(openedFiles) > 0: qsettings.setValue('openFiles/mainTab', openedFiles[0]) if len(openedFiles) == 2: qsettings.setValue('openFiles/secondaryTab', openedFiles[1]) qsettings.setValue('openFiles/currentFile', current_file) qsettings.setValue('openFiles/recentFiles', self.mainContainer._tabMain.get_recent_files_list()) qsettings.setValue('preferences/editor/bookmarks', settings.BOOKMARKS) qsettings.setValue('preferences/editor/breakpoints', settings.BREAKPOINTS) qsettings.setValue('preferences/general/toolbarArea', self.toolBarArea(self.toolbar)) #Save if the windows state is maximixed if(self.isMaximized()): qsettings.setValue("window/maximized", True) else: qsettings.setValue("window/maximized", False) #Save the size and position of the mainwindow qsettings.setValue("window/size", self.size()) qsettings.setValue("window/pos", self.pos()) #Save the size of de splitters qsettings.setValue("window/central/areaSize", self.central.get_area_sizes()) qsettings.setValue("window/central/mainSize", self.central.get_main_sizes()) #Save the toolbar visibility if not self.toolbar.isVisible() and self.menuBar().isVisible(): qsettings.setValue("window/hide_toolbar", True) else: qsettings.setValue("window/hide_toolbar", False) #Save Misc state qsettings.setValue("window/show_misc", self.misc.isVisible()) #Save Profiles if self.profile is not None: self.actions.save_profile(self.profile) else: qsettings.setValue('ide/profiles', settings.PROFILES) def load_window_geometry(self): """Load from QSettings the window size of de Ninja IDE""" qsettings = QSettings(resources.SETTINGS_PATH, QSettings.IniFormat) if qsettings.value("window/maximized", True, type=bool): self.setWindowState(Qt.WindowMaximized) else: self.resize(qsettings.value("window/size", QSizeF(800, 600).toSize(), type='QSize')) self.move(qsettings.value("window/pos", QPointF(100, 100).toPoint(), type='QPoint')) def closeEvent(self, event): if self.s_listener: self.s_listener.close() if (settings.CONFIRM_EXIT and self.mainContainer.check_for_unsaved_tabs()): unsaved_files = self.mainContainer.get_unsaved_files() txt = '\n'.join(unsaved_files) val = QMessageBox.question(self, _translate("_s_IDE", "Some changes were not saved"), (_translate("_s_IDE", "%s\n\nDo you want to save them?") % txt), QMessageBox.Yes, QMessageBox.No, QMessageBox.Cancel) if val == QMessageBox.Yes: #Saves all open files self.mainContainer.save_all() if val == QMessageBox.Cancel: event.ignore() self.goingDown.emit() self.save_settings() completion_daemon.shutdown_daemon() #close python documentation server (if running) self.mainContainer.close_python_doc() #Shutdown PluginManager self.plugin_manager.shutdown() def notify_plugin_errors(self): errors = self.plugin_manager.errors if errors: plugin_error_dialog = traceback_widget.PluginErrorDialog() for err_tuple in errors: plugin_error_dialog.add_traceback(err_tuple[0], err_tuple[1]) #show the dialog plugin_error_dialog.exec_() def show_python_detection(self): suggested = settings.detect_python_path() if suggested: dialog = python_detect_dialog.PythonDetectDialog(suggested, self) dialog.show()