Example #1
0
class RenamerWinBase(QtWidgets.QWidget):
    def __init__(self, parent=None):
        super(RenamerWinBase, self).__init__(parent)
        DIR, file_name = os.path.split(__file__)
        file_name = os.path.splitext(file_name)[0]
        load_ui(os.path.join(DIR, "%s.ui" % file_name), self)

        # NOTE 获取 convention 数据
        self.conventions = get_convention()

        name = "%s.ini" % self.__class__.__name__
        self.settings = QtCore.QSettings(name, QtCore.QSettings.IniFormat)
        # NOTE 配置 MComboBox
        self.Search_LE.setPlaceholderText(u"输入关键字")
        self.Replace_LE.setPlaceholderText(u"输入替换文字")
        self.Export_Setting_Action.triggered.connect(self.export_setting)
        self.Import_Setting_Action.triggered.connect(self.import_setting)
        self.Help_Action.triggered.connect(lambda: webbrowser.open_new_tab(
            'http://wiki.l0v0.com/unreal/PyToolkit/#/msic/2_renamer'))
        self.Convention_Action.triggered.connect(
            lambda: webbrowser.open_new_tab(
                'https://github.com/Allar/ue4-style-guide'))

        # NOTE 隐藏左侧配置项
        self.Splitter.splitterMoved.connect(lambda: self.settings.setValue(
            "splitter_size", self.Splitter.sizes()))
        splitter_size = self.settings.value("splitter_size")
        self.Splitter.setSizes(
            [int(i) for i in splitter_size] if splitter_size else [0, 1])

        # NOTE 配置 Header
        self.model = MTableModel()
        self.header_list = [{
            'label':
            data,
            'key':
            data,
            'bg_color':
            lambda x, y: y.get('bg_color', QtGui.QColor('transparent')),
            'tooltip':
            lambda x, y: y.get('asset').get_path_name(),
            'edit':
            lambda x, y: x or y.get('asset').get_name(),
            'display':
            lambda x, y: x or y.get('asset').get_name(),
            'editable':
            i == 1,
            'draggable':
            True,
            'width':
            100,
        } for i, data in enumerate([u"原名称", u"新名称", u"文件类型"])]
        self.model.set_header_list(self.header_list)
        self.model_sort = MSortFilterModel()
        self.model_sort.setSourceModel(self.model)

        self.Table_View.setModel(self.model_sort)
        self.Table_View.setShowGrid(True)
        header = self.Table_View.horizontalHeader()
        header.setStretchLastSection(True)

        self.setAcceptDrops(True)
        self.load_settings()

    def export_setting(self):
        path, _ = QFileDialog.getSaveFileName(self,
                                              caption=u"输出设置",
                                              filter=u"ini (*.ini)")
        if not path:
            return
        copyfile(self.settings.fileName(), path)
        toast(u"导出成功", "info")

    def import_setting(self):
        path, _ = QFileDialog.getOpenFileName(self,
                                              caption=u"获取设置",
                                              filter=u"ini (*.ini)")
        # NOTE 如果文件不存在则返回空
        if not path or not os.path.exists(path):
            return

        self.settings = QtCore.QSettings(path, QtCore.QSettings.IniFormat)
        self.settings.sync()
        self.load_settings()
        name = "%s.ini" % self.__class__.__name__
        self.settings = QtCore.QSettings(name, QtCore.QSettings.IniFormat)
        self.save_settings()
        toast(u"加载成功", "info")

    @staticmethod
    def check_loop(loop_list):
        for w in loop_list:
            name = w.objectName()
            if not hasattr(w, "objectName") or not name:
                continue
            elif isinstance(w,
                            QtWidgets.QLineEdit) and not name.endswith("_LE"):
                continue
            elif isinstance(w,
                            QtWidgets.QSpinBox) and not name.endswith("_SP"):
                continue
            elif isinstance(w,
                            QtWidgets.QCheckBox) and not name.endswith("_CB"):
                continue
            elif isinstance(
                    w, QtWidgets.QRadioButton) and not name.endswith("_RB"):
                continue
            elif isinstance(
                    w, QtWidgets.QComboBox) and not name.endswith("_Combo"):
                continue
            yield w

    def save_settings(self):
        CB_list = self.findChildren(QtWidgets.QCheckBox)
        RB_list = self.findChildren(QtWidgets.QRadioButton)
        LE_list = self.findChildren(QtWidgets.QLineEdit)
        SP_list = self.findChildren(QtWidgets.QSpinBox)
        Combo_list = self.findChildren(QtWidgets.QComboBox)

        for B in self.check_loop(CB_list + RB_list):
            self.settings.setValue(B.objectName(), B.isChecked())

        for LE in self.check_loop(LE_list):
            self.settings.setValue(LE.objectName(), LE.text())

        for SP in self.check_loop(SP_list):
            self.settings.setValue(SP.objectName(), SP.value())

        for Combo in self.check_loop(Combo_list):
            index = Combo.currentIndex()
            self.settings.setValue(Combo.objectName(), index)

        # NOTE 获取 Table 记录
        data_list = self.model.get_data_list()
        asset_data = [data.get("asset").get_path_name() for data in data_list]
        self.settings.setValue("table_asset", asset_data)

    def load_settings(self):
        CB_list = self.findChildren(QtWidgets.QCheckBox)
        RB_list = self.findChildren(QtWidgets.QRadioButton)
        LE_list = self.findChildren(QtWidgets.QLineEdit)
        SP_list = self.findChildren(QtWidgets.QSpinBox)
        Combo_list = self.findChildren(QtWidgets.QComboBox)
        widget_dict = {}
        for B in self.check_loop(CB_list + RB_list):
            val = self.settings.value(B.objectName())
            if val is not None:
                val = True if str(val).lower() == "true" else False
                widget_dict[B.setChecked] = val

        for LE in self.check_loop(LE_list):
            val = self.settings.value(LE.objectName())
            if val is not None:
                widget_dict[LE.setText] = val

        for SP in self.check_loop(SP_list):
            val = self.settings.value(SP.objectName())
            if val is not None:
                widget_dict[SP.setValue] = int(val)

        for Combo in self.check_loop(Combo_list):
            val = self.settings.value(Combo.objectName())
            if val is not None:
                widget_dict[Combo.setCurrentIndex] = int(val)

        # NOTE 添加 data_list
        asset_data = self.settings.value("table_asset")
        # NOTE 批量设置属性值
        for setter, val in widget_dict.items():
            setter(val)
        if not asset_data:
            return

        actor_list = []
        asset_list = []
        for path in asset_data:
            asset = unreal.load_object(None, path)
            if isinstance(asset, unreal.Actor):
                actor_list.append(asset)
            elif asset_lib.does_asset_exist(path):
                asset_list.append(asset)

        data_list = self.model.get_data_list()

        data_list.extend([{
            'bg_color': QtGui.QColor("transparent"),
            'asset': asset,
            u"原名称": asset.get_name(),
            u"新名称": "",
            u"文件类型": type(asset).__name__,
        } for asset in asset_list])

        data_list.extend([{
            'bg_color': QtGui.QColor("transparent"),
            'asset': actor,
            u"原名称": actor.get_actor_label(),
            u"新名称": "",
            u"文件类型": type(actor).__name__,
        } for actor in actor_list])

        self.model.set_data_list(data_list)
        self.update_table()

    def dragEnterEvent(self, event):
        event.accept() if event.mimeData().hasUrls() else event.ignore()

    def dropEvent(self, event):
        # Note 获取拖拽文件的地址
        if event.mimeData().hasUrls:
            event.setDropAction(QtCore.Qt.CopyAction)
            event.accept()
            print(event.mimeData().urls())
        else:
            event.ignore()

    def dragMoveEvent(self, event):
        if event.mimeData().hasUrls:
            event.setDropAction(QtCore.Qt.CopyAction)
            event.accept()
        else:
            event.ignore()

    def show(self):
        super(RenamerWinBase, self).show()

        # NOTE 配置 QGroupBox 的样式
        self.setStyleSheet(self.styleSheet() + """
        QGroupBox{
            border: 0.5px solid black;
            padding-top:10px;
        }
        """)

    def update_table(self):
        raise NotImplementedError
class PropertyTransferTool(QtWidgets.QWidget, MaterialTransfer, AssetList):

    state = PropertyTransferBinder()

    def update_icon(self):
        data = {
            "Del": {
                "icon": QtWidgets.QStyle.SP_BrowserStop,
                "callback": self.remove_assets,
                "tooltip": u"删除文件",
            },
            "Locate": {
                "icon": QtWidgets.QStyle.SP_FileDialogContentsView,
                "callback": self.locate_asset_location,
                "tooltip": u"定位文件",
            },
            "Drive": {
                "icon": QtWidgets.QStyle.SP_DriveHDIcon,
                "callback": self.locate_file_location,
                "tooltip": u"打开系统目录路径",
            },
            "Expand": {
                "icon": QtWidgets.QStyle.SP_ToolBarVerticalExtensionButton,
                "callback": self.Property_Tree.expandAll,
                "tooltip": u"扩展全部",
            },
            "Collapse": {
                "icon": QtWidgets.QStyle.SP_ToolBarHorizontalExtensionButton,
                "callback": self.Property_Tree.collapseAll,
                "tooltip": u"收缩全部",
            },
            "Source_Locate": {
                "icon":
                QtWidgets.QStyle.SP_FileDialogContentsView,
                "callback":
                lambda: asset_lib.sync_browser_to_objects(
                    [self.source.get_path_name()]),
                "tooltip":
                u"定位复制源",
            },
        }

        widget_dict = {
            "BTN": "clicked",
            "Action": "triggered",
        }

        style = QtWidgets.QApplication.style()
        icon = style.standardIcon(QtWidgets.QStyle.SP_BrowserReload)
        self.setWindowIcon(icon)
        for typ, info in data.items():
            icon = style.standardIcon(info.get("icon"))
            tooltip = info.get("tooltip", "")
            tooltip = '<span style="font-weight:600;">%s</span>' % tooltip
            callback = info.get("callback", lambda *args: None)

            for widget, signal in widget_dict.items():
                widget = "%s_%s" % (typ, widget)
                if hasattr(self, widget):
                    widget = getattr(self, widget)
                    widget.setIcon(icon)
                    getattr(widget, signal).connect(callback)
                    widget.setToolTip(tooltip)
                    widget.setEnabled(lambda: self.state.source_eanble)
                    widget.setEnabled(self.state.source_eanble)
                    # print(widget.isEnabled())

        # QtCore.QTimer.singleShot(0, lambda: self.state.source_eanble >> Set(False))

    def __init__(self, parent=None):
        super(PropertyTransferTool, self).__init__(parent)
        DIR, file_name = os.path.split(__file__)
        file_name = os.path.splitext(file_name)[0]
        load_ui(os.path.join(DIR, "%s.ui" % file_name), self)

        self.source = None

        help_link = "http://redarttoolkit.pages.oa.com/docs/posts/85c3f876.html"
        self.Help_Action.triggered.connect(
            lambda: webbrowser.open_new_tab(help_link))

        # NOTE 设置按钮图标
        QtCore.QTimer.singleShot(0, self.update_icon)

        menu_callback = lambda: self.Asset_Menu.popup(QtGui.QCursor.pos())
        self.Asset_List.customContextMenuRequested.connect(menu_callback)
        menu_callback = lambda: self.Property_Menu.popup(QtGui.QCursor.pos())
        self.Property_Tree.customContextMenuRequested.connect(menu_callback)

        self.Asset_Label.setVisible(lambda: self.state.lable_visible)
        self.Asset_Label.setText(lambda: self.state.lable_text)

        self.Src_BTN.clicked.connect(self.get_source)
        self.Dst_BTN.clicked.connect(self.get_destination)
        self.Transfer_BTN.clicked.connect(self.transfer_property)

        # NOTE 配置 splitter
        name = "%s.ini" % self.__class__.__name__
        self.settings = QtCore.QSettings(name, QtCore.QSettings.IniFormat)
        self.Splitter.splitterMoved.connect(lambda: self.settings.setValue(
            "splitter_size", self.Splitter.sizes()))
        splitter_size = self.settings.value("splitter_size")
        size = [int(i) for i in splitter_size] if splitter_size else [700, 200]
        self.Splitter.setSizes(size)

        # NOTE 配置搜索栏
        self.Dst_Filter_LE.search()
        self.Dst_Filter_LE.setPlaceholderText("")
        self.Prop_Filter_LE.search()
        self.Prop_Filter_LE.setPlaceholderText("")

        # NOTE 配置 Property_Tree
        self.property_model = MTableModel()
        columns = {u"property": "属性名", u"value": "数值"}
        self.property_header_list = [{
            "label":
            label,
            "key":
            key,
            "bg_color":
            lambda x, y: y.get("bg_color", QtGui.QColor("transparent")),
            "checkable":
            i == 0,
            "searchable":
            True,
            "width":
            300,
            "font":
            lambda x, y: {
                "bold": True
            },
        } for i, (key, label) in enumerate(columns.items())]
        self.property_model.set_header_list(self.property_header_list)
        self.property_model_sort = MSortFilterModel()
        self.property_model_sort.set_header_list(self.property_header_list)
        self.property_model_sort.setSourceModel(self.property_model)
        self.Prop_Filter_LE.textChanged.connect(
            self.property_model_sort.set_search_pattern)

        self.Property_Tree.setModel(self.property_model_sort)
        header = self.Property_Tree.header()
        header.setStretchLastSection(True)

        # NOTE 配置 Asset_List
        self.asset_model = MTableModel()
        self.asset_header_list = [{
            "label":
            "destination",
            "key":
            "destination",
            "bg_color":
            lambda x, y: y.get("bg_color", QtGui.QColor("transparent")),
            "tooltip":
            lambda x, y: y.get("asset").get_path_name(),
            "checkable":
            True,
            "searchable":
            True,
            "width":
            300,
            "font":
            lambda x, y: {
                "bold": True
            },
        }]
        self.asset_model.set_header_list(self.asset_header_list)
        self.asset_model_sort = MSortFilterModel()
        self.asset_model_sort.search_reg.setPatternSyntax(
            QtCore.QRegExp.RegExp)
        self.asset_model_sort.set_header_list(self.asset_header_list)
        self.asset_model_sort.setSourceModel(self.asset_model)
        self.Dst_Filter_LE.textChanged.connect(
            self.asset_model_sort.set_search_pattern)

        self.Asset_List.setModel(self.asset_model_sort)
        self.Asset_List.selectionModel().selectionChanged.connect(
            self.asset_selection_change)
        self.property_model.dataChanged.connect(self.asset_selection_change)

    def get_source(self):
        assets = util_lib.get_selected_assets()
        if len(assets) < 1:
            toast(u"请选择一个资产")
            return

        asset = assets[0]
        self.set_property_tree(asset)
        self.asset_model.set_data_list([])
        self.property_model_sort.sort(0, QtCore.Qt.AscendingOrder)

    def get_destination(self):
        if not self.source:
            toast(u"请拾取复制源")
            return

        data_list = self.asset_model.get_data_list()
        tooltip_list = [
            data.get("asset").get_path_name() for data in data_list
        ]

        assets = [
            asset for asset in util_lib.get_selected_assets()
            if isinstance(asset, type(self.source))
            and asset.get_path_name() not in tooltip_list
        ]

        if not assets:
            toast(u"请选择匹配的资产")
            return

        data_list.extend([
            {
                "bg_color": QtGui.QColor("transparent"),
                "asset": asset,
                "destination": asset.get_name(),
                # NOTE 默认勾选
                "destination_checked": QtCore.Qt.Checked,
            } for asset in assets if not asset is self.source
        ])

        self.asset_model.set_data_list(data_list)

    def asset_selection_change(self, *args):
        model = self.Asset_List.selectionModel()
        indexes = model.selectedRows()
        if not indexes:
            return
        index = indexes[0]
        data_list = self.asset_model.get_data_list()
        data = data_list[index.row()]
        asset = data.get("asset")
        if isinstance(asset, unreal.MaterialInterface):
            self.update_material_property(asset)

    def set_property_tree(self, asset):
        if isinstance(asset, unreal.MaterialInterface):
            data = self.get_material_property(asset)
        elif isinstance(asset, unreal.Blueprint):
            pass
        else:
            toast(u"不支持资产")
            return

        data_list = [{
            "property":
            group,
            "value":
            "",
            "children": [{
                "bg_color": QtGui.QColor("transparent"),
                "property": prop,
                "property_checked": QtCore.Qt.Unchecked,
                "value": value,
            } for prop, value in props.items()],
        } for group, props in data.items()]
        self.property_model.set_data_list(data_list)

        self.source = asset
        self.state.source_eanble = True
        self.state.lable_visible = True
        self.state.lable_text = asset.get_name()

    def transfer_property(self):

        for i, asset in enumerate(self.asset_model.get_data_list()):
            if not asset.get("destination_checked"):
                continue

            # NOTE 选择 Asset_List 的资产 更新颜色
            index = self.asset_model.index(i, 0)
            model = self.Asset_List.selectionModel()
            model.setCurrentIndex(index, model.SelectCurrent)

            property_list = [
                prop.get("property")
                for grp in self.property_model.get_data_list()
                for prop in grp.get("children")
                # NOTE 过滤没有勾选的资源
                if prop.get("bg_color") == QtGui.QColor("green")
                and prop.get("property_checked")
            ]

            # NOTE 传递属性
            asset = asset.get("asset")
            if isinstance(asset, type(self.source)):
                self.transfer_material_property(asset, property_list)
            else:
                raise RuntimeError(u"%s 和复制源类型不匹配" % asset.get_name())

        toast("传递完成", 'success')
class MItemViewSet(QtWidgets.QWidget):
    sig_double_clicked = QtCore.Signal(QtCore.QModelIndex)
    sig_left_clicked = QtCore.Signal(QtCore.QModelIndex)
    TableViewType = MTableView
    BigViewType = MBigView
    TreeViewType = MTreeView
    ListViewType = MListView

    def __init__(self, view_type=None, parent=None):
        super(MItemViewSet, self).__init__(parent)
        self.main_lay = QtWidgets.QVBoxLayout()
        self.main_lay.setSpacing(5)
        self.main_lay.setContentsMargins(0, 0, 0, 0)

        self.sort_filter_model = MSortFilterModel()
        self.source_model = MTableModel()
        self.sort_filter_model.setSourceModel(self.source_model)
        view_class = view_type or MItemViewSet.TableViewType
        self.item_view = view_class()
        self.item_view.doubleClicked.connect(self.sig_double_clicked)
        self.item_view.pressed.connect(self.slot_left_clicked)
        self.item_view.setModel(self.sort_filter_model)

        self._search_line_edit = MLineEdit().search().small()
        self._search_attr_button = (
            MToolButton().icon_only().svg("down_fill.svg").small())
        self._search_line_edit.set_prefix_widget(self._search_attr_button)
        self._search_line_edit.textChanged.connect(
            self.sort_filter_model.set_search_pattern)
        self._search_line_edit.setVisible(False)
        self._search_lay = QtWidgets.QHBoxLayout()
        self._search_lay.setContentsMargins(0, 0, 0, 0)
        self._search_lay.addStretch()
        self._search_lay.addWidget(self._search_line_edit)

        self.main_lay.addLayout(self._search_lay)
        self.main_lay.addWidget(self.item_view)
        self.setLayout(self.main_lay)

    @QtCore.Slot(QtCore.QModelIndex)
    def slot_left_clicked(self, start_index):
        button = QtWidgets.QApplication.mouseButtons()
        if button == QtCore.Qt.LeftButton:
            real_index = self.sort_filter_model.mapToSource(start_index)
            self.sig_left_clicked.emit(real_index)

    def set_header_list(self, header_list):
        self.source_model.set_header_list(header_list)
        self.sort_filter_model.set_header_list(header_list)
        self.sort_filter_model.setSourceModel(self.source_model)
        self.item_view.set_header_list(header_list)

    @QtCore.Slot()
    def setup_data(self, data_list):
        self.source_model.clear()
        if data_list:
            self.source_model.set_data_list(data_list)
            self.item_view.set_header_list(self.source_model.header_list)

    def get_data(self):
        return self.source_model.get_data_list()

    def searchable(self):
        """Enable search line edit visible."""
        self._search_line_edit.setVisible(True)
        return self

    def insert_widget(self, widget):
        """Use can insert extra widget into search layout."""
        self._search_lay.insertWidget(0, widget)
Example #4
0
class MItemViewFullSet(QtWidgets.QWidget):
    sig_double_clicked = QtCore.Signal(QtCore.QModelIndex)
    sig_left_clicked = QtCore.Signal(QtCore.QModelIndex)
    sig_current_changed = QtCore.Signal(QtCore.QModelIndex, QtCore.QModelIndex)
    sig_current_row_changed = QtCore.Signal(QtCore.QModelIndex,
                                            QtCore.QModelIndex)
    sig_current_column_changed = QtCore.Signal(QtCore.QModelIndex,
                                               QtCore.QModelIndex)
    sig_selection_changed = QtCore.Signal(QtCore.QItemSelection,
                                          QtCore.QItemSelection)
    sig_context_menu = QtCore.Signal(object)

    def __init__(self, table_view=True, big_view=False, parent=None):
        super(MItemViewFullSet, self).__init__(parent)
        self.sort_filter_model = MSortFilterModel()
        self.source_model = MTableModel()
        self.sort_filter_model.setSourceModel(self.source_model)

        self.stack_widget = QtWidgets.QStackedWidget()

        self.view_button_grp = MToolButtonGroup(exclusive=True)
        data_group = []
        if table_view:
            self.table_view = MTableView(show_row_count=True)
            self.table_view.doubleClicked.connect(self.sig_double_clicked)
            self.table_view.pressed.connect(self.slot_left_clicked)
            self.table_view.setModel(self.sort_filter_model)
            self.stack_widget.addWidget(self.table_view)
            data_group.append({
                "svg": "table_view.svg",
                "checkable": True,
                "tooltip": "Table View"
            })
        if big_view:
            self.big_view = MBigView()
            self.big_view.doubleClicked.connect(self.sig_double_clicked)
            self.big_view.pressed.connect(self.slot_left_clicked)
            self.big_view.setModel(self.sort_filter_model)
            self.stack_widget.addWidget(self.big_view)
            data_group.append({
                "svg": "big_view.svg",
                "checkable": True,
                "tooltip": "Big View"
            })

        # 设置多个view 共享 MItemSelectionModel
        leader_view = self.stack_widget.widget(0)
        self.selection_model = leader_view.selectionModel()
        for index in range(self.stack_widget.count()):
            if index == 0:
                continue
            other_view = self.stack_widget.widget(index)
            other_view.setSelectionModel(self.selection_model)

        self.selection_model.currentChanged.connect(self.sig_current_changed)
        self.selection_model.currentRowChanged.connect(
            self.sig_current_row_changed)
        self.selection_model.currentColumnChanged.connect(
            self.sig_current_column_changed)
        self.selection_model.selectionChanged.connect(
            self.sig_selection_changed)

        self.tool_bar = QtWidgets.QWidget()
        self.top_lay = QtWidgets.QHBoxLayout()
        self.top_lay.setContentsMargins(0, 0, 0, 0)
        if data_group and len(data_group) > 1:
            self.view_button_grp.sig_checked_changed.connect(
                self.stack_widget.setCurrentIndex)
            self.view_button_grp.set_button_list(data_group)
            self.view_button_grp.set_dayu_checked(0)
            self.top_lay.addWidget(self.view_button_grp)
        self.search_line_edit = MLineEdit().search().small()
        self.search_attr_button = MToolButton().icon_only().svg(
            "down_fill.svg").small()
        self.search_line_edit.set_prefix_widget(self.search_attr_button)
        self.search_line_edit.textChanged.connect(
            self.sort_filter_model.set_search_pattern)
        self.search_line_edit.setVisible(False)

        self.top_lay.addStretch()
        self.top_lay.addWidget(self.search_line_edit)
        self.tool_bar.setLayout(self.top_lay)

        self.page_set = MPage()
        self.main_lay = QtWidgets.QVBoxLayout()
        self.main_lay.setSpacing(5)
        self.main_lay.setContentsMargins(0, 0, 0, 0)
        self.main_lay.addWidget(self.tool_bar)
        self.main_lay.addWidget(self.stack_widget)
        self.main_lay.addWidget(self.page_set)
        self.setLayout(self.main_lay)

    def enable_context_menu(self):
        for index in range(self.stack_widget.count()):
            view = self.stack_widget.widget(index)
            view.enable_context_menu(True)
            view.sig_context_menu.connect(self.sig_context_menu)

    def set_no_data_text(self, text):
        for index in range(self.stack_widget.count()):
            view = self.stack_widget.widget(index)
            view.set_no_data_text(text)

    def set_selection_mode(self, mode):
        for index in range(self.stack_widget.count()):
            view = self.stack_widget.widget(index)
            view.setSelectionMode(mode)

    def tool_bar_visible(self, flag):
        self.tool_bar.setVisible(flag)

    @QtCore.Slot(QtCore.QModelIndex)
    def slot_left_clicked(self, start_index):
        button = QtWidgets.QApplication.mouseButtons()
        if button == QtCore.Qt.LeftButton:
            real_index = self.sort_filter_model.mapToSource(start_index)
            self.sig_left_clicked.emit(real_index)

    def set_header_list(self, header_list):
        self.source_model.set_header_list(header_list)
        self.sort_filter_model.set_header_list(header_list)
        self.sort_filter_model.setSourceModel(self.source_model)
        for index in range(self.stack_widget.count()):
            view = self.stack_widget.widget(index)
            view.set_header_list(header_list)

    def tool_bar_append_widget(self, widget):
        self.top_lay.addWidget(widget)

    def tool_bar_insert_widget(self, widget):
        self.top_lay.insertWidget(0, widget)

    @QtCore.Slot()
    def setup_data(self, data_list):
        self.source_model.clear()
        if data_list:
            self.source_model.set_data_list(data_list)
        self.set_record_count(len(data_list))
        for index in range(self.stack_widget.count()):
            view = self.stack_widget.widget(index)
            view.set_header_list(self.source_model.header_list)

    @QtCore.Slot(int)
    def set_record_count(self, total):
        self.page_set.set_total(total)

    def get_data(self):
        return self.source_model.get_data_list()

    def searchable(self):
        """Enable search line edit visible."""
        self.search_line_edit.setVisible(True)
        return self