Exemplo n.º 1
0
class MClickBrowserFolderToolButton(MToolButton):
    """A Clickable tool button to browser folders"""
    sig_folder_changed = Signal(str)
    sig_folders_changed = Signal(list)
    slot_browser_folder = _slot_browser_folder

    def __init__(self, multiple=False, parent=None):
        super(MClickBrowserFolderToolButton, self).__init__(parent=parent)

        self.set_dayu_svg('folder_line.svg')
        self.icon_only()
        self.clicked.connect(self.slot_browser_folder)
        self.setToolTip(self.tr('Click to browser folder'))

        self._path = None
        self._multiple = multiple

    def get_dayu_path(self):
        """
        Get last browser file path
        :return: str
        """
        return self._path

    def set_dayu_path(self, value):
        """
        Set browser file start path
        :param value: str
        :return: None
        """
        self._path = value

    def get_dayu_multiple(self):
        """
        Get browser can select multiple file or not
        :return: bool
        """
        return self._multiple

    def set_dayu_multiple(self, value):
        """
        Set browser can select multiple file or not
        :param value: bool
        :return: None
        """
        self._multiple = value

    dayu_multiple = Property(bool, get_dayu_multiple, set_dayu_multiple)
    dayu_path = Property(six.string_types[0], get_dayu_path, set_dayu_path)
Exemplo n.º 2
0
class MClickBrowserFolderPushButton(MPushButton):
    """A Clickable push button to browser folders"""
    sig_folder_changed = Signal(str)
    sig_folders_changed = Signal(list)
    slot_browser_folder = _slot_browser_folder

    def __init__(self, text='', multiple=False, parent=None):
        super(MClickBrowserFolderPushButton, self).__init__(text=text,
                                                            parent=parent)
        self.setProperty('multiple', multiple)
        self.clicked.connect(self.slot_browser_folder)
        self.setToolTip(self.tr('Click to browser folder'))

        self._path = None
        self._multiple = multiple

    def get_dayu_path(self):
        """
        Get last browser file path
        :return: str
        """
        return self._path

    def set_dayu_path(self, value):
        """
        Set browser file start path
        :param value: str
        :return: None
        """
        self._path = value

    def get_dayu_multiple(self):
        """
        Get browser can select multiple file or not
        :return: bool
        """
        return self._multiple

    def set_dayu_multiple(self, value):
        """
        Set browser can select multiple file or not
        :param value: bool
        :return: None
        """
        self._multiple = value

    dayu_multiple = Property(bool, get_dayu_multiple, set_dayu_multiple)
    dayu_path = Property(basestring, get_dayu_path, set_dayu_path)
Exemplo n.º 3
0
class MTreeView(QTreeView):
    set_header_list = set_header_list
    enable_context_menu = enable_context_menu
    slot_context_menu = slot_context_menu
    sig_context_menu = Signal(object)

    def __init__(self, parent=None):
        super(MTreeView, self).__init__(parent)
        self._no_data_image = None
        self._no_data_text = self.tr('No Data')
        self.header_list = []
        self.header_view = MHeaderView(Qt.Horizontal)
        self.setHeader(self.header_view)
        self.setSortingEnabled(True)
        self.setAlternatingRowColors(True)

    def paintEvent(self, event):
        """Override paintEvent when there is no data to show, draw the preset picture and text."""
        model = utils.real_model(self.model())
        if model is None:
            draw_empty_content(self.viewport(), self._no_data_text,
                               self._no_date_image)
        elif isinstance(model, MTableModel):
            if not model.get_data_list():
                draw_empty_content(self.viewport(), self._no_data_text,
                                   self._no_date_image)
        return super(MTreeView, self).paintEvent(event)

    def set_no_data_text(self, text):
        self._no_data_text = text
Exemplo n.º 4
0
class MRadioButtonGroup(MButtonGroupBase):
    """
    Property:
        dayu_checked
    """
    sig_checked_changed = Signal(int)

    def __init__(self, orientation=Qt.Horizontal, parent=None):
        super(MRadioButtonGroup, self).__init__(orientation=orientation, parent=parent)
        self.set_spacing(15)
        self._button_group.setExclusive(True)
        self._button_group.buttonClicked[int].connect(self.sig_checked_changed)

    def create_button(self, data_dict):
        return MRadioButton()

    def set_dayu_checked(self, value):
        if value == self.get_dayu_checked():
            return
        button = self._button_group.button(value)
        if button:
            button.setChecked(True)
            self.sig_checked_changed.emit(value)
        else:
            print('error')

    def get_dayu_checked(self):
        return self._button_group.checkedId()

    dayu_checked = Property(int, get_dayu_checked, set_dayu_checked, notify=sig_checked_changed)
Exemplo n.º 5
0
class MClickSaveFileToolButton(MToolButton):
    """A Clickable tool button to browser files"""
    sig_file_changed = Signal(str)
    slot_browser_file = _slot_save_file

    def __init__(self, multiple=False, parent=None):
        super(MClickSaveFileToolButton, self).__init__(parent=parent)
        self.set_dayu_svg('save_line.svg')
        self.icon_only()
        self.clicked.connect(self.slot_browser_file)
        self.setToolTip(self.tr('Click to save file'))

        self._path = None
        self._multiple = multiple
        self._filters = []

    def get_dayu_filters(self):
        """
        Get browser's format filters
        :return: list
        """
        return self._filters

    def set_dayu_filters(self, value):
        """
        Set browser file format filters
        :param value:
        :return: None
        """
        self._filters = value

    def get_dayu_path(self):
        """
        Get last browser file path
        :return: str
        """
        return self._path

    def set_dayu_path(self, value):
        """
        Set browser file start path
        :param value: str
        :return: None
        """
        self._path = value

    dayu_path = Property(six.string_types[0], get_dayu_path, set_dayu_path)
    dayu_filters = Property(list, get_dayu_filters, set_dayu_filters)
Exemplo n.º 6
0
class MToolButtonGroup(MButtonGroupBase):
    sig_checked_changed = Signal(int)

    def __init__(self,
                 size=None,
                 type=None,
                 exclusive=False,
                 orientation=Qt.Horizontal,
                 parent=None):
        super(MToolButtonGroup, self).__init__(orientation=orientation,
                                               parent=parent)
        self.set_spacing(1)
        self._button_group.setExclusive(exclusive)
        self._size = size
        self._type = type
        self._button_group.buttonClicked[int].connect(self.sig_checked_changed)

    def create_button(self, data_dict):
        button = MToolButton()
        if data_dict.get('svg'):
            button.svg(data_dict.get('svg'))
        if data_dict.get('text'):
            if data_dict.get('svg') or data_dict.get('icon'):
                button.text_beside_icon()
            else:
                button.text_only()
        else:
            button.icon_only()
        return button

    def set_dayu_checked(self, value):
        if value == self.get_dayu_checked():
            return
        button = self._button_group.button(value)
        if button:
            button.setChecked(True)
            self.sig_checked_changed.emit(value)
        else:
            print 'error'

    def get_dayu_checked(self):
        return self._button_group.checkedId()

    dayu_checked = Property(int,
                            get_dayu_checked,
                            set_dayu_checked,
                            notify=sig_checked_changed)
Exemplo n.º 7
0
class MBigView(QListView):
    set_header_list = set_header_list
    enable_context_menu = enable_context_menu
    slot_context_menu = slot_context_menu
    sig_context_menu = Signal(object)

    def __init__(self, parent=None):
        super(MBigView, self).__init__(parent)
        self._no_date_image = None
        self._no_data_text = self.tr('No Data')
        self.header_list = []
        self.header_view = None
        self.setViewMode(QListView.IconMode)
        self.setResizeMode(QListView.Adjust)
        self.setMovement(QListView.Static)
        self.setSpacing(10)
        self.setIconSize(QSize(128, 128))

    def wheelEvent(self, event):
        """Override wheelEvent while user press ctrl, zoom the list view icon size."""
        if event.modifiers() == Qt.ControlModifier:
            num_degrees = event.delta() / 8.0
            num_steps = num_degrees / 15.0
            factor = pow(1.125, num_steps)
            new_size = self.iconSize() * factor
            if new_size.width() > 200:
                new_size = QSize(200, 200)
            elif new_size.width() < 24:
                new_size = QSize(24, 24)
            self.setIconSize(new_size)
        else:
            super(MBigView, self).wheelEvent(event)

    def paintEvent(self, event):
        """Override paintEvent when there is no data to show, draw the preset picture and text."""
        model = utils.real_model(self.model())
        if model is None:
            draw_empty_content(self.viewport(), self._no_data_text,
                               self._no_date_image)
        elif isinstance(model, MTableModel):
            if not model.get_data_list():
                draw_empty_content(self.viewport(), self._no_data_text,
                                   self._no_date_image)
        return super(MBigView, self).paintEvent(event)

    def set_no_data_text(self, text):
        self._no_data_text = text
Exemplo n.º 8
0
class MListView(QListView):
    set_header_list = set_header_list
    enable_context_menu = enable_context_menu
    slot_context_menu = slot_context_menu
    sig_context_menu = Signal(object)

    def __init__(self, size=None, parent=None):
        super(MListView, self).__init__(parent)
        self._no_date_image = None
        self._no_data_text = self.tr('No Data')
        self.setProperty('dayu_size', size or dayu_theme.default_size)
        self.header_list = []
        self.header_view = None
        self.setModelColumn(0)
        self.setAlternatingRowColors(True)

    def set_show_column(self, attr):
        for index, attr_dict in enumerate(self.header_list):
            if attr_dict.get('key') == attr:
                self.setModelColumn(index)
                break
        else:
            self.setModelColumn(0)

    def paintEvent(self, event):
        """Override paintEvent when there is no data to show, draw the preset picture and text."""
        model = utils.real_model(self.model())
        if model is None:
            draw_empty_content(self.viewport(), self._no_data_text,
                               self._no_date_image)
        elif isinstance(model, MTableModel):
            if not model.get_data_list():
                draw_empty_content(self.viewport(), self._no_data_text,
                                   self._no_date_image)
        return super(MListView, self).paintEvent(event)

    def set_no_data_text(self, text):
        self._no_data_text = text

    def minimumSizeHint(self, *args, **kwargs):
        return QSize(200, 50)
Exemplo n.º 9
0
class MNewTag(QWidget):
    sig_add_tag = Signal(str)

    def __init__(self, text='New Tag', parent=None):
        super(MNewTag, self).__init__(parent)
        self.setAttribute(Qt.WA_StyledBackground)
        self._add_button = MToolButton().tiny().svg(
            'add_line.svg').text_beside_icon()
        self._add_button.setText(text)
        self._add_button.clicked.connect(self._slot_show_edit)
        self._line_edit = MLineEdit().tiny()
        self._line_edit.returnPressed.connect(self._slot_return_pressed)
        self._line_edit.setVisible(False)

        self._main_lay = QGridLayout()
        self._main_lay.setContentsMargins(3, 3, 3, 3)
        self._main_lay.addWidget(self._add_button, 0, 0)
        self._main_lay.addWidget(self._line_edit, 0, 0)
        self.setLayout(self._main_lay)

    def set_completer(self, completer):
        self._line_edit.setCompleter(completer)

    def _slot_show_edit(self):
        self._line_edit.setVisible(True)
        self._add_button.setVisible(False)
        self._line_edit.setFocus(Qt.MouseFocusReason)

    def _slot_return_pressed(self):
        self._line_edit.setVisible(False)
        self._add_button.setVisible(True)
        if self._line_edit.text():
            self.sig_add_tag.emit(self._line_edit.text())
        self._line_edit.clear()

    def focusOutEvent(self, *args, **kwargs):
        self._line_edit.setVisible(False)
        self._add_button.setVisible(True)
        return super(MNewTag, self).focusOutEvent(*args, **kwargs)
Exemplo n.º 10
0
class MBlockButtonGroup(MButtonGroupBase):
    """MBlockButtonGroup"""
    sig_checked_changed = Signal(int)

    def __init__(self, parent=None):
        super(MBlockButtonGroup, self).__init__(parent=parent)
        self.set_spacing(1)
        self._button_group.setExclusive(True)
        self._button_group.buttonClicked[int].connect(self.sig_checked_changed)

    def create_button(self, data_dict):
        button = MBlockButton()
        if data_dict.get('svg'):
            button.svg(data_dict.get('svg'))
        if data_dict.get('text'):
            if data_dict.get('svg') or data_dict.get('icon'):
                button.text_beside_icon()
            else:
                button.text_only()
        else:
            button.icon_only()
        button.set_dayu_size(dayu_theme.large)
        return button

    def set_dayu_checked(self, value):
        """Set current checked button's id"""
        button = self._button_group.button(value)
        button.setChecked(True)
        self.sig_checked_changed.emit(value)

    def get_dayu_checked(self):
        """Get current checked button's id"""
        return self._button_group.checkedId()

    dayu_checked = Property(int,
                            get_dayu_checked,
                            set_dayu_checked,
                            notify=sig_checked_changed)
Exemplo n.º 11
0
class MPage(QWidget, MFieldMixin):
    """
    MPage
    A long list can be divided into several pages by MPage,
    and only one page will be loaded at a time.
    """
    sig_page_changed = Signal(int, int)

    def __init__(self, parent=None):
        super(MPage, self).__init__(parent)
        self.register_field('page_size_selected', 25)
        self.register_field('page_size_list',
                            [{'label': '25 - Fastest', 'value': 25},
                             {'label': '50 - Fast', 'value': 50},
                             {'label': '75 - Medium', 'value': 75},
                             {'label': '100 - Slow', 'value': 100}])
        self.register_field('total', 0)
        self.register_field('current_page', 0)
        self.register_field('total_page',
                            lambda: utils.get_total_page(self.field('total'),
                                                         self.field('page_size_selected')))
        self.register_field('total_page_text',
                            lambda: str(self.field('total_page')))
        self.register_field('display_text',
                            lambda: utils.get_page_display_string(self.field('current_page'),
                                                                  self.field('page_size_selected'),
                                                                  self.field('total')))
        self.register_field('can_pre',
                            lambda: self.field('current_page') > 1)
        self.register_field('can_next',
                            lambda: self.field('current_page') < self.field('total_page'))
        page_setting_menu = MMenu(parent=self)

        self._display_label = MLabel()
        self._display_label.setAlignment(Qt.AlignCenter)
        self._change_page_size_button = MComboBox().small()
        self._change_page_size_button.setFixedWidth(110)
        self._change_page_size_button.set_menu(page_setting_menu)
        self._change_page_size_button.set_formatter(lambda x: u'{} per page'.format(x))
        self._change_page_size_button.sig_value_changed.connect(self._emit_page_changed)

        self._pre_button = MToolButton().icon_only().svg('left_fill.svg').small()
        self._pre_button.clicked.connect(functools.partial(self._slot_change_current_page, -1))
        self._next_button = MToolButton().small().icon_only().svg('right_fill.svg')
        self._next_button.clicked.connect(functools.partial(self._slot_change_current_page, 1))
        self._current_page_spin_box = MSpinBox()
        self._current_page_spin_box.setMinimum(1)
        self._current_page_spin_box.set_dayu_size(dayu_theme.small)
        self._current_page_spin_box.valueChanged.connect(self._emit_page_changed)
        self._total_page_label = MLabel()

        self.bind('page_size_list', page_setting_menu, 'data')
        self.bind('page_size_selected', page_setting_menu, 'value', signal='sig_value_changed')
        self.bind('page_size_selected', self._change_page_size_button, 'value',
                  signal='sig_value_changed')
        self.bind('current_page', self._current_page_spin_box, 'value', signal='valueChanged')
        self.bind('total_page', self._current_page_spin_box, 'maximum')
        self.bind('total_page_text', self._total_page_label, 'dayu_text')
        self.bind('display_text', self._display_label, 'dayu_text')
        self.bind('can_pre', self._pre_button, 'enabled')
        self.bind('can_next', self._next_button, 'enabled')

        main_lay = QHBoxLayout()
        main_lay.setContentsMargins(0, 0, 0, 0)
        main_lay.setSpacing(2)
        main_lay.addStretch()
        main_lay.addWidget(self._display_label)
        main_lay.addStretch()
        main_lay.addWidget(MLabel('|').secondary())
        main_lay.addWidget(self._change_page_size_button)
        main_lay.addWidget(MLabel('|').secondary())
        main_lay.addWidget(self._pre_button)
        main_lay.addWidget(MLabel('Page'))
        main_lay.addWidget(self._current_page_spin_box)
        main_lay.addWidget(MLabel('/'))
        main_lay.addWidget(self._total_page_label)
        main_lay.addWidget(self._next_button)
        self.setLayout(main_lay)

    def set_total(self, value):
        """Set page component total count."""
        self.set_field('total', value)
        self.set_field('current_page', 1)

    def _slot_change_current_page(self, offset):
        self.set_field('current_page', self.field('current_page') + offset)
        self._emit_page_changed()

    def set_page_config(self, data_list):
        """Set page component per page settings."""
        self.set_field('page_size_list',
                       [{'label': str(data), 'value': data} if isinstance(data, int) else data for
                        data in data_list])

    def _emit_page_changed(self):
        self.sig_page_changed.emit(self.field('page_size_selected'), self.field('current_page'))
Exemplo n.º 12
0
class MItemViewSet(QWidget):
    sig_double_clicked = Signal(QModelIndex)
    sig_left_clicked = Signal(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 = 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)
        _search_lay = QHBoxLayout()
        _search_lay.setContentsMargins(0, 0, 0, 0)
        _search_lay.addStretch()
        _search_lay.addWidget(self._search_line_edit)

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

    @Slot(QModelIndex)
    def slot_left_clicked(self, start_index):
        button = QApplication.mouseButtons()
        if button == 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)

    @Slot()
    def setup_data(self, data_list):
        self.source_model.clear()
        if data_list:
            self.source_model.set_data_list(data_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
Exemplo n.º 13
0
class MTableView(QTableView):
    sig_context_menu = Signal(object)
    set_header_list = set_header_list

    def __init__(self, size=None, show_row_count=False, parent=None):
        super(MTableView, self).__init__(parent)
        self._no_data_image = None
        self._no_data_text = self.tr('No Data')
        size = size or dayu_theme.default_size
        ver_header_view = MHeaderView(Qt.Vertical, parent=self)
        ver_header_view.setDefaultSectionSize(size)
        self.setVerticalHeader(ver_header_view)
        self.header_list = []
        self.header_view = MHeaderView(Qt.Horizontal, parent=self)
        self.header_view.setFixedHeight(size)
        if not show_row_count:
            ver_header_view.hide()
        self.setHorizontalHeader(self.header_view)
        self.setSortingEnabled(True)
        self.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.setAlternatingRowColors(True)
        self.setShowGrid(False)

    def set_no_data_text(self, text):
        self._no_data_text = text

    def set_no_data_image(self, image):
        self._no_data_image = image

    def setShowGrid(self, flag):
        self.header_view.setProperty('grid', flag)
        self.verticalHeader().setProperty('grid', flag)
        self.header_view.style().polish(self.header_view)

        return super(MTableView, self).setShowGrid(flag)

        # setting = {
        #     'key': attr,  # 必填,用来读取 model后台数据结构的属性
        #     'label': attr.title(),  # 选填,显示在界面的该列的名字
        #     'width': 100,  # 选填,单元格默认的宽度
        #     'default_filter': False,  # 选填,如果有组合的filter组件,该属性默认是否显示,默认False
        #     'searchable': False,  # 选填,如果有搜索组件,该属性是否可以被搜索,默认False
        #     'editable': False,  # 选填,该列是否可以双击编辑,默认False
        #     'selectable': False,  # 选填,该列是否可以双击编辑,且使用下拉列表选择。该下拉框的选项们,是通过 data 拿数据的
        #     'checkable': False,  # 选填,该单元格是否要加checkbox,默认False
        #     'exclusive': True,  # 配合selectable,如果是可以多选的则为 False,如果是单选,则为True
        #     'order': None,  # 选填,初始化时,该列的排序方式, 0 升序,1 降序
        #     # 下面的是每个单元格的设置,主要用来根据本单元格数据,动态设置样式
        #     'color': None,  # QColor选填,该单元格文字的颜色,例如根据百分比数据大小,大于100%显示红色,小于100%显示绿色
        #     'bg_color': None,  # 选填,该单元格的背景色,例如根据bool数据,True显示绿色,False显示红色
        #     'display': None,  # 选填,该单元显示的内容,例如数据是以分钟为单位,可以在这里给转换成按小时为单位
        #     'align': None,  # 选填,该单元格文字的对齐方式
        #     'font': None,  # 选填,该单元格文字的格式,例如加下划线、加粗等等
        #     'icon': None,  # 选填,该单格元的图标,注意,当 QListView 使用图标模式时,每个item的图片也是在这里设置
        #     'tooltip': None,  # 选填,鼠标指向该单元格时,显示的提示信息
        #     'size': None,  # 选填,该列的 hint size,设置
        #     'data': None,
        #     'edit': None
        # }

    def paintEvent(self, event):
        """Override paintEvent when there is no data to show, draw the preset picture and text."""
        model = utils.real_model(self.model())
        if model is None:
            draw_empty_content(self.viewport(), self._no_data_text,
                               self._no_data_image)
        elif isinstance(model, MTableModel):
            if not model.get_data_list():
                draw_empty_content(self.viewport(), self._no_data_text,
                                   self._no_data_image)
        return super(MTableView, self).paintEvent(event)

    def save_state(self, name):
        settings = QSettings(QSettings.IniFormat, QSettings.UserScope, 'DAYU',
                             'dayu_widgets')
        settings.setValue('{}/headerState'.format(
            name, self.header_view.saveState()))

    def load_state(self, name):
        settings = QSettings(QSettings.IniFormat, QSettings.UserScope, 'DAYU',
                             'dayu_widgets')
        if settings.value('{}/headerState'.format(name)):
            self.header_view.restoreState(
                settings.value('{}/headerState'.format(name)))
Exemplo n.º 14
0
class MClickBrowserFilePushButton(MPushButton):
    """A Clickable push button to browser files"""
    sig_file_changed = Signal(str)
    sig_files_changed = Signal(list)
    slot_browser_file = _slot_browser_file

    def __init__(self, text='Browser', multiple=False, parent=None):
        super(MClickBrowserFilePushButton, self).__init__(text=text,
                                                          parent=parent)
        self.setProperty('multiple', multiple)
        self.clicked.connect(self.slot_browser_file)
        self.setToolTip(self.tr('Click to browser file'))

        self._path = None
        self._multiple = multiple
        self._filters = []

    def get_dayu_filters(self):
        """
        Get browser's format filters
        :return: list
        """
        return self._filters

    def set_dayu_filters(self, value):
        """
        Set browser file format filters
        :param value:
        :return: None
        """
        self._filters = value

    def get_dayu_path(self):
        """
        Get last browser file path
        :return: str
        """
        return self._path

    def set_dayu_path(self, value):
        """
        Set browser file start path
        :param value: str
        :return: None
        """
        self._path = value

    def get_dayu_multiple(self):
        """
        Get browser can select multiple file or not
        :return: bool
        """
        return self._multiple

    def set_dayu_multiple(self, value):
        """
        Set browser can select multiple file or not
        :param value: bool
        :return: None
        """
        self._multiple = value

    dayu_multiple = Property(bool, get_dayu_multiple, set_dayu_multiple)
    dayu_path = Property(six.string_types[0], get_dayu_path, set_dayu_path)
    dayu_filters = Property(list, get_dayu_filters, set_dayu_filters)
Exemplo n.º 15
0
class MDragFolderButton(MToolButton):
    """A Clickable and draggable tool button to browser folders"""
    sig_folder_changed = Signal(str)
    sig_folders_changed = Signal(list)
    slot_browser_folder = _slot_browser_folder

    def __init__(self, multiple=False, parent=None):
        super(MDragFolderButton, self).__init__(parent=parent)
        self.setAcceptDrops(True)
        self.setMouseTracking(True)
        self.text_under_icon()
        self.set_dayu_svg('folder_line.svg')
        self.set_dayu_size(60)
        self.setIconSize(QSize(60, 60))
        self.setText(self.tr('Click or drag folder here'))
        self.clicked.connect(self.slot_browser_folder)
        self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        self.setToolTip(self.tr('Click to browser folder or drag folder here'))

        self._path = None
        self._multiple = multiple

    def get_dayu_path(self):
        """
        Get last browser file path
        :return: str
        """
        return self._path

    def set_dayu_path(self, value):
        """
        Set browser file start path
        :param value: str
        :return: None
        """
        self._path = value

    def get_dayu_multiple(self):
        """
        Get browser can select multiple file or not
        :return: bool
        """
        return self._multiple

    def set_dayu_multiple(self, value):
        """
        Set browser can select multiple file or not
        :param value: bool
        :return: None
        """
        self._multiple = value

    dayu_multiple = Property(bool, get_dayu_multiple, set_dayu_multiple)
    dayu_path = Property(bool, get_dayu_path, set_dayu_path)

    def dragEnterEvent(self, event):
        """Override dragEnterEvent. Validate dragged folders"""
        if event.mimeData().hasFormat("text/uri-list"):
            folder_list = [
                url.toLocalFile() for url in event.mimeData().urls()
                if os.path.isdir(url.toLocalFile())
            ]
            count = len(folder_list)
            if count == 1 or (count > 1 and self.get_dayu_multiple()):
                event.acceptProposedAction()
                return

    def dropEvent(self, event):
        """Override dropEvent to accept the dropped folders"""
        folder_list = [
            url.toLocalFile() for url in event.mimeData().urls()
            if os.path.isdir(url.toLocalFile())
        ]
        if self.get_dayu_multiple():
            self.sig_folders_changed.emit(folder_list)
        else:
            self.sig_folder_changed.emit(folder_list[0])
        self.set_dayu_path(folder_list[0])
Exemplo n.º 16
0
class MNewTag(QWidget):
    """New Tag input component."""
    sig_add_tag = Signal(str)

    def __init__(self, text='New Tag', parent=None):
        super(MNewTag, self).__init__(parent)
        self.setAttribute(Qt.WA_StyledBackground)
        self._add_button = MToolButton().text_beside_icon().tiny().svg(
            'add_line.svg')
        self._add_button.setText(text)
        self._add_button.clicked.connect(self._slot_show_edit)
        self._line_edit = MLineEdit().tiny()
        self._line_edit.returnPressed.connect(self._slot_return_pressed)
        self._line_edit.setVisible(False)
        self._line_edit.installEventFilter(self)

        self._main_lay = QGridLayout()
        self._main_lay.setContentsMargins(3, 3, 3, 3)
        self._main_lay.addWidget(self._add_button, 0, 0)
        self._main_lay.addWidget(self._line_edit, 0, 0)
        self.setLayout(self._main_lay)
        style = QssTemplate('''
            MNewTag{
                border: 1px dashed @border_color;
            }
            MNewTag MToolButton:hover{
                border:none;
            }
            ''')
        self.setStyleSheet(style.substitute(vars(dayu_theme)))

    def set_completer(self, completer):
        """Set the input completer"""
        self._line_edit.setCompleter(completer)

    def _slot_show_edit(self):
        self._line_edit.setVisible(True)
        self._add_button.setVisible(False)
        self._line_edit.setFocus(Qt.MouseFocusReason)

    def _slot_return_pressed(self):
        self._line_edit.setVisible(False)
        self._add_button.setVisible(True)
        if self._line_edit.text():
            self.sig_add_tag.emit(self._line_edit.text())
        self._line_edit.clear()

    def focusOutEvent(self, *args, **kwargs):
        """Override focusOutEvent to change the edit mode to button mode."""
        self._line_edit.setVisible(False)
        self._add_button.setVisible(True)
        return super(MNewTag, self).focusOutEvent(*args, **kwargs)

    def eventFilter(self, widget, event):
        if widget is self._line_edit:
            if event.type() == QEvent.Type.KeyPress and event.key(
            ) == Qt.Key_Escape:
                self._line_edit.setVisible(False)
                self._add_button.setVisible(True)

        return super(MNewTag, self).eventFilter(widget, event)
Exemplo n.º 17
0
class MDragFileButton(MToolButton):
    """A Clickable and draggable tool button to upload files"""
    sig_file_changed = Signal(str)
    sig_files_changed = Signal(list)
    slot_browser_file = _slot_browser_file

    def __init__(self, text='', multiple=False, parent=None):
        super(MDragFileButton, self).__init__(parent=parent)
        self.setAcceptDrops(True)
        self.setMouseTracking(True)
        self.text_under_icon()
        self.setText(text)

        self.set_dayu_size(60)
        self.set_dayu_svg('cloud_line.svg')
        self.setIconSize(QSize(60, 60))

        self.clicked.connect(self.slot_browser_file)
        self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        self.setToolTip(self.tr('Click to browser file'))

        self._path = None
        self._multiple = multiple
        self._filters = []

    def get_dayu_filters(self):
        """
        Get browser's format filters
        :return: list
        """
        return self._filters

    def set_dayu_filters(self, value):
        """
        Set browser file format filters
        :param value:
        :return: None
        """
        self._filters = value

    def get_dayu_path(self):
        """
        Get last browser file path
        :return: str
        """
        return self._path

    def set_dayu_path(self, value):
        """
        Set browser file start path
        :param value: str
        :return: None
        """
        self._path = value

    def get_dayu_multiple(self):
        """
        Get browser can select multiple file or not
        :return: bool
        """
        return self._multiple

    def set_dayu_multiple(self, value):
        """
        Set browser can select multiple file or not
        :param value: bool
        :return: None
        """
        self._multiple = value

    dayu_multiple = Property(bool, get_dayu_multiple, set_dayu_multiple)
    dayu_path = Property(six.string_types[0], get_dayu_path, set_dayu_path)
    dayu_filters = Property(list, get_dayu_filters, set_dayu_filters)

    def dragEnterEvent(self, event):
        """Override dragEnterEvent. Validate dragged files"""
        if event.mimeData().hasFormat("text/uri-list"):
            file_list = self._get_valid_file_list(event.mimeData().urls())
            count = len(file_list)
            if count == 1 or (count > 1 and self.get_dayu_multiple()):
                event.acceptProposedAction()
                return

    def dropEvent(self, event):
        """Override dropEvent to accept the dropped files"""
        file_list = self._get_valid_file_list(event.mimeData().urls())
        if self.get_dayu_multiple():
            self.sig_files_changed.emit(file_list)
            self.set_dayu_path(file_list)
        else:
            self.sig_file_changed.emit(file_list[0])
            self.set_dayu_path(file_list[0])

    def _get_valid_file_list(self, url_list):
        import subprocess
        import sys
        file_list = []
        for url in url_list:
            file_name = url.toLocalFile()
            if sys.platform == 'darwin':
                sub_process = subprocess.Popen(
                    'osascript -e \'get posix path of posix file \"file://{}\" -- kthxbai\''
                    .format(file_name),
                    stdout=subprocess.PIPE,
                    shell=True)
                # print sub_process.communicate()[0].strip()
                file_name = sub_process.communicate()[0].strip()
                sub_process.wait()

            if os.path.isfile(file_name):
                if self.get_dayu_filters():
                    if os.path.splitext(
                            file_name)[-1] in self.get_dayu_filters():
                        file_list.append(file_name)
                else:
                    file_list.append(file_name)

        return file_list
Exemplo n.º 18
0
class MTag(QLabel):
    """
    Tag for categorizing or markup.
    """
    sig_closed = Signal()
    sig_clicked = Signal()

    def __init__(self, text='', parent=None):
        super(MTag, self).__init__(text=text, parent=parent)
        self._is_pressed = False
        self._close_button = MToolButton().tiny().svg('close_line.svg').icon_only()
        self._close_button.clicked.connect(self.sig_closed)
        self._close_button.clicked.connect(self.close)
        self._close_button.setVisible(False)

        self._main_lay = QHBoxLayout()
        self._main_lay.setContentsMargins(0, 0, 0, 0)
        self._main_lay.addStretch()
        self._main_lay.addWidget(self._close_button)

        self.setLayout(self._main_lay)

        self._clickable = False
        self._border = True
        self._border_style = QssTemplate('''
            MTag{
                font-size: 12px;
                padding: 3px;
                color: @text_color;
                border-radius: @border_radius;
                border: 1px solid @border_color;
                background-color: @background_color;
            }
            MTag:hover{
                color: @hover_color;
            }
            ''')
        self._no_border_style = QssTemplate('''
            MTag{
                font-size: 12px;
                padding: 4px;
                border-radius: @border_radius;
                color: @text_color;
                border: 0 solid @border_color;
                background-color: @background_color;
            }
            MTag:hover{
                background-color:@hover_color;
            }
        ''')
        self.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)

        self._color = None
        self.set_dayu_color(dayu_theme.secondary_text_color)

    def minimumSizeHint(self, *args, **kwargs):
        """Override minimumSizeHint for expand width when the close button is visible."""
        orig = super(MTag, self).minimumSizeHint(*args, **kwargs)
        orig.setWidth(orig.width() + (dayu_theme.tiny if self._close_button.isVisible() else 0))
        return orig

    def get_dayu_color(self):
        """Get tag's color"""
        return self._color

    def set_dayu_color(self, value):
        """Set Tag primary color."""
        self._color = value
        self._update_style()

    def _update_style(self):
        if self._border:
            self.setStyleSheet(
                self._border_style.substitute(background_color=utils.fade_color(self._color, '15%'),
                                              border_radius=dayu_theme.border_radius_base,
                                              border_color=utils.fade_color(self._color, '35%'),
                                              hover_color=utils.generate_color(self._color, 5),
                                              text_color=self._color))
        else:
            self.setStyleSheet(self._no_border_style.substitute(
                background_color=utils.generate_color(self._color, 6),
                border_radius=dayu_theme.border_radius_base,
                border_color=utils.generate_color(self._color, 6),
                hover_color=utils.generate_color(self._color, 5),
                text_color=dayu_theme.text_color_inverse))

    dayu_color = Property(str, get_dayu_color, set_dayu_color)

    def mousePressEvent(self, event):
        """Override mousePressEvent to flag _is_pressed."""
        if event.button() == Qt.LeftButton:
            self._is_pressed = True
        return super(MTag, self).mousePressEvent(event)

    def leaveEvent(self, event):
        """Override leaveEvent to reset _is_pressed flag."""
        self._is_pressed = False
        return super(MTag, self).leaveEvent(event)

    def mouseReleaseEvent(self, event):
        """Override mouseReleaseEvent to emit sig_clicked signal."""
        if event.button() == Qt.LeftButton and self._is_pressed:
            if self._clickable:
                self.sig_clicked.emit()
        self._is_pressed = False
        return super(MTag, self).mouseReleaseEvent(event)

    def closeable(self):
        """Set Tag can be closed and show the close icon button."""
        self._close_button.setVisible(True)
        return self

    def clickable(self):
        """Set Tag can be clicked and change the cursor to pointing-hand shape when enter."""
        self.setCursor(Qt.PointingHandCursor)
        self._clickable = True
        return self

    def no_border(self):
        """Set Tag style is border or fill."""
        self._border = False
        self._update_style()
        return self

    def coloring(self, color):
        """Same as set_dayu_color. Support chain."""
        self.set_dayu_color(color)
        return self
Exemplo n.º 19
0
class MToast(QWidget):
    """
    MToast
    A Phone style message.
    """
    InfoType = 'info'
    SuccessType = 'success'
    WarningType = 'warning'
    ErrorType = 'error'
    LoadingType = 'loading'

    default_config = {
        'duration': 2,
    }

    sig_closed = Signal()

    def __init__(self, text, duration=None, dayu_type=None, parent=None):
        super(MToast, self).__init__(parent)
        self.setWindowFlags(Qt.FramelessWindowHint | Qt.Dialog
                            | Qt.WA_TranslucentBackground
                            | Qt.WA_DeleteOnClose)
        self.setAttribute(Qt.WA_StyledBackground)

        _icon_lay = QHBoxLayout()
        _icon_lay.addStretch()

        if dayu_type == MToast.LoadingType:
            _icon_lay.addWidget(
                MLoading(size=dayu_theme.huge,
                         color=dayu_theme.text_color_inverse))
        else:
            _icon_label = MAvatar()
            _icon_label.set_dayu_size(60)
            _icon_label.set_dayu_image(
                MPixmap('{}_line.svg'.format(dayu_type or MToast.InfoType),
                        dayu_theme.text_color_inverse))
            _icon_lay.addWidget(_icon_label)
        _icon_lay.addStretch()

        _content_label = MLabel()
        _content_label.setText(text)
        _content_label.setAlignment(Qt.AlignCenter)

        _main_lay = QVBoxLayout()
        _main_lay.setContentsMargins(0, 0, 0, 0)
        _main_lay.addStretch()
        _main_lay.addLayout(_icon_lay)
        _main_lay.addSpacing(10)
        _main_lay.addWidget(_content_label)
        _main_lay.addStretch()
        self.setLayout(_main_lay)
        self.setFixedSize(QSize(120, 120))

        _close_timer = QTimer(self)
        _close_timer.setSingleShot(True)
        _close_timer.timeout.connect(self.close)
        _close_timer.timeout.connect(self.sig_closed)
        _close_timer.setInterval(
            (duration or self.default_config.get('duration')) * 1000)

        _ani_timer = QTimer(self)
        _ani_timer.timeout.connect(self._fade_out)
        _ani_timer.setInterval(
            (duration or self.default_config.get('duration')) * 1000 - 300)

        _close_timer.start()
        _ani_timer.start()

        self._opacity_ani = QPropertyAnimation()
        self._opacity_ani.setTargetObject(self)
        self._opacity_ani.setDuration(300)
        self._opacity_ani.setEasingCurve(QEasingCurve.OutCubic)
        self._opacity_ani.setPropertyName(b'windowOpacity')
        self._opacity_ani.setStartValue(0.0)
        self._opacity_ani.setEndValue(0.9)

        self._get_center_position(parent)
        self._fade_int()

    def _fade_out(self):
        self._opacity_ani.setDirection(QAbstractAnimation.Backward)
        self._opacity_ani.start()

    def _fade_int(self):
        self._opacity_ani.start()

    def _get_center_position(self, parent):
        parent_geo = parent.geometry()
        pos = parent_geo.topLeft() \
            if parent.parent() is None else parent.mapToGlobal(parent_geo.topLeft())
        offset = 0
        for child in parent.children():
            if isinstance(child, MToast) and child.isVisible():
                offset = max(offset, child.y())
        target_x = pos.x() + parent_geo.width() / 2 - self.width() / 2
        target_y = pos.y() + parent_geo.height() / 2 - self.height() / 2
        self.setProperty('pos', QPoint(target_x, target_y))

    @classmethod
    def info(cls, text, parent, duration=None):
        """Show a normal toast message"""
        inst = cls(text,
                   duration=duration,
                   dayu_type=MToast.InfoType,
                   parent=parent)
        inst.show()
        return inst

    @classmethod
    def success(cls, text, parent, duration=None):
        """Show a success toast message"""
        inst = cls(text,
                   duration=duration,
                   dayu_type=MToast.SuccessType,
                   parent=parent)
        inst.show()
        return inst

    @classmethod
    def warning(cls, text, parent, duration=None):
        """Show a warning toast message"""
        inst = cls(text,
                   duration=duration,
                   dayu_type=MToast.WarningType,
                   parent=parent)
        inst.show()
        return inst

    @classmethod
    def error(cls, text, parent, duration=None):
        """Show an error toast message"""
        inst = cls(text,
                   duration=duration,
                   dayu_type=MToast.ErrorType,
                   parent=parent)
        inst.show()
        return inst

    @classmethod
    def loading(cls, text, parent):
        """Show a toast message with loading animation"""
        inst = cls(text, dayu_type=MToast.LoadingType, parent=parent)
        inst.show()
        return inst

    @classmethod
    def config(cls, duration):
        """
        Config the global MToast duration setting.
        :param duration: int (unit is second)
        :return: None
        """
        if duration is not None:
            cls.default_config['duration'] = duration
Exemplo n.º 20
0
class MComboBox(QComboBox):
    Separator = '/'
    sig_value_changed = Signal(list)

    def __init__(self, parent=None):
        super(MComboBox, self).__init__(parent)
        self._root_menu = None
        self._display_formatter = utils.display_formatter
        self.setEditable(True)
        line_edit = self.lineEdit()
        line_edit.setReadOnly(True)
        line_edit.setTextMargins(4, 0, 4, 0)
        line_edit.setStyleSheet('background-color:transparent')
        # line_edit.setCursor(Qt.PointingHandCursor)
        line_edit.installEventFilter(self)
        self._has_custom_view = False
        self.set_value('')
        self.set_placeholder(self.tr('Please Select'))
        self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum)
        self._dayu_size = dayu_theme.default_size

    def get_dayu_size(self):
        """
        Get the push button height
        :return: integer
        """
        return self._dayu_size

    def set_dayu_size(self, value):
        """
        Set the avatar size.
        :param value: integer
        :return: None
        """
        self._dayu_size = value
        self.lineEdit().setProperty('dayu_size', value)
        self.style().polish(self)

    dayu_size = Property(int, get_dayu_size, set_dayu_size)

    def set_formatter(self, func):
        self._display_formatter = func

    def set_placeholder(self, text):
        """Display the text when no item selected."""
        self.lineEdit().setPlaceholderText(text)

    def set_value(self, value):
        self.setProperty('value', value)

    def _set_value(self, value):
        self.lineEdit().setProperty('text', self._display_formatter(value))
        if self._root_menu:
            self._root_menu.set_value(value)

    def set_menu(self, menu):
        self._root_menu = menu
        self._root_menu.sig_value_changed.connect(self.sig_value_changed)
        self._root_menu.sig_value_changed.connect(self.set_value)

    def setView(self, *args, **kwargs):
        """Override setView to flag _has_custom_view variable."""
        self._has_custom_view = True
        super(MComboBox, self).setView(*args, **kwargs)

    def showPopup(self):
        """Override default showPopup. When set custom menu, show the menu instead."""
        if self._has_custom_view or self._root_menu is None:
            super(MComboBox, self).showPopup()
        else:
            QComboBox.hidePopup(self)
            self._root_menu.popup(self.mapToGlobal(QPoint(0, self.height())))

    def setCurrentIndex(self, index):
        raise NotImplementedError

    def eventFilter(self, widget, event):
        if widget is self.lineEdit():
            if event.type() == QEvent.MouseButtonPress:
                self.showPopup()
        return super(MComboBox, self).eventFilter(widget, event)

    def huge(self):
        """Set MComboBox to huge size"""
        self.set_dayu_size(dayu_theme.huge)
        return self

    def large(self):
        """Set MComboBox to large size"""
        self.set_dayu_size(dayu_theme.large)
        return self

    def medium(self):
        """Set MComboBox to  medium"""
        self.set_dayu_size(dayu_theme.medium)
        return self

    def small(self):
        """Set MComboBox to small size"""
        self.set_dayu_size(dayu_theme.small)
        return self

    def tiny(self):
        """Set MComboBox to tiny size"""
        self.set_dayu_size(dayu_theme.tiny)
        return self
Exemplo n.º 21
0
class MCheckBoxGroup(MButtonGroupBase):
    sig_checked_changed = Signal(list)

    def __init__(self, orientation=Qt.Horizontal, parent=None):
        super(MCheckBoxGroup, self).__init__(orientation=orientation, parent=parent)
        self.set_spacing(15)
        self._button_group.setExclusive(False)

        self.setContextMenuPolicy(Qt.CustomContextMenu)
        self.customContextMenuRequested.connect(self._slot_context_menu)

        self._button_group.buttonClicked[int].connect(self._slot_map_signal)
        self._dayu_checked = []

    def create_button(self, data_dict):
        return MCheckBox()

    @Slot(QPoint)
    def _slot_context_menu(self, point):
        context_menu = MMenu(parent=self)
        action_select_all = context_menu.addAction('Select All')
        action_select_none = context_menu.addAction('Select None')
        action_select_invert = context_menu.addAction('Select Invert')
        action_select_all.triggered.connect(functools.partial(self._slot_set_select, True))
        action_select_none.triggered.connect(functools.partial(self._slot_set_select, False))
        action_select_invert.triggered.connect(functools.partial(self._slot_set_select, None))
        context_menu.exec_(QCursor.pos() + QPoint(10, 10))

    @Slot(bool)
    def _slot_set_select(self, state):
        for check_box in self._button_group.buttons():
            if state is None:
                old_state = check_box.isChecked()
                check_box.setChecked(not old_state)
            else:
                check_box.setChecked(state)
        self._slot_map_signal()

    @Slot(int)
    def _slot_map_signal(self, state=None):
        self.sig_checked_changed.emit(
            [check_box.text() for check_box in self._button_group.buttons() if
             check_box.isChecked()])

    def set_dayu_checked(self, value):
        if not isinstance(value, list):
            value = [value]
        if value == self.get_dayu_checked():
            return

        self._dayu_checked = value
        for check_box in self._button_group.buttons():
            flag = Qt.Checked if check_box.text() in value else Qt.Unchecked
            if flag != check_box.checkState():
                check_box.setCheckState(flag)
        self.sig_checked_changed.emit(value)

    def get_dayu_checked(self):
        return [check_box.text() for check_box in self._button_group.buttons() if
                              check_box.isChecked()]

    # TODO: pyside 的 Property 不直接支持 list,需要寻求解决办法
    dayu_checked = Property('QVariantList', get_dayu_checked, set_dayu_checked, notify=sig_checked_changed)
Exemplo n.º 22
0
class MMessage(QWidget):
    """
    Display global messages as feedback in response to user operations.
    """
    InfoType = 'info'
    SuccessType = 'success'
    WarningType = 'warning'
    ErrorType = 'error'
    LoadingType = 'loading'

    default_config = {'duration': 2, 'top': 24}

    sig_closed = Signal()

    def __init__(self,
                 text,
                 duration=None,
                 dayu_type=None,
                 closable=False,
                 parent=None):
        super(MMessage, self).__init__(parent)
        self.setObjectName('message')
        self.setWindowFlags(Qt.FramelessWindowHint | Qt.Dialog
                            | Qt.WA_TranslucentBackground
                            | Qt.WA_DeleteOnClose)
        self.setAttribute(Qt.WA_StyledBackground)

        if dayu_type == MMessage.LoadingType:
            _icon_label = MLoading.tiny()
        else:
            _icon_label = MAvatar.tiny()
            current_type = dayu_type or MMessage.InfoType
            _icon_label.set_dayu_image(
                MPixmap('{}_fill.svg'.format(current_type),
                        vars(dayu_theme).get(current_type + '_color')))

        self._content_label = MLabel(parent=self)
        # self._content_label.set_elide_mode(Qt.ElideMiddle)
        self._content_label.setText(text)

        self._close_button = MToolButton(
            parent=self).icon_only().svg('close_line.svg').tiny()
        self._close_button.clicked.connect(self.close)
        self._close_button.setVisible(closable or False)

        self._main_lay = QHBoxLayout()
        self._main_lay.addWidget(_icon_label)
        self._main_lay.addWidget(self._content_label)
        self._main_lay.addStretch()
        self._main_lay.addWidget(self._close_button)
        self.setLayout(self._main_lay)

        _close_timer = QTimer(self)
        _close_timer.setSingleShot(True)
        _close_timer.timeout.connect(self.close)
        _close_timer.timeout.connect(self.sig_closed)
        _close_timer.setInterval(
            (duration or self.default_config.get('duration')) * 1000)

        _ani_timer = QTimer(self)
        _ani_timer.timeout.connect(self._fade_out)
        _ani_timer.setInterval(
            (duration or self.default_config.get('duration')) * 1000 - 300)

        _close_timer.start()
        _ani_timer.start()

        self._pos_ani = QPropertyAnimation(self)
        self._pos_ani.setTargetObject(self)
        self._pos_ani.setEasingCurve(QEasingCurve.OutCubic)
        self._pos_ani.setDuration(300)
        self._pos_ani.setPropertyName(b'pos')

        self._opacity_ani = QPropertyAnimation()
        self._opacity_ani.setTargetObject(self)
        self._opacity_ani.setDuration(300)
        self._opacity_ani.setEasingCurve(QEasingCurve.OutCubic)
        self._opacity_ani.setPropertyName(b'windowOpacity')
        self._opacity_ani.setStartValue(0.0)
        self._opacity_ani.setEndValue(1.0)

        self._set_proper_position(parent)
        self._fade_int()

    def _fade_out(self):
        self._pos_ani.setDirection(QAbstractAnimation.Backward)
        self._pos_ani.start()
        self._opacity_ani.setDirection(QAbstractAnimation.Backward)
        self._opacity_ani.start()

    def _fade_int(self):
        self._pos_ani.start()
        self._opacity_ani.start()

    def _set_proper_position(self, parent):
        parent_geo = parent.geometry()
        pos = parent_geo.topLeft(
        ) if parent.parent() is None else parent.mapToGlobal(
            parent_geo.topLeft())
        offset = 0
        for child in parent.children():
            if isinstance(child, MMessage) and child.isVisible():
                offset = max(offset, child.y())
        base = pos.y() + MMessage.default_config.get('top')
        target_x = pos.x() + parent_geo.width() / 2 - 100
        target_y = (offset + 50) if offset else base
        self._pos_ani.setStartValue(QPoint(target_x, target_y - 40))
        self._pos_ani.setEndValue(QPoint(target_x, target_y))

    @classmethod
    def info(cls, text, parent, duration=None, closable=None):
        """Show a normal message"""
        inst = cls(text,
                   dayu_type=MMessage.InfoType,
                   duration=duration,
                   closable=closable,
                   parent=parent)
        inst.show()
        return inst

    @classmethod
    def success(cls, text, parent, duration=None, closable=None):
        """Show a success message"""
        inst = cls(text,
                   dayu_type=MMessage.SuccessType,
                   duration=duration,
                   closable=closable,
                   parent=parent)

        inst.show()
        return inst

    @classmethod
    def warning(cls, text, parent, duration=None, closable=None):
        """Show a warning message"""
        inst = cls(text,
                   dayu_type=MMessage.WarningType,
                   duration=duration,
                   closable=closable,
                   parent=parent)
        inst.show()
        return inst

    @classmethod
    def error(cls, text, parent, duration=None, closable=None):
        """Show an error message"""
        inst = cls(text,
                   dayu_type=MMessage.ErrorType,
                   duration=duration,
                   closable=closable,
                   parent=parent)
        inst.show()
        return inst

    @classmethod
    def loading(cls, text, parent):
        """Show a message with loading animation"""
        inst = cls(text, dayu_type=MMessage.LoadingType, parent=parent)
        inst.show()
        return inst

    @classmethod
    def config(cls, duration=None, top=None):
        """
        Config the global MMessage duration and top setting.
        :param duration: int (unit is second)
        :param top: int (unit is px)
        :return: None
        """
        if duration is not None:
            cls.default_config['duration'] = duration
        if top is not None:
            cls.default_config['top'] = top
Exemplo n.º 23
0
class MMenu(QMenu):
    sig_value_changed = Signal(list)

    def __init__(self, exclusive=True, cascader=False, title='', parent=None):
        super(MMenu, self).__init__(title=title, parent=parent)
        self.setProperty('cascader', cascader)
        self.setCursor(Qt.PointingHandCursor)
        self._action_group = QActionGroup(self)
        self._action_group.setExclusive(exclusive)
        self._action_group.triggered.connect(self.slot_on_action_triggered)
        self._load_data_func = None
        self.set_value('')
        self.set_data([])
        self.set_separator('/')

    def set_separator(self, chr):
        self.setProperty('separator', chr)

    def set_load_callback(self, func):
        assert callable(func)
        self._load_data_func = func
        self.aboutToShow.connect(self.slot_fetch_data)

    def slot_fetch_data(self):
        data_list = self._load_data_func()
        self.set_data(data_list)

    def set_value(self, data):
        assert isinstance(data, (list, basestring, int, float))
        # if isinstance(data, int):
        #     action = self._action_group.actions()[data]
        #     data = action.property('value')
        if self.property('cascader') and isinstance(data, basestring):
            data = data.split(self.property('separator'))
        self.setProperty('value', data)

    def _set_value(self, value):
        data_list = value if isinstance(value, list) else [value]
        flag = False
        for act in self._action_group.actions():
            checked = act.property('value') in data_list
            if act.isChecked() != checked:  # 更新来自代码
                act.setChecked(checked)
                flag = True
        if flag:
            self.sig_value_changed.emit(value)

    def _add_menu(self, parent_menu, data_dict):
        if 'children' in data_dict:
            menu = MMenu(title=data_dict.get('label'), parent=self)
            menu.setProperty('value', data_dict.get('value'))
            parent_menu.addMenu(menu)
            if not (parent_menu is self):
                # 用来将来获取父层级数据
                menu.setProperty('parent_menu', parent_menu)
            for i in data_dict.get('children'):
                self._add_menu(menu, i)
        else:
            action = self._action_group.addAction(
                utils.display_formatter(data_dict.get('label')))
            if data_dict.get('icon'):
                action.setIcon(data_dict.get('icon'))
            action.setProperty('value', data_dict.get('value'))
            action.setCheckable(True)
            # 用来将来获取父层级数据
            action.setProperty('parent_menu', parent_menu)
            parent_menu.addAction(action)

    def set_data(self, option_list):
        assert isinstance(option_list, list)
        if option_list:
            if all(isinstance(i, basestring) for i in option_list):
                option_list = utils.from_list_to_nested_dict(
                    option_list, sep=self.property('separator'))
            if all(isinstance(i, (int, float)) for i in option_list):
                option_list = [{
                    'value': i,
                    'label': str(i)
                } for i in option_list]
        # 全部转换成 dict 类型的 list
        self.setProperty('data', option_list)

    def _set_data(self, option_list):
        self.clear()
        for act in self._action_group.actions():
            self._action_group.removeAction(act)

        for data_dict in option_list:
            self._add_menu(self, data_dict)

    def _get_parent(self, result, obj):
        if obj.property('parent_menu'):
            parent_menu = obj.property('parent_menu')
            result.insert(0, parent_menu.title())
            self._get_parent(result, parent_menu)

    @Slot(QAction)
    def slot_on_action_triggered(self, action):
        current_data = action.property('value')
        if self.property('cascader'):
            selected_data = [current_data]
            self._get_parent(selected_data, action)
        else:
            if self._action_group.isExclusive():
                selected_data = current_data
            else:
                selected_data = [
                    act.property('value')
                    for act in self._action_group.actions() if act.isChecked()
                ]
        self.set_value(selected_data)
        self.sig_value_changed.emit(selected_data)

    def set_loader(self, func):
        self._load_data_func = func
Exemplo n.º 24
0
class MDrawer(QWidget):
    """
    A panel which slides in from the edge of the screen.
    """
    LeftPos = 'left'
    RightPos = 'right'
    TopPos = 'top'
    BottomPos = 'bottom'

    sig_closed = Signal()

    def __init__(self, title, position='right', closable=True, parent=None):
        super(MDrawer, self).__init__(parent)
        self.setObjectName('message')
        # self.setWindowFlags(Qt.Popup )
        # self.setWindowFlags(
        #     Qt.FramelessWindowHint | Qt.Popup | Qt.WA_TranslucentBackground)
        self.setAttribute(Qt.WA_StyledBackground)

        self._title_label = MLabel(parent=self).h4()
        # self._title_label.set_elide_mode(Qt.ElideRight)
        self._title_label.setText(title)

        self._close_button = MToolButton(
            parent=self).icon_only().svg('close_line.svg').small()
        self._close_button.clicked.connect(self.close)
        self._close_button.setVisible(closable or False)

        _title_lay = QHBoxLayout()
        _title_lay.addWidget(self._title_label)
        _title_lay.addStretch()
        _title_lay.addWidget(self._close_button)
        self._button_lay = QHBoxLayout()
        self._button_lay.addStretch()

        self._scroll_area = QScrollArea()
        self._main_lay = QVBoxLayout()
        self._main_lay.addLayout(_title_lay)
        self._main_lay.addWidget(MDivider())
        self._main_lay.addWidget(self._scroll_area)
        self._main_lay.addWidget(MDivider())
        self._main_lay.addLayout(self._button_lay)
        self.setLayout(self._main_lay)

        self._position = position

        self._close_timer = QTimer(self)
        self._close_timer.setSingleShot(True)
        self._close_timer.timeout.connect(self.close)
        self._close_timer.timeout.connect(self.sig_closed)
        self._close_timer.setInterval(300)
        self._is_first_close = True

        self._pos_ani = QPropertyAnimation(self)
        self._pos_ani.setTargetObject(self)
        self._pos_ani.setEasingCurve(QEasingCurve.OutCubic)
        self._pos_ani.setDuration(300)
        self._pos_ani.setPropertyName('pos')

        self._opacity_ani = QPropertyAnimation()
        self._opacity_ani.setTargetObject(self)
        self._opacity_ani.setDuration(300)
        self._opacity_ani.setEasingCurve(QEasingCurve.OutCubic)
        self._opacity_ani.setPropertyName('windowOpacity')
        self._opacity_ani.setStartValue(0.0)
        self._opacity_ani.setEndValue(1.0)
        # self._shadow_effect = QGraphicsDropShadowEffect(self)
        # color = dayu_theme.red
        # self._shadow_effect.setColor(color)
        # self._shadow_effect.setOffset(0, 0)
        # self._shadow_effect.setBlurRadius(5)
        # self._shadow_effect.setEnabled(False)
        # self.setGraphicsEffect(self._shadow_effect)

        self.app = QApplication.instance()
        self.app.installEventFilter(self)
        self.protect_time = time.time()

    def retrieveChildren(self, parent, receiver):
        if parent is receiver:
            return True
        if not hasattr(parent, "children"):
            return

        for child in parent.children():

            ret = self.retrieveChildren(child, receiver)
            if ret:
                return ret

    def eventFilter(self, receiver, event):
        # Note QEvent.Type.MouseButtonPress 为 2
        if event.type() == 2:
            if self.retrieveChildren(self, receiver):
                self.protect_time = time.time()
            # NOTE 如果点击多次触发,通过时间进行保护
            if (time.time() - self.protect_time) > .1:
                self.close()
        elif event.type() == QEvent.Type.Resize and receiver is self.window():
            self.close()
        return False

    def set_widget(self, widget):
        self._scroll_area.setWidget(widget)

    def add_button(self, button):
        self._button_lay.addWidget(button)

    def _fade_out(self):
        self._pos_ani.setDirection(QAbstractAnimation.Backward)
        self._pos_ani.start()
        self._opacity_ani.setDirection(QAbstractAnimation.Backward)
        self._opacity_ani.start()

    def _fade_int(self):
        self._pos_ani.start()
        self._opacity_ani.start()

    def _set_proper_position(self):
        parent = self.parent()
        parent_geo = parent.geometry()
        if self._position == MDrawer.LeftPos:
            pos = parent_geo.topLeft(
            ) if parent.parent() is None else parent.mapToGlobal(
                parent_geo.topLeft())
            pos -= self.window().geometry().topLeft()
            target_x = pos.x()
            target_y = pos.y()
            self.setFixedHeight(parent_geo.height())
            self._pos_ani.setStartValue(
                QPoint(target_x - self.width(), target_y))
            self._pos_ani.setEndValue(QPoint(target_x, target_y))
        if self._position == MDrawer.RightPos:
            pos = parent_geo.topRight(
            ) if parent.parent() is None else parent.mapToGlobal(
                parent_geo.topRight())
            pos -= self.window().geometry().topLeft()
            self.setFixedHeight(parent_geo.height())
            target_x = pos.x() - self.width()
            target_y = pos.y()
            self._pos_ani.setStartValue(
                QPoint(target_x + self.width(), target_y))
            self._pos_ani.setEndValue(QPoint(target_x, target_y))
        if self._position == MDrawer.TopPos:
            pos = parent_geo.topLeft(
            ) if parent.parent() is None else parent.mapToGlobal(
                parent_geo.topLeft())
            pos -= self.window().geometry().topLeft()
            self.setFixedWidth(parent_geo.width())
            target_x = pos.x()
            target_y = pos.y()
            self._pos_ani.setStartValue(
                QPoint(target_x, target_y - self.height()))
            self._pos_ani.setEndValue(QPoint(target_x, target_y))
        if self._position == MDrawer.BottomPos:
            pos = parent_geo.bottomLeft(
            ) if parent.parent() is None else parent.mapToGlobal(
                parent_geo.bottomLeft())
            pos -= self.window().geometry().topLeft()
            self.setFixedWidth(parent_geo.width())
            target_x = pos.x()
            target_y = pos.y() - self.height()
            self._pos_ani.setStartValue(
                QPoint(target_x, target_y + self.height()))
            self._pos_ani.setEndValue(QPoint(target_x, target_y))

    def set_dayu_position(self, value):
        """
        Set the placement of the MDrawer.
        top/right/bottom/left, default is right
        :param value: str
        :return: None
        """
        self._position = value
        if value in [MDrawer.BottomPos, MDrawer.TopPos]:
            self.setFixedHeight(200)
        else:
            self.setFixedWidth(200)

    def get_dayu_position(self):
        """
        Get the placement of the MDrawer
        :return: str
        """
        return self._position

    dayu_position = Property(str, get_dayu_position, set_dayu_position)

    def left(self):
        """Set drawer's placement to left"""
        self.set_dayu_position(MDrawer.LeftPos)
        return self

    def right(self):
        """Set drawer's placement to right"""
        self.set_dayu_position(MDrawer.RightPos)
        return self

    def top(self):
        """Set drawer's placement to top"""
        self.set_dayu_position(MDrawer.TopPos)
        return self

    def bottom(self):
        """Set drawer's placement to bottom"""
        self.set_dayu_position(MDrawer.BottomPos)
        return self

    def show(self):
        self._set_proper_position()
        self._fade_int()
        return super(MDrawer, self).show()

    def closeEvent(self, event):
        self.app.removeEventFilter(self)
        if self._is_first_close:
            self._is_first_close = False
            self._close_timer.start()
            self._fade_out()
            event.ignore()
        else:
            event.accept()
Exemplo n.º 25
0
class MLineEdit(QLineEdit):
    """MLineEdit"""
    sig_delay_text_changed = Signal(basestring)

    def __init__(self, text='', parent=None):
        super(MLineEdit, self).__init__(text, parent)
        self._main_layout = QHBoxLayout()
        self._main_layout.setContentsMargins(0, 0, 0, 0)
        self._main_layout.addStretch()

        self._prefix_widget = None
        self._suffix_widget = None

        self.setLayout(self._main_layout)
        self.setProperty('history', self.property('text'))
        self.setTextMargins(2, 0, 2, 0)

        self._delay_timer = QTimer()
        self._delay_timer.setInterval(500)
        self._delay_timer.setSingleShot(True)
        self._delay_timer.timeout.connect(self._slot_delay_text_changed)

        self._dayu_size = dayu_theme.default_size

    def get_dayu_size(self):
        """
        Get the push button height
        :return: integer
        """
        return self._dayu_size

    def set_dayu_size(self, value):
        """
        Set the avatar size.
        :param value: integer
        :return: None
        """
        self._dayu_size = value
        if hasattr(self._prefix_widget, 'set_dayu_size'):
            self._prefix_widget.set_dayu_size(self._dayu_size)
        if hasattr(self._suffix_widget, 'set_dayu_size'):
            self._suffix_widget.set_dayu_size(self._dayu_size)
        self.style().polish(self)

    dayu_size = Property(int, get_dayu_size, set_dayu_size)

    def set_delay_duration(self, millisecond):
        """Set delay timer's timeout duration."""
        self._delay_timer.setInterval(millisecond)

    @Slot()
    def _slot_delay_text_changed(self):
        self.sig_delay_text_changed.emit(self.text())

    def get_prefix_widget(self):
        """Get the prefix widget for user to edit"""
        return self._prefix_widget

    def set_prefix_widget(self, widget):
        """Set the line edit left start widget"""
        if self._prefix_widget:
            index = self._main_layout.indexOf(self._prefix_widget)
            self._main_layout.takeAt(index)
            self._prefix_widget.setVisible(False)
        # if isinstance(widget, MPushButton):
        widget.setProperty('combine', 'horizontal')
        widget.setProperty('position', 'left')
        if hasattr(widget, 'set_dayu_size'):
            widget.set_dayu_size(self._dayu_size)

        margin = self.textMargins()
        margin.setLeft(margin.left() + widget.width())
        self.setTextMargins(margin)

        self._main_layout.insertWidget(0, widget)
        self._prefix_widget = widget
        return widget

    def get_suffix_widget(self):
        """Get the suffix widget for user to edit"""
        return self._suffix_widget

    def set_suffix_widget(self, widget):
        """Set the line edit right end widget"""
        if self._suffix_widget:
            index = self._main_layout.indexOf(self._suffix_widget)
            self._main_layout.takeAt(index)
            self._suffix_widget.setVisible(False)
        # if isinstance(widget, MPushButton):
        widget.setProperty('combine', 'horizontal')
        widget.setProperty('position', 'right')
        if hasattr(widget, 'set_dayu_size'):
            widget.set_dayu_size(self._dayu_size)

        margin = self.textMargins()
        margin.setRight(margin.right() + widget.width())
        self.setTextMargins(margin)
        self._main_layout.addWidget(widget)
        self._suffix_widget = widget
        return widget

    def setText(self, text):
        """Override setText save text to history"""
        self.setProperty('history', u'{}\n{}'.format(self.property('history'),
                                                     text))
        return super(MLineEdit, self).setText(text)

    def clear(self):
        """Override clear to clear history"""
        self.setProperty('history', '')
        return super(MLineEdit, self).clear()

    def keyPressEvent(self, event):
        """Override keyPressEvent to start delay timer"""
        if event.key() not in [Qt.Key_Enter, Qt.Key_Tab]:
            if self._delay_timer.isActive():
                self._delay_timer.stop()
            self._delay_timer.start()
        super(MLineEdit, self).keyPressEvent(event)

    def search(self):
        """Add a search icon button for MLineEdit."""
        suffix_button = MToolButton().icon_only().svg('close_line.svg')
        suffix_button.clicked.connect(self.clear)
        self.set_suffix_widget(suffix_button)
        self.setPlaceholderText(self.tr('Enter key word to search...'))
        return self

    def error(self):
        """A a toolset to MLineEdit to store error info with red style"""
        @Slot()
        def _slot_show_detail(self):
            dialog = QTextEdit(self)
            dialog.setReadOnly(True)
            geo = QApplication.desktop().screenGeometry()
            dialog.setGeometry(geo.width() / 2,
                               geo.height() / 2,
                               geo.width() / 4,
                               geo.height() / 4)
            dialog.setWindowTitle(self.tr('Error Detail Information'))
            dialog.setText(self.property('history'))
            dialog.setWindowFlags(Qt.Dialog)
            dialog.show()

        self.setProperty('dayu_type', 'error')
        self.setReadOnly(True)
        _suffix_button = MToolButton().icon_only().svg('detail_line.svg')
        _suffix_button.clicked.connect(
            functools.partial(_slot_show_detail, self))
        self.set_suffix_widget(_suffix_button)
        self.setPlaceholderText(self.tr('Error information will be here...'))
        return self

    def search_engine(self, text='Search'):
        """Add a MPushButton to suffix for MLineEdit"""
        _suffix_button = MPushButton(text=text).primary()
        _suffix_button.clicked.connect(self.returnPressed)
        _suffix_button.setFixedWidth(100)
        self.set_suffix_widget(_suffix_button)
        self.setPlaceholderText(self.tr('Enter key word to search...'))
        return self

    def file(self, filters=None):
        """Add a MClickBrowserFileToolButton for MLineEdit to select file"""
        _suffix_button = MClickBrowserFileToolButton()
        _suffix_button.sig_file_changed.connect(self.setText)
        _suffix_button.set_dayu_filters(filters or [])
        self.textChanged.connect(_suffix_button.set_dayu_path)
        self.set_suffix_widget(_suffix_button)
        self.setPlaceholderText(self.tr('Click button to browser files'))
        return self

    def folder(self):
        """Add a MClickBrowserFolderToolButton for MLineEdit to select folder"""
        _suffix_button = MClickBrowserFolderToolButton()
        _suffix_button.sig_folder_changed.connect(self.setText)
        self.textChanged.connect(_suffix_button.set_dayu_path)
        self.set_suffix_widget(_suffix_button)
        self.setPlaceholderText(self.tr('Click button to browser folder'))
        return self

    def huge(self):
        """Set MLineEdit to huge size"""
        self.set_dayu_size(dayu_theme.huge)
        return self

    def large(self):
        """Set MLineEdit to large size"""
        self.set_dayu_size(dayu_theme.large)
        return self

    def medium(self):
        """Set MLineEdit to  medium"""
        self.set_dayu_size(dayu_theme.medium)
        return self

    def small(self):
        """Set MLineEdit to small size"""
        self.set_dayu_size(dayu_theme.small)
        return self

    def tiny(self):
        """Set MLineEdit to tiny size"""
        self.set_dayu_size(dayu_theme.tiny)
        return self

    def password(self):
        """Set MLineEdit to password echo mode"""
        self.setEchoMode(QLineEdit.Password)
        return self
Exemplo n.º 26
0
class MItemViewFullSet(QWidget):
    sig_double_clicked = Signal(QModelIndex)
    sig_left_clicked = Signal(QModelIndex)
    sig_current_changed = Signal(QModelIndex, QModelIndex)
    sig_current_row_changed = Signal(QModelIndex, QModelIndex)
    sig_current_column_changed = Signal(QModelIndex, QModelIndex)
    sig_selection_changed = Signal(QItemSelection, QItemSelection)
    sig_context_menu = 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 = 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': u'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': u'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 = QWidget()
        self.top_lay = 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 = 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)

    @Slot(QModelIndex)
    def slot_left_clicked(self, start_index):
        button = QApplication.mouseButtons()
        if button == 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)

    @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))

    @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