def __init__(self, parent, cli_iface, iface_name): super(ControlPanelWindow, self).__init__(parent) self.setWindowTitle('SLCAN Adapter Control Panel') self.setAttribute(Qt.WA_DeleteOnClose) # This is required to stop background timers! self._cli_iface = cli_iface self._iface_name = iface_name self._state_widget = StateWidget(self, self._cli_iface) self._config_widget = ConfigWidget(self, self._cli_iface) self._cli_widget = CLIWidget(self, self._cli_iface) self._tab_widget = QTabWidget(self) self._tab_widget.addTab(self._state_widget, get_icon('dashboard'), 'Adapter State') self._tab_widget.addTab(self._config_widget, get_icon('wrench'), 'Configuration') self._tab_widget.addTab(self._cli_widget, get_icon('terminal'), 'Command Line') self._status_bar = QStatusBar(self) self._status_bar.setSizeGripEnabled(False) iface_name_label = QLabel(iface_name.split('/')[-1], self) iface_name_label.setFont(get_monospace_font()) layout = QVBoxLayout(self) layout.addWidget(iface_name_label) layout.addWidget(self._tab_widget) layout.addWidget(self._status_bar) left, top, right, bottom = layout.getContentsMargins() bottom = 0 layout.setContentsMargins(left, top, right, bottom) self.setLayout(layout) self.resize(400, 400)
def __init__( self, parent, node, target_node_id, file_server_widget, node_monitor, dynamic_node_id_allocator_widget ): super(NodePropertiesWindow, self).__init__(parent) self.setAttribute(Qt.WA_DeleteOnClose) # This is required to stop background timers! self.setWindowTitle("Node Properties [%d]" % target_node_id) self.setMinimumWidth(640) self._target_node_id = target_node_id self._node = node self._file_server_widget = file_server_widget self._info_box = InfoBox(self, target_node_id, node_monitor) self._controls = Controls(self, node, target_node_id, file_server_widget, dynamic_node_id_allocator_widget) self._config_params = ConfigParams(self, node, target_node_id) self._status_bar = QStatusBar(self) self._status_bar.setSizeGripEnabled(False) layout = QVBoxLayout(self) layout.addWidget(self._info_box) layout.addWidget(self._controls) layout.addWidget(self._config_params) layout.addWidget(self._status_bar) left, top, right, bottom = layout.getContentsMargins() bottom = 0 layout.setContentsMargins(left, top, right, bottom) self.setLayout(layout)
def __init__(self, parent, node, target_node_id, file_server_widget, node_monitor, dynamic_node_id_allocator_widget): super(NodePropertiesWindow, self).__init__(parent) self.setAttribute( Qt.WA_DeleteOnClose) # This is required to stop background timers! self.setWindowTitle('Node Properties [%d]' % target_node_id) self.setMinimumWidth(640) self._target_node_id = target_node_id self._node = node self._file_server_widget = file_server_widget self._info_box = InfoBox(self, target_node_id, node_monitor) self._controls = Controls(self, node, target_node_id, file_server_widget, dynamic_node_id_allocator_widget) self._config_params = ConfigParams(self, node, target_node_id) self._status_bar = QStatusBar(self) self._status_bar.setSizeGripEnabled(False) layout = QVBoxLayout(self) layout.addWidget(self._info_box) layout.addWidget(self._controls) layout.addWidget(self._config_params) layout.addWidget(self._status_bar) left, top, right, bottom = layout.getContentsMargins() bottom = 0 layout.setContentsMargins(left, top, right, bottom) self.setLayout(layout)
def refresh(self): addons = sorted(self._addons, key=lambda item: item.name) prev_checked = set(addon.identifier for addon in self.__checked_addons) prev_unchecked = set(addon.identifier for addon in self.__addons if addon not in self.__checked_addons) self.__addons = list(addons) self.__checked_addons = set() button_factory = self._addon_button_factory() self.setRowCount(len(self.__addons)) for no, addon in enumerate(self.__addons): check_widget = QWidget() check_layout = QHBoxLayout() check_layout.setAlignment(Qt.AlignCenter) check_check = QCheckBox() if addon.identifier not in prev_unchecked: check_check.setChecked(True) self.__checked_addons.add(addon) check_check.toggled.connect(partial(self.__check_checked, addon)) check_check.setFocusPolicy(Qt.NoFocus) check_layout.addWidget(check_check) check_widget.setLayout(check_layout) self.setCellWidget(no, 0, check_widget) if addon.icon: icon_widget = QWidget() icon_layout = QVBoxLayout() icon_layout.setSpacing(0) icon_widget.setLayout(icon_layout) icon_label = QLabel() icon_label.setAutoFillBackground(False) icon_label.setPixmap(image_loader.load(addon.icon)) icon_label.setAlignment(Qt.AlignTop) icon_layout.addWidget(icon_label) lp, tp, rp, bp = icon_layout.getContentsMargins() icon_layout.setContentsMargins(lp, tp, 0, bp) self.setCellWidget(no, 1, icon_widget) layout = QVBoxLayout() layout.setSpacing(0) layout.setAlignment(Qt.AlignTop) name_layout = QHBoxLayout() name_layout.setSpacing(20) name_layout.setAlignment(Qt.AlignLeft) name_label = QLabel(addon.name) name_label.setAutoFillBackground(False) name_label.setTextFormat(Qt.PlainText) font = name_label.font() font.setWeight(QFont.Bold) name_label.setFont(font) name_layout.addWidget(name_label) if isinstance(addon, OnlineAddOn): if self.__show_prev_version: version = "{0} ⟶ {1}".format(addon.local_addon.version, addon.latest_version.version) else: version = addon.latest_version.version else: version = addon.version version_label = QLabel(str(version)) version_label.setAutoFillBackground(False) version_label.setTextFormat(Qt.PlainText) name_layout.addWidget(version_label) layout.addLayout(name_layout) if addon.description: description_label = QLabel(addon.description) description_label.setAutoFillBackground(False) description_label.setTextFormat(Qt.PlainText) description_label.setWordWrap(True) layout.addWidget(description_label) addon_button_box = QHBoxLayout() addon_button_box.setAlignment(Qt.AlignRight) addon_button_box_widget = QWidget() addon_button_box_widget.setLayout(addon_button_box) if self.EXPANDING_BUTTON_BOX: addon_button_box_widget.setVisible(False) addon_button_box_widget.setObjectName("button_box") if button_factory is not None: button_factory.add_buttons(addon, addon_button_box, addon_button_box_widget) if addon_button_box.count() > 0: layout.addWidget(addon_button_box_widget) widget = QWidget() widget.setLayout(layout) self.setCellWidget(no, 2, widget) self.__refresh_selection_colors(widget, False) self.resizeColumnsToContents() self.resizeRowsToContents() cur_checked = set(addon.identifier for addon in self.__checked_addons) if prev_checked != cur_checked: self.check_changed.emit()
class AppCard(QFrame): """A Card widget derived from QFrame contains app icon, app name, app developer, app description, app rating, etc""" STYLES = STYLES Q_ENUM(STYLES) def __init__(self, parent=None): QFrame.__init__(self, parent) self.setFixedSize(200, 300) self.setObjectName("Card") self.setAutoFillBackground(True) self.appId = -1 self.appIcon = "./img/card/bird.png" self.appBack = "./img/card/card_back.png" self.appName = "PROS Smart CPQ for Manufacturing" self.appDev = "By PROS\nWeb apps" self.appRating = 0 self.appFeedback = 0 self.appState = 0 self.style_str = "border: 1px solid #ddd; background: qlineargradient(spread:pad, x1:0 y1:0, x2:1 y2:1, stop:0 rgba(255, 255, 255, 255), stop:1 rgba(225, 225, 225, 225));" self.appDesc = "Deliver Sales Automation and Profits Through Personalized Selling" self.setBackgroundImage(self.appBack) self.iconSize = 48, 48 self.iconMargins = 10, 10, 10, 10 self.iconFrameStyle = STYLES.STYLE_DEFAULT self.iconFrame = QLabel(self) self.iconFrame.setAutoFillBackground(True) self.iconFrame.setObjectName("IconFrame") self.iconFrame.setStyleSheet(self.iconFrameStyle) self.imgIcon = QLabel(self.iconFrame) self.imgIcon.setPixmap(QPixmap(self.appIcon)) self.imgIcon.setFixedSize(48, 48) self.imgIcon.setScaledContents(True) self.iconLayout = QHBoxLayout(self.iconFrame) self.iconLayout.setContentsMargins(10, 10, 10, 10) self.iconLayout.setAlignment(Qt.AlignLeft) self.iconLayout.addWidget(self.imgIcon, Qt.AlignLeft) self.iconFrame.setLayout(self.iconLayout) self.txtName = ElideLabel("", self) self.txtName.setText(self.appName) self.txtName.setFont(QFont("Roboto", 15)) self.txtName.setElideMode(1) self.txtName.setWordWrap(True) self.txtDev = ElideLabel("", self) self.txtDev.setWordWrap(True) self.txtDev.setText(self.appDev) self.txtDev.setFont(QFont("Roboto", 8)) self.txtDesc = ElideLabel("", self) self.txtDesc.setText(self.appDesc) self.txtDesc.setAlignment(Qt.AlignTop) self.txtDesc.setFont(QFont("Roboto", 10)) self.txtDesc.setElideMode(1) self.txtDesc.setWordWrap(True) self.starRating = StarRating(self) self.feedbackGiven = QLabel(self) self.feedbackGiven.setObjectName("Feedback") self.feedbackGiven.setStyleSheet("#Feedback{color: #ababab}") self.feedbackGiven.setFont(QFont("Roboto", 12)) self.feedbackGiven.setAlignment(Qt.AlignVCenter) self.feedbackGiven.setText("(" + str(self.appFeedback) + ")") self.btnInstall = Button('Install', self) self.btnInstall.clicked.connect(self.onInstallClicked) self.btnLaunch = Button('Launch', self) self.btnLaunch.setButtonType(Button.BUTTON_TYPE.LAUNCH) self.btnLaunch.clicked.connect(self.onLaunchClicked) self.btnLaunch.hide() self.btnUninstall = Button('Uninstall', self) self.btnUninstall.setButtonType(Button.BUTTON_TYPE.DELETE) self.btnUninstall.clicked.connect(self.onUninstallClicked) self.btnUninstall.hide() self.shadowEffect = QGraphicsDropShadowEffect(self) self.shadowEffect.setBlurRadius(9) self.shadowEffect.setColor(QColor(225, 225, 225)) self.shadowEffect.setOffset(5, 5) self.setGraphicsEffect(self.shadowEffect) self.frameLayout = QVBoxLayout(self) self.frameLayout.setContentsMargins(0, 0, 0, 0) self.mainLayout = QVBoxLayout() self.mainLayout.setSpacing(5) self.mainLayout.setContentsMargins(10, 0, 10, 15) self.ratingLayout = QHBoxLayout() self.ratingLayout.setSpacing(5) self.ratingLayout.addWidget(self.starRating, 1, Qt.AlignLeft) self.ratingLayout.addWidget(self.feedbackGiven, 1, Qt.AlignLeft) self.separator = QFrame(self) self.separator.setObjectName("line") self.separator.setFixedHeight(2) self.separator.setFixedWidth(self.width()) self.separator.setFrameShape(QFrame.HLine) self.separator.setFrameShadow(QFrame.Sunken) self.btnLayout = QHBoxLayout() self.btnLayout.setContentsMargins(5, 5, 5, 10) self.btnLayout.setSpacing(20) self.btnLayout.setAlignment(Qt.AlignHCenter) self.btnLayout.addWidget(self.btnInstall) self.btnLayout.addWidget(self.btnUninstall) self.btnLayout.addWidget(self.btnLaunch) self.mainLayout.addWidget(self.txtName, 1, Qt.AlignLeft) self.mainLayout.addWidget(self.txtDev, 1, Qt.AlignLeft) self.mainLayout.addWidget(self.txtDesc, 3, Qt.AlignLeft) self.mainLayout.addLayout(self.ratingLayout, Qt.AlignLeft) self.frameLayout.addWidget(self.iconFrame, 1) self.frameLayout.addLayout(self.mainLayout) self.frameLayout.addWidget(self.separator) self.frameLayout.addLayout(self.btnLayout) self.setLayout(self.frameLayout) self.setAppState(self.appState) self.show() #automatically adjust child widgets' sizes based on the frame's geometry #this might affect widgets' sizes def autoAdjust(self): self.starRating.adjustWidthByHeight(self.height()/15) self.feedbackGiven.setFixedHeight(self.height()/16) self.iconFrame.setFixedHeight(self.height()/5) self.setIconSize(self.iconFrame.height() * 4 / 5, self.iconFrame.height() *4 / 5) mm = self.iconFrame.height() / (5 * 2) self.setIconMargins(mm, mm, mm, mm) #set fixed size for the icon, maybe called logo or brand def setIconSize(self, aw, ah): self.iconSize = aw, ah self.imgIcon.setFixedSize(self.iconSize[0], self.iconSize[1]) #set icon margins within the icon frame def setIconMargins(self, ml, mt = 0, mr = 0, mb = 0): self.iconMargins = ml, mt, mr, mb mml, mmt, mmr, mmb = self.mainLayout.getContentsMargins() self.iconLayout.setContentsMargins(mml, mt, mmr, mb) #set icon frame's style, you can stylize background(single color, gradient or image), border, etc def setIconFrameStyle(self, style): self.iconFrameStyle = style self.iconFrame.setStyleSheet(self.iconFrameStyle) @pyqtSlot() def onInstallClicked(self): QMessageBox.information(None, "ID: " + str(self.appId), "Install button clicked") @pyqtSlot() def onUninstallClicked(self): QMessageBox.information(None, "ID: " + str(self.appId), "Gonna uninstall the app?") @pyqtSlot() def onLaunchClicked(self): QMessageBox.information(None, "ID: " + str(self.appId), "Launch is not ready yet!") #set whether the app is already installed or not, accordingly show or hide appropriate buttons def setAppState(self, state): if state == 0: self.btnInstall.show() self.btnUninstall.hide() self.btnLaunch.hide() elif state == 1: self.btnInstall.hide() self.btnUninstall.show() self.btnLaunch.show() self.autoAdjust() #return current app state def getAppState(self): return self.appState #set applicaton name def setAppName(self, name): if name != self.appName: self.appName = name self.txtName.setText(self.appName) #return application name def getAppName(self): return self.appName #set developer name, or could be company name def setAppDevName(self, name): if name != self.appDev: self.appDev = name self.txtDev.setText(self.appDev) #return developer name def getAppDevName(self): return self.appDev #set description about application def setAppDesc(self, desc): if desc != self.appDesc: self.appDesc = desc self.txtDesc.setText(self.appDesc) #return description of application def getAppDesc(self): return self.appDesc #set application icon with appropriate file path def setAppIcon(self, imgPath): if imgPath != self.appIcon: self.appIcon = imgPath self.imgIcon.setPixmap(QPixmap(self.appIcon)) #return QPixmap of icon def getAppIconPixmap(self): return QPixmap(self.appIcon) #return path to icon def getAppIconPath(self): return self.appIcon #set applicaiton star rating and count of given feedbacks def setAppRating(self, rating, feedback): if rating != self.appRating or feedback != self.appFeedback: self.appRating, self.appFeedback = rating, feedback self.starRating.setRating(rating) self.feedbackGiven.setText("(" + str(feedback) + ")") #return star rating value and the count of given feedbacks def getAppRating(self): return (self.appRating, self.appFeedback) #set path to background would be embedded into stylesheet string def setBackgroundImage(self, img): self.appBack = img self.setStyleSheet("#Card{" + self.style_str + " background-image: url(" + self.appBack + ")}") #set application ID def setAppId(self, id): self.appId = id #return app ID def getAppId(self): return self.appId #set blur radius of frame's shadow effect def setShadowBlurRadius(self, radius): self.shadowEffect.setBlurRadius(radius) #set shadow offset of frame's shadow effect def setShadowOffset(self, offX, offY): self.shadowEffect.setOffset(offX, offY) #set shadow color of frame's shadow effect def setShadowColor(self, color): self.shadowEffect.setColor(color) #set font of application name def setAppNameFont(self, font): self.txtName.setFont(font) #set font of developer name def setAppDevFont(self, font): self.txtDev.setFont(font) #set font of description to the app def setAppDescFont(self, font): self.txtDesc.setFont(font);
class BaseActivity(QDialog, BaseView): ''' 自定义标题栏 ''' bar: TitleBar = None ''' 窗口拖动位置 ''' _right_rect = [] _bottom_rect = [] _corner_rect = [] _drag_ignore_fixed = False # 拖动无视尺寸固定 ''' 扳机默认值 ''' _move_drag = False _corner_drag = False _bottom_drag = False _right_drag = False ''' 拖拽位置记录 ''' _move_drag_position = None def __init__(self, flags=None, *args, **kwargs): super().__init__(flags, *args, **kwargs) self._configure() def _configure(self): # 边距 self.margin = 0 # 日志对象 self.logger = logger self.logger_err = logger_err # 顶级窗口无边框 self.setWindowFlags(Qt.Window | Qt.FramelessWindowHint | Qt.WindowSystemMenuHint | Qt.WindowMinimizeButtonHint | Qt.WindowMaximizeButtonHint) # 窗口尺寸控制 self.minHeight = 768 self.minWidth = 1024 self.resize(self.minWidth, self.minHeight) # 背景透明 self.setAttribute(Qt.WA_TranslucentBackground, True) # 框架布局 self._main_layout = QVBoxLayout() self._main_layout.setSpacing(0) # 如果设置零边距,在窗口状态变更时(如最大化),边角会出现空白区域 self._main_layout.setContentsMargins(self.margin, self.margin, self.margin, self.margin) self.setLayout(self._main_layout) def addLayout(self, layout): ''' 往主布局中添加子布局 ''' self._main_layout.addLayout(layout) def alert(info): ''' 提示弹出框 ''' pass def isResizable(self): """是否可调整 """ return self.minimumSize() != self.maximumSize() def resizeEvent(self, _event): ''' 自定义窗口调整大小事件 采用三个列表生成式生成三个列表, 用以保存一个鼠标可以拖动的范围 ''' if not self.bar: return # 右侧边界 self._right_rect = [ QPoint(x, y) for x in range(self.width() - self.config.window_zoom_critical, self.width() + 1) for y in range(self.bar.height(), self.height() + self.config.window_zoom_critical) ] # 下边界 self._bottom_rect = [ QPoint(x, y) for x in range(1, self.width() + self.config.window_zoom_critical) for y in range(self.height() - self.config.window_zoom_critical, self.height() + 1) ] # 右下边界 self._corner_rect = [ QPoint(x, y) for x in range(self.width() - self.config.window_zoom_critical, self.width() + 1) for y in range(self.height() - self.config.window_zoom_critical, self.height() + 1) ] ''' 重新调整边界范围以备实现鼠标拖拽缩放窗口大小, 采用三个列表生成式生成三个列表 ''' # self._right_rect = [QPoint(x, y) for x in range(self.width() - self.config.window_zoom_critical, self.width() + 1) # for y in range(5, self.height() - self.config.window_zoom_critical)] # self._bottom_rect = [QPoint(x, y) for x in range(5, self.width() - self.config.window_zoom_critical) # for y in range(self.height() - self.config.window_zoom_critical, self.height() + 1)] # self._corner_rect = [QPoint(x, y) for x in range(self.width() - self.config.window_zoom_critical, self.width() + 1) # for y in range(self.height() - self.config.window_zoom_critical, self.height() + 1)] def mousePressEvent(self, event): ''' 重构鼠标点击事件 ''' if not self.bar: return if (event.button() == Qt.LeftButton) and (event.pos() in self._corner_rect): self._corner_drag = True event.accept() elif (event.button() == Qt.LeftButton) and (event.pos() in self._right_rect): self._right_drag = True event.accept() elif (event.button() == Qt.LeftButton) and (event.pos() in self._bottom_rect): self._bottom_drag = True event.accept() elif (event.button() == Qt.LeftButton) and (event.y() <= self.bar.height()): self._move_drag = True self._move_drag_position = event.globalPos() - self.pos() event.accept() ''' 重写鼠标点击的事件 ''' # if (event.button() == Qt.LeftButton) and (event.pos() in self._corner_rect): # self._corner_drag = True # event.accept() # elif (event.button() == Qt.LeftButton) and (event.pos() in self._right_rect): # self._right_drag = True # event.accept() # elif (event.button() == Qt.LeftButton) and (event.pos() in self._bottom_rect): # self._bottom_drag = True # event.accept() # elif (event.button() == Qt.LeftButton) and (event.y() < 40): # self._move_drag = True # self._move_drag_position = event.globalPos() - self.pos() # event.accept() def mouseMoveEvent(self, _): ''' 判断鼠标位置是否移动到了边界以便更换鼠标样式 ''' if self._drag_ignore_fixed: if self.isFullScreen(): # 非全屏均可移动 return elif self.isMaximized() or self.isFullScreen( ) or not self.isResizable(): # 最大化时不可移动 return if _.pos() in self._corner_rect: self.setCursor(Qt.SizeFDiagCursor) elif _.pos() in self._bottom_rect: self.setCursor(Qt.SizeVerCursor) elif _.pos() in self._right_rect: self.setCursor(Qt.SizeHorCursor) else: self.setCursor(Qt.ArrowCursor) if Qt.LeftButton and self._right_drag: self.resize(_.pos().x(), self.height()) _.accept() elif Qt.LeftButton and self._bottom_drag: self.resize(self.width(), _.pos().y()) _.accept() elif Qt.LeftButton and self._corner_drag: self.resize(_.pos().x(), _.pos().y()) _.accept() elif Qt.LeftButton and self._move_drag: self.move(_.globalPos() - self._move_drag_position) _.accept() def mouseReleaseEvent(self, _): ''' 鼠标释放后,各扳机复位 ''' self._move_drag = False self._corner_drag = False self._bottom_drag = False self._right_drag = False def dragEnterEvent(self, event): ''' 判断拖拽物体是否有路径,如果有则拖拽生效 ''' if event.mimeData().hasUrls: event.accept() else: event.ignore() def dragMoveEvent(self, event): ''' 拖拽移动 ''' if event.mimeData().hasUrls: try: event.setDropAction(Qt.CopyAction) except Exception as e: pass event.accept() else: event.ignore() def dropEvent(self, event): ''' 如果需要监听文件拖拽则重构此方法 获取拖拽文件 ''' try: if event.mimeData().hasUrls: event.setDropAction(Qt.CopyAction) event.accept() links = [] for url in event.mimeData().urls(): links.append(str(url.toLocalFile())) print(links) else: event.ignore() except Exception as e: raise e def mouseDoubleClickEvent(self, e: QMouseEvent): ''' 双击的坐标小于头部坐标时才最大化, 如果不需要这个功能,在继承子类时重构此方法即可 ''' if not self.bar: return if e.pos().y() < self.bar.y() + self.bar.height(): self.bar.on_window_change() def keyPressEvent(self, a0: QKeyEvent): ''' 键盘监听事件 ''' if a0.key() == Qt.Key_Escape: a0.ignore() else: a0.accept() def eventFilter(self, target, event): if self.bar and isinstance(event, QWindowStateChangeEvent): if self.isVisible() and not self.isMinimized() and bool( self.windowFlags() & Qt.WindowMinMaxButtonsHint): # 如果当前是最大化则隐藏最大化按钮 maximized = self.isMaximized() self.bar.showMaximizeButton(not maximized) self.bar.showNormalButton(maximized) # 修复最大化边距空白问题 if maximized: self._oldMargins = self._main_layout.getContentsMargins() self._main_layout.setContentsMargins(0, 0, 0, 0) else: if hasattr(self, '_oldMargins'): self._main_layout.setContentsMargins(*self._oldMargins) return super(BaseActivity, self).eventFilter(target, event) def paintEvent(self, event): ''' 由于是全透明背景窗口,重绘事件中绘制透明度为1的难以发现的边框,用于调整窗口大小 ''' painter = QPainter(self) painter.setPen(QPen(QColor(255, 255, 255, 1), 2 * self.margin)) painter.drawRect(self.rect())