class ThumbnailWidget(QtGui.QWidget): """ Thumbnail widget that provides screen capture functionality """ thumbnail_changed = QtCore.Signal() def __init__(self, parent=None): """ Construction """ QtGui.QWidget.__init__(self, parent) self._ui = Ui_ThumbnailWidget() self._ui.setupUi(self) # create layout to control buttons frame layout = QtGui.QHBoxLayout() layout.addWidget(self._ui.buttons_frame) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) self.setLayout(layout) # connect to buttons: self._ui.camera_btn.clicked.connect(self._on_camera_clicked) self._btns_transition_anim = None self._update_ui() # @property def _get_thumbnail(self): pm = self._ui.thumbnail.pixmap() return pm if pm and not pm.isNull() else None # @thumbnail.setter def _set_thumbnail(self, value): self._ui.thumbnail.setPixmap(value if value else QtGui.QPixmap()) self._update_ui() self.thumbnail_changed.emit() thumbnail = property(_get_thumbnail, _set_thumbnail) def enable_screen_capture(self, enable): self._ui.camera_btn.setVisible(enable) def resizeEvent(self, event): self._update_ui() def enterEvent(self, event): """ when the cursor enters the control, show the buttons """ if self.thumbnail and self._are_any_btns_enabled(): self._ui.buttons_frame.show() if hasattr(QtCore, "QAbstractAnimation"): self._run_btns_transition_anim( QtCore.QAbstractAnimation.Forward) else: # Q*Animation classes aren't available so just # make sure the button is visible: self.btn_visibility = 1.0 def leaveEvent(self, event): """ when the cursor leaves the control, hide the buttons """ if self.thumbnail and self._are_any_btns_enabled(): if hasattr(QtCore, "QAbstractAnimation"): self._run_btns_transition_anim( QtCore.QAbstractAnimation.Backward) else: # Q*Animation classes aren't available so just # make sure the button is hidden: self._ui.buttons_frame.hide() self.btn_visibility = 0.0 def _are_any_btns_enabled(self): """ Return if any of the buttons are enabled """ return not (self._ui.camera_btn.isHidden()) """ button visibility property used by QPropertyAnimation """ def get_btn_visibility(self): return self._btns_visibility def set_btn_visibility(self, value): self._btns_visibility = value self._ui.buttons_frame.setStyleSheet( "#buttons_frame {border-radius: 2px; background-color: rgba(32, 32, 32, %d);}" % (64 * value)) btn_visibility = QtCore.Property(float, get_btn_visibility, set_btn_visibility) def _run_btns_transition_anim(self, direction): """ Run the transition animation for the buttons """ if not self._btns_transition_anim: # set up anim: self._btns_transition_anim = QtCore.QPropertyAnimation( self, "btn_visibility") self._btns_transition_anim.setDuration(150) self._btns_transition_anim.setStartValue(0.0) self._btns_transition_anim.setEndValue(1.0) self._btns_transition_anim.finished.connect( self._on_btns_transition_anim_finished) if self._btns_transition_anim.state( ) == QtCore.QAbstractAnimation.Running: if self._btns_transition_anim.direction() != direction: self._btns_transition_anim.pause() self._btns_transition_anim.setDirection(direction) self._btns_transition_anim.resume() else: pass # just let animation continue! else: self._btns_transition_anim.setDirection(direction) self._btns_transition_anim.start() def _on_btns_transition_anim_finished(self): if self._btns_transition_anim.direction( ) == QtCore.QAbstractAnimation.Backward: self._ui.buttons_frame.hide() def _on_camera_clicked(self): pm = self._on_screenshot() if pm: self.thumbnail = pm def _update_ui(self): # maximum size of thumbnail is widget geom: thumbnail_geom = self.geometry() thumbnail_geom.moveTo(0, 0) scale_contents = False pm = self.thumbnail if pm: # work out size thumbnail should be to maximize size # whilst retaining aspect ratio pm_sz = pm.size() h_scale = float(thumbnail_geom.height() - 4) / float( pm_sz.height()) w_scale = float(thumbnail_geom.width() - 4) / float(pm_sz.width()) scale = min(1.0, h_scale, w_scale) scale_contents = (scale < 1.0) new_height = min(int(pm_sz.height() * scale), thumbnail_geom.height()) new_width = min(int(pm_sz.width() * scale), thumbnail_geom.width()) new_geom = QtCore.QRect(thumbnail_geom) new_geom.moveLeft(( (thumbnail_geom.width() - 4) / 2 - new_width / 2) + 2) new_geom.moveTop(( (thumbnail_geom.height() - 4) / 2 - new_height / 2) + 2) new_geom.setWidth(new_width) new_geom.setHeight(new_height) thumbnail_geom = new_geom self._ui.thumbnail.setScaledContents(scale_contents) self._ui.thumbnail.setGeometry(thumbnail_geom) # now update buttons based on current thumbnail: if not self._btns_transition_anim or self._btns_transition_anim.state( ) == QtCore.QAbstractAnimation.Stopped: if self.thumbnail or not self._are_any_btns_enabled(): self._ui.buttons_frame.hide() self._btns_visibility = 0.0 else: self._ui.buttons_frame.show() self._btns_visibility = 1.0 def _safe_get_dialog(self): """ Get the widgets dialog parent. just call self.window() but this is unstable in Nuke Previously this would causing a crash on exit - suspect that it's caching something internally which then doesn't get cleaned up properly... """ current_widget = self while current_widget: if isinstance(current_widget, QtGui.QDialog): return current_widget current_widget = current_widget.parentWidget() return None def _on_screenshot(self): """ Perform the actual screenshot """ # hide the containing window - we can't actuall hide # the window as this will break modality! Instead # we have to move the window off the screen: win = self._safe_get_dialog() win_geom = None if win: win_geom = win.geometry() win.setGeometry(1000000, 1000000, win_geom.width(), win_geom.height()) # make sure this event is processed: QtCore.QCoreApplication.processEvents() QtCore.QCoreApplication.sendPostedEvents(None, 0) QtCore.QCoreApplication.flush() try: # get temporary file to use: # to be cross-platform and python 2.5 compliant, we can't use # tempfile.NamedTemporaryFile with delete=False. Instead, we # use tempfile.mkstemp which does practically the same thing! tf, path = tempfile.mkstemp(suffix=".png", prefix="tanktmp") if tf: os.close(tf) pm = screen_grab.screen_capture() finally: # restore the window: if win: win.setGeometry(win_geom) QtCore.QCoreApplication.processEvents() return pm
class ThumbnailWidget(QtGui.QWidget): """ Thumbnail widget that provides screen capture functionality """ thumbnail_changed = QtCore.Signal() def __init__(self, parent=None): """ Construction """ QtGui.QWidget.__init__(self, parent) self._ui = Ui_ThumbnailWidget() self._ui.setupUi(self) # create layout to control buttons frame layout = QtGui.QHBoxLayout() layout.addWidget(self._ui.buttons_frame) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) self.setLayout(layout) # connect to buttons: self._ui.camera_btn.clicked.connect(self._on_camera_clicked) self._btns_transition_anim = None self._update_ui() @property def thumbnail(self): pm = self._ui.thumbnail.pixmap() return pm if pm and not pm.isNull() else None @thumbnail.setter def thumbnail(self, value): self._ui.thumbnail.setPixmap(value if value else QtGui.QPixmap()) self._update_ui() self.thumbnail_changed.emit() def enable_screen_capture(self, enable): self._ui.camera_btn.setVisible(enable) def resizeEvent(self, event): self._update_ui() def enterEvent(self, event): """ when the cursor enters the control, show the buttons """ if self.thumbnail and self._are_any_btns_enabled(): self._ui.buttons_frame.show() self._run_btns_transition_anim(QtCore.QAbstractAnimation.Forward) def leaveEvent(self, event): """ when the cursor leaves the control, hide the buttons """ if self.thumbnail and self._are_any_btns_enabled(): self._run_btns_transition_anim(QtCore.QAbstractAnimation.Backward) def _are_any_btns_enabled(self): """ Return if any of the buttons are enabled """ return not (self._ui.camera_btn.isHidden()) """ button visibility property used by QPropertyAnimation """ def get_btn_visibility(self): return self._btns_visibility def set_btn_visibility(self, value): self._btns_visibility = value self._ui.buttons_frame.setStyleSheet( "#buttons_frame {border-radius: 2px; background-color: rgba(32, 32, 32, %d);}" % (64 * value)) btn_visibility = QtCore.Property(float, get_btn_visibility, set_btn_visibility) def _run_btns_transition_anim(self, direction): """ Run the transition animation for the buttons """ if not self._btns_transition_anim: # set up anim: self._btns_transition_anim = QtCore.QPropertyAnimation( self, "btn_visibility") self._btns_transition_anim.setDuration(150) self._btns_transition_anim.setStartValue(0.0) self._btns_transition_anim.setEndValue(1.0) self._btns_transition_anim.finished.connect( self._on_btns_transition_anim_finished) if self._btns_transition_anim.state( ) == QtCore.QAbstractAnimation.Running: if self._btns_transition_anim.direction() != direction: self._btns_transition_anim.pause() self._btns_transition_anim.setDirection(direction) self._btns_transition_anim.resume() else: pass # just let animation continue! else: self._btns_transition_anim.setDirection(direction) self._btns_transition_anim.start() def _on_btns_transition_anim_finished(self): if self._btns_transition_anim.direction( ) == QtCore.QAbstractAnimation.Backward: self._ui.buttons_frame.hide() def _on_camera_clicked(self): pm = self._on_screenshot() if pm: self.thumbnail = pm def _update_ui(self): # maximum size of thumbnail is widget geom: thumbnail_geom = self.geometry() thumbnail_geom.moveTo(0, 0) scale_contents = False pm = self.thumbnail if pm: # work out size thumbnail should be to maximize size # whilst retaining aspect ratio pm_sz = pm.size() h_scale = float(thumbnail_geom.height() - 4) / float( pm_sz.height()) w_scale = float(thumbnail_geom.width() - 4) / float(pm_sz.width()) scale = min(1.0, h_scale, w_scale) scale_contents = (scale < 1.0) new_height = min(int(pm_sz.height() * scale), thumbnail_geom.height()) new_width = min(int(pm_sz.width() * scale), thumbnail_geom.width()) new_geom = QtCore.QRect(thumbnail_geom) new_geom.moveLeft(( (thumbnail_geom.width() - 4) / 2 - new_width / 2) + 2) new_geom.moveTop(( (thumbnail_geom.height() - 4) / 2 - new_height / 2) + 2) new_geom.setWidth(new_width) new_geom.setHeight(new_height) thumbnail_geom = new_geom self._ui.thumbnail.setScaledContents(scale_contents) self._ui.thumbnail.setGeometry(thumbnail_geom) # now update buttons based on current thumbnail: if not self._btns_transition_anim or self._btns_transition_anim.state( ) == QtCore.QAbstractAnimation.Stopped: if self.thumbnail or not self._are_any_btns_enabled(): self._ui.buttons_frame.hide() self._btns_visibility = 0.0 else: self._ui.buttons_frame.show() self._btns_visibility = 1.0 def _safe_get_dialog(self): """ Get the widgets dialog parent. just call self.window() but this is unstable in Nuke Previously this would causing a crash on exit - suspect that it's caching something internally which then doesn't get cleaned up properly... """ current_widget = self while current_widget: if isinstance(current_widget, QtGui.QDialog): return current_widget current_widget = current_widget.parentWidget() return None class ScreenshotThread(QtCore.QThread): """ Wrap screenshot call in a thread just to be on the safe side! This helps avoid the os thinking the application has hung for certain applications (e.g. Softimage on Windows) """ def __init__(self, path): QtCore.QThread.__init__(self) self._path = path self._error = None def get_error(self): return self._error def run(self): try: if sys.platform == "darwin": # use built-in screenshot command on the mac os.system("screencapture -m -i -s %s" % self._path) elif sys.platform == "linux2": # use image magick os.system("import %s" % self._path) elif sys.platform == "win32": # use external boxcutter tool bc = os.path.abspath( os.path.join(__file__, "../resources/boxcutter.exe")) subprocess.check_call([bc, self._path]) except Exception, e: self._error = str(e)