123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618 |
- from PyQt6.QtCore import *
- from PyQt6.QtWidgets import *
- from PyQt6.QtGui import *
- from qfluentwidgets import *
- from .common.config import cfg, FEEDBACK_URL, HELP_URL, EXAMPLE_URL
- from .common.icon import Icon
- from .common.style_sheet import StyleSheet
- from .common.signal_bus import signalBus
- from demeter.core import *
- from qfluentwidgets.components.dialog_box.mask_dialog_base import MaskDialogBase
- import gc
- class List(ScrollArea):
- def __init__(self, title: str, subtitle: str, model : str, parent=None, button=None, addButton=True):
- super().__init__(parent = parent)
- self.box = 350
- self.base = QWidget(self)
- self.model = model;
- self.title = title;
- self.toolBar = ToolBar(title, subtitle, self, button, addButton)
- self.vBoxLayout = QVBoxLayout(self.base)
- self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
- self.setViewportMargins(0, self.toolBar.height(), 0, 0)
- self.setWidget(self.base)
- self.setWidgetResizable(True)
- self.vBoxLayout.setSpacing(30)
- self.vBoxLayout.setAlignment(Qt.AlignmentFlag.AlignTop)
- self.vBoxLayout.setContentsMargins(36, 20, 36, 36)
- self.base.setObjectName('view')
- StyleSheet.LIST.apply(self)
- self.pageWidgets = []
- def addCard(self, title, widget, sourcePath: str, stretch = 0):
- self.widget = widget
- self.card = Card(title, widget, sourcePath, stretch, self.base)
- self.vBoxLayout.addWidget(self.card, 0, Qt.AlignmentFlag.AlignTop)
- return self.card
- def scrollToCard(self, index: int):
- """ scroll to example card """
- w = self.vBoxLayout.itemAt(index).widget()
- self.verticalScrollBar().setValue(w.y())
- def resizeEvent(self, e):
- super().resizeEvent(e)
- self.toolBar.resize(self.width(), self.toolBar.height())
- def initPage(self):
- show_page = 10
- total_page = Demeter.config['page']['total']
- current_page = Demeter.config['page']['current']
-
- start_page = max(1, current_page - show_page // 2)
- end_page = min(total_page, current_page + show_page // 2)
- if end_page - start_page < show_page:
- if start_page == 1:
- end_page = min(total_page, show_page)
- else:
- start_page = max(1, end_page - show_page + 1)
- prev_page = max(1, current_page - 1)
- next_page = min(total_page, current_page + 1)
- layout = QHBoxLayout()
- widgets = []
- widgets.append(self.createPageLabel('<', prev_page))
- if start_page > 1:
- widgets.append(self.createPageLabel(1, 1))
- widgets.append(self.createPageLabel('...', 2))
- for v in range(start_page, end_page+1, 1):
- widgets.append(self.createPageLabel(v, v, current_page))
- if end_page < total_page:
- widgets.append(self.createPageLabel('...', v+1))
- widgets.append(self.createPageLabel(total_page, total_page))
- widgets.append(self.createPageLabel('>', next_page))
- page_info = QLabel(f"共 {Demeter.config['page']['totalNum']} 条")
- page_info.setObjectName('pageCurrent')
- widgets.append(page_info)
- for key, value in enumerate(self.pageWidgets):
- self.vBoxLayout.removeWidget(value)
- for key, value in enumerate(widgets):
- layout.addWidget(value)
- layout.setAlignment(Qt.AlignmentFlag.AlignVCenter | Qt.AlignmentFlag.AlignCenter)
-
- layout.setSpacing(10)
- self.vBoxLayout.addLayout(layout)
- self.pageWidgets = widgets
- def createPageLabel(self, label, page, current_page = 0):
- label = QPushButton(str(label))
- if page:
- label.mousePressEvent = lambda event: self.initData(page)
- if current_page and current_page == page:
- label.setObjectName('pageCurrent')
- else:
- label.setObjectName('page')
- return label
- def initButton(self, config):
- if config:
- button = QWidget()
- layout = QHBoxLayout()
- for key, value in config.items():
- click = PushButton(key)
- click.setObjectName('operButton')
- layout.addWidget(click)
- click.clicked.connect(config[key])
- button.setLayout(layout)
- return button
- return False
- def getInfo(self, id):
- return Demeter.service('common').one(self.model, id=id)
- def update(self):
- id = self.widget.getItem()
- self.showUpdate('编辑', id)
- def delete(self):
- if hasattr(self, 'deleteCheck'):
- state = self.deleteCheck()
- if state:
- InfoBar.error(
- title='错误提示',
- content=state,
- orient=Qt.Orientation.Horizontal,
- isClosable=True,
- position=InfoBarPosition.TOP,
- duration=2000,
- parent=self.window()
- )
- return
- id = self.widget.getItem()
- self.showOper('删除', id, {'state' : 2})
- def showUpdate(self, title='', id=0):
- self.id = id
- box = MessageBoxBase(self.window())
- title = SubtitleLabel(title + self.title)
- box.viewLayout.addWidget(title)
- self.showUpdateItem(id)
- for key, value in self.updateItem.items():
- box.viewLayout.addWidget(value)
-
- box.widget.setMinimumWidth(self.box)
- box.yesButton.setText("保存")
- box.cancelButton.setText("取消")
- if box.exec():
- self.save(box)
- def showOper(self, title = '', id = 0, data = {}):
- self.id = id
- box = MessageBoxBase(self.window())
- label = BodyLabel('确定要' + title + '吗?')
- title = SubtitleLabel(title + self.title)
- box.viewLayout.addWidget(title)
- box.viewLayout.addWidget(label)
-
- box.widget.setMinimumWidth(350)
- box.yesButton.setText("确定")
- box.cancelButton.setText("取消")
- if box.exec():
- self.up(data)
- def save(self, box):
- up = True
- data = {}
- for key, value in self.updateItem.items():
- if 'text' not in key:
- if 'check' in key:
- key = key.replace('_check', '')
- data[key] = value.isChecked()
- else:
- data[key] = value.text()
- if hasattr(self, 'updateFunc') and key in self.updateFunc:
- data[key] = self.updateFunc[key](data[key])
- if hasattr(self, 'updateDefault'):
- for key, value in self.updateDefault.items():
- data[key] = value
- if 'name' in data:
- if not data['name']:
- InfoBar.error(
- title='错误提示',
- content="名称不能为空",
- orient=Qt.Orientation.Horizontal,
- isClosable=True,
- position=InfoBarPosition.TOP,
- duration=2000,
- parent=self.window()
- )
- up = False
- if box.exec():
- return self.save(box)
- else:
- model = Demeter.model(self.model)
- model.name = data['name']
- if hasattr(self, 'updateDefault'):
- for key, value in self.updateDefault.items():
- setattr(model, key, value)
- model.id.nq(self.id)
- info = model.select(type='fetchone')
- if info:
- up = False
- InfoBar.error(
- title='错误提示',
- content="名称已存在",
- orient=Qt.Orientation.Horizontal,
- isClosable=True,
- position=InfoBarPosition.TOP,
- duration=2000,
- parent=self.window()
- )
- if box.exec():
- return self.save(box)
- if up:
- self.up(data)
- def up(self, data = {}):
- Demeter.service('common').update(self.model, id=self.id, data=data)
- InfoBar.success(
- title='系统提示',
- content="操作已成功",
- orient=Qt.Orientation.Horizontal,
- isClosable=True,
- position=InfoBarPosition.TOP,
- duration=2000,
- parent=self.window()
- )
- if hasattr(self, 'upFunc'):
- for key, value in data.items():
- if key in self.upFunc:
- self.upFunc[key](data[key])
- self.initData()
- def showTip(self, title, cur, data, click):
- position = TeachingTipTailPosition.LEFT
- view = TeachingTipView(
- icon=None,
- title=title,
- content="",
- isClosable=True,
- tailPosition=position,
- )
- self.listWidget = ListWidget()
- index = 0
- stands = []
- for key, value in enumerate(data):
- if (cur['id'] == value['id']):
- index = key
- stands.append(value['name'])
-
- for stand in stands:
- item = QListWidgetItem(stand)
-
- self.listWidget.addItem(item)
- self.listWidget.setCurrentRow(index)
- self.listWidget.itemClicked.connect(click)
- view.addWidget(self.listWidget)
-
- '''
- button = PushButton('确认选择')
- button.setFixedWidth(120)
- view.addWidget(button, align=Qt.AlignCenter)
- '''
-
- self.tip = PopupTeachingTip.make(
- target=self.toolBar.button[0],
- view=view,
- duration=-1,
- tailPosition=position,
- parent=self
- )
- view.closed.connect(self.tip.close)
- class TableBase(TableWidget):
- def __init__(self, parent=None):
- super().__init__(parent)
- self.parent = parent
-
-
- self.verticalHeader().hide()
- self.setBorderRadius(8)
- self.setBorderVisible(True)
- self.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Stretch)
- self.verticalHeader().setSectionResizeMode(QHeaderView.ResizeMode.ResizeToContents)
- width = parent.window().width()
- height = parent.window().height()
- self.setFixedSize(width-170, height-380)
-
- self.resizeColumnsToContents()
- self.resizeRowsToContents()
- self.init()
- def initData(self, page=1, param={}):
- column = len(self.head)
- self.setColumnCount(column)
- body = Demeter.model(self.parent.model).getList(page, param)
- self.setRowCount(len(body))
- for i, info in enumerate(body):
- id = info[0]
- for j in range(column):
- info[j] = QTableWidgetItem(info[j])
- info[j].setTextAlignment(Qt.AlignmentFlag.AlignCenter)
- self.setItem(i, j, info[j])
- self.setCellWidget(i, column-1, self.parent.initButton(self.button))
- self.setHorizontalHeaderLabels(self.head)
- if Demeter.config['page']['total'] > 0:
- self.parent.initPage()
- def getItem(self, index = 0):
- item = ''
- pos = self.getPos()
- if pos:
- row = pos.row()
- column = pos.column()
- item = self.item(row, index).text()
- return item
- def getPos(self):
- button = self.sender()
- if button:
- pos = self.indexAt(button.parent().pos())
- return pos
- return False
-
- class SeparatorWidget(QWidget):
- """ Seperator widget """
- def __init__(self, parent=None):
- super().__init__(parent=parent)
- self.setFixedSize(6, 16)
- def paintEvent(self, e):
- painter = QPainter(self)
- pen = QPen(1)
- pen.setCosmetic(True)
- c = QColor(255, 255, 255, 21) if isDarkTheme() else QColor(0, 0, 0, 15)
- pen.setColor(c)
- painter.setPen(pen)
- x = self.width() // 2
- painter.drawLine(x, 0, x, self.height())
- class ToolBar(QWidget):
- """ Tool bar """
- def __init__(self, title, subtitle, parent=None, button=None, addButton=True):
- super().__init__(parent=parent)
- self.parent = parent
- self.button = button
- self.titleLabel = TitleLabel(title, self)
- self.subtitleLabel = CaptionLabel(subtitle, self)
- self.addButton = addButton
- if self.addButton:
- self.createButton = PushButton(
- '添加', self, FluentIcon.ADD)
-
- self.helpButton = ToolButton(FluentIcon.FEEDBACK, self)
- self.separator = SeparatorWidget(self)
- self.themeButton = ToolButton(FluentIcon.CONSTRACT, self)
- self.vBoxLayout = QVBoxLayout(self)
- self.buttonLayout = QHBoxLayout()
- self.__initWidget()
- def __initWidget(self):
- self.setFixedHeight(138)
- self.vBoxLayout.setSpacing(0)
- self.vBoxLayout.setContentsMargins(36, 22, 36, 12)
- self.vBoxLayout.addWidget(self.titleLabel)
- self.vBoxLayout.addSpacing(4)
- self.vBoxLayout.addWidget(self.subtitleLabel)
- self.vBoxLayout.addSpacing(4)
- self.vBoxLayout.addLayout(self.buttonLayout, 1)
- self.vBoxLayout.setAlignment(Qt.AlignmentFlag.AlignTop)
- self.buttonLayout.setSpacing(4)
- self.buttonLayout.setContentsMargins(0, 0, 0, 0)
- if self.addButton:
- self.buttonLayout.addWidget(self.createButton, 0, Qt.AlignmentFlag.AlignLeft)
- if self.button:
- for key, value in enumerate(self.button):
- self.button[key] = PushButton(value['name'], self, value['icon'])
- self.button[key].clicked.connect(value['click'])
- self.buttonLayout.addWidget(self.button[key], 0, Qt.AlignmentFlag.AlignLeft)
-
- self.buttonLayout.addStretch(1)
- self.buttonLayout.addWidget(self.helpButton, 0, Qt.AlignmentFlag.AlignRight)
- self.buttonLayout.addWidget(self.separator, 0, Qt.AlignmentFlag.AlignRight)
- self.buttonLayout.addWidget(self.themeButton, 0, Qt.AlignmentFlag.AlignRight)
- self.buttonLayout.setAlignment(Qt.AlignmentFlag.AlignVCenter | Qt.AlignmentFlag.AlignLeft)
- self.themeButton.installEventFilter(ToolTipFilter(self.themeButton))
- self.helpButton.installEventFilter(ToolTipFilter(self.helpButton))
- self.themeButton.setToolTip(self.tr('Toggle theme'))
- self.helpButton.setToolTip('在线教程')
- self.themeButton.clicked.connect(lambda: toggleTheme(True))
- if self.addButton:
- self.createButton.clicked.connect(self.add)
-
- self.helpButton.clicked.connect(
- lambda: QDesktopServices.openUrl(QUrl(HELP_URL)))
- self.subtitleLabel.setTextColor(QColor(96, 96, 96), QColor(216, 216, 216))
- def add(self):
- self.parent.showUpdate('添加')
-
- class Card(QWidget):
- """ card """
- def __init__(self, title, widget: QWidget, sourcePath, stretch=0, parent=None):
- super().__init__(parent=parent)
- self.widget = widget
- self.stretch = stretch
- self.titleLabel = StrongBodyLabel(title, self)
- self.card = QFrame(self)
- self.sourceWidget = QFrame(self.card)
- self.sourcePath = sourcePath
- self.sourcePathLabel = BodyLabel(
- self.tr('Source code'), self.sourceWidget)
- self.linkIcon = IconWidget(FluentIcon.LINK, self.sourceWidget)
- self.vBoxLayout = QVBoxLayout(self)
- self.cardLayout = QVBoxLayout(self.card)
- self.topLayout = QHBoxLayout()
-
- self.__initWidget()
- def __initWidget(self):
- self.linkIcon.setFixedSize(16, 16)
- self.__initLayout()
- self.sourceWidget.setCursor(Qt.CursorShape.PointingHandCursor)
- self.sourceWidget.installEventFilter(self)
- self.card.setObjectName('card')
- self.sourceWidget.setObjectName('sourceWidget')
- def __initLayout(self):
- '''
- setSizeConstraint 方法用于设置布局的大小约束,它可以接受以下参数:
- QLayout.SizeConstraint.SetDefaultConstraint:默认约束,布局将根据其内容自动调整大小。
- QLayout.SizeConstraint.SetFixedSize:固定大小约束,布局的大小将保持不变。
- QLayout.SizeConstraint.SetMinimumSize:最小尺寸约束,布局的大小将不小于指定的最小尺寸。
- QLayout.SizeConstraint.SetMaximumSize:最大尺寸约束,布局的大小将不大于指定的最大尺寸。
- '''
- self.vBoxLayout.setSizeConstraint(QLayout.SizeConstraint.SetMinimumSize)
- self.cardLayout.setSizeConstraint(QLayout.SizeConstraint.SetMinimumSize)
- self.topLayout.setSizeConstraint(QLayout.SizeConstraint.SetMinimumSize)
- self.vBoxLayout.setSpacing(12)
- self.vBoxLayout.setContentsMargins(0, 0, 0, 0)
- self.topLayout.setContentsMargins(12, 12, 12, 12)
-
- self.cardLayout.setContentsMargins(0, 0, 0, 0)
- self.vBoxLayout.addWidget(self.titleLabel, 0, Qt.AlignmentFlag.AlignTop)
- self.vBoxLayout.addWidget(self.card, 0, Qt.AlignmentFlag.AlignTop)
- self.vBoxLayout.setAlignment(Qt.AlignmentFlag.AlignTop)
- self.cardLayout.setSpacing(0)
- self.cardLayout.setAlignment(Qt.AlignmentFlag.AlignTop)
- self.cardLayout.addLayout(self.topLayout, 0)
- self.cardLayout.addWidget(self.sourceWidget, 0, Qt.AlignmentFlag.AlignBottom)
- self.widget.setParent(self.card)
- self.topLayout.addWidget(self.widget)
- if self.stretch == 0:
- self.topLayout.addStretch(1)
- self.widget.show()
-
-
-
-
- def eventFilter(self, obj, e):
- if obj is self.sourceWidget:
- if e.type() == QEvent.Type.MouseButtonRelease:
- QDesktopServices.openUrl(QUrl(self.sourcePath))
- return super().eventFilter(obj, e)
- class WorkerSignals(QObject):
- finished = pyqtSignal()
- error = pyqtSignal(tuple)
- result = pyqtSignal(object)
- progress = pyqtSignal(int)
- class Worker(QThread):
- def __init__(self, fn, *args, **kwargs):
- super().__init__()
- self.fn = fn
- self.args = args
- self.kwargs = kwargs
- self.signals = WorkerSignals()
- self._is_running = True
- @pyqtSlot()
- def run(self):
- try:
-
- result = self.fn(self.report_progress, *self.args, **self.kwargs)
- if self._is_running:
- self.signals.result.emit(result)
- except Exception as e:
- self.signals.error.emit((e.__class__, e, e.__traceback__))
- finally:
- self.signals.finished.emit()
- def report_progress(self, progress):
- if self._is_running:
- self.signals.progress.emit(progress)
- def stop(self):
- self._is_running = False
- def terminate(self):
- self._is_running = False
- super().terminate()
- self.clean_up()
- def clean_up(self):
-
- del self.fn
- del self.args
- del self.kwargs
- gc.collect()
- class Progress(MaskDialogBase):
- cancelSignal = pyqtSignal()
- def __init__(self, title, parent=None):
- super().__init__(parent=parent)
- self.work = False
- self.title = BodyLabel(title)
-
- self.vBoxLayout = QVBoxLayout(self.widget)
- self.viewLayout = QVBoxLayout()
- self.__initWidget()
- def __initWidget(self):
- self.__setQss()
- self.__initLayout()
- self.setShadowEffect(60, (0, 10), QColor(0, 0, 0, 50))
- self.setMaskColor(QColor(0, 0, 0, 76))
- def __initLayout(self):
- self._hBoxLayout.removeWidget(self.widget)
- self._hBoxLayout.addWidget(self.widget, 1, Qt.AlignmentFlag.AlignCenter)
- self.vBoxLayout.setSpacing(0)
- self.vBoxLayout.setContentsMargins(0, 0, 0, 0)
- self.vBoxLayout.addLayout(self.viewLayout, 1)
- self.viewLayout.setSpacing(12)
- self.viewLayout.setContentsMargins(24, 24, 24, 24)
-
- self.progressBar = ProgressBar()
-
- self.progressBar.setRange(0, 100)
- self.progressBar.setValue(0)
- self.viewLayout.addWidget(self.title)
- self.viewLayout.addWidget(self.progressBar)
- self.show()
- def __setQss(self):
- FluentStyleSheet.DIALOG.apply(self)
- def setValue(self, value):
- self.progressBar.setValue(value)
- def __onCancelButtonClicked(self):
- self.cancelSignal.emit()
- self.reject()
- '''
- if self.work:
- self.work.close()
- '''
- def bind(self, work):
- self.work = work
|