class CollapsibleBox(QWidget): def __init__(self, title="", parent=None): super(CollapsibleBox, self).__init__(parent) self.toggle_button = QToolButton(text=title, checkable=True, checked=False) self.toggle_button.setStyleSheet("QToolButton { border: none; }") self.toggle_button.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) self.toggle_button.setArrowType(Qt.RightArrow) self.toggle_button.pressed.connect(self.on_pressed) self.toggle_animation = QParallelAnimationGroup(self) self.content_area = QScrollArea(maximumHeight=0, minimumHeight=0) self.content_area.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.content_area.setFrameShape(QFrame.NoFrame) lay = QVBoxLayout(self) lay.setSpacing(0) lay.setContentsMargins(0, 0, 0, 0) lay.addWidget(self.toggle_button) lay.addWidget(self.content_area) self.toggle_animation.addAnimation( QPropertyAnimation(self, b"minimumHeight")) self.toggle_animation.addAnimation( QPropertyAnimation(self, b"maximumHeight")) self.toggle_animation.addAnimation( QPropertyAnimation(self.content_area, b"maximumHeight")) @pyqtSlot() def on_pressed(self): checked = self.toggle_button.isChecked() self.toggle_button.setArrowType( Qt.DownArrow if not checked else Qt.RightArrow) self.toggle_animation.setDirection( QAbstractAnimation.Forward if not checked else QAbstractAnimation. Backward) self.toggle_animation.start() def setContentLayout(self, layout): lay = self.content_area.layout() del lay self.content_area.setLayout(layout) collapsed_height = self.sizeHint().height( ) - self.content_area.maximumHeight() content_height = layout.sizeHint().height() for i in range(self.toggle_animation.animationCount()): animation = self.toggle_animation.animationAt(i) animation.setDuration(500) animation.setStartValue(collapsed_height) animation.setEndValue(collapsed_height + content_height) content_animation = self.toggle_animation.animationAt( self.toggle_animation.animationCount() - 1) content_animation.setDuration(500) content_animation.setStartValue(0) content_animation.setEndValue(content_height)
def step_ani(self, is_back=False): title_move_ani, title_fade_ani = self.fade_to_left_ani(self.title) content1_move_ani, content1_fade_ani = self.fade_to_left_ani( self.content1) content2_move_ani, content2_fade_ani = self.fade_to_left_ani( self.content2) form_move_ani, form_fade_ani = self.fade_to_left_ani(self.form) # 动画队列 ani_group = QParallelAnimationGroup(self) ani_group.addAnimation(title_move_ani) ani_group.addAnimation(title_fade_ani) ani_group.addAnimation(content1_move_ani) ani_group.addAnimation(content1_fade_ani) ani_group.addAnimation(content2_move_ani) ani_group.addAnimation(content2_fade_ani) ani_group.addAnimation(form_move_ani) ani_group.addAnimation(form_fade_ani) if is_back: ani_group.setDirection(ani_group.Backward) ani_group.start() return ani_group
def step_ani(self, is_back=False): img_ani = QPropertyAnimation(self.img, b'geometry', self) img_ani.setStartValue(QRect(0, 0, self.img_width, self.img_height)) img_ani.setEndValue( QRect(0, -self.img_height * 2, self.img_width * 2, self.img_height * 2)) img_ani.setDuration(200) img_ani.setEasingCurve(QEasingCurve.OutCurve) title_ani = QPropertyAnimation(self.title, b'pos', self) title_ani.setStartValue(QPoint(self.title_x, self.title_y)) title_ani.setEndValue(QPoint(244, 78)) title_ani.setDuration(200) title_ani.setEasingCurve(QEasingCurve.OutCurve) content1_ani = QPropertyAnimation(self.content1, b'pos', self) content1_ani.setStartValue(QPoint(self.content1_x, self.content1_y)) content1_ani.setEndValue(QPoint(244, 112)) content1_ani.setDuration(200) content1_ani.setEasingCurve(QEasingCurve.OutCurve) content2_ani = QPropertyAnimation(self.content2, b'pos', self) content2_ani.setStartValue(QPoint(self.content2_x, self.content2_y)) content2_ani.setEndValue(QPoint(244, 134)) content2_ani.setDuration(200) content2_ani.setEasingCurve(QEasingCurve.OutCurve) # 动画队列 ani_group = QParallelAnimationGroup(self) ani_group.addAnimation(img_ani) ani_group.addAnimation(title_ani) ani_group.addAnimation(content1_ani) ani_group.addAnimation(content2_ani) if is_back: ani_group.setDirection(ani_group.Backward) ani_group.start() return ani_group
class Spoiler(QWidget): def __init__(self, title="", animationDuration=300, parent=None): QWidget.__init__(self, parent) self.mainLayout = QGridLayout() self.toggleButton = QToolButton() self.toggleAnimation = QParallelAnimationGroup() self.contentArea = QScrollArea() self.headerLine = QFrame() self.animationDuration = animationDuration self.toggleButton.setStyleSheet("QToolButton { border: none; }") self.toggleButton.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) self.toggleButton.setArrowType(Qt.ArrowType.RightArrow) self.toggleButton.setText(title) self.toggleButton.setCheckable(True) self.toggleButton.setChecked(False) self.headerLine.setFrameShape(QFrame.HLine) self.headerLine.setFrameShadow(QFrame.Sunken) self.headerLine.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Maximum) self.contentArea.setStyleSheet( "QScrollArea { background-color: white; border: none; }") self.contentArea.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.contentArea.setMaximumHeight(0) self.contentArea.setMinimumHeight(0) # let the entire widget grow and shrink with its content self.toggleAnimation.addAnimation(QPropertyAnimation()) self.toggleAnimation.addAnimation(QPropertyAnimation(self)) self.toggleAnimation.addAnimation( QPropertyAnimation(self, b"minimumHeight")) self.toggleAnimation.addAnimation( QPropertyAnimation(self, b"maximumHeight")) self.toggleAnimation.addAnimation( QPropertyAnimation(self.contentArea, b"maximumHeight")) # don't waste space self.mainLayout.setVerticalSpacing(0) self.mainLayout.setContentsMargins(0, 0, 0, 0) row = 0 self.mainLayout.addWidget(self.toggleButton, 0, 0, 1, 1, Qt.AlignLeft) self.mainLayout.addWidget(self.headerLine, 0, 2, 1, 1) self.mainLayout.addWidget(self.contentArea, 1, 0, 1, 3) self.setLayout(self.mainLayout) self.toggleButton.clicked[bool].connect(self.expandShrinkContent) def expandShrinkContent(self, checked): if checked: self.toggleButton.setArrowType(Qt.ArrowType.DownArrow) self.toggleAnimation.setDirection(QAbstractAnimation.Forward) else: self.toggleButton.setArrowType(Qt.ArrowType.RightArrow) self.toggleAnimation.setDirection(QAbstractAnimation.Backward) self.toggleAnimation.start() def setContentLayout(self, contentLayout): self.contentArea.setLayout(contentLayout) collapsedHeight = self.sizeHint().height( ) - self.contentArea.maximumHeight() contentHeight = contentLayout.sizeHint().height() i = 0 while i < self.toggleAnimation.animationCount(): i += 1 spoilerAnimation = self.toggleAnimation.animationAt(i) if spoilerAnimation is not None: spoilerAnimation.setDuration(self.animationDuration) spoilerAnimation.setStartValue(collapsedHeight) spoilerAnimation.setEndValue(collapsedHeight + contentHeight) contentAnimation = self.toggleAnimation.animationAt( self.toggleAnimation.animationCount() - 1) contentAnimation.setDuration(self.animationDuration) contentAnimation.setStartValue(0) contentAnimation.setEndValue(contentHeight)
class Spoiler(QWidget): class Orientation(Enum): HORIZONTAL = 1 VERTICAL = 2 def __init__(self, orientation=Orientation.HORIZONTAL, animationDuration=120, parent=None): QWidget.__init__(self, parent) self.opened = False self.orientation = orientation self.animationDuration = animationDuration self.mainLayout = None self.animator = QParallelAnimationGroup() if orientation is self.Orientation.HORIZONTAL: self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.setMaximumWidth(0) self.setMinimumWidth(0) # let the entire widget grow and shrink with its content self.animator.addAnimation(QPropertyAnimation(self)) self.animator.addAnimation( QPropertyAnimation(self, b"minimumWidth")) self.animator.addAnimation( QPropertyAnimation(self, b"maximumWidth")) elif orientation is self.Orientation.VERTICAL: self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.setMaximumHeight(0) self.setMinimumHeight(0) # let the entire widget grow and shrink with its content self.animator.addAnimation(QPropertyAnimation(self)) self.animator.addAnimation( QPropertyAnimation(self, b"minimumHeight")) self.animator.addAnimation( QPropertyAnimation(self, b"maximumHeight")) def open(self): self.animator.setDirection(QAbstractAnimation.Forward) self.animator.start() self.opened = True def close(self): self.animator.setDirection(QAbstractAnimation.Backward) self.animator.start() self.opened = False def isOpened(self): return self.opened def setContentLayout(self, contentLayout): self.setLayout(contentLayout) if self.orientation is self.Orientation.HORIZONTAL: collapsedSize = self.maximumWidth() contentSize = contentLayout.sizeHint().width() elif self.orientation is self.Orientation.VERTICAL: collapsedSize = self.maximumHeight() contentSize = contentLayout.sizeHint().height() i = 0 while i < self.animator.animationCount(): animation = self.animator.animationAt(i) animation.setDuration(self.animationDuration) animation.setStartValue(collapsedSize) animation.setEndValue(collapsedSize + contentSize) i += 1
class CollapsibleWidget(QWidget): def __init__(self, title="", parent=None, animation_duration=300): """ References: # Adapted from c++ version http://stackoverflow.com/questions/32476006/how-to-make-an-expandable-collapsable-section-widget-in-qt """ super(CollapsibleWidget, self).__init__(parent) self.title = title self.toggle_button = QToolButton() self.toggle_animation = QParallelAnimationGroup(self) self.content_area = QScrollArea() self.animation_duration = animation_duration self._init_base_ui() def _init_base_ui(self): self.toggle_button.setStyleSheet("QToolButton { border: none; }") self.toggle_button.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) self.toggle_button.setArrowType(Qt.RightArrow) self.toggle_button.pressed.connect(self.on_pressed) self.toggle_button.setText(str(self.title)) self.toggle_button.setCheckable(True) self.toggle_button.setChecked(False) self.content_area.setMaximumHeight(0) self.content_area.setMinimumHeight(0) self.content_area.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.content_area.setFrameShape(QFrame.NoFrame) self.toggle_animation.addAnimation( QPropertyAnimation(self, b"minimumHeight")) self.toggle_animation.addAnimation( QPropertyAnimation(self, b"maximumHeight")) self.toggle_animation.addAnimation( QPropertyAnimation(self.content_area, b"maximumHeight")) layout = QVBoxLayout(self) layout.setSpacing(0) layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(self.toggle_button) layout.addWidget(self.content_area) def on_pressed(self): checked = self.toggle_button.isChecked() self.toggle_button.setArrowType( Qt.DownArrow if not checked else Qt.RightArrow) self.toggle_animation.setDirection( QAbstractAnimation.Forward if not checked else QAbstractAnimation. Backward) self.toggle_animation.start() def set_content_layout(self, layout): initial_layout = self.content_area.layout() del initial_layout self.content_area.setLayout(layout) collapsed_height = (self.sizeHint().height() - self.content_area.maximumHeight()) content_height = layout.sizeHint().height() for i in range(self.toggle_animation.animationCount()): animation = self.toggle_animation.animationAt(i) animation.setDuration(self.animation_duration) animation.setStartValue(collapsed_height) animation.setEndValue(collapsed_height + content_height) content_animation = self.toggle_animation.animationAt( self.toggle_animation.animationCount() - 1) content_animation.setDuration(self.animation_duration) content_animation.setStartValue(0) content_animation.setEndValue(content_height) def set_content_widget(self, widget): initial_layout = self.content_area.layout() del initial_layout self.content_area.setWidget(widget) collapsed_height = (self.sizeHint().height() - self.content_area.maximumHeight()) content_height = widget.sizeHint().height() for i in range(self.toggle_animation.animationCount()): animation = self.toggle_animation.animationAt(i) animation.setDuration(self.animation_duration) animation.setStartValue(collapsed_height) animation.setEndValue(collapsed_height + content_height) content_animation = self.toggle_animation.animationAt( self.toggle_animation.animationCount() - 1) content_animation.setDuration(self.animation_duration) content_animation.setStartValue(0) content_animation.setEndValue(content_height)
class ContentEditor(AnimateableEditor): contentChanged = pyqtSignal(object) preview = pyqtSignal(object) def __init__(self, win, site, content): AnimateableEditor.__init__(self) self.win = win self.site = site self.content = content self.is_new = False self.editor = None self.undoStack = QUndoStack() self.changed = False self.setAutoFillBackground(True) self.previewLink = HyperLink("") self.vbox = QVBoxLayout() self.layout = QGridLayout() self.titleLabel = QLabel() fnt = self.titleLabel.font() fnt.setPointSize(20) fnt.setBold(True) self.titleLabel.setFont(fnt) self.script = QPushButton("Page Script") self.title = QLineEdit() self.source = QLineEdit() self.source.setPlaceholderText("*.qml") self.excerpt = QLineEdit() self.date = QLineEdit() self.labelPermalink = QLabel("Permalink") self.labelTitle = QLabel("Title") self.labelAuthor = QLabel("Author") self.labelKeyword = QLabel("Keywords") self.labelLayout = QLabel("Layout") self.labelMenu = QLabel("Menu") self.author = QLineEdit() self.keywords = QLineEdit() self.menus = QComboBox() self.layouts = QComboBox() self.layouts.setMaximumWidth(100) for menu in self.site.menus.menus: self.menus.addItem(menu.name) for root, dirs, files in os.walk( os.path.join(self.site.source_path, "layouts")): for file in files: self.layouts.addItem(Path(file).stem) for root, dirs, files in os.walk( os.path.join(Generator.themesPath(), self.site.theme, "layouts")): for file in files: self.layouts.addItem(Path(file).stem) self.close = FlatButton(":/images/close_normal.png", ":/images/close_hover.png") self.close.setToolTip("Close Content Editor") self.undo = FlatButton(":/images/undo_normal.png", ":/images/undo_hover.png", "", ":/images/undo_disabled.png") self.redo = FlatButton(":/images/redo_normal.png", ":/images/redo_hover.png", "", ":/images/redo_disabled.png") self.undo.setToolTip("Undo") self.redo.setToolTip("Redo") self.undo.setEnabled(False) self.redo.setEnabled(False) hbox = QHBoxLayout() hbox.addWidget(self.undo) hbox.addWidget(self.redo) hbox.addWidget(self.close) self.scroll = QScrollArea() self.scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded) self.scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) self.scroll.setWidgetResizable(True) self.scroll.installEventFilter(self) self.layout.addWidget(self.titleLabel, 0, 0) self.layout.addWidget(self.previewLink, 0, 1) self.layout.addLayout(hbox, 0, 3) self.layout.addWidget(self.labelTitle, 1, 0) self.layout.addWidget(self.title, 2, 0) self.layout.addWidget(self.labelPermalink, 1, 1) self.layout.addWidget(self.source, 2, 1) self.layout.addWidget(self.labelAuthor, 3, 0) self.layout.addWidget(self.author, 4, 0) self.layout.addWidget(self.labelKeyword, 3, 1) self.layout.addWidget(self.keywords, 4, 1) self.layout.addWidget(self.labelMenu, 3, 2) self.layout.addWidget(self.menus, 4, 2) self.layout.addWidget(self.labelLayout, 3, 3) self.layout.addWidget(self.layouts, 4, 3) self.layout.addWidget(self.scroll, 7, 0, 1, 4) self.layout.addWidget(self.script, 8, 0, 1, 4) self.vbox.addLayout(self.layout) self.setLayout(self.vbox) if self.content.content_type == ContentType.POST: self.previewLink.setText("view post") self.excerptLabel = QLabel("Excerpt") self.layout.addWidget(self.excerptLabel, 5, 0) self.layout.addWidget(self.excerpt, 6, 0, 1, 2) self.datelabel = QLabel("Date") self.layout.addWidget(self.datelabel, 5, 2) self.layout.addWidget(self.date, 6, 2, 1, 2) self.filename = self.site.source_path + "/posts/" + content.source else: self.previewLink.setText("view page") self.filename = self.site.source_path + "/pages/" + content.source self.load() self.close.clicked.connect(self.closeEditor) self.title.editingFinished.connect(self.titleFinished) self.title.textChanged.connect(self.titleChanged) self.source.editingFinished.connect(self.sourceChanged) self.excerpt.editingFinished.connect(self.excerptChanged) self.date.editingFinished.connect(self.dateChanged) self.author.editingFinished.connect(self.authorChanged) self.keywords.editingFinished.connect(self.keywordsChanged) self.menus.currentTextChanged.connect(self.menuChanged) self.layouts.currentTextChanged.connect(self.layoutChanged) self.undoStack.canUndoChanged.connect(self.canUndoChanged) self.undoStack.canRedoChanged.connect(self.canRedoChanged) self.undoStack.undoTextChanged.connect(self.undoTextChanged) self.undoStack.redoTextChanged.connect(self.redoTextChanged) self.undo.clicked.connect(self.undoAction) self.redo.clicked.connect(self.redoAction) self.previewLink.clicked.connect(self.previewPage) self.script.clicked.connect(self.scriptClicked) def scriptClicked(self): self.editor = Plugins.element_plugins["TextEditor"] self.editor.setContent(None) self.editor.setText(self.content.script) self.editor.setCaption("Page Script") self.editor.close.connect(self.scriptEditorClose) self.animate(self.scroll.widget().placeholder) def scriptEditorClose(self): if self.editor and self.editor.changed: self.content.script = self.editor.getText() self.editChanged("Update Script") self.editor.close.disconnect() self.editorClosed() def previewPage(self): self.preview.emit(self.content) def rowEdit(self, re): from widgets.rowpropertyeditor import RowPropertyEditor self.row_editor = re self.editor = RowPropertyEditor() self.editor.setRow(re.row) self.editor.close.connect(self.rowEditorClose) self.animate(re) def rowEditorClose(self): if self.editor and self.editor.changed: self.row_editor.load(self.editor.row) self.editChanged("Update Row") self.editor.close.disconnect() self.editorClosed() def canUndoChanged(self, can): self.undo.setEnabled(can) def canRedoChanged(self, can): self.redo.setEnabled(can) def undoTextChanged(self, text): self.undo.setToolTip("Undo " + text) def redoTextChanged(self, text): self.redo.setToolTip("Redo " + text) def undoAction(self): self.undoStack.undo() def redoAction(self): self.undoStack.redo() def menuChanged(self, menu): if menu != self.content.menu: self.content.menu = menu self.contentChanged.emit(self.content) self.editChanged("Menu Changed") def layoutChanged(self, layout): if layout != self.content.layout: self.content.layout = layout self.contentChanged.emit(self.content) self.editChanged("Layout Changed") def keywordsChanged(self): if self.keywords.text() != self.content.keywords: self.content.keywords = self.keywords.text() self.contentChanged.emit(self.content) self.editChanged("Keywords Changed") def authorChanged(self): if self.author.text() != self.content.author: self.content.author = self.author.text() self.contentChanged.emit(self.content) self.editChanged("Author Changed") def excerptChanged(self): if self.excerpt.text() != self.content.excerpt: self.content.excerpt = self.excerpt.text() self.contentChanged.emit(self.content) self.editChanged("Excerpt Changed") def dateChanged(self): if self.date.text() != self.content.date.toString("dd.MM.yyyy"): self.content.date = QDate.fromString(self.date.text(), "dd.MM.yyyy") self.contentChanged.emit(self.content) self.editChanged("Date Changed") def sourceChanged(self): if self.source.text() != self.content.source: oldname = self.filename self.content.source = self.source.text() if self.content.content_type == ContentType.PAGE: self.filename = self.site.source_path + "/pages/" + self.content.source else: self.filename = self.site.source_path + "/posts/" + self.content.source self.contentChanged.emit(self.content) renameCommand = RenameContentCommand(self, oldname, self.filename, "content file renamed") self.undoStack.push(renameCommand) def titleChanged(self, title): if self.is_new: source = title.lower().replace(" ", "_") + ".qml" self.source.setText(source) def titleFinished(self): if self.title.text() != self.content.title: if self.is_new: self.sourceChanged() self.content.title = self.title.text() self.contentChanged.emit(self.content) self.editChanged("Titel Changed") def sectionEdit(self, se): self.section_editor = se self.editor = SectionPropertyEditor() self.editor.setSection(se.section) self.editor.close.connect(self.sectionEditorClose) self.animate(se) def sectionEditorClose(self): if self.editor.changed: self.section_editor.setSection(self.editor.section) self.editChanged("Update Section") self.editor.close.disconnect() self.editorClosed() def load(self): from widgets.sectioneditor import SectionEditor self.content = self.site.loadContent(self.content.source, self.content.content_type) self.is_new = not self.content.title self.title.setText(self.content.title) self.source.setText(self.content.source) self.author.setText(self.content.author) self.keywords.setText(self.content.keywords) self.menus.setCurrentText(self.content.menu) self.layouts.setCurrentText(self.content.layout) if self.content.content_type == ContentType.POST: self.excerpt.setText(self.content.excerpt) self.date.setText(self.content.date.toString("dd.MM.yyyy")) pe = PageEditor() self.scroll.setWidget(pe) for item in self.content.items: if isinstance(item, Section): se = SectionEditor(item.fullwidth) se.load(item) pe.addSection(se) # todo other types def siteLoaded(self, site): self.site = site if self.content.contentType == ContentType.PAGE: for c in self.site.pages: if c.source == self.content.source: self.title.setText(c.title) else: for c in self.site.posts: if c.source == self.content.source: self.excerpt.setText(c.excerpt) self.title.setText(c.title) def closeEditor(self): if self.editor: self.editor.closeEditor() self.closes.emit() def elementEdit(self, ee): self.element_editor = ee plugin_name = "" if ee.type: plugin_name = Plugins.getElementPluginByTagname(ee.type) if plugin_name: self.editor = Plugins.element_plugins[plugin_name] else: self.editor = Plugins.element_plugins["TextEditor"] self.editor.setCaption("Text Module") self.editor.site = self.site self.editor.setContent(ee.getContent()) self.editor.close.connect(self.editorClose) self.animate(ee) def animate(self, widget): self.sourcewidget = widget pos = widget.mapTo(self.scroll, QPoint(0, 0)) self.editor.setParent(self.scroll) self.editor.move(pos) self.editor.resize(widget.size()) self.editor.show() self.animationgroup = QParallelAnimationGroup() self.animx = QPropertyAnimation() self.animx.setDuration(300) self.animx.setStartValue(pos.x()) self.animx.setEndValue(0) self.animx.setTargetObject(self.editor) self.animx.setPropertyName("x".encode("utf-8")) self.animationgroup.addAnimation(self.animx) self.animy = QPropertyAnimation() self.animy.setDuration(300) self.animy.setStartValue(pos.y()) self.animy.setEndValue(0) self.animy.setTargetObject(self.editor) self.animy.setPropertyName("y".encode("utf-8")) self.animationgroup.addAnimation(self.animy) self.animw = QPropertyAnimation() self.animw.setDuration(300) self.animw.setStartValue(widget.size().width()) self.animw.setEndValue(self.scroll.size().width()) self.animw.setTargetObject(self.editor) self.animw.setPropertyName("width".encode("utf-8")) self.animationgroup.addAnimation(self.animw) self.animh = QPropertyAnimation() self.animh.setDuration(300) self.animh.setStartValue(widget.size().height()) self.animh.setEndValue(self.scroll.size().height()) self.animh.setTargetObject(self.editor) self.animh.setPropertyName("height".encode("utf-8")) self.animationgroup.addAnimation(self.animh) self.animationgroup.finished.connect(self.animationFineshedZoomIn) self.animationgroup.start() def animationFineshedZoomIn(self): self.scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.title.setEnabled(False) self.author.setEnabled(False) self.keywords.setEnabled(False) self.menus.setEnabled(False) self.layouts.setEnabled(False) self.labelAuthor.setEnabled(False) self.labelKeyword.setEnabled(False) self.labelMenu.setEnabled(False) self.labelLayout.setEnabled(False) self.labelTitle.setEnabled(False) self.labelPermalink.setEnabled(False) self.previewLink.hide() self.undo.hide() self.redo.hide() self.close.hide() self.source.setEnabled(False) if self.content.content_type == ContentType.POST: self.excerpt.setEnabled(False) self.excerptLabel.setEnabled(False) self.animationgroup.finished.disconnect(self.animationFineshedZoomIn) def editorClose(self): if self.editor.changed: self.element_editor.setContent(self.editor.getContent()) self.editChanged("Update Element") self.editor.close.disconnect() self.editorClosed() def editorClosed(self): self.scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded) self.scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) pos = self.sourcewidget.mapTo(self.scroll, QPoint(0, 0)) # correct end values in case of resizing the window self.animx.setStartValue(pos.x()) self.animy.setStartValue(pos.y()) self.animw.setStartValue(self.sourcewidget.size().width()) self.animh.setStartValue(self.sourcewidget.size().height()) self.animationgroup.setDirection(QAbstractAnimation.Backward) self.animationgroup.finished.connect(self.animationFineshedZoomOut) self.animationgroup.start() def animationFineshedZoomOut(self): from widgets.rowpropertyeditor import RowPropertyEditor from widgets.sectionpropertyeditor import SectionPropertyEditor self.title.setEnabled(True) self.source.setEnabled(True) self.author.setEnabled(True) self.keywords.setEnabled(True) self.menus.setEnabled(True) self.layouts.setEnabled(True) self.labelAuthor.setEnabled(True) self.labelKeyword.setEnabled(True) self.labelMenu.setEnabled(True) self.labelLayout.setEnabled(True) self.labelTitle.setEnabled(True) self.labelPermalink.setEnabled(True) self.previewLink.show() self.undo.show() self.redo.show() self.close.show() if self.content.content_type == ContentType.POST: self.excerpt.setEnabled(True) self.excerptLabel.setEnabled(True) del self.animationgroup self.editor.hide() # parent has to be set to NULL, otherwise the plugin will be dropped by parent self.editor.setParent(None) # only delete Row- and SectionPropertyEditor the other editors are plugins if isinstance(self.editor, RowPropertyEditor): del self.editor elif isinstance(self.editor, SectionPropertyEditor): self.editor.close.disconnect(self.sectionEditorClose) del self.editor self.editor = None def editChanged(self, text): changeCommand = ChangeContentCommand(self.win, self, text) self.undoStack.push(changeCommand) def save(self): self.content.save(self.filename) def contentRenamed(self, name): self.filename = name base = os.path.basename(name) self.source.setText(base) self.content.source = base
class GroupWidget(QWidget): def __init__(self, parent=None, title='', animation_duration=300): """ References: # Adapted from c++ version http://stackoverflow.com/questions/32476006/how-to-make-an-expandable-collapsable-section-widget-in-qt """ super(GroupWidget, self).__init__(parent=parent) self.animation_duration = animation_duration self.toggle_animation = QParallelAnimationGroup() self.content_area = QScrollArea() self.header_line = QFrame() self.toggle_button = QToolButton() self.main_layout = QGridLayout() toggle_button = self.toggle_button toggle_button.setStyleSheet("QToolButton { border: none; }") toggle_button.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) toggle_button.setArrowType(Qt.RightArrow) toggle_button.setText(str(title)) toggle_button.setCheckable(True) toggle_button.setChecked(False) header_line = self.header_line header_line.setFrameShape(QFrame.HLine) header_line.setFrameShadow(QFrame.Sunken) header_line.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Maximum) self.content_area.setStyleSheet( "QScrollArea { background-color: white; border: none; }") self.content_area.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) # start out collapsed self.content_area.setMaximumHeight(0) self.content_area.setMinimumHeight(0) # let the entire widget grow and shrink with its content toggle_animation = self.toggle_animation toggle_animation.addAnimation( QPropertyAnimation(self, bytes("minimumHeight", "utf-8"))) toggle_animation.addAnimation( QPropertyAnimation(self, bytes("maximumHeight", "utf-8"))) toggle_animation.addAnimation( QPropertyAnimation(self.content_area, bytes("maximumHeight", "utf-8"))) # don't waste space main_layout = self.main_layout main_layout.setVerticalSpacing(0) main_layout.setContentsMargins(0, 0, 0, 0) row = 0 main_layout.addWidget(self.toggle_button, row, 0, 1, 1, Qt.AlignLeft) main_layout.addWidget(self.header_line, row, 2, 1, 1) row += 1 main_layout.addWidget(self.content_area, row, 0, 1, 3) self.setLayout(self.main_layout) def start_animation(checked): arrow_type = Qt.DownArrow if checked else Qt.RightArrow direction = QAbstractAnimation.Forward if checked else QAbstractAnimation.Backward toggle_button.setArrowType(arrow_type) self.toggle_animation.setDirection(direction) self.toggle_animation.start() self.toggle_button.clicked.connect(start_animation) def set_content_layout(self, content_layout): # Not sure if this is equivalent to self.contentArea.destroy() self.content_area.destroy() self.content_area.setLayout(content_layout) collapsed_height = self.sizeHint().height( ) - self.content_area.maximumHeight() content_height = content_layout.sizeHint().height() for i in range(self.toggle_animation.animationCount() - 1): spoiler_animation = self.toggle_animation.animationAt(i) spoiler_animation.setDuration(self.animation_duration) spoiler_animation.setStartValue(collapsed_height) spoiler_animation.setEndValue(collapsed_height + content_height) content_animation = self.toggle_animation.animationAt( self.toggle_animation.animationCount() - 1) content_animation.setDuration(self.animation_duration) content_animation.setStartValue(0) content_animation.setEndValue(content_height)
class CollapsibleBox(QWidget): def __init__(self, title="", parent=None): super(CollapsibleBox, self).__init__(parent) self.toggle_button = QToolButton(text=title, checkable=True, checked=False) self.toggle_button.setStyleSheet("QToolButton {border: none;\ border: 1px solid #FF17365D;\ border-top-left-radius: 15px;\ border-top-right-radius: 15px;\ background-color: #FF17365D;\ padding: 5px 0px;\ color: rgb(255, 255, 255);\ max-height: 30px;\ font-size: 14px;\ }\ QToolButton:hover {\ background-color: lightgreen;\ color: black;\ }") self.toggle_button.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) self.toggle_button.setArrowType(Qt.RightArrow) self.toggle_button.pressed.connect(self.on_pressed) self.toggle_animation = QParallelAnimationGroup(self) self.content_area = QScrollArea(maximumHeight=0, minimumHeight=0) self.content_area.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) lay = QVBoxLayout(self) lay.setSpacing(0) lay.setContentsMargins(0, 0, 0, 0) lay.addWidget(self.toggle_button) lay.addWidget(self.content_area) self.toggle_animation.addAnimation( QPropertyAnimation(self, b"minimumHeight")) self.toggle_animation.addAnimation( QPropertyAnimation(self, b"maximumHeight")) self.toggle_animation.addAnimation( QPropertyAnimation(self.content_area, b"maximumHeight")) @QtCore.pyqtSlot() def on_pressed(self): checked = self.toggle_button.isChecked() self.toggle_button.setArrowType( Qt.DownArrow if not checked else Qt.RightArrow) self.toggle_animation.setDirection( QAbstractAnimation.Forward if not checked else QAbstractAnimation. Backward) self.toggle_animation.start() def clear_layout(self, layout): try: for i in reversed(range(layout.count())): widgetToRemove = layout.itemAt(i).widget() layout.removeWidget(widgetToRemove) widgetToRemove.setPArent(None) except AttributeError: pass def setContentLayout(self, layout): lay = self.content_area.layout() self.clear_layout(lay) self.content_area.setLayout(layout) collapsed_height = (self.sizeHint().height() - self.content_area.maximumHeight()) content_height = layout.sizeHint().height() for i in range(self.toggle_animation.animationCount()): animation = self.toggle_animation.animationAt(i) animation.setDuration(500) animation.setStartValue(collapsed_height) animation.setEndValue(collapsed_height + content_height) content_animation = self.toggle_animation.animationAt( self.toggle_animation.animationCount() - 1) content_animation.setDuration(500) content_animation.setStartValue(0) content_animation.setEndValue(content_height)
class PopupPost(QMainWindow, Ui_PopupPost): def __init__(self, read_the_weibo): super().__init__( None, Qt.ToolTip | Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint) self.setupUi(self) self._close_timer = QTimer(self) self._close_timer.setSingleShot(True) self._close_timer.timeout.connect(self.close) self._read_the_weibo = read_the_weibo def setupUi(self, popup_post): super().setupUi(popup_post) self.setAttribute(Qt.WA_TranslucentBackground) desktop = QApplication.desktop() self.move(desktop.width() - 50 - self.width(), desktop.height() - 300 - self.height()) # 因为带浏览器的透明窗口不能渲染,浏览器放在独立窗口 self.content_view = WeiboWebView(self) self.content_view.setWindowOpacity(0) # 淡出淡入动画 self._window_fade_anim = QPropertyAnimation(self, b'windowOpacity', self) self._window_fade_anim.setDuration(300) self._window_fade_anim.setStartValue(0) self._window_fade_anim.setEndValue(0.5) self._content_fade_anim = QPropertyAnimation(self.content_view, b'windowOpacity', self) self._content_fade_anim.setDuration(300) self._content_fade_anim.setStartValue(0) self._content_fade_anim.setEndValue(0.7) self._fade_anim_group = QParallelAnimationGroup(self) self._fade_anim_group.addAnimation(self._window_fade_anim) self._fade_anim_group.addAnimation(self._content_fade_anim) self._fade_anim_group.finished.connect(self._on_anim_finish) def moveEvent(self, event): logger.debug('moveEvent') # 如果在这里更新,content_widget位置和尺寸不对 QTimer.singleShot(0, self._update_content_view_geometry) def resizeEvent(self, event): logger.debug('resizeEvent') # 如果在这里更新,content_widget位置和尺寸不对 QTimer.singleShot(0, self._update_content_view_geometry) def _update_content_view_geometry(self): pos = self.mapToGlobal(self.content_widget.pos()) size = self.content_widget.size() logger.debug('content_widget: (%d, %d) %d x %d', pos.x(), pos.y(), size.width(), size.height()) self.content_view.setGeometry(pos.x(), pos.y(), size.width(), size.height()) def show_post(self, post): """ 显示一条微博 :param post: 微博 """ self.show() self.content_view.show_post(post) # 淡入 self._fade_anim_group.setDirection(QPropertyAnimation.Forward) self._fade_anim_group.start() # 如果只弹窗不发声则过一段时间自动关闭 if not self._read_the_weibo.speak_post: content_len = len(post.content) if post.is_repost: content_len += len(post.original_post.content) self._close_timer.start(int((3 + content_len * 0.1) * 1000)) def closeEvent(self, event): """ 取消关闭事件,改成淡出、隐藏窗口 """ logger.debug('closeEvent') event.ignore() self._close_timer.stop() if (self._fade_anim_group.state() != QPropertyAnimation.Running or self._fade_anim_group.direction() != QPropertyAnimation.Backward): self._read_the_weibo.on_popup_post_close() # 淡出 self._fade_anim_group.setDirection(QPropertyAnimation.Backward) self._fade_anim_group.start() def _on_anim_finish(self): if self._fade_anim_group.direction() == QPropertyAnimation.Backward: logger.debug('_on_anim_finish, direction = Backward') self.content_view.hide() self.hide() self._read_the_weibo.on_popup_post_hide() else: logger.debug('_on_anim_finish, direction = Forward')
class RevolutionSliderEditor(ElementEditorInterface): def __init__(self): ElementEditorInterface.__init__(self) self.class_name = "RevolutionSliderEditor" self.display_name = "RevolutionSlider" self.tag_name = "RevolutionSlider" self.version = "1.0" self.icon = QImage(":/revolution.png") self.changed = False self.setAutoFillBackground(True) grid = QGridLayout() self.id = QLineEdit() self.id.setMaximumWidth(200) self.adminlabel = QLineEdit() self.adminlabel.setMaximumWidth(200) titleLabel = QLabel("Slider Module") fnt = titleLabel.font() fnt.setPointSize(16) fnt.setBold(True) titleLabel.setFont(fnt) close = FlatButton(":/images/close_normal.png", ":/images/close_hover.png") close.setToolTip("Close Editor") addSlide = QPushButton("Add Slide") addSlide.setMaximumWidth(120) self.list = QTableWidget(0, 2, self) self.list.verticalHeader().hide() self.list.setSelectionMode(QAbstractItemView.SingleSelection) self.list.setSelectionBehavior(QAbstractItemView.SelectRows) self.list.horizontalHeader().setSectionResizeMode( 1, QHeaderView.Stretch) self.list.setToolTip("Double click to edit item") labels = ["", "Name"] self.list.setHorizontalHeaderLabels(labels) grid.addWidget(titleLabel, 0, 0) grid.addWidget(close, 0, 2, 1, 1, Qt.AlignRight) grid.addWidget(addSlide, 1, 0) grid.addWidget(self.list, 2, 0, 1, 3) grid.addWidget(QLabel("Id"), 4, 0) grid.addWidget(self.id, 5, 0) grid.addWidget(QLabel("Admin Label"), 6, 0) grid.addWidget(self.adminlabel, 7, 0) self.setLayout(grid) addSlide.clicked.connect(self.addSlide) self.adminlabel.textChanged.connect(self.contentChanged) self.id.textChanged.connect(self.contentChanged) close.clicked.connect(self.closeEditor) self.list.cellDoubleClicked.connect(self.tableDoubleClicked) self.installEventFilter(self) def closeEditor(self): if self.changed: if self.content: self.content.removeSlides() self.content.adminlabel = self.adminlabel.text() #self.content.text = html.escape(self.html.toPlainText()) for i in range(self.list.rowCount()): item = self.list.item(i, 1) slide = item.data(Qt.UserRole) self.content.addSlide(slide) self.close.emit() def registerContenType(self): qmlRegisterType(RevolutionSlider, 'RevolutionSlider', 1, 0, 'RevolutionSlider') qmlRegisterType(Slide, 'RevolutionSlider', 1, 0, 'Slide') def getImportString(self): return "import RevolutionSlider 1.0\n" def pluginStyles(self): return "<link href=\"assets/plugins/revolution-slider/css/settings.css\" rel=\"stylesheet\" type=\"text/css\"/>\n" def pluginScripts(self): script = "<script type=\"text/javascript\" src=\"assets/plugins/revolution-slider/js/jquery.themepunch.plugins.min.js\"></script>\n" script += "<script type=\"text/javascript\" src=\"assets/plugins/revolution-slider/js/jquery.themepunch.revolution.min.js\"></script>\n" script += "<script type=\"text/javascript\" src=\"assets/js/slider_revolution.js\"></script>\n" return script def installAssets(self, assets_path): assets = QDir(assets_path) assets.mkdir("plugins") assets.cd("plugins") assets.mkdir("revolution-slider") assets.cd("revolution-slider") assets.mkdir("css") assets.mkdir("js") assets.mkdir("assets") QFile.copy(":/css", assets_path + "/plugins/revolution-slider/css") QFile.copy(":/js", assets_path + "/js") QFile.copy(":/js/plugins", assets_path + "/plugins/revolution-slider/js") QFile.copy(":/assets", assets_path + "/plugins/revolution-slider/assets") def getDefaultContent(self): return RevolutionSlider() def setContent(self, content): self.content = content if content: #self.adminlabel.setText(content.adminlabel) self.changed = False self.list.setRowCount(0) for slide in content._items: self.addListItem(slide) self.changed = False def getContent(self): return self.content def addSlide(self): slide = Slide() self.addListItem(slide) self.contentChanged() self.tableDoubleClicked(self.list.rowCount() - 1) def addListItem(self, slide): rows = self.list.rowCount() self.list.setRowCount(rows + 1) tcb = TableCellButtons() tcb.setItem(slide) tcb.deleteItem.connect(self.deleteSlide) tcb.editItem.connect(self.editSlide) self.list.setCellWidget(rows, 0, tcb) self.list.setRowHeight(rows, tcb.sizeHint().height()) titleItem = QTableWidgetItem(slide.title) titleItem.setFlags(titleItem.flags() ^ Qt.ItemIsEditable) titleItem.setData(Qt.UserRole, slide) self.list.setItem(rows, 1, titleItem) def tableDoubleClicked(self, row): item = self.list.item(row, 1) slide = item.data(Qt.UserRole) self.editor = SlideEditor() self.editor.setSite(self.site) self.editor.setSlide(slide) self.editor.closes.connect(self.editorClosed) self.animate(item) def animate(self, item): self.row = item.row() # create a cell widget to get the right position in the table self.sourcewidget = QWidget() self.list.setCellWidget(self.row, 1, self.sourcewidget) pos = self.sourcewidget.mapTo(self, QPoint(0, 0)) self.editor.setParent(self) self.editor.move(pos) self.editor.resize(self.sourcewidget.size()) self.editor.show() self.animationgroup = QParallelAnimationGroup() self.animx = QPropertyAnimation() self.animx.setDuration(300) self.animx.setStartValue(pos.x()) self.animx.setEndValue(0) self.animx.setTargetObject(self.editor) self.animx.setPropertyName("x".encode("utf-8")) self.animationgroup.addAnimation(self.animx) self.animy = QPropertyAnimation() self.animy.setDuration(300) self.animy.setStartValue(pos.y()) self.animy.setEndValue(0) self.animy.setTargetObject(self.editor) self.animy.setPropertyName("y".encode("utf-8")) self.animationgroup.addAnimation(self.animy) self.animw = QPropertyAnimation() self.animw.setDuration(300) self.animw.setStartValue(self.sourcewidget.size().width()) self.animw.setEndValue(self.size().width()) self.animw.setTargetObject(self.editor) self.animw.setPropertyName("width".encode("utf-8")) self.animationgroup.addAnimation(self.animw) self.animh = QPropertyAnimation() self.animh.setDuration(300) self.animh.setStartValue(self.sourcewidget.size().height()) self.animh.setEndValue(self.size().height()) self.animh.setTargetObject(self.editor) self.animh.setPropertyName("height".encode("utf-8")) self.animationgroup.addAnimation(self.animh) self.animationgroup.finished.connect(self.animationFineshedZoomIn) self.animationgroup.start() def animationFineshedZoomIn(self): pass def editorClosed(self): pos = self.sourcewidget.mapTo(self, QPoint(0, 0)) # correct end values in case of resizing the window self.animx.setStartValue(pos.x()) self.animy.setStartValue(pos.y()) self.animw.setStartValue(self.sourcewidget.size().width()) self.animh.setStartValue(self.sourcewidget.size().height()) self.animationgroup.setDirection(QAbstractAnimation.Backward) #self.animationgroup.finished()), this, SLOT(animationFineshedZoomIn())) #connect(m_animationgroup, SIGNAL(finished()), this, SLOT(animationFineshedZoomOut())) self.animationgroup.start() item = self.list.item(self.row, 1) item.setData(Qt.UserRole, self.editor.slide) item.setText(self.editor.slide.title) if self.editor.changed: self.contentChanged() def animationFineshedZoomOut(self): #delete m_animationgroup #delete m_editor #self.editor = None pass def deleteSlide(self, slide): for row in range(self.list.rowCount()): item = self.list.item(row, 1) m = item.data(Qt.UserRole) if m == slide: self.list.removeRow(row) self.contentChanged() break def editSlide(self, slide): for row in range(self.list.rowCount()): item = self.list.item(row, 1) m = item.data(Qt.UserRole) if m == slide: self.list.selectRow(row) self.tableDoubleClicked(row) break
class CarouselEditor(ElementEditorInterface): def __init__(self): ElementEditorInterface.__init__(self) self.class_name = "CarouselEditor" self.display_name = QCoreApplication.translate("CarouselEditor", "Carousel") self.tag_name = "Carousel" self.version = "1.0" self.icon = QImage(":/carousel.png") self.changed = False #self.editor = 0 self.setAutoFillBackground(True) grid = QGridLayout() self.id = QLineEdit() self.id.setMaximumWidth(200) self.adminlabel = QLineEdit() self.adminlabel.setMaximumWidth(200) titleLabel = QLabel(QCoreApplication.translate("CarouselEditor", "Carousel Module")) fnt = titleLabel.font() fnt.setPointSize(16) fnt.setBold(True) titleLabel.setFont(fnt) close = FlatButton(":/images/close_normal.png", ":/images/close_hover.png") close.setToolTip(QCoreApplication.translate("general", "Close Editor")) addSlide = QPushButton(QCoreApplication.translate("CarouselEditor", "Add Slide")) addSlide.setMaximumWidth(120) self.list = QTableWidget(0, 2, self) self.list.verticalHeader().hide() self.list.setSelectionMode(QAbstractItemView.SingleSelection) self.list.setSelectionBehavior(QAbstractItemView.SelectRows) self.list.horizontalHeader().setSectionResizeMode(1, QHeaderView.Stretch ) self.list.setToolTip(QCoreApplication.translate("CarouselEditor", "Double click to edit item")) labels = ["", "Name"] self.list.setHorizontalHeaderLabels(labels) grid.addWidget(titleLabel, 0, 0) grid.addWidget(close, 0, 2, 1, 1, Qt.AlignRight) grid.addWidget(addSlide, 1, 0) grid.addWidget(self.list, 2, 0, 1, 3) grid.addWidget(QLabel("Id"), 4, 0) grid.addWidget(self.id, 5, 0) grid.addWidget(QLabel(QCoreApplication.translate("CarouselEditor", "Admin Label")), 6, 0) grid.addWidget(self.adminlabel, 7, 0) self.setLayout(grid) addSlide.clicked.connect(self.addSlide) self.adminlabel.textChanged.connect(self.contentChanged) self.id.textChanged.connect(self.contentChanged) close.clicked.connect(self.closeEditor) self.list.cellDoubleClicked.connect(self.tableDoubleClicked) self.installEventFilter(self) def closeEditor(self): if self.changed: if self.content: self.content.id = self.id.text() self.content.adminlabel = self.adminlabel.text() self.close.emit() def setContent(self, content): self.content = content if content: self.id.setText(content.id) self.adminlabel.setText(content.adminlabel) self.changed = False def getContent(self): return self.content def registerContenType(self): qmlRegisterType(Carousel, 'Carousel', 1, 0, 'Carousel') qmlRegisterType(Slide, 'Carousel', 1, 0, 'Slide') def getImportString(self): return "import Carousel 1.0\n" def getDefaultContent(self): return Carousel() def addSlide(self): slide = Slide() self.addListItem(slide) self.contentChanged() self.tableDoubleClicked(self.list.rowCount() - 1) def addListItem(self, slide): rows = self.list.rowCount() self.list.setRowCount(rows + 1) tcb = TableCellButtons() tcb.setItem(slide) tcb.deleteItem.connect(self.deleteSlide) tcb.editItem.connect(self.editSlide) self.list.setCellWidget(rows, 0, tcb) self.list.setRowHeight(rows, tcb.sizeHint().height()) titleItem = QTableWidgetItem(slide.title) titleItem.setFlags(titleItem.flags() ^ Qt.ItemIsEditable) titleItem.setData(Qt.UserRole, slide) self.list.setItem(rows, 1, titleItem) def tableDoubleClicked(self, row): item = self.list.item(row, 1) slide = item.data(Qt.UserRole) self.editor = SlideEditor() #self.editor.setSite(self.site) self.editor.setSlide(slide) self.editor.closes.connect(self.editorClosed) self.animate(item) def animate(self, item): self.row = item.row() # create a cell widget to get the right position in the table self.sourcewidget = QWidget() self.list.setCellWidget(self.row, 1, self.sourcewidget) pos = self.sourcewidget.mapTo(self, QPoint(0,0)) self.editor.setParent(self) self.editor.move(pos) self.editor.resize(self.sourcewidget.size()) self.editor.show() self.animationgroup = QParallelAnimationGroup() self.animx = QPropertyAnimation() self.animx.setDuration(300) self.animx.setStartValue(pos.x()) self.animx.setEndValue(0) self.animx.setTargetObject(self.editor) self.animx.setPropertyName("x".encode("utf-8")) self.animationgroup.addAnimation(self.animx) self.animy = QPropertyAnimation() self.animy.setDuration(300) self.animy.setStartValue(pos.y()) self.animy.setEndValue(0) self.animy.setTargetObject(self.editor) self.animy.setPropertyName("y".encode("utf-8")) self.animationgroup.addAnimation(self.animy) self.animw = QPropertyAnimation() self.animw.setDuration(300) self.animw.setStartValue(self.sourcewidget.size().width()) self.animw.setEndValue(self.size().width()) self.animw.setTargetObject(self.editor) self.animw.setPropertyName("width".encode("utf-8")) self.animationgroup.addAnimation(self.animw) self.animh = QPropertyAnimation() self.animh.setDuration(300) self.animh.setStartValue(self.sourcewidget.size().height()) self.animh.setEndValue(self.size().height()) self.animh.setTargetObject(self.editor) self.animh.setPropertyName("height".encode("utf-8")) self.animationgroup.addAnimation(self.animh) self.animationgroup.finished.connect(self.animationFineshedZoomIn) self.animationgroup.start() def animationFineshedZoomIn(self): pass def editorClosed(self): pos = self.sourcewidget.mapTo(self, QPoint(0,0)) # correct end values in case of resizing the window self.animx.setStartValue(pos.x()) self.animy.setStartValue(pos.y()) self.animw.setStartValue(self.sourcewidget.size().width()) self.animh.setStartValue(self.sourcewidget.size().height()) self.animationgroup.setDirection(QAbstractAnimation.Backward) #self.animationgroup.finished()), this, SLOT(animationFineshedZoomIn())); #connect(m_animationgroup, SIGNAL(finished()), this, SLOT(animationFineshedZoomOut())); self.animationgroup.start() item = self.list.item(self.row, 1) item.setData(Qt.UserRole, self.editor.slide) item.setText(self.editor.slide.title) if self.editor.changed: self.contentChanged() def animationFineshedZoomOut(self): #delete m_animationgroup #delete m_editor #self.editor = None pass def deleteSlide(self, slide): for row in range(self.list.rowCount()): item = self.list.item(row, 1) m = item.data(Qt.UserRole) if m == slide: self.list.removeRow(row) self.contentChanged() break def editSlide(self, slide): for row in range(self.list.rowCount()): item = self.list.item(row, 1) m = item.data(Qt.UserRole) if m == slide: self.list.selectRow(row) self.tableDoubleClicked(row) break
class CollapsibleMessageBox(QWidget): ''' docstring: 消息显示类,按钮触发折叠 ''' def __init__(self, Title="", parent=None, defaultLayout=False, Message=None): super().__init__(parent) self.toggle_button = QToolButton(text=Title, checkable=True, checked=False) self.toggle_button.setStyleSheet("QToolButton { border: none; }") self.toggle_button.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) self.toggle_button.setArrowType(Qt.RightArrow) self.toggle_button.pressed.connect(self.on_pressed) self.toggle_animation = QParallelAnimationGroup(self) self.content_area = QScrollArea(maximumHeight=0, minimumHeight=0) self.content_area.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.content_area.setFrameShape(QFrame.NoFrame) lay = QVBoxLayout(self) lay.setSpacing(0) lay.setContentsMargins(0, 0, 0, 0) lay.addWidget(self.toggle_button) lay.addWidget(self.content_area) self.toggle_animation.addAnimation( QPropertyAnimation(self, b"minimumHeight")) self.toggle_animation.addAnimation( QPropertyAnimation(self, b"maximumHeight")) self.toggle_animation.addAnimation( QPropertyAnimation(self.content_area, b"maximumHeight")) if defaultLayout: lay = QVBoxLayout() self.text = QLabel() pa = QPalette() pa.setColor(pa.Background, Qt.white) pa.setColor(pa.Foreground, Qt.black) self.text.setAutoFillBackground(True) self.text.setPalette(pa) self.text.setTextInteractionFlags(Qt.TextSelectableByMouse) self.text.setTextFormat(Qt.MarkdownText) self.text.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Minimum) self.text.setWordWrap(True) if not Message: Message = '空' self.text.setText(Message.replace('\n', '\n\n') + '\n') lay.addWidget(self.text) self.setContentLayout(lay) @pyqtSlot() def on_pressed(self): ''' docstring: 按钮函数,设置动画参数并触发 ''' checked = self.toggle_button.isChecked() self.toggle_button.setArrowType( Qt.DownArrow if not checked else Qt.RightArrow) self.toggle_animation.setDirection( QAbstractAnimation.Forward if not checked else QAbstractAnimation. Backward) self.toggle_animation.start() def setContentLayout(self, layout): ''' docstring: 重新设置布局,并计算按钮动画参数 ''' lay = self.content_area.layout() del lay self.content_area.setLayout(layout) collapsed_height = (self.sizeHint().height() - self.content_area.maximumHeight()) content_height = layout.sizeHint().height() for i in range(self.toggle_animation.animationCount()): animation = self.toggle_animation.animationAt(i) animation.setDuration(500) animation.setStartValue(collapsed_height) animation.setEndValue(collapsed_height + content_height) content_animation = self.toggle_animation.animationAt( self.toggle_animation.animationCount() - 1) content_animation.setDuration(500) content_animation.setStartValue(0) content_animation.setEndValue(content_height)
class SidebarItem(QWidget): on_pressed = pyqtSignal(bool) def __init__(self, text, pixmap=None, parent=None): super().__init__(parent) rgb_value = 255 if options.theme == 'dark' else 0 self.theme = options.theme # You are supposed to set size policy like this, and not to override sizePolicy() # If vertical policy was preferred, then you would have to implement minimumSizeHint and # maximumSize to limit how much the item can shrink and grow. self.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Fixed) self.text = text self.pixmap = pixmap self._prepare_pixmap(QColor(rgb_value, rgb_value, rgb_value, 200)) self.spacing = 12 self.font = QApplication.font() self.font.setBold(True) self.fm = QFontMetrics(self.font) self.mouse_over = False self.checked = False self.active = False self.setMouseTracking(True) self.background_color = QColor(rgb_value, rgb_value, rgb_value, 0) self.background_animation = QPropertyAnimation(self, b'opacity') self.background_animation.setStartValue(0) self.background_animation.setEndValue(40) self.background_animation.setDuration(300) self.indicator_pos = 5 self.indicator_position_anim = QPropertyAnimation( self, b'indicator_position') self.indicator_position_anim.setStartValue(5) self.indicator_position_anim.setEndValue(-5) self.indicator_position_anim.setDuration(300) self.indicator_background = QColor(rgb_value, rgb_value, rgb_value, 0) self.indicator_opacity_anim = QPropertyAnimation( self, b'indicator_opacity') self.indicator_opacity_anim.setStartValue(0) self.indicator_opacity_anim.setEndValue(140) self.indicator_opacity_anim.setDuration(300) self.all_animations = QParallelAnimationGroup() self.all_animations.addAnimation(self.background_animation) self.all_animations.addAnimation(self.indicator_position_anim) self.all_animations.addAnimation(self.indicator_opacity_anim) OptionEventChannel.subscribe('theme', self.handle_theme_change) def mousePressEvent(self, event): super().mousePressEvent(event) self.on_pressed.emit(True) def set_checked(self, checked): self.checked = checked self.check_active() def check_active(self): old_active = self.active if self.checked: self.active = True else: self.active = self.mouse_over if old_active == self.active: return if self.active is True: self.all_animations.setDirection(QParallelAnimationGroup.Forward) self.all_animations.start() else: self.all_animations.setDirection(QParallelAnimationGroup.Backward) self.all_animations.start() # Calling update 10 times will result in only one update. # So it's fine to do this even after starting the animations. self.update() def enterEvent(self, event): self.mouse_over = True self.check_active() def leaveEvent(self, event): self.mouse_over = False self.check_active() def sizeHint(self): # sizeHint is used by layouts to get the recommended size of the widget. return QSize(100, 40) # def minimumSizeHint(self): # # minimumSizeHint is used by layouts to get the minimum size of the widget. # # It's ignored if minimumSize is also implemented # return self.sizeHint() def paintEvent(self, paint_event): super().paintEvent(paint_event) widget_rect = self.rect() painter = QPainter(self) painter.setRenderHint(QPainter.Antialiasing) painter.fillRect(widget_rect, self.background_color) if self.mouse_over: indicator_rect = QRect( widget_rect.width() + self.indicator_pos - 6, (widget_rect.height() - 6) // 2, 6, 9) triangle_path = QPainterPath(indicator_rect.topRight()) triangle_path.lineTo(indicator_rect.bottomRight()) mid_left = indicator_rect.bottomLeft() mid_left.setY(mid_left.y() - indicator_rect.height() // 2) triangle_path.lineTo(mid_left) triangle_path.lineTo(indicator_rect.topRight()) painter.fillPath(triangle_path, self.indicator_background) if self.pixmap: pixmap_rect = QRect(self.spacing, (widget_rect.height() - 20) // 2, 20, 20) painter.drawPixmap(pixmap_rect, self.pixmap, self.pixmap.rect()) # painter.drawRect(pixmap_rect) else: pixmap_rect = QRect(0, 0, 0, 0) text_rect = QRect(pixmap_rect.right() + self.spacing, 0, 0, widget_rect.height()) text_rect.setWidth(widget_rect.width() - text_rect.left()) # painter.drawRect(text_rect) text = self.fm.elidedText(self.text, Qt.ElideRight, text_rect.width()) painter.setFont(self.font) painter.drawText(text_rect, Qt.AlignVCenter | Qt.AlignLeft, text) def _prepare_pixmap(self, qcolor): if self.pixmap is None: return painter = QPainter(self.pixmap) painter.setCompositionMode(QPainter.CompositionMode_SourceAtop) painter.fillRect(self.pixmap.rect(), qcolor) painter.end() def _get_opacity(self): return self.background_color.alpha() def _set_opacity(self, new_value): self.background_color.setAlpha(new_value) self.update() opacity = pyqtProperty('int', _get_opacity, _set_opacity) def _get_indicator_position(self): return self.indicator_pos def _set_indicator_position(self, new_value): self.indicator_pos = new_value self.update() indicator_position = pyqtProperty('int', _get_indicator_position, _set_indicator_position) def _get_indicator_opacity(self): return self.indicator_background.alpha() def _set_indicator_opacity(self, new_value): self.indicator_background.setAlpha(new_value) self.update() indicator_opacity = pyqtProperty('int', _get_indicator_opacity, _set_indicator_opacity) def handle_theme_change(self, theme): if theme == self.theme: return self.theme = theme if theme == 'dark': rgb_value = 255 else: rgb_value = 0 self.background_color.setRgb(rgb_value, rgb_value, rgb_value, self.background_color.alpha()) self.indicator_background.setRgb(rgb_value, rgb_value, rgb_value, self.indicator_background.alpha()) self._prepare_pixmap(QColor(rgb_value, rgb_value, rgb_value, 200)) self.update()