def paintEvent(self, event: QPaintEvent): painter = QPainter(self) painter.drawImage(self.rect(), self.image) if self.show_frame: painter.save() pen = QPen() pen.setWidth(2) pen.setColor(QColor("black")) painter.setPen(pen) rect = QRect(1, 1, self.width() - 2, self.height() - 2) painter.drawRect(rect) pen.setColor(QColor("white")) painter.setPen(pen) rect = QRect(3, 3, self.width() - 6, self.height() - 6) painter.drawRect(rect) painter.restore() if self.show_arrow: painter.save() triangle = QPolygonF() dist = 4 point1 = QPoint(self.width() - self.triangle_width, 0) size = QSize(20, self.height() // 2) rect = QRect(point1, size) painter.fillRect(rect, QColor("white")) triangle.append(point1 + QPoint(dist, dist)) triangle.append(point1 + QPoint(size.width() - dist, dist)) triangle.append(point1 + QPoint(size.width() // 2, size.height() - dist)) painter.setBrush(Qt.black) painter.drawPolygon(triangle, Qt.WindingFill) painter.restore()
def readSettings(self, settings): qapp = QApplication.instance() # get the saved window geometry window_size = settings.get('MainWindow/size') if not isinstance(window_size, QSize): window_size = QSize(*window_size) window_pos = settings.get('MainWindow/position') if not isinstance(window_pos, QPoint): window_pos = QPoint(*window_pos) if settings.has('MainWindow/font'): font_string = settings.get('MainWindow/font').split(',') font = QFontDatabase().font(font_string[0], font_string[-1], int(font_string[1])) qapp.setFont(font) # reset font for ipython console to ensure it stays monospace self.ipythonconsole.console.reset_font() # make sure main window is smaller than the desktop desktop = QDesktopWidget() # this gives the maximum screen number if the position is off screen screen = desktop.screenNumber(window_pos) # recalculate the window size desktop_geom = desktop.availableGeometry(screen) w = min(desktop_geom.size().width(), window_size.width()) h = min(desktop_geom.size().height(), window_size.height()) window_size = QSize(w, h) # and position it on the supplied desktop screen x = max(window_pos.x(), desktop_geom.left()) y = max(window_pos.y(), desktop_geom.top()) if x + w > desktop_geom.right(): x = desktop_geom.right() - w if y + h > desktop_geom.bottom(): y = desktop_geom.bottom() - h window_pos = QPoint(x, y) # set the geometry self.resize(window_size) self.move(window_pos) # restore window state if settings.has('MainWindow/state'): if not self.restoreState(settings.get('MainWindow/state'), SAVE_STATE_VERSION): logger.warning( "The previous layout of workbench is not compatible with this version, reverting to default layout." ) else: self.setWindowState(Qt.WindowMaximized) # read in settings for children AlgorithmInputHistory().readSettings(settings) for widget in self.widgets: if hasattr(widget, 'readSettingsIfNotDone'): widget.readSettingsIfNotDone(settings)
def readSettings(self, settings): qapp = QApplication.instance() qapp.setAttribute(Qt.AA_UseHighDpiPixmaps) if hasattr(Qt, 'AA_EnableHighDpiScaling'): qapp.setAttribute(Qt.AA_EnableHighDpiScaling, settings.get('high_dpi_scaling')) # get the saved window geometry window_size = settings.get('MainWindow/size') if not isinstance(window_size, QSize): window_size = QSize(*window_size) window_pos = settings.get('MainWindow/position') if not isinstance(window_pos, QPoint): window_pos = QPoint(*window_pos) if settings.has('MainWindow/font'): font_string = settings.get('MainWindow/font').split(',') font = QFontDatabase().font(font_string[0], font_string[-1], int(font_string[1])) qapp.setFont(font) # make sure main window is smaller than the desktop desktop = QDesktopWidget() # this gives the maximum screen number if the position is off screen screen = desktop.screenNumber(window_pos) # recalculate the window size desktop_geom = desktop.availableGeometry(screen) w = min(desktop_geom.size().width(), window_size.width()) h = min(desktop_geom.size().height(), window_size.height()) window_size = QSize(w, h) # and position it on the supplied desktop screen x = max(window_pos.x(), desktop_geom.left()) y = max(window_pos.y(), desktop_geom.top()) if x + w > desktop_geom.right(): x = desktop_geom.right() - w if y + h > desktop_geom.bottom(): y = desktop_geom.bottom() - h window_pos = QPoint(x, y) # set the geometry self.resize(window_size) self.move(window_pos) # restore window state if settings.has('MainWindow/state'): self.restoreState(settings.get('MainWindow/state')) else: self.setWindowState(Qt.WindowMaximized) # read in settings for children AlgorithmInputHistory().readSettings(settings) for widget in self.widgets: if hasattr(widget, 'readSettings'): widget.readSettings(settings)
def qwtScaleBoundingRect(graphic, size): scaledSize = QSize(size) if scaledSize.isEmpty(): scaledSize = graphic.defaultSize() sz = graphic.controlPointRect().size() sx = 1.0 if sz.width() > 0.0: sx = scaledSize.width() / sz.width() sy = 1.0 if sz.height() > 0.0: sy = scaledSize.height() / sz.height() return graphic.scaledBoundingRect(sx, sy)
def SetInitialSize(self, size: QC.QSize): display_size = ClientGUIFunctions.GetDisplaySize(self) width = min(display_size.width(), size.width()) height = min(display_size.height(), size.height()) self.resize(QC.QSize(width, height)) min_width = min(240, width) min_height = min(240, height) self.setMinimumSize(QC.QSize(min_width, min_height))
def __honour_image_aspect_ratio(self): ''' resizes the window to fit the image aspect ratio based on the chart size ''' if len(self.image.text()) > 0: width, height = Image.open(self.image.text()).size width_delta = width - self.__x height_delta = height - self.__y if width_delta != 0 or height_delta != 0: win_size = self.size() target_size = QSize(win_size.width() + width_delta, win_size.height() + height_delta) available_size = QDesktopWidget().availableGeometry() if available_size.width() < target_size.width( ) or available_size.height() < target_size.height(): target_size.scale(available_size.width() - 48, available_size.height() - 48, Qt.KeepAspectRatio) self.resize(target_size)
def readSettings(self, settings): qapp = QApplication.instance() qapp.setAttribute(Qt.AA_UseHighDpiPixmaps) if hasattr(Qt, 'AA_EnableHighDpiScaling'): qapp.setAttribute(Qt.AA_EnableHighDpiScaling, settings.get('high_dpi_scaling')) # get the saved window geometry window_size = settings.get('MainWindow/size') if not isinstance(window_size, QSize): window_size = QSize(*window_size) window_pos = settings.get('MainWindow/position') if not isinstance(window_pos, QPoint): window_pos = QPoint(*window_pos) # make sure main window is smaller than the desktop desktop = QDesktopWidget() # this gives the maximum screen number if the position is off screen screen = desktop.screenNumber(window_pos) # recalculate the window size desktop_geom = desktop.screenGeometry(screen) w = min(desktop_geom.size().width(), window_size.width()) h = min(desktop_geom.size().height(), window_size.height()) window_size = QSize(w, h) # and position it on the supplied desktop screen x = max(window_pos.x(), desktop_geom.left()) y = max(window_pos.y(), desktop_geom.top()) window_pos = QPoint(x, y) # set the geometry self.resize(window_size) self.move(window_pos) # restore window state if settings.has('MainWindow/state'): self.restoreState(settings.get('MainWindow/state')) else: self.setWindowState(Qt.WindowMaximized) # read in settings for children AlgorithmInputHistory().readSettings(settings) for widget in self.widgets: if hasattr(widget, 'readSettings'): widget.readSettings(settings)
def readSettings(self, settings): qapp = QApplication.instance() qapp.setAttribute(Qt.AA_UseHighDpiPixmaps) if hasattr(Qt, 'AA_EnableHighDpiScaling'): qapp.setAttribute(Qt.AA_EnableHighDpiScaling, settings.get('main/high_dpi_scaling')) # get the saved window geometry window_size = settings.get('main/window/size') if not isinstance(window_size, QSize): window_size = QSize(*window_size) window_pos = settings.get('main/window/position') if not isinstance(window_pos, QPoint): window_pos = QPoint(*window_pos) # make sure main window is smaller than the desktop desktop = QDesktopWidget() # this gives the maximum screen number if the position is off screen screen = desktop.screenNumber(window_pos) # recalculate the window size desktop_geom = desktop.screenGeometry(screen) w = min(desktop_geom.size().width(), window_size.width()) h = min(desktop_geom.size().height(), window_size.height()) window_size = QSize(w, h) # and position it on the supplied desktop screen x = max(window_pos.x(), desktop_geom.left()) y = max(window_pos.y(), desktop_geom.top()) window_pos = QPoint(x, y) # set the geometry self.resize(window_size) self.move(window_pos) # restore window state if settings.has('main/window/state'): self.restoreState(settings.get('main/window/state')) else: self.setWindowState(Qt.WindowMaximized) # have algorithm dialogs do their thing AlgorithmInputHistory().readSettings(settings)
def ExpandTLWIfPossible(tlw: QW.QWidget, frame_key, desired_size_delta: QC.QSize): new_options = HG.client_controller.new_options (remember_size, remember_position, last_size, last_position, default_gravity, default_position, maximised, fullscreen) = new_options.GetFrameLocation(frame_key) if not tlw.isMaximized() and not tlw.isFullScreen(): current_size = tlw.size() current_width = current_size.width() current_height = current_size.height() desired_delta_width = desired_size_delta.width() desired_delta_height = desired_size_delta.height() desired_width = current_width if desired_delta_width > 0: desired_width = current_width + desired_delta_width + FUZZY_PADDING desired_height = current_height if desired_delta_height > 0: desired_height = current_height + desired_delta_height + FUZZY_PADDING desired_size = QC.QSize(desired_width, desired_height) new_size = GetSafeSize(tlw, desired_size, default_gravity) if new_size.width() > current_width or new_size.height( ) > current_height: tlw.resize(new_size) #tlw.setMinimumSize( tlw.sizeHint() ) SlideOffScreenTLWUpAndLeft(tlw)
class WebcamFeedWidget(MWB, QWidget): def __init__(self, params): MWB.__init__(self, params) QWidget.__init__(self) self.video_size = QSize(400, 300) self.timer = QTimer(self) self.capture = None self.image_label = QLabel() self.image_label.setFixedSize(self.video_size) self.main_layout = QVBoxLayout() self.main_layout.addWidget(self.image_label) self.setLayout(self.main_layout) self.setup_camera() self.resize(self.video_size) def setup_camera(self): self.capture = cv2.VideoCapture(0 + cv2.CAP_DSHOW) self.capture.set(cv2.CAP_PROP_FRAME_WIDTH, self.video_size.width()) self.capture.set(cv2.CAP_PROP_FRAME_HEIGHT, self.video_size.height()) self.timer.timeout.connect(self.display_video_stream) self.timer.start(20) def display_video_stream(self): _, frame = self.capture.read() frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) # frame = cv2.flip(frame, 1) image = QImage(frame, frame.shape[1], frame.shape[0], frame.strides[0], QImage.Format_RGB888) scaled_image = image.scaled(self.video_size) self.image_label.setPixmap(QPixmap.fromImage(scaled_image)) self.node.video_picture_updated(frame) def remove_event(self): self.timer.stop()
def paintEvent(self, event): """ Paint events are sent to widgets that need to update themselves, for instance when part of a widget is exposed because a covering widget was moved. At PyDMSymbolEditor this method handles the image preview. Parameters ---------- event : QPaintEvent """ if not self.preview: return size = QSize(140, 140) _painter = QPainter() _painter.begin(self) opt = QStyleOption() opt.initFrom(self) self.style().drawPrimitive(QStyle.PE_Widget, opt, _painter, self) image_to_draw = self.preview_file if isinstance(image_to_draw, QPixmap): w = float(image_to_draw.width()) h = float(image_to_draw.height()) sf = min(size.width() / w, size.height() / h) scale = (sf, sf) _painter.scale(scale[0], scale[1]) _painter.drawPixmap(335 / sf, 120 / sf, image_to_draw) elif isinstance(image_to_draw, QSvgRenderer): draw_size = QSizeF(image_to_draw.defaultSize()) draw_size.scale(QSizeF(size), Qt.KeepAspectRatio) image_to_draw.render( _painter, QRectF(335, 120, draw_size.width(), draw_size.height())) _painter.end() self.preview = False
def _GetNumPyImage(self, clip_rect: QC.QRect, target_resolution: QC.QSize): if self._numpy_image is None: return numpy.zeros( (target_resolution.height(), target_resolution.width()), dtype='uint8') clip_size = clip_rect.size() clip_width = clip_size.width() clip_height = clip_size.height() (my_width, my_height) = self._resolution my_full_rect = QC.QRect(0, 0, my_width, my_height) ZERO_MARGIN = QC.QMargins(0, 0, 0, 0) clip_padding = ZERO_MARGIN target_padding = ZERO_MARGIN if clip_rect == my_full_rect: # full image source = self._numpy_image else: if target_resolution.width() > clip_width: # this is a tile that is being scaled up! # to reduce tiling artifacts (disagreement at otherwise good borders), we want to oversample the clip for our tile so lanczos and friends can get good neighbour data and then crop it # therefore, we'll figure out some padding for the clip, and then calculate what that means in the target end, and do a crop at the end # we want to pad. that means getting a larger resolution and keeping a record of the padding # can't pad if we are at 0 for x or y, or up against width/height max, but no problem in that case obviously # there is the float-int precision calculation problem again. we can't pick a padding of 3 in the clip if we are zooming by 150%--what do we clip off in the target: 4 or 5 pixels? whatever, we get warping # first let's figure a decent zoom estimate: zoom_estimate = target_resolution.width( ) / clip_width if target_resolution.width( ) > target_resolution.height( ) else target_resolution.height() / clip_height # now, if zoom is 150% (as a fraction, 3/2), we want a padding at the target of something that divides by 3 cleanly, or, since we are choosing at the clip in this case and will be multiplying, something that divides cleanly to 67% zoom_estimate_for_clip_padding_multiplier = 1 / zoom_estimate # and we want a nice padding size limit, big enough to make clean numbers but not so big that we are rendering the 8 tiles in a square around the one we want no_bigger_than = max(4, (clip_width + clip_height) // 4) nice_number = HydrusData.GetNicelyDivisibleNumberForZoom( zoom_estimate_for_clip_padding_multiplier, no_bigger_than) if nice_number != -1: # lanczos, I think, uses 4x4 neighbour grid to render. we'll say padding of 4 pixels to be safe for now, although 2 or 3 is probably correct??? # however it works, numbers these small are not a big deal while nice_number < 4: nice_number *= 2 PADDING_AMOUNT = nice_number # LIMITATION: There is still a problem here for the bottom and rightmost edges. These tiles are not squares, so the shorter/thinner dimension my be an unpleasant number and be warped _anyway_, regardless of nice padding # perhaps there is a way to boost left or top padding so we are rendering a full square tile but still cropping our target at the end, but with a little less warping # I played around with this idea but did not have much success LEFT_PADDING_AMOUNT = PADDING_AMOUNT TOP_PADDING_AMOUNT = PADDING_AMOUNT left_padding = min(LEFT_PADDING_AMOUNT, clip_rect.x()) top_padding = min(TOP_PADDING_AMOUNT, clip_rect.y()) right_padding = min(PADDING_AMOUNT, (my_width - 1) - clip_rect.bottomRight().x()) bottom_padding = min(PADDING_AMOUNT, (my_height - 1) - clip_rect.bottomRight().y()) clip_padding = QC.QMargins(left_padding, top_padding, right_padding, bottom_padding) target_padding = clip_padding * zoom_estimate clip_rect_with_padding = clip_rect + clip_padding (x, y, clip_width, clip_height) = (clip_rect_with_padding.x(), clip_rect_with_padding.y(), clip_rect_with_padding.width(), clip_rect_with_padding.height()) source = self._numpy_image[y:y + clip_height, x:x + clip_width] if target_resolution == clip_size: # 100% zoom result = source else: if clip_padding == ZERO_MARGIN: result = ClientImageHandling.ResizeNumPyImageForMediaViewer( self._mime, source, (target_resolution.width(), target_resolution.height())) else: target_width_with_padding = target_resolution.width( ) + target_padding.left() + target_padding.right() target_height_with_padding = target_resolution.height( ) + target_padding.top() + target_padding.bottom() result = ClientImageHandling.ResizeNumPyImageForMediaViewer( self._mime, source, (target_width_with_padding, target_height_with_padding)) y = target_padding.top() x = target_padding.left() result = result[y:y + target_resolution.height(), x:x + target_resolution.width()] if not result.data.c_contiguous: result = result.copy() return result
class OpenCVWidget(QLabel): def __init__(self, parent=None): super(OpenCVWidget, self).__init__(parent) self.video_size = QSize(320, 240) self.setAttribute(Qt.WA_OpaquePaintEvent, True) if not IN_DESIGNER: self._enable_edge = False self._video_device = '/dev/video0' self._edge_min_threshold = 190 self._edge_max_threshold = 200 self._enable_crosshairs = True self._line_color = (255, 127, 0) # R G B self._line_thickness = 1 self._h_lines = 0 self._v_lines = 0 self._c_radius = 25 self.setup_camera() # Video def setup_camera(self): """Initialize camera. """ self.capture = cv2.VideoCapture(self._video_device) w = self.capture.get(cv2.CAP_PROP_FRAME_WIDTH) h = self.capture.get(cv2.CAP_PROP_FRAME_HEIGHT) self.video_size = QSize(w, h) self.setScaledContents(True) self.setMinimumSize(w, h) self.timer = QTimer() self.timer.timeout.connect(self.display_video_stream) self.timer.start(30) def minimumSizeHint(self): return self.video_size def timerEvent(self, QTimerEvent): print("hello") def display_video_stream(self): """Read frame from camera and repaint QLabel widget. """ if self.capture.isOpened(): result, frame = self.capture.read() if result is True: if self._enable_edge is True: frame = cv2.Canny(frame, self._edge_min_threshold, self._edge_max_threshold) if self._enable_crosshairs is True: self.draw_crosshairs(frame) image = QImage(frame.data, self.video_size.width(), self.video_size.height(), QImage.Format_Indexed8) else: frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) frame = cv2.flip(frame, 1) if self._enable_crosshairs is True: self.draw_crosshairs(frame) image = QImage(frame, frame.shape[1], frame.shape[0], frame.strides[0], QImage.Format_RGB888) self.setPixmap(QPixmap.fromImage(image)) # Helpers def draw_crosshairs(self, frame): w = self.video_size.width() h = self.video_size.height() cv2.line(frame, ((w / 2) + self._v_lines, 0), ((w / 2) + self._v_lines, h), self._line_color, self._line_thickness) cv2.line(frame, (0, (h / 2) - self._h_lines), (w, (h / 2) - self._h_lines), self._line_color, self._line_thickness) if self._c_radius > 1: cv2.circle(frame, ((w / 2) + self._v_lines, (h / 2) - self._h_lines), self._c_radius, self._line_color, self._line_thickness) # Slots @Slot(int) def setHorizontalLine(self, value): self._h_lines = value @Slot(int) def setVerticalLine(self, value): self._v_lines = value @Slot(int) def setCenterRadius(self, value): self._c_radius = value @Slot(bool) def enableCrosshairs(self, enabled): self._enable_crosshairs = enabled @Slot(bool) def enableEdge(self, enabled): self._enable_edge = enabled @Slot(int) def setEdgeMinThreshold(self, value): self._edge_min_threshold = value @Slot(int) def setEdgeMaxThreshold(self, value): self._edge_max_threshold = value @Slot(str) def setVideoDevice(self, path): self._video_device = path
class _QtMainWindow(QMainWindow): # This was added so that someone can patch # `napari._qt.qt_main_window._QtMainWindow._window_icon` # to their desired window icon _window_icon = NAPARI_ICON_PATH # To track window instances and facilitate getting the "active" viewer... # We use this instead of QApplication.activeWindow for compatibility with # IPython usage. When you activate IPython, it will appear that there are # *no* active windows, so we want to track the most recently active windows _instances: ClassVar[List['_QtMainWindow']] = [] def __init__(self, qt_viewer: QtViewer, parent=None) -> None: super().__init__(parent) self.qt_viewer = qt_viewer self._quit_app = False self.setWindowIcon(QIcon(self._window_icon)) self.setAttribute(Qt.WA_DeleteOnClose) self.setUnifiedTitleAndToolBarOnMac(True) center = QWidget(self) center.setLayout(QHBoxLayout()) center.layout().addWidget(qt_viewer) center.layout().setContentsMargins(4, 0, 4, 0) self.setCentralWidget(center) self.setWindowTitle(qt_viewer.viewer.title) self._maximized_flag = False self._preferences_dialog = None self._preferences_dialog_size = QSize() self._status_bar = self.statusBar() # set SETTINGS plugin defaults. SETTINGS._defaults['plugins'].call_order = plugin_manager.call_order() # set the values in plugins to match the ones saved in SETTINGS if SETTINGS.plugins.call_order is not None: plugin_manager.set_call_order(SETTINGS.plugins.call_order) _QtMainWindow._instances.append(self) # Connect the notification dispacther to correctly propagate # notifications from threads. See: `napari._qt.qt_event_loop::get_app` application_instance = QApplication.instance() if application_instance: application_instance._dispatcher.sig_notified.connect( self.show_notification) @classmethod def current(cls): return cls._instances[-1] if cls._instances else None def event(self, e): if e.type() == QEvent.Close: # when we close the MainWindow, remove it from the instances list try: _QtMainWindow._instances.remove(self) except ValueError: pass if e.type() in {QEvent.WindowActivate, QEvent.ZOrderChange}: # upon activation or raise_, put window at the end of _instances try: inst = _QtMainWindow._instances inst.append(inst.pop(inst.index(self))) except ValueError: pass return super().event(e) def _load_window_settings(self): """ Load window layout settings from configuration. """ window_size = SETTINGS.application.window_size window_state = SETTINGS.application.window_state preferences_dialog_size = SETTINGS.application.preferences_size window_position = SETTINGS.application.window_position # It's necessary to verify if the window/position value is valid with the current screen. width, height = window_position screen_shape = QApplication.desktop().geometry() current_width = screen_shape.width() current_height = screen_shape.height() if current_width < width or current_height < height: window_position = (self.x(), self.y()) window_maximized = SETTINGS.application.window_maximized window_fullscreen = SETTINGS.application.window_fullscreen return ( window_state, window_size, window_position, window_maximized, window_fullscreen, preferences_dialog_size, ) def _get_window_settings(self): """ Return current window settings. Symmetric to the 'set_window_settings' setter. """ window_size = (self.width(), self.height()) window_fullscreen = self.isFullScreen() if window_fullscreen: window_maximized = self._maximized_flag else: window_maximized = self.isMaximized() window_position = (self.x(), self.y()) preferences_dialog_size = ( self._preferences_dialog_size.width(), self._preferences_dialog_size.height(), ) window_state = qbytearray_to_str(self.saveState()) return ( window_state, window_size, window_position, window_maximized, window_fullscreen, preferences_dialog_size, ) def _set_window_settings( self, window_state, window_size, window_position, window_maximized, window_fullscreen, preferences_dialog_size, ): """ Set window settings. Symmetric to the 'get_window_settings' accessor. """ self.setUpdatesEnabled(False) self.setWindowState(Qt.WindowNoState) if preferences_dialog_size: self._preferences_dialog_size = QSize(*preferences_dialog_size) if window_position: window_position = QPoint(*window_position) self.move(window_position) if window_size: window_size = QSize(*window_size) self.resize(window_size) if window_state: self.restoreState(str_to_qbytearray(window_state)) if window_fullscreen: self.setWindowState(Qt.WindowFullScreen) self._maximized_flag = window_maximized elif window_maximized: self.setWindowState(Qt.WindowMaximized) self.setUpdatesEnabled(True) def _save_current_window_settings(self): """Save the current geometry of the main window.""" ( window_state, window_size, window_position, window_maximized, window_fullscreen, preferences_dialog_size, ) = self._get_window_settings() if SETTINGS.application.save_window_geometry: SETTINGS.application.window_maximized = window_maximized SETTINGS.application.window_fullscreen = window_fullscreen SETTINGS.application.window_position = window_position SETTINGS.application.window_size = window_size SETTINGS.application.window_statusbar = ( not self._status_bar.isHidden()) SETTINGS.application.preferences_size = preferences_dialog_size if SETTINGS.application.save_window_state: SETTINGS.application.window_state = window_state def _update_preferences_dialog_size(self, size): """Save preferences dialog size.""" self._preferences_dialog_size = size def close(self, quit_app=False): """Override to handle closing app or just the window.""" self._quit_app = quit_app return super().close() def close_window(self): """Close active dialog or active window.""" parent = QApplication.focusWidget() while parent is not None: if isinstance(parent, QMainWindow): self.close() break if isinstance(parent, QDialog): parent.close() break parent = parent.parent() def closeEvent(self, event): """This method will be called when the main window is closing. Regardless of whether cmd Q, cmd W, or the close button is used... """ # Close any floating dockwidgets for dock in self.findChildren(QtViewerDockWidget): if dock.isFloating(): dock.setFloating(False) self._save_current_window_settings() # On some versions of Darwin, exiting while fullscreen seems to tickle # some bug deep in NSWindow. This forces the fullscreen keybinding # test to complete its draw cycle, then pop back out of fullscreen. if self.isFullScreen(): self.showNormal() for _i in range(5): time.sleep(0.1) QApplication.processEvents() if self._quit_app: quit_app() event.accept() def restart(self): """Restart the napari application in a detached process.""" process = QProcess() process.setProgram(sys.executable) if not running_as_bundled_app(): process.setArguments(sys.argv) process.startDetached() self.close(quit_app=True) @staticmethod @Slot(Notification) def show_notification(notification: Notification): """Show notification coming from a thread.""" NapariQtNotification.show_notification(notification)
def _save_size(sz: QSize): app_pref.preferences_size = (sz.width(), sz.height())
def _GetNumPyImage(self, clip_rect: QC.QRect, target_resolution: QC.QSize): clip_size = clip_rect.size() (my_width, my_height) = self._resolution my_full_rect = QC.QRect(0, 0, my_width, my_height) ZERO_MARGIN = QC.QMargins(0, 0, 0, 0) clip_padding = ZERO_MARGIN target_padding = ZERO_MARGIN if clip_rect == my_full_rect: # full image source = self._numpy_image else: if target_resolution.width() > clip_size.width(): # this is a tile that is being scaled! # to reduce tiling artifacts, we want to oversample the clip for our tile so lanczos and friends can get good neighbour data and then crop it # therefore, we'll figure out some padding for the clip, and then calculate what that means in the target end, and do a crop at the end # we want to pad. that means getting a larger resolution and keeping a record of the padding # can't pad if we are at 0 for x or y, or up against width/height max # but if we can pad, we will get a larger clip size and then _clip_ a better target endpoint. this is tricky. PADDING_AMOUNT = 4 left_padding = min(PADDING_AMOUNT, clip_rect.x()) top_padding = min(PADDING_AMOUNT, clip_rect.y()) right_padding = min(PADDING_AMOUNT, my_width - clip_rect.bottomRight().x()) bottom_padding = min(PADDING_AMOUNT, my_height - clip_rect.bottomRight().y()) clip_padding = QC.QMargins(left_padding, top_padding, right_padding, bottom_padding) # this is ugly and super inaccurate target_padding = clip_padding * (target_resolution.width() / clip_size.width()) clip_rect_with_padding = clip_rect + clip_padding (x, y, clip_width, clip_height) = (clip_rect_with_padding.x(), clip_rect_with_padding.y(), clip_rect_with_padding.width(), clip_rect_with_padding.height()) source = self._numpy_image[y:y + clip_height, x:x + clip_width] if target_resolution == clip_size: # 100% zoom result = source else: if clip_padding == ZERO_MARGIN: result = ClientImageHandling.ResizeNumPyImageForMediaViewer( self._mime, source, (target_resolution.width(), target_resolution.height())) else: target_width_with_padding = target_resolution.width( ) + target_padding.left() + target_padding.right() target_height_with_padding = target_resolution.height( ) + target_padding.top() + target_padding.bottom() result = ClientImageHandling.ResizeNumPyImageForMediaViewer( self._mime, source, (target_width_with_padding, target_height_with_padding)) y = target_padding.top() x = target_padding.left() result = result[y:y + target_resolution.height(), x:x + target_resolution.width()] if not result.data.c_contiguous: result = result.copy() return result
def GetSafeSize(tlw: QW.QWidget, min_size: QC.QSize, gravity) -> QC.QSize: min_width = min_size.width() min_height = min_size.height() frame_padding = tlw.frameGeometry().size() - tlw.size() parent = tlw.parentWidget() if parent is None: width = min_width height = min_height else: parent_window = parent.window() # when we initialise, we might not have a frame yet because we haven't done show() yet # so borrow main gui's if frame_padding.isEmpty(): main_gui = HG.client_controller.gui if main_gui is not None and QP.isValid( main_gui) and not main_gui.isFullScreen(): frame_padding = main_gui.frameGeometry().size( ) - main_gui.size() if parent_window.isFullScreen(): parent_available_size = parent_window.size() else: parent_frame_size = parent_window.frameGeometry().size() parent_available_size = parent_frame_size - frame_padding parent_available_width = parent_available_size.width() parent_available_height = parent_available_size.height() (width_gravity, height_gravity) = gravity if width_gravity == -1: width = min_width else: max_width = parent_available_width - (2 * CHILD_POSITION_PADDING) width = int(width_gravity * max_width) if height_gravity == -1: height = min_height else: max_height = parent_available_height - (2 * CHILD_POSITION_PADDING) height = int(height_gravity * max_height) display_size = GetDisplaySize(tlw) display_available_size = display_size - frame_padding width = min(display_available_size.width() - 2 * CHILD_POSITION_PADDING, width) height = min(display_available_size.height() - 2 * CHILD_POSITION_PADDING, height) return QC.QSize(width, height)
class _QtMainWindow(QMainWindow): # This was added so that someone can patch # `napari._qt.qt_main_window._QtMainWindow._window_icon` # to their desired window icon _window_icon = NAPARI_ICON_PATH def __init__(self, parent=None) -> None: super().__init__(parent) self._quit_app = False self.setWindowIcon(QIcon(self._window_icon)) self.setAttribute(Qt.WA_DeleteOnClose) self.setUnifiedTitleAndToolBarOnMac(True) center = QWidget(self) center.setLayout(QHBoxLayout()) center.layout().setContentsMargins(4, 0, 4, 0) self.setCentralWidget(center) self._maximized_flag = False self._preferences_dialog_size = QSize() self._status_bar = self.statusBar() def _load_window_settings(self): """ Load window layout settings from configuration. """ window_size = SETTINGS.application.window_size window_state = SETTINGS.application.window_state preferences_dialog_size = SETTINGS.application.preferences_size window_position = SETTINGS.application.window_position # It's necessary to verify if the window/position value is valid with the current screen. width, height = window_position screen_shape = QApplication.desktop().geometry() current_width = screen_shape.width() current_height = screen_shape.height() if current_width < width or current_height < height: window_position = (self.x(), self.y()) window_maximized = SETTINGS.application.window_maximized window_fullscreen = SETTINGS.application.window_fullscreen return ( window_state, window_size, window_position, window_maximized, window_fullscreen, preferences_dialog_size, ) def _get_window_settings(self): """ Return current window settings. Symmetric to the 'set_window_settings' setter. """ window_size = (self.width(), self.height()) window_fullscreen = self.isFullScreen() if window_fullscreen: window_maximized = self._maximized_flag else: window_maximized = self.isMaximized() window_position = (self.x(), self.y()) preferences_dialog_size = ( self._preferences_dialog_size.width(), self._preferences_dialog_size.height(), ) window_state = qbytearray_to_str(self.saveState()) return ( window_state, window_size, window_position, window_maximized, window_fullscreen, preferences_dialog_size, ) def _set_window_settings( self, window_state, window_size, window_position, window_maximized, window_fullscreen, preferences_dialog_size, ): """ Set window settings. Symmetric to the 'get_window_settings' accessor. """ self.setUpdatesEnabled(False) self.setWindowState(Qt.WindowNoState) if preferences_dialog_size: self._preferences_dialog_size = QSize(*preferences_dialog_size) if window_position: window_position = QPoint(*window_position) self.move(window_position) if window_size: window_size = QSize(*window_size) self.resize(window_size) if window_state: self.restoreState(str_to_qbytearray(window_state)) if window_fullscreen: self.setWindowState(Qt.WindowFullScreen) self._maximized_flag = window_maximized elif window_maximized: self.setWindowState(Qt.WindowMaximized) self.setUpdatesEnabled(True) def _save_current_window_settings(self): """Save the current geometry of the main window.""" ( window_state, window_size, window_position, window_maximized, window_fullscreen, preferences_dialog_size, ) = self._get_window_settings() SETTINGS.application.window_size = window_size SETTINGS.application.window_maximized = window_maximized SETTINGS.application.window_fullscreen = window_fullscreen SETTINGS.application.window_position = window_position SETTINGS.application.window_state = window_state SETTINGS.application.preferences_size = preferences_dialog_size SETTINGS.application.window_statusbar = not self._status_bar.isHidden() def _update_preferences_dialog_size(self, event): """Save preferences dialog size.""" self._preferences_dialog_size = event.size() def close(self, quit_app=False): """Override to handle closing app or just the window.""" self._quit_app = quit_app return super().close() def closeEvent(self, event): """This method will be called when the main window is closing. Regardless of whether cmd Q, cmd W, or the close button is used... """ # Close any floating dockwidgets for dock in self.findChildren(QtViewerDockWidget): if dock.isFloating(): dock.setFloating(False) self._save_current_window_settings() # On some versions of Darwin, exiting while fullscreen seems to tickle # some bug deep in NSWindow. This forces the fullscreen keybinding # test to complete its draw cycle, then pop back out of fullscreen. if self.isFullScreen(): self.showNormal() for _i in range(5): time.sleep(0.1) QApplication.processEvents() if self._quit_app: quit_app() event.accept()
class RealizationDelegate(QStyledItemDelegate): def __init__(self, width, height, parent=None) -> None: super(RealizationDelegate, self).__init__(parent) self._size = QSize(width, height) def paint(self, painter, option: QStyleOptionViewItem, index: QModelIndex) -> None: text = index.data(RealLabelHint) colors = index.data(RealJobColorHint) painter.save() painter.setRenderHint(QPainter.Antialiasing, True) painter.setRenderHint(QPainter.SmoothPixmapTransform, True) border_pen = QPen() border_pen.setColor(QColorConstants.Black) border_pen.setWidth(1) if option.state & QStyle.State_Selected: # selection outline select_color = QColorConstants.Blue painter.setBrush(select_color) painter.setPen(border_pen) painter.drawRect(option.rect) # job status margin = 5 rect = QRect( option.rect.x() + margin, option.rect.y() + margin, option.rect.width() - (margin * 2), option.rect.height() - (margin * 2), ) painter.fillRect(rect, index.data(RealStatusColorHint)) self._paint_inner_grid(painter, option.rect, colors) text_pen = QPen() text_pen.setColor(select_color) painter.setPen(text_pen) painter.drawText(option.rect, Qt.AlignCenter, text) else: # # job status painter.setBrush(index.data(RealStatusColorHint)) painter.setPen(border_pen) painter.drawRect(option.rect) self._paint_inner_grid(painter, option.rect, colors) text_pen = QPen() text_pen.setColor(QColorConstants.Black) painter.setPen(text_pen) painter.drawText(option.rect, Qt.AlignCenter, text) painter.restore() def _paint_inner_grid(self, painter: QPainter, rect: QRect, colors) -> None: margin = 10 inner_grid_w = self._size.width() - (margin * 2) inner_grid_h = self._size.height() - (margin * 2) inner_grid_x = rect.x() + margin inner_grid_y = rect.y() + margin job_nr = len(colors) grid_dim = math.ceil(math.sqrt(job_nr)) w = math.ceil(inner_grid_w / grid_dim) h = math.ceil(inner_grid_h / grid_dim) k = 0 for y in range(grid_dim): for x in range(grid_dim): x_pos = inner_grid_x + (x * w) y_pos = inner_grid_y + (y * h) rect = QRect(x_pos, y_pos, w, h) if k >= job_nr: color = QColorConstants.Gray else: color = colors[k] painter.fillRect(rect, color) k += 1 def sizeHint(self, option, index) -> QSize: return self._size
class FileView(QAbstractItemView): def __init__(self, parent=None): super(FileView, self).__init__(parent) self.setSelectionMode(self.NoSelection) self._line_offsets = [0] self._line_sizes = [] self._size = QSize() self._force_follow = False self._init_font() def paintEvent(self, event): painter = QPainter(self.viewport()) view_rect = self._translate(event.rect()) if view_rect.y() < 0: rect = QRect() rect.setHeight(self.viewport().height() + view_rect.y()) rect.setWidth(self.viewport().width()) brush = self.palette().brush(QPalette.Disabled, QPalette.Window) painter.fillRect(rect, brush) for index in self._intersecting_rows(view_rect): option = self._style_option(index) delegate = self.itemDelegate(index) delegate.paint(painter, option, index) def setSelection(self, _rect, _flags): """setSelection is a pure virtual member function of QAbstractItemView""" def scrollTo(self, _index, _hint): """scrollTo is a pure virtual member function of QAbstractItemView""" def indexAt(self, point): """indexAt is a pure virtual member function of QAbstractItemView""" return self.model().index(self._resolve_row(point), 0) def moveCursor(self, _action, _modifiers): """moveCursor is a pure virtual member function of QAbstractItemView""" return QModelIndex() def visualRect(self, index): """visualRect is a pure virtual member function of QAbstractItemView""" if index.row() < 0 or index.row() >= len(self._line_sizes): return QRect() point = QPoint( -self.horizontalOffset(), self._line_offsets[index.row()] - self.verticalOffset(), ) return QRect(point, self._line_sizes[index.row()]) def visualRegionForSelection(self, _selection): """visualRegionForSelection is a pure virtual member function of QAbstractItemView""" return QRegion() def horizontalOffset(self): """horizontalOffset is a pure virtual member function of QAbstractItemView""" return self.horizontalScrollBar().value() def verticalOffset(self): """verticalOffset is a pure virtual member function of QAbstractItemView""" if self._force_follow: return self._size.height() - self.viewport().height() else: return self.verticalScrollBar().value() def isIndexHidden(self, _index): """isIndexHidden is a pure virtual member function of QAbstractItemView""" return False @Slot(QModelIndex, int, int) def rowsInserted(self, parent, first, last): """rowsInserted is an overriden slot of QAbstractItemView""" if first != len(self._line_sizes): raise NotImplementedError("FileView can only be appended to") model = self.model() follow = (self._force_follow or self.verticalOffset() == self.verticalScrollBar().maximum()) for idx in range(first, last): end = self._size.height() index = model.index(idx, 0) option = self._style_option(index) delegate = self.itemDelegate(index) size = delegate.sizeHint(option, index) self._line_sizes.append(size) self._line_offsets.append(end + size.height()) self._size.setWidth(max(size.width(), self._size.width())) self._size.setHeight(end + size.height()) self.updateGeometries() if follow: self.verticalScrollBar().setValue( self.verticalScrollBar().maximum()) self.viewport().update() @Slot() def updateGeometries(self): """updateGeometries is an overridden slot of QAbstractItemView""" horizontal_max = self._size.width() - self.viewport().width() self.horizontalScrollBar().setRange(0, horizontal_max) if self._force_follow: self.verticalScrollBar().setRange(0, 0) else: vertical_max = self._size.height() - self.viewport().height() self.verticalScrollBar().setRange(0, vertical_max) QAbstractItemView.updateGeometries(self) @Slot(bool) def enable_follow_mode(self, follow=True): """Enables or disabled follow mode, as in tail -f""" self._force_follow = follow self.updateGeometries() self.verticalScrollBar().setValue(self.verticalScrollBar().maximum()) self.viewport().update() def _intersecting_rows(self, rect): """Get rows that intersect with rect""" model = self.model() first = max(0, bisect_left(self._line_offsets, rect.top()) - 1) last = min(len(self._line_sizes), bisect_right(self._line_offsets, rect.bottom())) return [model.index(row, 0) for row in range(first, last)] def _resolve_row(self, point): """Get row that is at point""" y = point.y() + self.verticalOffset() return max(0, bisect_left(self._line_offsets, y) - 1) def _init_font(self): # There isn't a standard way of getting the system default monospace # font in Qt4 (it was introduced in Qt5.2). If QFontDatabase.FixedFont # exists, then we can assume that this functionality exists and ask for # the correct font directly. Otherwise we ask for a font that doesn't # exist and specify our requirements. Qt then finds an existing font # that best matches our parameters. if hasattr(QFontDatabase, "systemFont") and hasattr( QFontDatabase, "FixedFont"): font = QFontDatabase.systemFont(QFontDatabase.FixedFont) else: font = QFont("") font.setFixedPitch(True) font.setStyleHint(QFont.Monospace) self.setFont(font) def _style_option(self, index): """Get default style option for index""" option = QStyleOptionViewItem() option.font = self.font() option.rect = self.visualRect(index) option.state = QStyle.State_Enabled return option def _translate(self, rect): """Translate rect from viewport to document coordinates""" return rect.translated(self.horizontalOffset(), self.verticalOffset())