def __init__(self, title='', animation_duration=300, parent=None): super(ExpandableLine, self).__init__(parent=parent) self._animation_duration = animation_duration base_layout = layouts.GridLayout(margins=(0, 0, 0, 0)) base_layout.setVerticalSpacing(0) self.setLayout(base_layout) self.expand_btn = QToolButton() self.expand_btn.setText(str(title)) self.expand_btn.setStyleSheet('QToolButton { border : none; }') self.expand_btn.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) self.expand_btn.setArrowType(Qt.ArrowType.RightArrow) self.expand_btn.setCheckable(True) self.expand_btn.setChecked(True) header_line = QFrame() header_line.setFrameShape(QFrame.HLine) header_line.setFrameShadow(QFrame.Sunken) header_line.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Maximum) self.content_area = QScrollArea() self.content_area.setStyleSheet('QScrollArea { border: none;}') self.content_area.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.content_area.setMaximumHeight(0) self.content_area.setMinimumHeight(0) self.toggle_anim = QParallelAnimationGroup() self.toggle_anim.addAnimation(QPropertyAnimation(self, 'minimumHeight')) self.toggle_anim.addAnimation(QPropertyAnimation(self, 'maximumHeight')) self.toggle_anim.addAnimation(QPropertyAnimation(self.content_area, 'maximumHeight')) row = 0 base_layout.addWidget(self.expand_btn, row, 0, 1, 1, Qt.AlignLeft) base_layout.addWidget(header_line, row, 2, 1, 1) row += 1 base_layout.addWidget(self.content_area, row, 0, 1, 3) def expand_view(checked): arrow_type = Qt.DownArrow if checked else Qt.RightArrow direction = QAbstractAnimation.Forward if checked else QAbstractAnimation.Backward self.expand_btn.setArrowType(arrow_type) self.toggle_anim.setDirection(direction) self.toggle_anim.start() # === SIGNALS === # self.expand_btn.toggled.connect(expand_view) expand_view(True)
class MessageBox(QDialog, object): MAX_WIDTH = 320 MAX_HEIGHT = 220 @staticmethod def input(parent, title, text, input_text='', width=None, height=None, buttons=None, header_pixmap=None, header_color=None, theme_to_apply=None): """ Helper dialog function to get a single text value from the user :param parent: QWidget :param title: str :param text: str :param input_text: str :param width: int :param height: int :param buttons: list(QDialogButtonBox.StandardButton) :param header_pixmap: QPixmap :param header_color: str :param theme_to_apply: Theme :return: QMessageBox.StandardButton """ buttons = buttons or QDialogButtonBox.Ok | QDialogButtonBox.Cancel dialog = create_message_box(parent=parent, title=title, text=text, width=width, height=height, buttons=buttons, header_pixmap=header_pixmap, header_color=header_color, enable_input_edit=True, theme_to_apply=theme_to_apply) dialog.set_input_text(input_text) dialog.exec_() clicked_btn = dialog.clicked_standard_button() return dialog.input_text(), clicked_btn @staticmethod def question(parent, title, text, width=None, height=None, buttons=None, header_pixmap=None, header_color=None, enable_dont_show_checkbox=False, theme_to_apply=None): """ Helper dialog function to get a single text value from the user :param parent: QWidget :param title: str :param text: str :param width: int :param height: int :param buttons: list(QDialogButtonBox.StandardButton) :param header_pixmap: QPixmap :param header_color: str :param enable_dont_show_checkbox: bool :param theme_to_apply: Theme :return: QDialogButtonBox.StandardButton """ buttons = buttons or QDialogButtonBox.Yes | QDialogButtonBox.No | QDialogButtonBox.Cancel clicked_btn = show_message_box( parent=parent, title=title, text=text, width=width, height=height, buttons=buttons, header_pixmap=header_pixmap, header_color=header_color, enable_dont_show_checkbox=enable_dont_show_checkbox, theme_to_apply=theme_to_apply) return clicked_btn @staticmethod def warning(parent, title, text, width=None, height=None, buttons=None, header_pixmap=None, header_color='rgb(250, 160, 0)', enable_dont_show_checkbox=False, force=False): """ Helper dialog function to open a warning message box with the given options :param parent: QWidget :param title: str :param text: str :param width: int :param height: int :param buttons: list(QDialogButtonBox.StandardButton) :param header_pixmap: QPixmap :param header_color: str :param enable_dont_show_checkbox: bool :param force: bool :return: QDialogButtonBox.StandardButton """ buttons = buttons or QDialogButtonBox.Yes | QDialogButtonBox.No clicked_btn = show_message_box( parent=parent, title=title, text=text, width=width, height=height, buttons=buttons, header_pixmap=header_pixmap, header_color=header_color, enable_dont_show_checkbox=enable_dont_show_checkbox, force=force) return clicked_btn @staticmethod def critical(parent, title, text, width=None, height=None, buttons=None, header_pixmap=None, header_color='rgb(230, 80, 80)'): """ Helper dialog function to open a critical/error message box with the given options :param parent: QWidget :param title: str :param text: str :param width: int :param height: int :param buttons: list(QDialogButtonBox.StandardButton) :param header_pixmap: QPixmap :param header_color: str :return: QDialogButtonBox.StandardButton """ buttons = buttons or QDialogButtonBox.Ok | QDialogButtonBox.Cancel clicked_btn = show_message_box(parent=parent, title=title, text=text, width=width, height=height, buttons=buttons, header_pixmap=header_pixmap, header_color=header_color) return clicked_btn def __init__(self, name='messageBox', width=None, height=None, enable_input_edit=False, enable_dont_show_checkbox=False, parent=None): super(MessageBox, self).__init__(parent=parent) self._frame = None self._animation = None self._dont_show_checkbox = False self._clicked_button = None self._clicked_standard_button = None self.setMinimumWidth(width or self.MAX_WIDTH) self.setMinimumHeight(height or self.MAX_HEIGHT) self.setObjectName(name) self.setWindowFlags(self.windowFlags() | Qt.FramelessWindowHint) self.setAttribute(Qt.WA_TranslucentBackground) # self.setStyleSheet('background-color: rgb(68, 68, 68, 255);') parent = self.parent() self._frame = None if parent and parent != dcc.get_main_window(): parent.installEventFilter(self) self._frame = QFrame(parent) self._frame.setStyleSheet( 'background-color: rgba(25, 25, 25, 150);') self._frame.setObjectName('messageBoxFrame') self._frame.show() self.setParent(self._frame) self.main_layout = layouts.VerticalLayout(spacing=0, margins=(0, 0, 0, 0)) self.setLayout(self.main_layout) self._header = QFrame(self) self._header.setFixedHeight(46) self._header.setObjectName('messageBoxHeaderFrame') self._header.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self._icon = label.BaseLabel(parent=self._header) self._icon.hide() self._icon.setFixedHeight(32) self._icon.setFixedHeight(32) self._icon.setScaledContents(True) self._icon.setAlignment(Qt.AlignTop) self._icon.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) self._title = label.BaseLabel(parent=self._header) self._title.setObjectName('messageBoxHeaderLabel') self._title.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) hlayout = layouts.HorizontalLayout(spacing=10, margins=(15, 7, 15, 10)) hlayout.addWidget(self._icon) hlayout.addWidget(self._title) self._header.setLayout(hlayout) body_layout = layouts.VerticalLayout() self._body = QFrame(self) self._body.setObjectName('messageBoxBody') self._body.setLayout(body_layout) self._message = label.BaseLabel(parent=self._body) self._message.setWordWrap(True) self._message.setMinimumHeight(15) self._message.setAlignment(Qt.AlignLeft) self._message.setTextInteractionFlags(Qt.TextSelectableByMouse) self._message.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) body_layout.addWidget(self._message) body_layout.setContentsMargins(15, 15, 15, 15) if enable_input_edit: self._input_edit = lineedit.BaseLineEdit(parent=self._body) self._input_edit.setObjectName('messageBoxInputEdit') self._input_edit.setMinimumHeight(32) self._input_edit.setFocus() body_layout.addStretch(1) body_layout.addWidget(self._input_edit) body_layout.addStretch(10) if enable_dont_show_checkbox: msg = 'Do not show this message again' self._dont_show_checkbox = checkbox.BaseCheckBox(msg, parent=self._body) body_layout.addStretch(10) body_layout.addWidget(self._dont_show_checkbox) body_layout.addStretch(2) self._button_box = QDialogButtonBox(None, Qt.Horizontal, self) self._button_box.clicked.connect(self._on_clicked) self._button_box.accepted.connect(self._on_accept) self._button_box.rejected.connect(self._on_reject) self.main_layout.addWidget(self._header) self.main_layout.addWidget(self._body) body_layout.addWidget(self._button_box) self.updateGeometry() def eventFilter(self, object, event): """ Overrides base QDialog eventFilter function Updates the geometry when the parnet widget changes size :param object: QWidget :param event: QEvent """ if event.type() == QEvent.Resize: self.updateGeometry() return super(MessageBox, self).eventFilter(object, event) def showEvent(self, event): """ Overrides base QDialog showEvent function Fade in hte dialog on show :param event: QEvent """ self.updateGeometry() self._fade_in() def updateGeometry(self): """ Overrides base QDialog updateGeometry function Updates the geometry to be in the center of it's parent """ frame = self._frame if frame: frame.setGeometry(self._frame.parent().geometry()) frame.move(0, 0) geometry = self.geometry() center_point = frame.geometry().center() geometry.moveCenter(center_point) geometry.setY(geometry.y() - 50) self.move(geometry.topLeft()) def exec_(self): """ Overrides base QDialog exec_ function Shows the dialog as a modal dialog :return: variant, int or None """ super(MessageBox, self).exec_() return self.clicked_index() def button_box(self): """ Returns the button box widget for the dialog :return: QDialogButtonBox """ return self._button_box def header(self): """ Returns the header frame :return: QFrame """ return self._header def set_header_color(self, color): """ Sets the header color for the message box :param color: str """ self.header().setStyleSheet('background-color: {}'.format(color)) def set_title_text(self, text): """ Sets the title text to be displayed :param text: str """ self._title.setText(text) def set_text(self, text): """ Sets the text message to be displayed :param text: str """ self._message.setText(str(text)) def input_text(self): """ Returns the text that the user has given in the input edit :return: str """ return self._input_edit.text() def set_input_text(self, text): """ Sets the input text :param text: str """ self._input_edit.setText(text) def add_button(self, *args): """ Adds a new upsh button with the given text and roled """ self.button_box().addButton(*args) def set_buttons(self, buttons): """ Sets the buttons to be displayed in message box :param buttons: QMessageBox.StandardButton """ self.button_box().setStandardButtons(buttons) def set_pixmap(self, pixmap): """ Sets the pixmap for the message box :param pixmap: QPixmap """ self._icon.setPixmap(pixmap) self._icon.show() def clicked_button(self): """ Returns the button that was clicked :return: variant, QPushButton or None """ return self._clicked_button def clicked_index(self): """ Returns the button that was clicked by its index :return: variant, int or None """ for i, btn in enumerate(self.button_box().buttons()): if btn == self.clicked_button(): return i def clicked_standard_button(self): """ Returns the button that was clicked by the user :return: variant, QMessageBox.StandardButton or None """ return self._clicked_standard_button def is_dont_show_checkbox_checked(self): """ Returns the checked state of the dont show again checkbox :return: bool """ if self._dont_show_checkbox: return self._dont_show_checkbox.isChecked() else: return False def _fade_in(self, duration=200): """ Internal function that fade in the dialog using opacity effect :param duration: int :return: QPropertyAnimation """ if self._frame: self._animation = animation.fade_in_widget(self._frame, duration=duration) return self._animation def _fade_out(self, duration=200): """ Internal function that fade out the dialog using opacity effect :param duration: int :return: QPropertyAnimation """ if self._frame: self._animation = animation.fade_out_widget(self._frame, duration=duration) return self._animation def _on_clicked(self, button): """ Internal callback function triggered when the user clicks a button :param button: QPushButton """ self._clicked_button = button self._clicked_standard_button = self.button_box().standardButton( button) def _on_accept(self): """ Internal callback function triggered when the DialogButtonBox has been accepted """ anim = self._fade_out() if anim: anim.finished.connect(self._on_accept_animation_finished) else: self._on_accept_animation_finished() def _on_reject(self): """ Internal callback function triggered when the DialogButtonBox has been rejected """ anim = self._fade_out() if anim: anim.finished.connect(self._on_reject_animation_finished) else: self._on_reject_animation_finished() def _on_accept_animation_finished(self): """ Internal callback function triggered when the animation has finished on accepted """ parent = self._frame or self parent.close() self.accept() def _on_reject_animation_finished(self): parent = self._frame or self parent.close() self.reject()
class SaveWidget(BaseSaveWidget, object): def __init__(self, item, settings, temp_path=None, parent=None): self._script_job = None self._sequence_path = None self._icon_path = '' super(SaveWidget, self).__init__(item=item, settings=settings, temp_path=temp_path, parent=parent) self.create_sequence_widget() self.update_thumbnail_size() try: self._on_selection_changed() # self.set_script_job_enabled(True) except NameError as e: LOGGER.error('{} | {}'.format(e, traceback.format_exc())) def ui(self): super(SaveWidget, self).ui() model_panel_layout = layouts.HorizontalLayout() model_panel_layout.setContentsMargins(0, 0, 0, 0) model_panel_layout.setSpacing(0) thumbnail_layout = layouts.VerticalLayout() thumbnail_layout.setContentsMargins(0, 0, 0, 0) thumbnail_layout.setSpacing(0) self._thumbnail_frame = QFrame() self._thumbnail_frame.setMinimumSize(QSize(50, 50)) self._thumbnail_frame.setMaximumSize(QSize(150, 150)) self._thumbnail_frame.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self._thumbnail_frame.setFrameShape(QFrame.NoFrame) self._thumbnail_frame.setFrameShadow(QFrame.Plain) self._thumbnail_frame.setLineWidth(0) self._thumbnail_frame.setLayout(thumbnail_layout) model_panel_layout.addWidget(self._thumbnail_frame) self._thumbnail_btn = QPushButton() self._thumbnail_btn.setMinimumSize(QSize(0, 0)) self._thumbnail_btn.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self._thumbnail_btn.setMaximumSize(QSize(150, 150)) self._thumbnail_btn.setToolTip('Take snapshot') self._thumbnail_btn.setStyleSheet( 'color: rgb(40, 40, 40);border: 0px solid rgb(0, 0, 0, 150);background-color: rgb(254, 255, 230, 200);' ) self._thumbnail_btn.setIcon(resources.icon('thumbnail')) self._thumbnail_btn.setToolTip(""" Click to capture a thumbnail from the current viewport.\n CTRL + Click to show the capture window for better framing """) thumbnail_layout.addWidget(self._thumbnail_btn) self._extra_layout.addLayout(model_panel_layout) def setup_signals(self): super(SaveWidget, self).setup_signals() self._thumbnail_btn.clicked.connect(self._on_thumbnail_capture) def resizeEvent(self, event): """ Overrides base QWidget resizeEvent function :param event: QResizeEvent """ self.update_thumbnail_size() def icon_path(self): """ Returns the icon path to be used for the thumbnail :return: str """ return self._icon_path def set_icon(self, icon): """ Sets the icon for the create widget thumbnail :param icon: QIcon """ self._thumbnail_btn.setIcon(icon) self._thumbnail_btn.setIconSize(QSize(200, 200)) self._thumbnail_btn.setText('') def sequence_path(self): """ Returns the playblast path :return: str """ return self._sequence_path def set_sequence_path(self, path): """ Sets the disk location for the image sequence to be saved :param path: str """ self._sequence_path = path self._thumbnail_btn.set_dirname(os.path.dirname(path)) def create_sequence_widget(self): """ Creates a sequence widget to replace the static thumbnail widget """ sequence_widget = widgets.LibraryImageSequenceWidget(self) sequence_widget.setObjectName('thumbnailButton') sequence_widget.setStyleSheet(self._thumbnail_btn.styleSheet()) sequence_widget.setToolTip(self._thumbnail_btn.toolTip()) camera_icon = resources.get('icons', 'camera.svg') expand_icon = resources.get('icons', 'expand.svg') folder_icon = resources.get('icons', 'folder.svg') sequence_widget.addAction(camera_icon, 'Capture new image', 'Capture new image', self._on_thumbnail_capture) sequence_widget.addAction(expand_icon, 'Show Capture window', 'Show Capture window', self._on_show_capture_window) sequence_widget.addAction(folder_icon, 'Load image from disk', 'Load image from disk', self._on_show_browse_image_dialog) sequence_widget.setIcon(resources.icon('thumbnail2')) self._thumbnail_frame.layout().insertWidget(0, sequence_widget) self._thumbnail_btn.hide() self._thumbnail_btn = sequence_widget self._thumbnail_btn.clicked.connect(self._on_thumbnail_capture) def set_sequence(self, source): """ Sets the sequenced path for the thumbnail widget :param source: str """ self.set_thumbnail(source, sequence=True) def set_thumbnail(self, source, sequence=False): """ Sets the thumbnail :param source: str :param sequence: bool """ source = os.path.normpath(source) # TODO: Find a way to remove temp folder afteer saving the file # filename, extension = os.path.splitext(source) # with path_utils.temp_dir() as dir_path: # dir_path = path_utils.temp_dir() # target = os.path.join(dir_path, 'thumbnail{}'.format(extension)) # shutil.copyfile(source, target) # tpQtLib.logger.debug('Source Thumbnail: {}'.format(source)) # tpQtLib.logger.debug('Target Thumbnail: {}'.format(target)) # self._icon_path = target # self._thumbnail_btn.set_path(target) self._icon_path = source self._thumbnail_btn.set_path(source) if sequence: self.set_sequence_path(source) def update_thumbnail_size(self): """ Updates the thumbnail button to teh size of the widget """ width = self.width() - 10 if width > 250: width = 250 size = QSize(width, width) self._thumbnail_btn.setIconSize(size) self._thumbnail_btn.setMaximumSize(size) self._thumbnail_frame.setMaximumSize(size) def show_by_frame_dialog(self): """ Show the by frame dialog """ help_text = """ To help speed up the playblast you can set the "by frame" to another greather than 1. For example if the "by frame" is set to 2 it will playblast every second frame """ options = self._options_widget.values() by_frame = options.get('byFrame', 1) start_frame, end_frame = options.get('frameRange', [None, None]) duration = 1 if start_frame is not None and end_frame is not None: duration = end_frame - start_frame if duration > 100 and by_frame == 1: buttons = QDialogButtonBox.Ok | QDialogButtonBox.Cancel result = messagebox.MessageBox.question( self.library_window(), title='Tip', text=help_text, buttons=buttons, enable_dont_show_checkbox=True) if result != QDialogButtonBox.Ok: raise Exception('Cancelled by user') def show_thumbnail_capture_dialog(self): """ Asks the user if they would like to capture a thumbnail :return: int """ buttons = QDialogButtonBox.Yes | QDialogButtonBox.Ignore | QDialogButtonBox.Cancel parent = self.item().library_window() btn = messagebox.MessageBox.question( None, 'Create a thumbnail', 'Would you like to capture a thumbnail?', buttons=buttons) if btn == QDialogButtonBox.Yes: self.thumbnail_capture() return btn def thumbnail_capture(self, show=False): """ Captures a playblast and saves it to the temporal thumbnail path :param show: bool """ options = self._options_widget.values() start_frame, end_frame = options.get('frameRange', [None, None]) step = options.get('byFrame', 1) if not qtutils.is_control_modifier(): self.show_by_frame_dialog() if not self._temp_path or not os.path.isdir(self._temp_path): self._temp_path = tempfile.mkdtemp() self._temp_path = os.path.join(self._temp_path, 'thumbnail.jpg') try: snapshot.SnapshotWindow(path=self._temp_path, on_save=self._on_thumbnail_captured) # thumbnail.ThumbnailCaptureDialog.thumbnail_capture( # path=self._temp_path, # show=show, # start_frame=start_frame, # end_frame=end_frame, # step=step, # clear_cache=False, # captured=self._on_thumbnail_captured # ) except Exception as e: messagebox.MessageBox.critical(self.library_window(), 'Error while capturing thumbnail', str(e)) LOGGER.error(traceback.format_exc()) def save(self, path, icon_path, objects=None): """ Saves the item with the given objects to the given disk location path :param path: list(str) :param icon_path: str :param objects: str """ item = self.item() options = self._options_widget.values() sequence_path = self.sequence_path() if sequence_path: sequence_path = os.path.dirname(sequence_path) item.save(path=path, objects=objects, icon_path=icon_path, sequence_path=sequence_path, **options) self.close() def _on_selection_changed(self): """ Internal callback functino that is called when DCC selection changes """ if self._options_widget: self._options_widget.validate() def _on_thumbnail_capture(self): self.thumbnail_capture(show=False) def _on_thumbnail_captured(self, captured_path): """ Internal callback function that is called when thumbnail is captured :param captured_path: str """ self.set_sequence(captured_path) def _on_show_capture_window(self): """ Internal callback function that shows the capture window for framing """ self.thumbnail_capture(show=True) def _on_show_browse_image_dialog(self): """ Internal callback function that shows a file dialog for choosing an image from disk """ file_dialog = QFileDialog(self, caption='Open Image', filter='Image Files (*.png *.jpg)') file_dialog.fileSelected.connect(self.set_thumbnail) file_dialog.exec_()