from functools import partial import os from .pyside import QtWidgets, QtCore, QtGui from maya import cmds from .designer.highlighter import get_highlighter from .optionvar import ( save_optionvar, CHECK_FOR_UPDATE, SEARCH_FIELD_INDEX, LAST_IMAGE_DIRECTORY_USED, SETTINGS_GROUP_TO_COPY, SHAPES_FILTER_INDEX, SETTINGS_TO_COPY) from .languages import MEL, PYTHON from .path import get_image_directory from .qtutils import icon from .namespace import selected_namespace from .templates import BUTTON SEARCH_AND_REPLACE_FIELDS = 'Targets', 'Label', 'Image path', 'Command' SHAPES_FILTERS = 'All shapes', 'Selected shapes' COMMAND_PLACEHOLDER = """\ PYTHON: __targets__: List[str] (variable available by default in the script) __shape__: dict (clicked shape data as dict. Dict is editable). example to toggle the background color: current_color = __shape__['bgcolor.normal'] __shape__['bgcolor.normal'] = ( "black" if current_color == 'white' else "white") MEL: var $targets[] is availables by default. """ def warning(title, message, parent=None): return QtWidgets.QMessageBox.warning( parent, title, message, QtWidgets.QMessageBox.Ok, QtWidgets.QMessageBox.Ok) def question(title, message, buttons=None, parent=None): buttons = buttons or QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel result = QtWidgets.QMessageBox.question( parent, title, message, buttons, QtWidgets.QMessageBox.Ok) return result == QtWidgets.QMessageBox.Ok def get_image_path(parent=None, dialog_title="Repath image..."): filename = QtWidgets.QFileDialog.getOpenFileName( parent, dialog_title, get_image_directory(), filter="Images (*.jpg *.gif *.png *.tga)")[0] if not filename: return None directory = os.path.dirname(filename) save_optionvar(LAST_IMAGE_DIRECTORY_USED, directory) return filename class NamespaceDialog(QtWidgets.QDialog): def __init__(self, parent=None): super(NamespaceDialog, self).__init__(parent=parent) self.setWindowTitle('Select namespace ...') self.namespace_combo = QtWidgets.QComboBox() self.namespace_combo.setEditable(True) namespaces = [':'] + cmds.namespaceInfo( listOnlyNamespaces=True, recurse=True) self.namespace_combo.addItems(namespaces) self.namespace_combo.setCurrentText(selected_namespace()) self.detect_selection = QtWidgets.QPushButton('Detect from selection') self.detect_selection.released.connect(self.call_detect_selection) self.ok = QtWidgets.QPushButton('Ok') self.ok.released.connect(self.accept) self.cancel = QtWidgets.QPushButton('Cancel') self.cancel.released.connect(self.reject) self.button_layout = QtWidgets.QHBoxLayout() self.button_layout.setContentsMargins(0, 0, 0, 0) self.button_layout.addStretch(1) self.button_layout.addWidget(self.detect_selection) self.button_layout.addSpacing(16) self.button_layout.addWidget(self.ok) self.button_layout.addWidget(self.cancel) self.layout = QtWidgets.QVBoxLayout(self) self.layout.addWidget(self.namespace_combo) self.layout.addLayout(self.button_layout) @property def namespace(self): return self.namespace_combo.currentText() def call_detect_selection(self): self.namespace_combo.setCurrentText(selected_namespace()) class SettingsPaster(QtWidgets.QDialog): def __init__(self, parent=None): super(SettingsPaster, self).__init__(parent) self.setWindowTitle('Paste settings') self.groups = {} self.categories = {} enable_settings = cmds.optionVar(query=SETTINGS_TO_COPY).split(';') for setting in sorted(BUTTON.keys()): text = ' '.join(setting.split('.')[1:]).capitalize() checkbox = QtWidgets.QCheckBox(text or setting.capitalize()) checkbox.setting = setting checkbox.setChecked(setting in enable_settings) checkbox.stateChanged.connect(self.updated) name = setting.split('.')[0] self.categories.setdefault(name, []).append(checkbox) enable_groups = cmds.optionVar(query=SETTINGS_GROUP_TO_COPY).split(';') groups_layout = QtWidgets.QVBoxLayout() self.group_layouts = QtWidgets.QHBoxLayout() checkboxes_count = 0 for category, checkboxes in self.categories.items(): if checkboxes_count > 12: checkboxes_count = 0 groups_layout.addStretch(1) self.group_layouts.addLayout(groups_layout) groups_layout = QtWidgets.QVBoxLayout() group = QtWidgets.QGroupBox(category) group.setCheckable(True) group.setChecked(category in enable_groups) group.toggled.connect(self.updated) group_layout = QtWidgets.QVBoxLayout(group) for checkbox in checkboxes: group_layout.addWidget(checkbox) self.groups[category] = group groups_layout.addWidget(group) checkboxes_count += len(checkboxes) groups_layout.addStretch(1) self.group_layouts.addLayout(groups_layout) self.paste = QtWidgets.QPushButton('Paste') self.paste.released.connect(self.accept) self.cancel = QtWidgets.QPushButton('Cancel') self.cancel.released.connect(self.reject) self.buttons_layout = QtWidgets.QHBoxLayout() self.buttons_layout.addStretch(1) self.buttons_layout.addWidget(self.paste) self.buttons_layout.addWidget(self.cancel) self.layout = QtWidgets.QVBoxLayout(self) self.layout.addLayout(self.group_layouts) self.layout.addLayout(self.buttons_layout) @property def settings(self): return [ cb.setting for category, checkboxes in self.categories.items() for cb in checkboxes if cb.isChecked() and self.groups[category].isChecked()] def updated(self, *_): cat = ';'.join([c for c, g in self.groups.items() if g.isChecked()]) save_optionvar(SETTINGS_GROUP_TO_COPY, cat) save_optionvar(SETTINGS_TO_COPY, ';'.join(self.settings)) class SearchAndReplaceDialog(QtWidgets.QDialog): def __init__(self, parent=None): super(SearchAndReplaceDialog, self).__init__(parent=parent) self.setWindowTitle('Search and replace in shapes') self.sizeHint = lambda: QtCore.QSize(320, 80) self.filters = QtWidgets.QComboBox() self.filters.addItems(SHAPES_FILTERS) self.filters.setCurrentIndex(cmds.optionVar(query=SHAPES_FILTER_INDEX)) function = partial(save_optionvar, SHAPES_FILTER_INDEX) self.filters.currentIndexChanged.connect(function) self.fields = QtWidgets.QComboBox() self.fields.addItems(SEARCH_AND_REPLACE_FIELDS) self.fields.setCurrentIndex(cmds.optionVar(query=SEARCH_FIELD_INDEX)) function = partial(save_optionvar, SEARCH_FIELD_INDEX) self.fields.currentIndexChanged.connect(function) self.search = QtWidgets.QLineEdit() self.replace = QtWidgets.QLineEdit() self.ok = QtWidgets.QPushButton('Replace') self.ok.released.connect(self.accept) self.cancel = QtWidgets.QPushButton('Cancel') self.cancel.released.connect(self.reject) self.options = QtWidgets.QFormLayout() self.options.setContentsMargins(0, 0, 0, 0) self.options.addRow('Apply on: ', self.filters) self.options.addRow('Field to search: ', self.fields) self.options.addRow('Search: ', self.search) self.options.addRow('Replace by: ', self.replace) self.button_layout = QtWidgets.QHBoxLayout() self.button_layout.setContentsMargins(0, 0, 0, 0) self.button_layout.addStretch(1) self.button_layout.addWidget(self.ok) self.button_layout.addWidget(self.cancel) self.layout = QtWidgets.QVBoxLayout(self) self.layout.addLayout(self.options) self.layout.addLayout(self.button_layout) @property def field(self): ''' 0 = Targets 1 = Label 2 = Command 3 = Image path ''' return self.fields.currentIndex() @property def filter(self): ''' 0 = Apply on all shapes 1 = Apply on selected shapes ''' return self.filters.currentIndex() class MissingImages(QtWidgets.QDialog): def __init__(self, paths, parent=None): super(MissingImages, self).__init__(parent) self.setWindowTitle('Missing images') self.model = PathModel(paths) self.paths = QtWidgets.QTableView() self.paths.setAlternatingRowColors(True) self.paths.setShowGrid(False) self.paths.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection) mode = QtWidgets.QHeaderView.ResizeToContents self.paths.verticalHeader().resizeSections(mode) self.paths.verticalHeader().hide() self.paths.horizontalHeader().show() self.paths.horizontalHeader().resizeSections(mode) self.paths.horizontalHeader().setStretchLastSection(True) mode = QtWidgets.QAbstractItemView.ScrollPerPixel self.paths.setHorizontalScrollMode(mode) self.paths.setVerticalScrollMode(mode) self.paths.setModel(self.model) self.browse = QtWidgets.QPushButton(icon('mini-open.png'), '') self.browse.setFixedWidth(30) self.browse.released.connect(self.call_browse) self.update = QtWidgets.QPushButton('Update') self.update.released.connect(self.accept) self.skip = QtWidgets.QPushButton('Skip') self.skip.released.connect(self.reject) self.validators = QtWidgets.QHBoxLayout() self.validators.addStretch(1) self.validators.addWidget(self.browse) self.validators.addWidget(self.update) self.validators.addWidget(self.skip) self.layout = QtWidgets.QVBoxLayout(self) self.layout.addWidget(self.paths) self.layout.addLayout(self.validators) def output(self, path): for p, output in zip(self.model.paths, self.model.outputs): if p == path: return output @property def outputs(self): return self.model.outputs def resizeEvent(self, _): mode = QtWidgets.QHeaderView.ResizeToContents self.paths.verticalHeader().resizeSections(mode) self.paths.horizontalHeader().resizeSections(mode) def call_browse(self): directory = QtWidgets.QFileDialog.getExistingDirectory( self, "Select image folder") if not directory: return filenames = os.listdir(directory) self.model.layoutAboutToBeChanged.emit() for i, path in enumerate(self.model.paths): filename = os.path.basename(path) if filename in filenames: filepath = os.path.join(directory, filename) self.model.outputs[i] = filepath self.model.layoutChanged.emit() class PathModel(QtCore.QAbstractTableModel): HEADERS = 'filename', 'directory' def __init__(self, paths, parent=None): super(PathModel, self).__init__(parent) self.paths = paths self.outputs = paths[:] def rowCount(self, *_): return len(self.paths) def columnCount(self, *_): return 2 def flags(self, index): flags = super(PathModel, self).flags(index) if index.column() == 1: flags |= QtCore.Qt.ItemIsEditable return flags def headerData(self, position, orientation, role): if orientation != QtCore.Qt.Horizontal: return if role != QtCore.Qt.DisplayRole: return return self.HEADERS[position] def data(self, index, role): if not index.isValid(): return row, col = index.row(), index.column() if role == QtCore.Qt.DisplayRole: if col == 0: return os.path.basename(self.outputs[row]) if col == 1: return os.path.dirname(self.outputs[row]) elif role == QtCore.Qt.BackgroundColorRole: if not os.path.exists(self.outputs[row]): return QtGui.QColor(QtCore.Qt.darkRed) class UpdateAvailableDialog(QtWidgets.QDialog): def __init__(self, version, parent=None): super(UpdateAvailableDialog, self).__init__(parent=parent) self.setWindowTitle('Update available') # Widgets text = '\n New DreamWall Picker version "{0}" is available ! \n' label = QtWidgets.QLabel(text.format(version)) ok_btn = QtWidgets.QPushButton('Open GitHub page') ok_btn.released.connect(self.accept) cancel_btn = QtWidgets.QPushButton('Close') cancel_btn.released.connect(self.reject) self.check_cb = QtWidgets.QCheckBox('Check for update at startup') self.check_cb.stateChanged.connect( self.change_check_for_update_preference) self.check_cb.setChecked(cmds.optionVar(query=CHECK_FOR_UPDATE)) # Layouts button_layout = QtWidgets.QHBoxLayout() button_layout.addStretch(1) button_layout.addWidget(ok_btn) button_layout.addWidget(cancel_btn) cb_layout = QtWidgets.QHBoxLayout() cb_layout.addStretch(1) cb_layout.addWidget(self.check_cb) layout = QtWidgets.QVBoxLayout(self) layout.addWidget(label) layout.addLayout(cb_layout) layout.addLayout(button_layout) def change_check_for_update_preference(self): save_optionvar(CHECK_FOR_UPDATE, int(self.check_cb.isChecked())) class CommandEditorDialog(QtWidgets.QDialog): def __init__(self, command, parent=None): super(CommandEditorDialog, self).__init__(parent) self.setWindowTitle('Edit/Create command') self.languages = QtWidgets.QComboBox() self.languages.addItems([MEL, PYTHON]) self.languages.setCurrentText(command['language']) self.languages.currentIndexChanged.connect(self.language_changed) self.button = QtWidgets.QComboBox() self.button.addItems(['left', 'right']) self.button.setCurrentText(command['button']) self.enabled = QtWidgets.QCheckBox('Enabled') self.enabled.setChecked(command['enabled']) self.ctrl = QtWidgets.QCheckBox('Ctrl') self.ctrl.setChecked(command['ctrl']) self.shift = QtWidgets.QCheckBox('Shift') self.shift.setChecked(command['shift']) self.eval_deferred = QtWidgets.QCheckBox('Eval deferred (python only)') self.eval_deferred.setChecked(command['deferred']) self.unique_undo = QtWidgets.QCheckBox('Unique undo') self.unique_undo.setChecked(command['force_compact_undo']) self.command = QtWidgets.QTextEdit() self.command.setPlaceholderText(COMMAND_PLACEHOLDER) self.command.setPlainText(command['command']) self.ok = QtWidgets.QPushButton('Ok') self.ok.released.connect(self.accept) self.cancel = QtWidgets.QPushButton('Cancel') self.cancel.released.connect(self.reject) form = QtWidgets.QFormLayout() form.setSpacing(0) form.addRow('Language', self.languages) form.addRow('Mouse button', self.button) modifiers_group = QtWidgets.QGroupBox('Modifiers') modifiers_layout = QtWidgets.QVBoxLayout(modifiers_group) modifiers_layout.addWidget(self.ctrl) modifiers_layout.addWidget(self.shift) options_group = QtWidgets.QGroupBox('Options') options_layout = QtWidgets.QVBoxLayout(options_group) options_layout.addWidget(self.eval_deferred) options_layout.addWidget(self.unique_undo) options_layout.addLayout(form) code = QtWidgets.QGroupBox('Code') code_layout = QtWidgets.QVBoxLayout(code) code_layout.setSpacing(0) code_layout.addWidget(self.command) buttons_layout = QtWidgets.QHBoxLayout() buttons_layout.addStretch(1) buttons_layout.addWidget(self.ok) buttons_layout.addWidget(self.cancel) layout = QtWidgets.QVBoxLayout(self) layout.addWidget(options_group) layout.addWidget(modifiers_group) layout.addWidget(code) layout.addLayout(buttons_layout) self.language_changed() def sizeHint(self): return QtCore.QSize(400, 550) def language_changed(self, *_): language = self.languages.currentText() highlighter = get_highlighter(language) highlighter(self.command.document()) def command_data(self): return { 'enabled': self.enabled.isChecked(), 'button': self.button.currentText(), 'language': self.languages.currentText(), 'command': self.command.toPlainText(), 'ctrl': self.ctrl.isChecked(), 'shift': self.shift.isChecked(), 'deferred': self.eval_deferred.isChecked(), 'force_compact_undo': self.unique_undo.isChecked()} class MenuCommandEditorDialog(QtWidgets.QDialog): def __init__(self, command, parent=None): super(MenuCommandEditorDialog, self).__init__(parent) self.setWindowTitle('Edit/Create command') self.languages = QtWidgets.QComboBox() self.languages.addItems([MEL, PYTHON]) self.languages.setCurrentText(command['language']) self.languages.currentIndexChanged.connect(self.language_changed) self.eval_deferred = QtWidgets.QCheckBox('Eval deferred (python only)') self.eval_deferred.setChecked(command['deferred']) self.unique_undo = QtWidgets.QCheckBox('Unique undo') self.unique_undo.setChecked(command['force_compact_undo']) self.caption = QtWidgets.QLineEdit() self.caption.setText(command['caption']) self.command = QtWidgets.QTextEdit() self.command.setPlaceholderText(COMMAND_PLACEHOLDER) self.command.setPlainText(command['command']) self.ok = QtWidgets.QPushButton('Ok') self.ok.released.connect(self.accept) self.cancel = QtWidgets.QPushButton('Cancel') self.cancel.released.connect(self.reject) form = QtWidgets.QFormLayout() form.setSpacing(0) form.addRow('Caption', self.caption) form.addRow('Language', self.languages) options_group = QtWidgets.QGroupBox('Options') options_layout = QtWidgets.QVBoxLayout(options_group) options_layout.addWidget(self.eval_deferred) options_layout.addWidget(self.unique_undo) options_layout.addLayout(form) code = QtWidgets.QGroupBox('Code') code_layout = QtWidgets.QVBoxLayout(code) code_layout.setSpacing(0) code_layout.addWidget(self.command) buttons_layout = QtWidgets.QHBoxLayout() buttons_layout.addStretch(1) buttons_layout.addWidget(self.ok) buttons_layout.addWidget(self.cancel) layout = QtWidgets.QVBoxLayout(self) layout.addWidget(options_group) layout.addWidget(code) layout.addLayout(buttons_layout) self.language_changed() def sizeHint(self): return QtCore.QSize(400, 550) def language_changed(self, *_): language = self.languages.currentText() highlighter = get_highlighter(language) highlighter(self.command.document()) def command_data(self): return { 'caption': self.caption.text(), 'language': self.languages.currentText(), 'command': self.command.toPlainText(), 'deferred': self.eval_deferred.isChecked(), 'force_compact_undo': self.unique_undo.isChecked()}