# -*- coding: utf-8 -*- """ :author: Jeremy Ernst :description: This module provides utilities for general QT functions and classes to be used as widgets or templates. """ import os try: import shiboken as shiboken except ImportError: import shiboken2 as shiboken import maya.OpenMayaUI as mui import maya.cmds as cmds from maya.app.general.mayaMixin import MayaQWidgetBaseMixin from artv2.third_party.Qt import QtWidgets, QtGui, QtCore import artv2.utilities.general_utilities as utils # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # def get_maya_window(): """ Return Maya's main window as a QWidget""" pointer = mui.MQtUtil.mainWindow() return shiboken.wrapInstance(long(pointer), QtWidgets.QWidget) # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # def get_maya_dpi(): """ Gets the DPI setting value in Maya and returns the multiplier. """ dpi_percentages = {96: 1, 120: 1.25, 144: 1.5, 192: 2} maya_app = QtWidgets.QApplication.instance() screens = maya_app.screens() dpi = screens[0].logicalDotsPerInch() return dpi_percentages.get(round(dpi)) # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # def scale_by_dpi(value): """ Scales the given value by the dpi multiplier. """ scalar = get_maya_dpi() return int(round(value * scalar)) # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # def get_style_sheet(file_path): """ Read the style sheet data, and then replace any urls with proper file paths on the user's drive.""" settings = utils.return_settings() style_sheet_file = utils.path_join(settings[0], "resources/_styleSheets/" + file_path + ".qss") file_object = open(style_sheet_file, "r") data = file_object.readlines() style = create_style_sheet(data) file_object.close() return style # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # def create_style_sheet(data): """ Take in a read stylesheet, and replace any urls with actual file paths, then return the altered data.""" settings = utils.return_settings() new_lines = [] # todo: could be simplified for line in data: if line.find("url(") != -1: if line.find("resources/icons/general") != -1: old_path = line.partition("(")[2].rpartition("/")[0] replace_path = utils.path_join(settings[2], "general") new_line = line.replace(old_path, replace_path) new_lines.append(new_line) if line.find("resources/icons/general") == -1: old_path = line.partition("(")[2].rpartition("/")[0] replace_path = settings[2] new_line = line.replace(old_path, replace_path) new_lines.append(new_line) else: new_lines.append(line) user_dir = utils.path_unify(os.path.join(settings[0], "user")) if not os.path.exists(user_dir): os.makedirs(user_dir) full_path = (utils.path_join(user_dir, "style.qss")) file_object = open(full_path, 'w') file_object.writelines(new_lines) file_object.close() file_object = open(full_path, 'r') style = file_object.read() file_object.close() os.remove(full_path) return style # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # noinspection PyUnusedLocal def search_list_widget(search_field, list_widget, *args): """ Searches the given list_widget for items with the search term in the given search_field. Items that do not match are hidden in the list. :param search_field: The QLineEdit to grab the search term from :param list_widget: the QListWidget to manipulate """ search_term = search_field.text() all_items = {} for i in range(list_widget.count()): item = list_widget.item(i) all_items[item.text()] = item item.setHidden(True) item.setSelected(False) for each in list_widget.findItems(search_term, QtCore.Qt.MatchFlag.MatchContains): each.setHidden(False) each.setSelected(True) # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # def clear_layout(layout): """ Clears the given layout of all child widgets. """ while layout.count(): child = layout.takeAt(0) if child.widget() is not None: child.widget().deleteLater() elif child.layout() is not None: clear_layout(child.layout()) # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # def toggle_group(ctrl, args): """ Sets the given ctrl (groupbox) height based on the check state. """ state = ctrl.isChecked() if state: ctrl.setFixedHeight(ctrl.sizeHint().height()) else: ctrl.setFixedHeight(30) # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # def filter_list(list_widget, search_widget): """ Filters the list widget by the search term in the given search widget. :param list_widget: QListWidget widget to filter items in. :param search_widget: QLineEdit widget to grab text from for search term. """ search_term = search_widget.text() items = get_items(list_widget) for item in items: widget_item = items.get(item) if search_term in item: widget_item.setHidden(False) else: widget_item.setHidden(True) # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # def get_items(list_widget): """ Given a list widget, get and return the text and pointer to the list widget item in a dict. :param list_widget: List widget to query items from. :return: Returns a dict of the listWidgetItems' text and pointer to the items. """ items = {} for i in range(list_widget.count()): item = list_widget.item(i) text = item.text() items[text] = item return items # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # class ARTv2Message(QtWidgets.QMessageBox): """ Abstract class that adds extra functionality onto QMessageBox. Depending on the message type (warning, error, etc), different buttons and icons will be shown. This class is not meant to be instantiated on its own. """ def __init__(self, width, height, title, critical=False, warning=False, info=False, question=False, parent=None): super(ARTv2Message, self).__init__(parent) if cmds.window(self.WINDOW_NAME, exists=True): cmds.deleteUI(self.WINDOW_NAME) self.setObjectName(self.WINDOW_NAME) self.settings = utils.return_settings() self.tools_path = self.settings[0] self.script_path = self.settings[1] self.icon_path = self.settings[2] self.project_path = self.settings[3] self.setMinimumSize(QtCore.QSize(width, height)) self.setMaximumSize(QtCore.QSize(width, height)) self.setWindowTitle(title) os.environ["QT_AUTO_SCREEN_SCALE_FACTOR"] = "1" qapp = QtWidgets.QApplication.instance() qapp.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling) if critical: self.setIcon(QtWidgets.QMessageBox.Critical) elif warning: self.setIcon(QtWidgets.QMessageBox.Warning) elif info: self.setIcon(QtWidgets.QMessageBox.Information) elif question: self.setIcon(QtWidgets.QMessageBox.Question) else: self.setIcon(QtWidgets.QMessageBox.NoIcon) # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # class ARTv2Window(MayaQWidgetBaseMixin, QtWidgets.QMainWindow): """ Abstract class that adds some extra default functionality for things like grabbing settings, dealing with different screen resolutions, styling the UI, and saving screen position. This class is not meant to be instantiated on its own. """ def __init__(self, width, height, window_title, parent=None): super(ARTv2Window, self).__init__(parent) if cmds.window(self.WINDOW_NAME, exists=True): cmds.deleteUI(self.WINDOW_NAME) self.setObjectName(self.WINDOW_NAME) self.settings = utils.return_settings() self.tools_path = self.settings[0] self.script_path = self.settings[1] self.icon_path = self.settings[2] self.project_path = self.settings[3] self.setGeometry(200, 200, width, height) self.style_sheet = get_style_sheet("artv2_style_basic") self.setStyleSheet(self.style_sheet) self.setWindowTitle(window_title) window_icon = QtGui.QIcon(os.path.join(self.icon_path, "general/logo.png")) self.setWindowIcon(window_icon) os.environ["QT_AUTO_SCREEN_SCALE_FACTOR"] = "1" qapp = QtWidgets.QApplication.instance() qapp.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling) self.restoreGeometry(self.settings[4].value(self.SETTINGS_NAME)) # noinspection PyPep8Naming def closeEvent(self, event): """ Override QMainWindow's close event to remember window position.""" self.settings[4].setValue(self.SETTINGS_NAME, self.saveGeometry()) QtWidgets.QMainWindow.closeEvent(self, event) @staticmethod def launch_help_doc(path): """ Launches a html page from the artv2 help documents. :param path: The path of the html file to launch. """ html_file = os.path.join(utils.return_settings()[0], path) utils.launch_web_docs(html_file) # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # class ARTv2Dialog(MayaQWidgetBaseMixin, QtWidgets.QDialog): """ Abstract class that adds some extra default functionality for things like grabbing settings, dealing with different screen resolutions, styling the UI, and saving screen position. This class is not meant to be instantiated on its own. """ def __init__(self, width, height, window_title, parent=None): super(ARTv2Dialog, self).__init__(parent) if cmds.window(self.WINDOW_NAME, exists=True): cmds.deleteUI(self.WINDOW_NAME) self.setObjectName(self.WINDOW_NAME) self.settings = utils.return_settings() self.tools_path = self.settings[0] self.script_path = self.settings[1] self.icon_path = self.settings[2] self.project_path = self.settings[3] self.setGeometry(200, 200, width, height) self.style_sheet = get_style_sheet("artv2_style_basic") self.setStyleSheet(self.style_sheet) self.setWindowTitle(window_title) window_icon = QtGui.QIcon(os.path.join(self.icon_path, "general/logo.png")) self.setWindowIcon(window_icon) os.environ["QT_AUTO_SCREEN_SCALE_FACTOR"] = "1" qapp = QtWidgets.QApplication.instance() qapp.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling) self.restoreGeometry(self.settings[4].value(self.SETTINGS_NAME)) # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # class ErrorWidget(ARTv2Message): """ Simple class that displays a QMessageBox widget with an error message, and error icon, and a simple "OK" button. Takes in a string message to display. .. image:: /images/errorWidget.png """ SETTINGS_NAME = "ARTv2ErrorWidget" WINDOW_NAME = "artv2_error_message_window" def __init__(self, message, parent=None): super(ErrorWidget, self).__init__(300, 150, "Error", critical=True, parent=parent) self.setText(message) # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # class InfoWidget(ARTv2Message): """ Simple class that displays a QMessageBox widget with an error message, and error icon, and a simple "OK" button. This class takes in required arguments: A string message, and a string for details. The details string could be empty if not required. .. code-block:: python message = ui_utils.InfoWidget("this is an info widget", "these are some details\\nas are these") .. image:: /images/infoWidget.png """ SETTINGS_NAME = "ARTv2InfoWidget" WINDOW_NAME = "artv2_info_message_window" def __init__(self, message, details, parent=None): super(InfoWidget, self).__init__(300, 200, "Attention!", info=True, parent=parent) self.setText(message) self.setDetailedText(details) # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # class ConfirmWidget(ARTv2Message): """ Simple class that displays a QMessageBox widget with a question, a question icon, and Yes or Cancel buttons. Takes in a string message to display, a string for any detailed text to display, and a string for the title. """ SETTINGS_NAME = "ARTv2ConfirmWidget" WINDOW_NAME = "artv2_confirm_message_window" def __init__(self, message, details, title, parent=None): super(ConfirmWidget, self).__init__(300, 200, title, question=True, parent=parent) self.setText(message) self.setDetailedText(details) self.setStandardButtons(QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.Cancel) self.setDefaultButton(QtWidgets.QMessageBox.Yes)