Source code for bgdev.tools.toolbar.main

"""Main toolbar code.

:created: 09/11/2018
:author: Benoit GIELLY <benoit.gielly@gmail.com>
"""
from collections import OrderedDict
from functools import partial
import logging
import os
import traceback

from PySide2 import QtGui, QtWidgets

from . import cfg, ui, utils

logging.basicConfig()
LOG = logging.getLogger(__name__)

PROJECT = os.environ.get("PROJECT")
HOME = os.path.normpath(os.environ.get("HOME", os.environ.get("USERPROFILE")))
TABS = os.path.join(".toolbar", "tabs")


[docs]def show(*args, **kwargs): """Show the window.""" dcc_module = utils.get_dcc_package() return dcc_module.show(*args, **kwargs)
[docs]def get_class_name(cls): """Get the class full path name.""" return "{}.{}".format(cls.__module__, cls.__name__)
class BaseToolbar(QtWidgets.QDialog): """Create a script launcher window. Args: parent (QObject): Instance of parent widget. """ DCC_TABS_FOLDER = None PROJECT_TABS_FOLDER = os.path.join(PROJECT, TABS) if PROJECT else "" USER_TABS_FOLDER = os.path.join(HOME, TABS) if HOME else "" CUSTOM_TABS_FOLDER = os.environ.get("TOOLBAR_PATH", "").split(os.pathsep) def __init__(self, parent=None): """Initialise the class.""" super(BaseToolbar, self).__init__(parent=parent) self.config_name = ".config.yaml" self.setObjectName("Toolbar_mainWindow") # default variables os.chdir(os.path.dirname(__file__)) self.scroll_areas = [] self.group_boxes = [] # reset config variables cfg.ICON_PATHS = [] cfg.COMMAND_CALLBACK = None # gather data folders self.data_folders = [ self.DCC_TABS_FOLDER, self.PROJECT_TABS_FOLDER, self.USER_TABS_FOLDER, ] self.data_folders.extend([x for x in self.CUSTOM_TABS_FOLDER if x]) # create the toolbar folder if not exising if not os.path.exists(self.USER_TABS_FOLDER): os.makedirs(self.USER_TABS_FOLDER) # get data files self.data_files = self.get_data_files() # delete previous instances for each in QtWidgets.QApplication.topLevelWidgets(): bases = [get_class_name(x) for x in type(each).__bases__] if ( each != self and each.objectName() == self.objectName() and get_class_name(BaseToolbar) in bases ): each.deleteLater() # create ui and setup properties self.setWindowTitle("Toolbar") self.setup_ui() self.adjustSize() self.setMinimumWidth(150) def get_data_files(self): """Find data files based on existing folders.""" data_files = [] for path in self.data_folders: if not os.path.exists(path or ""): continue config = None if self.config_name in os.listdir(path): config = os.path.join(path, self.config_name) files = [] for each in sorted(os.listdir(path)): if each.endswith(".yaml") and each != self.config_name: files.append(os.path.join(path, each)) data_files.append((config, files)) return data_files def setup_ui(self): """Create the gui widgets.""" main_layout = QtWidgets.QVBoxLayout(self) main_layout.setSpacing(0) main_layout.setContentsMargins(0, 0, 0, 0) # create header widget self.header_widget = ui.Header() self.header_widget.label.setText("Toolbar") self.header_widget.refresh_btn.clicked.connect(self.refresh_content) self.header_widget.settings_btn.clicked.connect( partial(LOG.warn, "Settings window not yet implemented.") ) main_layout.addWidget(self.header_widget) # create tabWidget self.tab_widget = ui.TabWidget(self) self.tab_widget.setStyleSheet("") main_layout.addWidget(self.tab_widget) self.create_toolbar_content() def refresh_content(self): """Remove all tabs and recreate them from fresh.""" self.data_files = self.get_data_files() index = self.tab_widget.currentIndex() self.tab_widget.clear() self.scroll_areas = [] self.group_boxes = [] self.create_toolbar_content() self.tab_widget.setCurrentIndex(index) def create_toolbar_content(self): """Create all the widgets for the tab widget.""" for config, files in self.data_files: settings = {} if config: config_data = utils.yaml_load(config, ordered=True) settings = config_data.get("settings", {}) for data_file in files: name = os.path.basename(data_file).rsplit(".")[0] content = self.create_tab_content(data_file, settings) self.tab_widget.addTab(content, name.upper()) # resize window to fit children # self.resize_window() @staticmethod def sort_config(files, data): """Sort each files based their basename compared to the ordered list. Args: files (list): List of file paths. data (dict): The tab config data. Returns: list: The sorted files path. """ names = {os.path.basename(x).rsplit(".")[0]: x for x in files} # sort the matching names first sorted_data = OrderedDict() for each, settings in data.items(): if each in names: settings["path"] = names.get(each) sorted_data[each] = settings # add the un-found ones after for each in sorted(names): if each not in sorted_data: sorted_data[each] = OrderedDict({"path": names.get(each)}) return sorted_data def resize_window(self): """Resize window to fit children.""" # turn all groupboxes visible for group_box in self.group_boxes: group_box.setChecked(True) # resize scroll area to fit children for scroll_area in self.scroll_areas: scroll_area.resize_to_children() # restore default visibility for group_box in self.group_boxes: group_box.setChecked(group_box.default_visibility) def create_tab_content(self, data_file, settings=None): """Create a QWidget to add to the tab.""" settings = settings or {} data = utils.yaml_load(data_file, ordered=True) if not data: return None # append icon path path = os.path.join(os.path.dirname(data_file or ""), "icons") if path not in cfg.ICON_PATHS: cfg.ICON_PATHS.append(path) tab_content_widget = QtWidgets.QWidget(self) tab_layout = QtWidgets.QVBoxLayout(tab_content_widget) tab_layout.setSpacing(0) tab_layout.setContentsMargins(2, 2, 2, 2) scroll_area = ui.ScrollArea(tab_content_widget) tab_layout.addWidget(scroll_area) self.scroll_areas.append(scroll_area) # read data and create group boxes self.group_boxes = [] for title, group_data in data.items(): # extract settings for group_boxes visible = group_data.pop("visible", True) type_ = group_data.get("type") color = group_data.get("color", settings.get("color")) height = group_data.get("height") # continue if type is not specified if not type_: msg = "The 'type' key is missing for %r category. skipped..." LOG.warning(msg, title) continue # create group box group_box = ui.GroupBox(title, visible, color, height) self.group_boxes.append(group_box) scroll_area.addWidget(group_box) # add a separator line line = ui.Separator() scroll_area.addWidget(line) # create widgets if type_ == "custom": source = group_data.get("source") group_box.addWidget(self.create_custom_widget(source)) elif type_ == "flow": content = group_data.get("content", []) group_box.addWidget(self.create_flow_widget(content)) elif type_ == "box": content = group_data.get("content", []) for each in content: group_box.addWidget(self.create_box_widget(each)) # add stretcher and (somehow needs to) force the last child stretch scroll_area.layout.addStretch() index = scroll_area.layout.count() - 1 scroll_area.layout.setStretch(index, 1) return tab_content_widget def create_custom_widget(self, source): """Create a custom widget from source code. Warning: The source code MUST return the class object and not an instance! Args: source (str): Source code to import and create the widget. Returns: QWidget: A QWidget containing the custom widgets. """ base = QtWidgets.QWidget(self) layout = QtWidgets.QVBoxLayout(base) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) try: callback = utils.create_module_callback(source) widget_class = callback() class Widget(widget_class): """Ensures show method isn't triggered when instanciated.""" def show(self): """Clean up the show method.""" pass widget = Widget() except Exception: # pylint: disable=broad-except widget = self.create_broken_widget() layout.addWidget(widget) return base def create_flow_widget(self, content): """Create widget in either a flow layout. Args: content (dict): Data about widgets to create. Returns: QWidget: A parent QWidget containing the widgets in layout. """ import bgdev.ui.flow_layout base = QtWidgets.QWidget(self) flow = bgdev.ui.flow_layout.FlowLayout(base) flow.setContentsMargins(0, 0, 0, 0) flow.setSpacing(1) for data in content: for name, settings in data.items(): icon_btn = ui.IconButton(name, settings=settings) icon_btn.repeatable = self.repeatable flow.addWidget(icon_btn) if utils.get_dcc() == "maya": flow.set_style_offsets(10, 9) return base def create_box_widget(self, content): """Create widget in either row or column layout. Args: content (dict): Data about widgets to create. Returns: QWidget: A parent QWidget containing the widgets in layout. """ base = QtWidgets.QWidget(self) if "row" in content: layout = QtWidgets.QHBoxLayout(base) else: layout = QtWidgets.QVBoxLayout(base) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(1) for widgets in content.values(): for each in widgets: for name, settings in each.items(): widget = self.create_type_widget(name, settings) if widget: layout.addWidget(widget) return base def create_type_widget(self, name, settings): """Create a new widget based on the settings. Args: name (str): Name used as button label. settings (dict): Data used to create the widget. Returns: QObject: Created widget instance. """ widget = None if settings.get("type") == "button": widget = ui.PushButton(name, settings=settings) widget.repeatable = self.repeatable elif settings.get("type") == "icon": widget = ui.IconButton(name, settings=settings) widget.repeatable = self.repeatable elif settings.get("type") == "label": widget = ui.LabelWidget(name) return widget def create_broken_widget(self): """Create a button for broken widgets.""" widget = QtWidgets.QPushButton("\tBroken :-/") icon = "../broken_icon.png" icon = utils.check_image(icon) widget.setIcon(QtGui.QIcon(icon)) widget.clicked.connect(partial(LOG.error, traceback.format_exc())) return widget @staticmethod def repeatable(callback): """Make the command repeatable. Args: callback (function): Method executed by the button. Returns: method: The method passed in. Note: This method needs to be overwritten by DDCs with their own command in order to make the button command repeateable. """ return callback