class Page(QWidget): Hiding = QtCore.pyqtSignal(int) Showing = QtCore.pyqtSignal(int) def __init__(self, index: int, page_name: str, parent: QWidget = None): super(Page, self).__init__(parent) self.myIndex: int = index self.setObjectName(page_name) self.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.MinimumExpanding) self.setContentsMargins(0, 0, 0, 0) self.hboxlayout = QHBoxLayout(self) self.hboxlayout.setContentsMargins(0, 0, 0, 0) self.hboxlayout.setSpacing(0) self.setLayout(self.hboxlayout) scroll_area = QScrollArea(self) scroll_area.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) scroll_area.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) scroll_area.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.MinimumExpanding) scroll_area.setFrameShape(QFrame.NoFrame) scroll_area.setFrameShadow(QFrame.Plain) scroll_area.setLineWidth(0) scroll_area.setWidgetResizable(True) scroll_area.installEventFilter(TTScroller(scroll_area)) self.inner_area = QFrame() self.inner_area.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Ignored) self.inner_area.setProperty("TTPage", QtCore.QVariant(True)) self.inner_area.setContentsMargins(0, 0, 0, 0) self.inner_area.setFrameShape(QFrame.Box) self.inner_area.setLineWidth(0) self.innerLayout = QHBoxLayout(self.inner_area) self.innerLayout.setContentsMargins(0, 0, 0, 0) self.innerLayout.setSpacing(2) self.inner_area.setLayout(self.innerLayout) spacer = QSpacerItem(0, 0, QSizePolicy.Expanding, QSizePolicy.Minimum) self.innerLayout.addItem(spacer) scroll_area.setWidget(self.inner_area) self.hboxlayout.addWidget(scroll_area) def add_group(self, name: str): grp = group.Group(name, self.inner_area) self.innerLayout.insertWidget(self.innerLayout.count() - 1, grp) parent_toolbar = tabtoolbar.find_tabtoolbar(self) # type: ignore[attr-defined] if not parent_toolbar: raise Exception("Could not find Parent Tabtoolbar") parent_toolbar.adjust_verticalsize(grp.height()) return grp def hide(self): self.Hiding.emit(self.myIndex) def show(self): self.Showing.emit(self.myIndex)
class Tool(QFrame): def __init__(self, cfg): super().__init__() self.cfg = cfg self.custom_groups = {} self.selected_line_text = [] self.setFixedHeight(cfg["tool_height"]) # self.setStyleSheet("background-color: lightgrey") self.box = QHBoxLayout(self) self.word_line = LineEdit() # self.word_line.selectionChanged.connect(self.set_select_text) # self.word_line.editingFinished.connect(self.set_edit_text) self.search_btn = ToolBtn("search") self.config_btn = ToolBtn("config") self.box.addWidget(self.word_line) self.box.addWidget(self.search_btn) self.box.addWidget(CustomSpace(min_w=10)) # self.add_btn("uk") self.box.addStretch(1) self.box.addWidget(self.config_btn) # def set_select_text(self): self.selected_line_text.append(self.word_line.selectedText()) def set_edit_text(self): print(self.word_line.selectedText(), 888) @property def line_edit_text(self): return self.word_line.toPlainText() def set_line_validator(self, reg=None): if reg is None: reg = "[\d\w!]+" validator = QRegExpValidator(QRegExp(reg)) self.word_line.setValidator(validator) def set_custom_dict(self): self.custom_groups["dict"] = CustomButton("ru") self.box.insertWidget(3, self.custom_groups["dict"]) def del_custom_dict(self): try: self.box.removeWidget(self.custom_groups["dict"]) except KeyError: pass def selected_text(self): self.word_line.setFocus() cursor = self.word_line.textCursor() textSelected = cursor.selectedText() return textSelected
class PreviewWidget(QScrollArea): def __init__(self, parent=None): super(PreviewWidget, self).__init__(parent) self.setWidgetResizable(True) self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOn) self.widget = QWidget() self.layout = QHBoxLayout() self.layout.addStretch(1) self.layout.setContentsMargins(0, 0, 0, 0) self.widget.setLayout(self.layout) self.setWidget(self.widget) def removeLast(self): item = self.layout.takeAt(0) if item: item.widget().deleteLater() def resizeEvent(self, event): self.widget.setFixedHeight(self.viewport().height()) super(PreviewWidget, self).resizeEvent(event) def addPixmap(self, pixmap): label = ImageLabel(pixmap, self) self.layout.insertWidget(0, label)
class ContainerItemWidget(QWidget): def __init__(self, label_text, init_color=(0., 0., 0., 0.)): super().__init__() self.layout = QHBoxLayout() self.layout.setContentsMargins(0, 0, 0, 0) self.layout.setAlignment(Qt.AlignLeft) self.check_box = QCheckBox() # self.check_box.setMaximumWidth(18) self.check_box.setCheckState(Qt.Checked) self.color_select = ColorButton(init_color) self.label = QLabel(label_text) self.setLayout(self.layout) self.layout.addWidget(self.check_box) self.layout.addWidget(self.color_select) self.layout.addWidget(self.label) def start_edit_label(self): self.line_edit = QLineEdit(self.label.text()) self.layout.insertWidget(2, self.line_edit) self.line_edit.returnPressed.connect(self.finish_edit_label) def finish_edit_label(self): self.label.setText(self.line_edit.text()) self.line_edit.hide() def set_edit_label(self, text): self.label.setText(text)
class MenuBar(QTabWidget): def __init__(self, parent=None): super(MenuBar, self).__init__(parent) tabbar = TabBar(parent) self.setTabBar(tabbar) self.setMinimumHeight(135) self.setMouseTracking(True) self._drop = False self.currentChanged.connect(self.currentChangedFunc) def currentChangedFunc(self, index): tab_text = self.tabText(index) menu = self.findChild(MenuWidget, tab_text) self.anim = QPropertyAnimation(menu, b'_height') self.anim.setDuration(100) self.anim.setStartValue(0) self.anim.setEndValue(100) self.anim.start() def addMenu(self, p_str): p_str = " {p_str} ".format(p_str=p_str) menu = MenuWidget() menu.setObjectName(p_str) self.addTab(menu, p_str) self.hlayout = QHBoxLayout(menu) self.hlayout.setObjectName(p_str) self.hlayout.setContentsMargins(0, 0, 0, 0) self.hlayout.setSpacing(0) hs = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) self.hlayout.addItem(hs) return (menu) def addGroup(self, p_str, menu): group = GroupWidget(p_str, menu) group.setObjectName('group') insert_index = len(menu.findChildren(GroupWidget, 'group')) - 1 self.hlayout.insertWidget(insert_index, group) return (group) def listGroups(self, menu): self.group_list = [] for i in range(self.hlayout.count()): try: w = self.hlayout.itemAt(i).widget() self.group_list.append(w._title) except: AttributeError return (self.group_list) def addSliderChoiceWidget(self, menu): slider_choice = SliderChoiceWidget() insert_index = len(menu.findChildren(GroupWidget, 'group')) self.hlayout.insertWidget(insert_index, slider_choice) return (slider_choice)
class ItemDialog(QWidget): def __init__(self, parent=None): QListWidgetItem.__init__(self, parent) layNameMessage = QVBoxLayout() self.user_id = 0 self.name = QLabel() self.message = QLabel() self.layMessage = QHBoxLayout() self.layMessage.addWidget(self.message, alignment=Qt.AlignLeft) layNameMessage.addWidget(self.name) layNameMessage.addLayout(self.layMessage) allLayout = QHBoxLayout() allLayout.addLayout(layNameMessage) self.w = QWidget() self.w.setLayout(allLayout) mainLayout = QHBoxLayout() mainLayout.addWidget(self.w) self.setLayout(mainLayout) def setUnread(self, unread): if unread > 0: self.w.setStyleSheet("background-color:" + MyData.COLOR_BACKGROUND + ";") def setReadState(self, readState): if readState == 0: myAvatar = QLabel() pixmap = QPixmap() self.layMessage.insertWidget(0, myAvatar, alignment=Qt.AlignLeft) pixmap.load('src/sheep_icon.png') myPixmap = pixmap.scaled(20, 20, Qt.KeepAspectRatio, Qt.FastTransformation) myAvatar.setPixmap(myPixmap) self.message.setStyleSheet('color:gray;') def setName(self, name, user_id): self.user_id = user_id self.name.setText("<p> <strong>" + name + "</strong></p>") def setMessage(self, message): self.message.setText(message)
class GameView(QObject): unit_changed = pyqtSignal(UnitType) def __init__(self): super().__init__() self.game_window_ = QMainWindow() self.main_widget_ = QWidget() self.view_widget_ = QGraphicsView() self.british_creating_panel_ = UnitCreatingPanel( Game().get_british_player()) self.french_creating_panel_ = UnitCreatingPanel( Game().get_french_player()) self.current_creating_panel = self.british_creating_panel_ self.init_window() self.init_main_widget() self.british_creating_panel_.connect_radios(self.unit_changed) self.french_creating_panel_.connect_radios(self.unit_changed) def init_window(self): self.game_window_.setCentralWidget(self.main_widget_) self.game_window_.setWindowTitle('rly stupid game') self.game_window_.show() def init_main_widget(self): self.main_widget_.setMinimumSize(1000, 700) self.main_widget_layout = QHBoxLayout() self.main_widget_layout.addWidget(self.british_creating_panel_) self.main_widget_layout.addWidget(self.view_widget_) self.main_widget_.setLayout(self.main_widget_layout) def set_scene(self, scene): self.view_widget_.setScene(scene) scene.add_map_tiles() def update_after_adding_unit(self): self.british_creating_panel_.update_after_adding_unit() self.french_creating_panel_.update_after_adding_unit() def update_after_turn_change(self): self.main_widget_layout.removeWidget(self.current_creating_panel) self.current_creating_panel.hide() if self.current_creating_panel is self.british_creating_panel_: self.current_creating_panel = self.french_creating_panel_ self.main_widget_layout.addWidget(self.current_creating_panel) else: self.current_creating_panel = self.british_creating_panel_ self.main_widget_layout.insertWidget(0, self.current_creating_panel) self.current_creating_panel.show()
class PopupContainer(BaseView): on_drop = pyqtSignal() def __init__(self, *args, widget:QWidget, size_w=600, backdrop=True, **kwargs): super(PopupContainer, self).__init__(*args, **kwargs) self.backdrop = backdrop self.widget = widget self.size_w = size_w self.procedure() def set_ui(self): self.container = QWidget() self.container.setObjectName('Container') self.container.setFixedWidth(self.size_w) self.btn_close = QPushButton() self.btn_close.setText("关闭") def place(self): layout = QGridLayout(self) layout.setSpacing(0) layout.setContentsMargins(0, 0, 0, 0) layout_container = QVBoxLayout() layout_container.setSpacing(0) layout_container.setContentsMargins(12, 12, 12, 12) self.layout_action = QHBoxLayout() self.layout_action.setContentsMargins(0, 10, 0, 0) layout.addItem(QSpacerItem(1, 1, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding), 0, 1, 1, 1) layout.addWidget(self.container, 2, 2, 1, 1) self.container.setLayout(layout_container) layout_container.addWidget(self.widget) self.layout_action.addSpacerItem(QSpacerItem(0, 0, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)) self.layout_action.addWidget(self.btn_close) layout_container.addLayout(self.layout_action) def configure(self): self.btn_close.clicked.connect(lambda :self.on_drop.emit()) def add_action(self, data, callback): btn = QPushButton() btn.setText(data) self.layout_action.insertWidget(0, btn) btn.clicked.connect(callback) def mousePressEvent(self, event) -> None: pos = event.pos() if self.backdrop and self.childAt(pos) == None and pos.x() < self.container.x() or pos.x() > self.container.x() + self.size_w or pos.y() < self.container.y(): self.on_drop.emit() return super(PopupContainer, self).mousePressEvent(event)
def __init__(self, layout, parent=None): super().__init__(parent) self.player = QMediaPlayer(self) self.player.setAudioRole(QAudio.MusicRole) self.player.durationChanged.connect(self.duration_changed) self.player.positionChanged.connect(self.position_changed) self.player.mediaStatusChanged.connect(self.status_changed) self.player.bufferStatusChanged.connect(self.buffering_progress) self.player.stateChanged.connect(self.state_changed) self.player.error[QMediaPlayer.Error].connect( self.display_error_message) self.play_control = PlayControl(layout, parent) self.play_control.set_state(self.player.state()) self.play_control.play.connect(self.player.play) self.play_control.pause.connect(self.player.pause) self.play_control.stop.connect(self.player.stop) self.player.stateChanged.connect(self.play_control.set_state) self.song_label = QLabel() self.song_label.setText(self.NO_PLAYING_SONG) self.song_label.setWordWrap(True) song_grid = QGridLayout(parent) song_grid.addWidget(self.song_label, 0, 0) self.duration_label = QLabel() self.duration_label.setText("00:00 / 00:00") self.slider = QSlider(Qt.Horizontal, parent) self.slider.setRange(0, self.player.duration() / 1000) self.slider.sliderMoved.connect(self.seek) progress_layout = QHBoxLayout(parent) progress_layout.insertWidget(0, self.duration_label) progress_layout.insertWidget(0, self.slider) song_grid.addLayout(progress_layout, 1, 0) self.status_label = QLabel() song_grid.addWidget(self.status_label, 2, 0) layout.addLayout(song_grid) if not self.is_player_available(): QMessageBox.warning(self, "警告", "QMediaPlayer 对象无可用的服务") return self.song = None
def createCategories(self): pass #NOTE might want to store the category labels in case they need to change or whatever. # self.categories = {} categoriesLayout = QHBoxLayout() for category, clues in triviaInfo.items(): pass theLabel = QLabel(category) theLabel.setFrameShape(QFrame.Box) theLabel.setLineWidth(2) theLabel.setFixedSize(150, 100) theLabel.setStyleSheet('background-color : blue; color : yellow') categoriesLayout.insertWidget(int(clues[0].getX()), theLabel) self.categories[category] = theLabel self.generalLayout.addLayout(categoriesLayout)
class AnnotationItemWidget(QWidget): def __init__(self, label_text, count=0): super().__init__() self.layout = QHBoxLayout() self.layout.setContentsMargins(0, 0, 0, 0) self.layout.setAlignment(Qt.AlignLeft) self.check_box = QCheckBox() self.check_box.setMaximumWidth(18) self.check_box.setCheckState(Qt.Checked) self.color_select = ColorButton() self.label = QLabel(label_text) self.setLayout(self.layout) self.count_label = QLabel(str(count)) img_path = get_image_path('spreadsheet.svg') self.view_btn = QPushButton() self.view_btn.setFixedWidth(20) self.view_btn.setFixedHeight(20) self.view_btn.setStyleSheet("QPushButton{border: 0px;}") self.view_btn.setIcon(QIcon(img_path)) self.layout.addWidget(self.check_box) self.layout.addWidget(self.color_select) self.layout.addWidget(self.label) self.layout.addWidget(self.count_label) self.layout.addWidget(self.view_btn) def set_count(self, count): self.count_label.setText(str(count)) def start_edit_label(self): self.line_edit = QLineEdit(self.label.text()) self.layout.insertWidget(2, self.line_edit) self.line_edit.returnPressed.connect(self.finish_edit_label) def finish_edit_label(self): self.label.setText(self.line_edit.text()) self.line_edit.hide()
class MyRow(QWidget): def __init__(self, parent): super(MyRow,self).__init__(parent) self.__initUI() def __initUI(self): self.setStyleSheet('margin:1px;') self.layout = QHBoxLayout(self) self.layout.addStretch(1) pass def addWidget(self,item): self.layout.insertWidget(self.layout.count()-1,item) pass
class GamePlayerWidget(QGroupBox): def __init__(self, nick, colour=None, parent=None): super(GamePlayerWidget, self).__init__(parent) self.player = nick self.pcolour = colour self.initUI() def initUI(self): # self.setMinimumWidth(300) self.mainLayout = QHBoxLayout(self) # self.mainLayout.addStretch() self.scoreLCD = QLCDNumber(self) self.scoreLCD.setSegmentStyle(QLCDNumber.Flat) self.mainLayout.addWidget(self.scoreLCD) self.scoreLCD.setNumDigits(3) self.scoreLCD.setFixedSize(100, 60) self.scoreLCD.display(0) css = "QLCDNumber {{ color:rgb({},{},{});}}" self.scoreLCD.setStyleSheet(css.format(self.pcolour.red(), self.pcolour.green(), self.pcolour.blue())) self.nameLabel = QLabel(self) self.nameLabel.setText(self.player) sh = ("QLabel {{ font-size: 32px; font-weight: " "bold; color:rgb({},{},{});}}") self.nameLabel.setStyleSheet(sh.format(self.pcolour.red(), self.pcolour.green(), self.pcolour.blue())) self.mainLayout.addWidget(self.nameLabel) self.dealerPixmap = QtGui.QPixmap('icons/cards.png') self.nonDealerPixmap = QtGui.QPixmap() self.winnerPixmap = QtGui.QPixmap('icons/winner.png') self.iconlabel = IconLabel(self) self.iconlabel.setFixedSize(50, 50) self.iconlabel.setScaledContents(True) self.mainLayout.insertWidget(0, self.iconlabel) # self.mainLayout.addStretch() self.unsetDealer() def updateDisplay(self, points): if points >= 1000: self.scoreLCD.setNumDigits(4) self.scoreLCD.display(points) def setDealer(self): self.iconlabel.setPixmap(self.dealerPixmap) def unsetDealer(self): self.iconlabel.setPixmap(self.nonDealerPixmap) def setWinner(self): self.iconlabel.setPixmap(self.winnerPixmap) def setColour(self, colour): self.pcolour = colour css = "QLCDNumber {{ color:rgb({},{},{});}}" self.scoreLCD.setStyleSheet(css.format(self.pcolour.red(), self.pcolour.green(), self.pcolour.blue())) sh = ("QLabel {{ font-size: 32px; font-weight: bold; " "color:rgb({},{},{});}}") self.nameLabel.setStyleSheet(sh.format(self.pcolour.red(), self.pcolour.green(), self.pcolour.blue()))
class TaskWidget(QWidget): on_remove = pyqtSignal(int) on_review = pyqtSignal(int) def __init__(self, rid, text, actions, icon=None, max_text_width=200, parent=None): super().__init__(parent) self.rid = rid self.actions = actions self.setObjectName("TaskWidget") self.label = QLabel(text) self.label.setWordWrap(True) self.label.setFixedWidth(max_text_width) if icon: self.icon_btn = QToolButton() self.icon_btn.setIcon(icon) self.icon_btn.setMaximumSize(20, 20) self.icon_btn.setAutoRaise(True) else: self.icon_btn = None self.checker = None self.layout = QHBoxLayout() self.layout.addWidget(self.label) if icon: self.layout.addWidget(self.icon_btn) self.layout.addStretch(0) self.setLayout(self.layout) def add_checker(self): # if checker is present just return if self.checker: return self.checker = QCheckBox() self.layout.insertWidget(0, self.checker) def remove_checker(self): self.layout.removeWidget(self.checker) self.checker.deleteLater() self.checker = None def set_text(self, text): self.label.setText(text) def mousePressEvent(self, event): super().mousePressEvent(event) if event.button() != Qt.RightButton: return action_menu = QMenu() action_map = {} for action in self.actions: if action.icon is None: a = action_menu.addAction(action.text) else: a = action_menu.addAction(action.icon, action.text) action_map[a] = action chosen_action = action_menu.exec_(self.mapToGlobal(event.pos())) if chosen_action is None: return action_map[chosen_action].signal.emit(self.mapToGlobal(event.pos()))
class Main(UI_Main): resized = QtCore.pyqtSignal() total_price: int = 0 def __init__(self): super().__init__() self.v = QVBoxLayout() self.h = QHBoxLayout() self.v.addStretch(1) self.h.addStretch(1) self.v.addLayout(self.h) self.v.addStretch(1) self.h.addStretch(1) self.modal_container = QFrame(self) self.modal_container.setObjectName('modal_container') self.modal_container.setLayout(self.v) self.resized.connect(self.on_mainWindow_resized) self.open_modal(self.login_form) def resizeEvent(self, event): self.resized.emit() def sync_cart(self): self.clear_cart() layout = self.table.products_layout for i in database.get_cart(): self.update_total_price(i._selling_price * i._count) layout.insertWidget(layout.count() - 1, ProductTableItemWidget(i)) def clear_cart(self): self.clear_layout(self.table.products_layout, 1) self.total_price = 0 self.update_payment() def add2cart(self): product = self.sender().product database.add2cart(product.id) layout = self.table.products_layout product._count = 1 self.update_total_price(product._selling_price * product._count) layout.insertWidget(0, ProductTableItemWidget(product)) def update_total_price(self, add: int): self.total_price += add self.update_payment() def on_mainWindow_resized(self): if self.modal_container: self.modal_container.setFixedSize(self.size()) @QtCore.pyqtSlot() def on_plus_clicked(self): self.open_modal(self.add_product_form) # self.open_add_product() @QtCore.pyqtSlot(str) def on_search_textEdited(self, filter_str: str = ''): self.update_list(filter_str) @QtCore.pyqtSlot(int) def on_to_pay_value_valueChanged(self, value: int = 0): self.update_payment() @QtCore.pyqtSlot() def on_pay_btn_clicked(self): database.sell() self.payment.to_pay_value.setValue(0) self.clear_cart() def update_payment(self): self.payment.surrender_value.setText( f'{self.payment.to_pay_value.value() - self.total_price}') self.payment.total_value.setText(f'{self.total_price}') def open_add_product(self): self.add_product_form.show() def update_list(self, filter_str: str = ''): self.clear_list() for p in database.get_products(filter_str): product = ProductWidget(p) product.clicked.connect(self.add2cart) self.sidebar.products_layout.insertWidget( self.sidebar.products_layout.count() - 1, product) def clear_list(self): self.clear_layout(self.sidebar.products_layout, 1) @staticmethod def clear_layout(layout, offset=0): for i in range(layout.count() - offset): layout.itemAt(i).widget().close() def open_modal(self, modal: QWidget): self.modal_container.setFixedSize(self.size()) self.modal_container.show() self.h.insertWidget(1, modal, 1) self.h.itemAt(1).widget().show() shadow = QGraphicsDropShadowEffect() shadow.setColor(QColor(0, 0, 0, 100)) shadow.setBlurRadius(50) shadow.setOffset(0, 10) modal.setGraphicsEffect(shadow) modal.setObjectName("modal") def close_modal(self): self.modal_container.hide() def close_add_product_form(self): self.close_modal() self.update_list() def close_login_form(self): self.close_modal() self.navbar.shop.setText(database.dbname) self.update_data() def update_data(self): self.update_list() self.sync_cart()
class MyWindow(QWidget): """main window""" def __init__(self): super().__init__() self.auths = {} self.activeaccounts = [] self.streams = [] self.tweets = [] self.tagarray = [] self.tweettags = [] self.receivetags = [] self.following = [] self.searchstream = None self.init_main() self.init_accounts() self.init_tweets() self.init_widgets() self.show() sys.exit(app.exec_()) def init_main(self): """options of main window""" self.setGeometry(300, 100, 1000, 600) self.setWindowTitle("tomoebi") self.timer = QTimer() self.timer.timeout.connect(self.update_timeline) self.timer.start(500) def init_tweets(self): """create initial tweet""" self.tweets = ["start"] #initialize widgets def init_widgets(self): """initialize widgets""" #upper half of main window consists of accounts, composer and buttons self.compose_vbox = QVBoxLayout() self.accounts_hbox = QHBoxLayout() self.accbuttons = [] for a in self.auths: accbutton = QPushButton(self) accbutton.setWhatsThis(a) accbutton.setCheckable(True) accbutton.toggled.connect(self.choose_account) accbutton.setChecked(True) accbutton.setIcon(PyQt5.QtGui.QIcon('images/' + a + '.jpg')) accbutton.setIconSize(QSize(48, 48)) self.accounts_hbox.addWidget(accbutton) self.addaccbutton = QPushButton("+", self) self.addaccbutton.clicked.connect(self.add_account) self.accounts_hbox.addWidget(self.addaccbutton) self.composer = ComposeTextEdit(self) #self.composer.setPlaceholderText("いまなにしてる?") self.composer.setMaximumHeight(60) self.compose_hbox = QHBoxLayout() self.imagebutton = QPushButton("image", self) self.submitbutton = QPushButton("tweet", self) self.submitbutton.clicked.connect(self.submit) self.hashtag_hbox = QHBoxLayout() self.hashtagedit = QLineEdit(self) self.hashtagedit.setPlaceholderText("hashtags") self.hashtagbutton = QPushButton("set") self.hashtagbutton.setCheckable(True) self.hashtagbutton.toggled.connect(self.sethashtag) self.hashtag_hbox.addWidget(self.hashtagedit) self.hashtag_hbox.addWidget(self.hashtagbutton) self.compose_hbox.addWidget(self.imagebutton) self.compose_hbox.addWidget(self.submitbutton) self.compose_vbox.addLayout(self.accounts_hbox) self.compose_vbox.addWidget(self.composer) self.compose_vbox.addLayout(self.compose_hbox) self.compose_vbox.addLayout(self.hashtag_hbox) #lower half of main window consists of timeline l = QTextEdit() l.setPlainText(self.tweets[0]) l.setReadOnly(True) l.setFixedHeight(350) self.inner = QWidget() self.timeline_vbox = QVBoxLayout(self.inner) self.timeline_vbox.addWidget(l) self.tweets = [] self.timeline_vbox.addStretch() self.scroll = QScrollArea() self.scroll.setWidgetResizable(True) self.scroll.setWidget(self.inner) #integrate upper and lower part of main window self.whole_vbox = QVBoxLayout() self.whole_vbox.addLayout(self.compose_vbox) self.whole_vbox.addWidget(self.scroll) #right half of the main window self.image_collumn = QVBoxLayout() self.imageinner = QWidget() self.imagetimeline = QVBoxLayout(self.imageinner) self.imagescroll = QScrollArea() self.imagescroll.setWidgetResizable(True) self.imagescroll.setWidget(self.imageinner) self.imagetext = QTextEdit() self.imagetext.setMaximumHeight(60) self.imagetext.setReadOnly(True) self.image_collumn.addWidget(self.imagetext) self.image_collumn.addWidget(self.imagescroll) self.whole_hbox = QHBoxLayout() self.whole_hbox.addLayout(self.whole_vbox) self.whole_hbox.addLayout(self.image_collumn) self.setLayout(self.whole_hbox) #initialize registered accounts def init_accounts(self): """load account AT and AS from local and create api object and stream""" if not os.path.exists("images"): os.mkdir("images") if os.path.isfile("auth.json"): with open('auth.json', 'r') as f: authdic = json.load(f) for name, keys in authdic["Twitter"].items(): api = twitter.connect(keys["ACCESS_TOKEN"], keys["ACCESS_SECRET"]) self.auths[name] = api #self.following = self.following + api.friends_ids(k) self.streams.append( twitter.open_userstream(api, self.receive_tweet, name)) if not os.path.isfile("images/" + name + ".jpg"): twitter.getmyicon(api, name) #twitter.open_filterstream(self.auths["XXXX"], self.receive_tweet, # "XXXX", [str(x) for x in self.following]) else: default = {"Twitter": {}, "Mastodon": {}} with open('auth.json', 'w') as f: json.dump(default, f, indent=2) self.authdic = {} def add_account(self): """add account and register it to local file""" api, screen_name = twitter.authentication() self.auths[screen_name] = api self.streams.append( twitter.open_userstream(api, self.receive_tweet, screen_name)) twitter.getmyicon(api, screen_name) accbutton = QPushButton(self) accbutton.setWhatsThis(screen_name) accbutton.setCheckable(True) accbutton.toggled.connect(self.choose_account) accbutton.setIcon(PyQt5.QtGui.QIcon('images/' + screen_name + '.jpg')) accbutton.setIconSize(QSize(48, 48)) self.accounts_hbox.insertWidget(self.accounts_hbox.count() - 1, accbutton) def choose_account(self): """ called when accbutton are toggled. add or remove active accounts """ acc = self.sender() if acc.isChecked(): self.activeaccounts.append(acc.whatsThis()) else: self.activeaccounts.remove(acc.whatsThis()) def receive_tweet(self, status, name, icon): """called when stream receive a tweet""" self.tweets.append((status, name, icon)) def update_timeline(self): """called every 500ms and update gui timeline according to self.tweets""" for t in self.tweets: if hasattr(t[0], "in_reply_to_status_id"): self.addTweet(*t) elif hasattr(t[0], "event"): self.addEvent(*t) self.tweets = [] def addTweet(self, t, name, icon): rtby = None if hasattr(t, "retweeted_status"): rtby = [t.user.profile_image_url_https, t.user.screen_name] t = t.retweeted_status tweet = self.create_tweet(t) tweet_hbox = QHBoxLayout() if icon: if not glob.glob("images/" + t.user.screen_name + ".*"): twitter.geticon(t.user.profile_image_url_https, t.user.screen_name) icon = PyQt5.QtGui.QPixmap( glob.glob("images/" + t.user.screen_name + ".*")[0]) scaled_icon = icon.scaled(QSize(48, 48), 1, 1) iconviewer = IconLabel(t.id, name, self.retweet, self.reply) iconviewer.setPixmap(scaled_icon) icon_vbox = QVBoxLayout() icon_vbox.addWidget(iconviewer, alignment=Qt.AlignTop) if rtby: if not glob.glob("images/" + rtby[1] + ".*"): twitter.geticon(*rtby) icon = PyQt5.QtGui.QPixmap( glob.glob("images/" + rtby[1] + ".*")[0]) scaled_icon = icon.scaled(QSize(24, 24), 1, 1) rticonviewer = QLabel() rticonviewer.setPixmap(scaled_icon) rticon_hbox = QHBoxLayout() #rticon_hbox.addStretch() rticon_hbox.addWidget(rticonviewer, alignment=(Qt.AlignRight | Qt.AlignTop)) icon_vbox.addLayout(rticon_hbox) icon_vbox.addStretch() tweet_hbox.addLayout(icon_vbox) tweet_hbox.addWidget(tweet) favbutton = QPushButton("fav") favbutton.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding) favbutton.setCheckable(True) favbutton.toggled.connect(lambda: self.fav(t.id, name)) tweet_hbox.addWidget(favbutton) self.timeline_vbox.insertLayout(0, tweet_hbox) if "media" in t.entities: images = twitter.get_allimages(self.auths[name], t.id) self.imagetext.setPlainText("@" + t.user.screen_name + "\n" + t.text) for n, _ in enumerate(images): pixmap = PyQt5.QtGui.QPixmap() pixmap.loadFromData(QByteArray(images[n])) scaled = pixmap.scaled(QSize(320, 180), 1, 1) imageviewer = QLabel() imageviewer.setPixmap(scaled) self.imagetimeline.insertWidget(0, imageviewer) def addEvent(self, t, name, icon): if t.event == "favorite" and not (t.source["screen_name"] in self.auths.keys()): text = t.source["screen_name"] + " favored " + t.target_object[ "text"] favLabel = QLabel(text) self.timeline_vbox.insertWidget(0, favLabel) def create_tweet(self, t): """create tweet widget""" text = "@" + t.user.screen_name + "\n" + t.text tweetdocument = PyQt5.QtGui.QTextDocument() tweetdocument.setTextWidth( 300) #this line is not working so it needs to be fixed someday tweetdocument.setPlainText(text) tweettext = QTextEdit() tweettext.setDocument(tweetdocument) tweettext.setReadOnly(True) tweettext.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) tweettext.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) tweettext.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) tweettext.setAttribute(103) tweettext.show() tweettext.setFixedHeight(tweettext.document().size().height() + tweettext.contentsMargins().top() * 2) return tweettext def submit(self): """called when tweet button is pressed and submit tweet""" if not self.activeaccounts: return submittext = self.composer.toPlainText() for t in self.tweettags: submittext = submittext + " " + t if not submittext: return for a in self.activeaccounts: self.auths[a].update_status(submittext) self.composer.setPlainText("") def fav(self, tweetid, name): '''favor or unfavor a tweet from an account from which the tweet was obtained''' switch = self.sender() if switch.isChecked(): try: self.auths[name].create_favorite(tweetid) except: print("already favored") else: try: self.auths[name].destroy_favorite(tweetid) except: print("not favored") def retweet(self, id, account): self.auths[account].retweet(id) def reply(self, id, account): if not self.activeaccounts: return submittext = self.composer.toPlainText() if not submittext: return self.auths[account].update_status(submittext, in_reply_to_status_id=id, auto_populate_reply_metadata=True) self.composer.setPlainText("") def sethashtag(self): """set hashtab for receive and tweet""" switch = self.sender() if switch.isChecked(): htinput = self.hashtagedit.text() htlist = htinput.strip().split() for t in htlist: if not t[0] == "*": self.receivetags.append(t) self.tweettags.append(t) else: self.receivetags.append(t[1:]) repl_screen_name = list(self.auths.keys())[0] self.searchstream = twitter.open_filterstream( self.auths[repl_screen_name], self.receive_tweet, repl_screen_name, self.receivetags) else: self.receivetags = [] self.tweettags = [] self.searchstream.disconnect() def closeEvent(self, event): """called when gui window is closed and terminate all streams and thread""" for s in self.streams: s.disconnect() os._exit(1)
class E5LineEdit(QLineEdit): """ Class implementing a line edit widget showing some inactive text. """ LeftSide = 0 RightSide = 1 def __init__(self, parent=None, inactiveText=""): """ Constructor @param parent reference to the parent widget (QWidget) @param inactiveText text to be shown on inactivity (string) """ super(E5LineEdit, self).__init__(parent) self.setMinimumHeight(22) self.setPlaceholderText(inactiveText) self.__mainLayout = QHBoxLayout(self) self.__mainLayout.setContentsMargins(0, 0, 0, 0) self.__mainLayout.setSpacing(0) self.__leftMargin = 0 self.__leftWidget = E5LineEditSideWidget(self) self.__leftWidget.resize(0, 0) self.__leftLayout = QHBoxLayout(self.__leftWidget) self.__leftLayout.setContentsMargins(0, 0, 2, 0) if QApplication.isRightToLeft(): self.__leftLayout.setDirection(QBoxLayout.RightToLeft) else: self.__leftLayout.setDirection(QBoxLayout.LeftToRight) self.__leftLayout.setSizeConstraint(QLayout.SetFixedSize) self.__rightWidget = E5LineEditSideWidget(self) self.__rightWidget.resize(0, 0) self.__rightLayout = QHBoxLayout(self.__rightWidget) self.__rightLayout.setContentsMargins(0, 0, 2, 0) if self.isRightToLeft(): self.__rightLayout.setDirection(QBoxLayout.RightToLeft) else: self.__rightLayout.setDirection(QBoxLayout.LeftToRight) horizontalSpacer = QSpacerItem(0, 0, QSizePolicy.Expanding, QSizePolicy.Minimum) self.__mainLayout.addWidget(self.__leftWidget, 0, Qt.AlignVCenter | Qt.AlignLeft) self.__mainLayout.addItem(horizontalSpacer) self.__mainLayout.addWidget(self.__rightWidget, 0, Qt.AlignVCenter | Qt.AlignRight) if self.isRightToLeft(): self.__mainLayout.setDirection(QBoxLayout.RightToLeft) else: self.__mainLayout.setDirection(QBoxLayout.LeftToRight) self.setWidgetSpacing(3) self.__leftWidget.sizeHintChanged.connect(self._updateTextMargins) self.__rightWidget.sizeHintChanged.connect(self._updateTextMargins) def setLeftMargin(self, margin): """ Public method to set the left margin. @param margin left margin in pixel (integer) """ self.__leftMargin = margin def leftMargin(self): """ Public method to get the size of the left margin. @return left margin in pixel (integer) """ return self.__leftMargin def event(self, evt): """ Public method to handle events. @param evt reference to the event (QEvent) @return flag indicating, whether the event was recognized (boolean) """ if evt.type() == QEvent.LayoutDirectionChange: if self.isRightToLeft(): self.__mainLayout.setDirection(QBoxLayout.RightToLeft) self.__leftLayout.setDirection(QBoxLayout.RightToLeft) self.__rightLayout.setDirection(QBoxLayout.RightToLeft) else: self.__mainLayout.setDirection(QBoxLayout.LeftToRight) self.__leftLayout.setDirection(QBoxLayout.LeftToRight) self.__rightLayout.setDirection(QBoxLayout.LeftToRight) return QLineEdit.event(self, evt) def paintEvent(self, evt): """ Protected method handling a paint event. @param evt reference to the paint event (QPaintEvent) """ super(E5LineEdit, self).paintEvent(evt) def _updateTextMargins(self): """ Protected slot to update the text margins. """ if self.__leftMargin == 0: left = self.__leftWidget.sizeHint().width() else: left = self.__leftMargin right = self.__rightWidget.sizeHint().width() top = 0 bottom = 0 self.setTextMargins(left, top, right, bottom) def addWidget(self, widget, position): """ Public method to add a widget to a side. @param widget reference to the widget to add (QWidget) @param position position to add to (E5LineEdit.LeftSide, E5LineEdit.RightSide) """ if widget is None: return if self.isRightToLeft(): if position == self.LeftSide: position = self.RightSide else: position = self.LeftSide if position == self.LeftSide: self.__leftLayout.addWidget(widget) else: self.__rightLayout.insertWidget(1, widget) def removeWidget(self, widget): """ Public method to remove a widget from a side. @param widget reference to the widget to remove (QWidget) """ if widget is None: return self.__leftLayout.removeWidget(widget) self.__rightLayout.removeWidget(widget) widget.hide() def widgetSpacing(self): """ Public method to get the side widget spacing. @return side widget spacing (integer) """ return self.__leftLayout.spacing() def setWidgetSpacing(self, spacing): """ Public method to set the side widget spacing. @param spacing side widget spacing (integer) """ self.__leftLayout.setSpacing(spacing) self.__rightLayout.setSpacing(spacing) self._updateTextMargins() def textMargin(self, position): """ Public method to get the text margin for a side. @param position side to get margin for (E5LineEdit.LeftSide, E5LineEdit.RightSide) @return text margin (integer) """ spacing = self.__rightLayout.spacing() w = 0 if position == self.LeftSide: w = self.__leftWidget.sizeHint().width() else: w = self.__rightWidget.sizeHint().width() if w == 0: return 0 return w + spacing * 2 def inactiveText(self): """ Public method to get the inactive text. @return inactive text (string) """ if qVersionTuple() < (4, 7, 0): return self.__inactiveText else: return self.placeholderText() def setInactiveText(self, inactiveText): """ Public method to set the inactive text. @param inactiveText text to be shown on inactivity (string) """ self.setPlaceholderText(inactiveText)
class SpaceLineEdit(QWidget): SPLIT_CHARS = ' ,;' def __init__(self, parent=None, validator=None, flags=Qt.WindowFlags()): # type: (QWidget, QValidator, Qt.WindowFlags) -> None super(SpaceLineEdit, self).__init__(parent, flags) self.setFocusPolicy(Qt.StrongFocus) self._validator = validator self._layout = QHBoxLayout(self) self._layout.setContentsMargins(0, 0, 0, 0) self._prepareLineEdit(0) def setValidator(self, validator): # type: (QValidator) -> None self._validator = validator def getValues(self): # type: () -> List[str] values = [] for index in range(self._layout.count()): text = self._layout.itemAt(index).widget().text() values.append(text) return values def setValues(self, values): # type: (List[str]) -> None self._cleanAll() if not values: values = [''] for index, value in enumerate(values): self._prepareLineEdit(index, value) def _cleanAll(self): for index in reversed(range(self._layout.count())): widget = self._layout.takeAt(index).widget() # type: QWidget widget.setParent(None) widget.deleteLater() def _prepareLineEdit(self, position, text=''): # type: (int, str) -> None lineEdit = QLineEdit(str(text), self) lineEdit.setValidator(self._validator) lineEdit.textChanged.connect(partial(self.onTextChanged, lineEdit)) lineEdit.installEventFilter(self) self._layout.insertWidget(position, lineEdit) def focusInEvent(self, event): item = self._layout.itemAt(0) if item: item.widget().setFocus() def onTextChanged(self, lineEdit, text): # type: (QLineEdit, str) -> None if not text: self._removeLineEdit(lineEdit) text = self._convert(text) if self._isSplit(text): self._addLineEdit(lineEdit, text) def _removeLineEdit(self, lineEdit, changeFocus=True): # type: (QLineEdit, bool) -> None if self._layout.count() <= 1: return if changeFocus: if not self._setFocus(lineEdit, offset=-1): self._setFocus(lineEdit, offset=1) self._layout.removeWidget(lineEdit) lineEdit.deleteLater() @staticmethod def _convert(text): # type: (str) -> str for sch in SpaceLineEdit.SPLIT_CHARS[1:]: text = text.replace(sch, ' ') return text @staticmethod def _isSplit(text): # type: (str) -> bool return ' ' in text def _addLineEdit(self, lineEdit, text): # type: (QLineEdit, str) -> None texts = text.split() if not texts: lineEdit.setText('') return else: lineEdit.setText(texts[0]) if len(texts) == 1: texts.append('') insertIndex = self._layout.indexOf(lineEdit) + 1 for index, text in enumerate(texts[1:], insertIndex): self._prepareLineEdit(index, text) self._setFocus(lineEdit, offset=1) def eventFilter(self, watched, event): # type: (QWidget, QEvent) -> bool if isinstance(watched, QLineEdit): lineEdit = watched # type: QLineEdit if event.type() == QEvent.FocusOut and not lineEdit.text(): self._removeLineEdit(lineEdit, changeFocus=False) elif event.type() == QEvent.KeyPress and isinstance( event, QKeyEvent): if event.key() == Qt.Key_Backspace and not lineEdit.text(): self._removeLineEdit(lineEdit) if event.key() == Qt.Key_Down: self._setFocus(lineEdit, offset=1) elif event.key() == Qt.Key_Up: self._setFocus(lineEdit, offset=-1) return super(SpaceLineEdit, self).eventFilter(watched, event) def _setFocus(self, lineEdit, offset): # type: (QLineEdit, int) -> bool index = self._layout.indexOf(lineEdit) + offset item = self._layout.itemAt(index) if item: item.widget().setFocus(Qt.OtherFocusReason) return True return False
class TitleBar(QWidget): def __init__(self, parent=None): super(TitleBar, self).__init__(parent) self.title = 'no title' self._init_ui() self.setMouseTracking(True) font = QFont('Webdings') # close self.button_close = TitleButton('r') self.button_close.setFont(font) self.button_close.setObjectName('ButtonClose') self._r_hl.insertWidget(0, self.button_close) # max self.button_max = TitleButton('1') self.button_max.setFont(font) self.button_max.setObjectName('ButtonMax') self._r_hl.insertWidget(0, self.button_max) # min self.button_min = TitleButton('0') self.button_min.setFont(font) self.button_min.setObjectName('ButtonMin') self._r_hl.insertWidget(0, self.button_min) def _init_ui(self): hl = QHBoxLayout(self) hl.setContentsMargins(0, 0, 0, 0) hl.setSpacing(0) l_widget = BaseWidget() self._l_hl = QHBoxLayout(l_widget) self._l_hl.setContentsMargins(0, 0, 0, 0) self._l_hl.setSpacing(0) hl.addWidget(l_widget) hl.setContentsMargins(0, 0, 0, 0) self._label_title = QLabel(self.title) self._label_title.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter) self._label_title.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum) hl.addWidget(self._label_title) r_widget = BaseWidget() self._r_hl = QHBoxLayout(r_widget) self._r_hl.setContentsMargins(0, 0, 0, 0) self._r_hl.setSpacing(0) hl.addWidget(r_widget) def set_title(self, title): if not isinstance(title, str): raise TypeError("'title' requires 'str' type.") self.title = title self._label_title.setText(self.title) def add_widget(self, icon, left=True): if not isinstance(icon, str): raise TypeError("'icon' requires 'str' type.") widget = TitleWidget() widget.setIcon(QIcon(icon)) if left: self._l_hl.insertWidget(-1, widget) else: self._r_hl.insertWidget(0, widget)
class MenuBar(QTabWidget): def __init__(self, parent=None): super(MenuBar, self).__init__(parent) tabbar = TabBar(parent) self.setTabBar(tabbar) self._init_ui() self.setMinimumHeight(125) self.setMouseTracking(True) def _set_height(self, height): self.setFixedHeight(height) _height = pyqtProperty(int, fset=_set_height) def _init_ui(self): font = QFont('Webdings') self._drop = False self._corner = CornerButton('6') self._corner.setObjectName('BUttonCorner') self._corner.setFont(font) self.setCornerWidget(self._corner, Qt.BottomRightCorner) self._corner.clicked.connect(self._corner_clicked) self.currentChanged.connect(self._current_changed) def _corner_clicked(self): self._ani = QPropertyAnimation(self, b'_height') self._ani.setDuration(500) if self._drop: self._corner.setText('5') self._drop = False self._ani.setStartValue(30) self._ani.setEndValue(125) else: self._corner.setText('6') self._drop = True self._ani.setStartValue(125) self._ani.setEndValue(30) self._ani.start() def _current_changed(self, index): tab_text = self.tabText(index) menu = self.findChild(MenuWidget, tab_text) self._ani1 = QPropertyAnimation(menu, b'_height') self._ani1.setDuration(500) self._ani1.setStartValue(0) self._ani1.setEndValue(95) self._ani1.start() def add_menu(self, p_str): p_str = " {p_str} ".format(p_str=p_str) menu = MenuWidget() menu.setObjectName(p_str) self.addTab(menu, p_str) self._hl = QHBoxLayout(menu) self._hl.setObjectName(p_str) self._hl.setContentsMargins(0, 0, 0, 0) self._hl.setSpacing(0) hs = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) self._hl.addItem(hs) return menu def add_group(self, p_str, menu): group = GroupWidget(p_str, menu) group.setObjectName('group') insert_index = len(menu.findChildren(GroupWidget, 'group')) - 1 self._hl.insertWidget(insert_index, group) return group
class LineEdit(QLineEdit): inactiveText = QtDynamicProperty('inactiveText', str) widgetSpacing = QtDynamicProperty('widgetSpacing', int) def __init__(self, parent=None, contents=""): super(LineEdit, self).__init__(contents, parent) box_direction = QBoxLayout.RightToLeft if self.isRightToLeft( ) else QBoxLayout.LeftToRight self.inactiveText = "" self.left_widget = SideWidget(self) self.left_widget.resize(0, 0) self.left_layout = QHBoxLayout(self.left_widget) self.left_layout.setContentsMargins(0, 0, 0, 0) self.left_layout.setDirection(box_direction) self.left_layout.setSizeConstraint(QLayout.SetFixedSize) self.right_widget = SideWidget(self) self.right_widget.resize(0, 0) self.right_layout = QHBoxLayout(self.right_widget) self.right_layout.setContentsMargins(0, 0, 0, 0) self.right_layout.setDirection(box_direction) self.right_layout.addItem( QSpacerItem(0, 0, QSizePolicy.Expanding, QSizePolicy.Minimum)) self.widgetSpacing = 2 self.left_widget.sizeHintChanged.connect(self._update_text_margins) self.right_widget.sizeHintChanged.connect(self._update_text_margins) @property def left_margin(self): return self.left_widget.sizeHint().width( ) + 2 * self.left_layout.spacing() @property def right_margin(self): return self.right_widget.sizeHint().width( ) + 2 * self.right_layout.spacing() def _update_text_margins(self): self.setTextMargins(self.left_margin, 0, self.right_margin, 0) self._update_side_widget_locations() def _update_side_widget_locations(self): option = QStyleOptionFrame() self.initStyleOption(option) spacing = self.right_layout.spacing() text_rect = self.style().subElementRect(QStyle.SE_LineEditContents, option, self) text_rect.adjust(spacing, 0, -spacing, 0) mid_height = text_rect.center().y() + 1 - ( text_rect.height() % 2) # need -1 correction for odd heights -Dan if self.left_layout.count() > 0: left_height = int(mid_height - self.left_widget.height() / 2) left_width = self.left_widget.width() if left_width == 0: left_height = int(mid_height - self.left_widget.sizeHint().height() / 2) self.left_widget.move(text_rect.x(), left_height) text_rect.setX(self.left_margin) text_rect.setY( int(mid_height - self.right_widget.sizeHint().height() / 2.0)) text_rect.setHeight(self.right_widget.sizeHint().height()) self.right_widget.setGeometry(text_rect) def event(self, event): event_type = event.type() if event_type == QEvent.LayoutDirectionChange: box_direction = QBoxLayout.RightToLeft if self.isRightToLeft( ) else QBoxLayout.LeftToRight self.left_layout.setDirection(box_direction) self.right_layout.setDirection(box_direction) elif event_type == QEvent.DynamicPropertyChange: property_name = event.propertyName() if property_name == 'widgetSpacing': self.left_layout.setSpacing(self.widgetSpacing) self.right_layout.setSpacing(self.widgetSpacing) self._update_text_margins() elif property_name == 'inactiveText': self.update() return QLineEdit.event(self, event) def resizeEvent(self, event): self._update_side_widget_locations() QLineEdit.resizeEvent(self, event) def paintEvent(self, event): QLineEdit.paintEvent(self, event) if not self.hasFocus() and not self.text() and self.inactiveText: options = QStyleOptionFrame() self.initStyleOption(options) text_rect = self.style().subElementRect(QStyle.SE_LineEditContents, options, self) text_rect.adjust(self.left_margin + 2, 0, -self.right_margin, 0) painter = QPainter(self) painter.setPen(self.palette().brush(QPalette.Disabled, QPalette.Text).color()) painter.drawText(text_rect, Qt.AlignLeft | Qt.AlignVCenter, self.inactiveText) def addHeadWidget(self, widget): if self.isRightToLeft(): self.right_layout.insertWidget(1, widget) else: self.left_layout.addWidget(widget) def addTailWidget(self, widget): if self.isRightToLeft(): self.left_layout.addWidget(widget) else: self.right_layout.insertWidget(1, widget) def removeWidget(self, widget): self.left_layout.removeWidget(widget) self.right_layout.removeWidget(widget) widget.hide()
class DiagramFieldView(QWidget): """ A Diagram field where up to three diagrams would be presented Attributes: main (QWidget): The main widget. """ class Draw(QThread): """ Args: QThread """ def __init__(self, diagram: DiagramView): super().__init__() self.__diagram: DiagramView = diagram def run(self) -> NoReturn: self.__diagram.update_diagram() __diagram_field: 'DiagramFieldView' def __init__(self, parent: QWidget): """Initialising of a diagram field view Args: parent(QWidget) """ super().__init__(parent) DiagramFieldView.__diagram_field = self self.__list: List[DiagramView] = [] self.__dialog: Dialog = None self.__diagram_layout: QVBoxLayout = QVBoxLayout() self.__button_layout: QHBoxLayout = QHBoxLayout() self.__start_button: StartButtonView = StartButtonView() self.__maximize_button: QPushButton = QPushButton() self.__diagram_group: QtWidgets.QGroupBox = QtWidgets.QGroupBox(self) self.__group_layout: QtWidgets.QVBoxLayout = QtWidgets.QVBoxLayout( self.__diagram_group) self.__stretch_widget: QtWidgets.QWidget = QtWidgets.QWidget(self) self.__diagram_count: int = 0 self.__start_button.start_signal.connect(self.__clear_diagrams) self.__maximize_button.clicked.connect(self.__maximize_on_click) ManagerModel.set_diagram_notifier(self) self.__init_ui() def __init_ui(self): """ this method initialises the user interface of the diagram field""" self.__maximize_button.setFixedSize(31, 31) self.__maximize_button.setIcon( QIcon(SystemInfo.RESOURCES + 'images/buttons/maximize.svg')) self.__diagram_group.setStyleSheet( "QGroupBox { border: 1px solid gray; background: white; }") self.__diagram_layout.addWidget(self.__diagram_group) self.__button_layout = QHBoxLayout() self.__button_layout.addWidget(self.__start_button) self.__button_layout.addStretch() self.__button_layout.addWidget(self.__maximize_button) main_layout = QVBoxLayout() main_layout.addLayout(self.__button_layout, 1) main_layout.addLayout(self.__diagram_layout, 1) main_layout.addStretch(0) self.setLayout(main_layout) def __clear_diagrams(self) -> NoReturn: """Clears the DiagramViews""" for diagram in self.__list: diagram.clear_diagram() def __maximize_on_click(self): """if maximize button clicked open dialog that contains diagrams on screen""" self.__dialog = Dialog(self.__list, self.__start_button) self.__dialog.close_signal.connect(self.__update_diagrams) self.__maximize_button.clearFocus() def update_view(self): threads = [self.Draw(diagram) for diagram in self.__list] [thread.start() for thread in threads] [thread.wait() for thread in threads] @pyqtSlot() def __update_diagrams(self): """adds diagrams in the diagram field view when maximized window is closed""" for diagram in self.__list: diagram.resize(280, 350) self.__group_layout.addWidget(diagram, 10, Qt.AlignTop) if self.__diagram_count == 1: self.add_stretch() self.__button_layout.insertWidget(0, self.__start_button) @staticmethod def add_diagram(diagram: DiagramView) -> NoReturn: """adds diagram to list of diagrams & add it to the layout """ if len(DiagramFieldView.__diagram_field.__list) >= 3: raise DiagramMaximumReached DiagramFieldView.__diagram_field.__list.append(diagram) DiagramFieldView.__diagram_field.__diagram_count += 1 if DiagramFieldView.__diagram_field.__diagram_count == 2: DiagramFieldView.__diagram_field.__group_layout.removeWidget( DiagramFieldView.__diagram_field.__stretch_widget) DiagramFieldView.__diagram_field.__group_layout.addWidget( diagram, 10, Qt.AlignTop) if DiagramFieldView.__diagram_field.__diagram_count == 1: DiagramFieldView.add_stretch() @staticmethod def delete_diagram(diagram: DiagramView) -> NoReturn: """delete diagram from list of diagrams and then removes it from layout""" DiagramFieldView.__diagram_field.__list.remove(diagram) DiagramFieldView.__diagram_field.__diagram_count -= 1 DiagramFieldView.__diagram_field.__group_layout.removeWidget(diagram) if DiagramFieldView.__diagram_field.__diagram_count == 1: DiagramFieldView.add_stretch() diagram.close() @staticmethod def add_stretch() -> NoReturn: """adds a stretch to the diagram field""" DiagramFieldView.__diagram_field.__group_layout.addWidget( DiagramFieldView.__diagram_field.__stretch_widget, 10, Qt.AlignBottom)
class PMessage(PSticky): def __init__(self, parent: QWidget, rect: QRect = None): super().__init__(parent) if rect is None: x = parent.x() + parent.width() / 2 - 150 y = parent.y() + parent.height() / 2 - 50 window_rect = QRect(x, y, 300, 100) else: x = rect.x() + rect.width() / 2 - 150 y = rect.y() + rect.height() / 2 - 100 window_rect = QRect(x, y, 300, 100) self.setGeometry(window_rect) self.action = QHBoxLayout() self.action.addStretch() self.action.addStretch() self.text = QLabel() self.text.setWordWrap(True) self.text.setStyleSheet("color:#666666;") layout = QVBoxLayout() layout.setSpacing(18) layout.addWidget(self.text, alignment=Qt.AlignTop) layout.addLayout(self.action) layout.setContentsMargins(12, 12, 12, 0) widget = QWidget() widget.setLayout(layout) widget.setContentsMargins(0, 8, 0, 0) widget.setObjectName(Parapluie.Object_Raised_Off) self.setCentralWidget(widget) def addButton(self, text, tpe, action=None, data=None): apply = QPushButton() apply.setText(text) if action: if data is None: apply.clicked.connect(action) else: apply.clicked.connect(lambda: action(text, data)) if tpe == Parapluie.Button_Positive: apply.setObjectName(Parapluie.Object_OptimizeButton) elif tpe == Parapluie.Button_Negative: apply.setObjectName(Parapluie.Object_NegativeButton) self.action.insertWidget(1, apply, alignment=Qt.AlignCenter) def setMessage(self, message): self.text.setText(message) def initInformation(self, message, title="Notice"): self.setWindowTitle(title.upper()) self.setMessage(message) self.addButton("CLOSE", Parapluie.Button_Neutral, lambda: self.completeDestroy(0)) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) def initWarning(self, message, title="Warning!!!", negative="CONTINUE"): self.setWindowTitle(title.upper()) self.setMessage(message) self.addButton("CLOSE", Parapluie.Button_Neutral, lambda: self.completeDestroy(0)) self.addButton(negative, Parapluie.Button_Negative, lambda: self.completeDestroy(1)) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) def initQuestion(self, message, option: list, title="Choose a option..."): self.setWindowTitle(title.upper()) self.setMessage(message) for o in option: if isinstance(o, str): self.addButton(o, Parapluie.Button_Positive, self.onSelectedOption, option.index(o)) elif isinstance(o, dict): if "text" in o and "type" in o: self.addButton(o["text"], o["type"], self.onSelectedOption, option.index(o)) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) def onSelectedOption(self, text, data): self.completeDestroy(data)
class MainWindow(QMainWindow): def __init__(self, dataBaseName): super().__init__() self._dataBase = myDatabase.MyDataBase(dataBaseName) self._dictexe = { "первый запрос": (self.firstQuery, self.firstExe), "второй запрос": (self.secondQuery, self.secondExe), "третий запрос": (self.thirdQuery, self.thirdExe) } self._view = QTreeView() self._buttonAdd = QPushButton("Добавить") self._buttonAdd.clicked.connect(self.getItems) self._addSpinBox = QSpinBox() self._addComboBox = QComboBox() self._addComboBox.addItems(self._dataBase.dishes()) self._layout = QHBoxLayout() self._qSpinBox = QSpinBox() self._qComboBox = QComboBox() self._qLineEdit = QLineEdit() self._qCalendarWidget = QCalendarWidget() self._queryDisc = QLabel() self._buttonExe = QPushButton("Исполнить") self._buttonExe.clicked.connect(self.onButtonExe) self._combox = QComboBox() self._combox.currentTextChanged.connect(self.comboChanged) self._combox.addItems(self._dictexe.keys()) self.initUi() def initUi(self): self.setGeometry(300, 300, 200, 200) self.setWindowTitle('Ресторан') #self.setWindowIcon(QIcon('')) w = QWidget() mainLayout = QVBoxLayout() w.setLayout(mainLayout) self.setCentralWidget(w) mainLayout.addWidget(self._view) tmpLayout = QHBoxLayout() mainLayout.addLayout(tmpLayout) tmpLayout.addWidget(QLabel("Добавления ингредиента")) tmpLayout.addWidget(self._addSpinBox) tmpLayout.addWidget(self._addComboBox) tmpLayout.addWidget(self._buttonAdd) mainLayout.addWidget(self._queryDisc) tmpLayout = QHBoxLayout() mainLayout.addLayout(tmpLayout) tmpLayout.addWidget(self._combox) tmpLayout.addLayout(self._layout) tmpLayout.addWidget(self._buttonExe) self.adjustSize() def comboChanged(self, text): self._dictexe[text][0]() def clearLayout(self): while self._layout.count() > 0: self._layout.itemAt(0).widget().setParent(None) #Названия и калорийность блюд по рецептам автора X def firstQuery(self): self._queryDisc.setText( "Названия и калорийность блюд по рецептам автора X") self.clearLayout() #self._qSpinBox.setValue(0) #self._layout.insertWidget(1,self._qSpinBox) self._qComboBox.clear() self._qComboBox.addItems(self._dataBase.authors()) self._layout.insertWidget(1, self._qComboBox) def firstExe(self): model = self._dataBase.first(self._qComboBox.currentText()) self.setModel(model) #Названия ресторанов, к которым относятся повара, готовящие блюда содержащие в #названии подстроку X (например, «картофельный»), отсортированные по алфавиту def secondQuery(self): self._queryDisc.setText( "Названия ресторанов, к которым относятся повара,\n готовящие блюда содержащие в названии подстроку X (например, «картофельный»),\n отсортированные по алфавиту" ) self.clearLayout() self._qLineEdit.clear() self._layout.insertWidget(1, self._qLineEdit) def secondExe(self): model = self._dataBase.second(self._qLineEdit.text()) self.setModel(model) #Названия и количества ингредиентов и названия мероприятий, на которых разливают #напитки в количестве меньше X после даты Y def thirdQuery(self): self._queryDisc.setText( "Названия и количества ингредиентов и названия мероприятий, на которых разливают\n напитки в количестве меньше X после даты Y" ) self.clearLayout() self._layout.insertWidget(1, self._qCalendarWidget) self._qSpinBox.setMaximum(self._dataBase.maxDrinkCount() * 10) self._qSpinBox.setValue(0) self._layout.insertWidget(1, self._qSpinBox) def thirdExe(self): model = self._dataBase.third( self._qSpinBox.value(), self._qCalendarWidget.selectedDate().toPyDate()) self.setModel(model) def setModel(self, model): if model is None: return self._view.setVisible(False) self._view.setModel(model) for i in range(model.columnCount()): self._view.resizeColumnToContents(i) self._view.setVisible(True) self.adjustSize() def onButtonExe(self): self._dictexe[self._combox.currentText()][1]() def getItems(self): name, ok = QInputDialog.getText(self, "Ингредиент", "Введите название") if not ok: return self._dataBase.add(self._addSpinBox.value(), self._addComboBox.currentText(), name)
class DualEntry(QDialog): """ Just a simple Dialog for retrieving 2 values """ def __init__(self, entry1, entry2, input_type, text, min, max, parent=None): """ Note: Use the static method """ super().__init__(parent=parent) self.initUI(entry1, entry2, input_type, text, min, max) def initUI(self, entry1, entry2, input_type, text, min, max): self.setObjectName('Dialog') self.setStyleSheet("font: 10pt \"Arial\";") self.setWindowTitle('Input dialog') # Setting the parent makes it the default layout self.vlayout = QVBoxLayout(self) self.vlayout.setObjectName('vlayout') self.main_text = QLabel(self) self.main_text.setText(text) self.vlayout.insertWidget(0, self.main_text) self.hlayout = QHBoxLayout() self.hlayout.setObjectName('hlayout') self.vlayout.insertLayout(1, self.hlayout) if input_type is float: self.entry1 = QDoubleSpinBox(self) self.entry2 = QDoubleSpinBox(self) elif input_type is int: self.entry1 = QSpinBox(self) self.entry2 = QSpinBox(self) self.entry1.setMaximum(max) self.entry1.setMinimum(min) self.entry2.setMaximum(max) self.entry2.setMinimum(min) self.entry1.setValue(entry1) self.entry2.setValue(entry2) self.hlayout.insertWidget(0, self.entry1) self.hlayout.insertWidget(1, self.entry2) self.bb = QDialogButtonBox(self) self.bb.setStandardButtons(QDialogButtonBox.Cancel | QDialogButtonBox.Ok) self.bb.accepted.connect(self.accept) self.bb.rejected.connect(self.reject) self.vlayout.insertWidget(-1, self.bb) self.show() @staticmethod def getDualEntries(entry1, entry2, input_type=float, text='Enter Values:', min=-10000000000, max=10000000000, parent=None): """ Pops up a dialog box that retrieves 2 values, either float or int Parameters ---------- entry1: int or float Default value in the left box entry2: int or float Default value in the right box input_type : type Either float or int are currently supported text : str Text describing the values to set min : int or float Minimum value for entries max : int or float Maximum value for entries parent : object or None Parent of the Dialog (QDialog) box. Returns ------- (numeric, numeric), int Returns (left value, right value), Ok pressed. If Ok was pressed, value == 1, else 0. """ dialog = DualEntry(entry1, entry2, input_type, text, min, max, parent) result = dialog.exec_() if result: return (dialog.entry1.value(), dialog.entry2.value()), result else: return (), result
class MyPlotWidget(QWidget): COLORS=('r','g','b','c','m') def __init__(self): QWidget.__init__(self) vBox = QVBoxLayout(self) self._labels = QHBoxLayout() #self._labels.setSpacing(0) self._labels.addStretch(1) vBox.addLayout(self._labels) self.pw = pg.PlotWidget() vBox.addWidget(self.pw) self.pw.setBackground('w') self.pw.showGrid(x=True, y=True) self.setAcceptDrops(True) self.pw.setAcceptDrops(True) self.cidx = 0 def dragEnterEvent(self, e): if e.mimeData().hasFormat("application/x-DataItem"): e.accept() else: e.ignore() def dropEvent(self, e): data = e.mimeData() bstream = data.retrieveData("application/x-DataItem", QVariant.ByteArray) selected = pickle.loads(bstream) name = f"{e.source().filename} : {selected.var_name}" print(type(selected.data)) item = self.pw.getPlotItem().plot(x=selected.time.to_numpy(), y=selected.data.to_numpy(), pen=pg.mkPen(color=MyPlotWidget.COLORS[self.cidx], width=2), name=name) label = self.makeLabel(item) self._labels.insertWidget(self._labels.count()-1, label) e.source().onClose.connect(lambda : self.removeItem(item, label)) self.pw.autoRange() self.cidx = (self.cidx + 1) % len(MyPlotWidget.COLORS) e.accept() def makeLabel(self, plot_item): label = QLabel(plot_item.name()) label.setSizePolicy(QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)) palette = QPalette() color = plot_item.opts['pen'].color() palette.setColor(QPalette.WindowText, color) label.setPalette(palette) return label def removeItem(self, item, label): self.pw.removeItem(item) self._labels.removeWidget(label) # self._labels.takeAt(self._labels.indexOf(label)) label.deleteLater()
class ViewDataWindow(QDialog): formSubmitted = pyqtSignal() closeSignal = pyqtSignal() def __init__(self, LOGIN, parent=None): #super(ViewDataWindow, self).__init__(parent) super().__init__(parent) self.LOGIN = LOGIN self.db = db(self.LOGIN, "Brewing") self.displayNo = 0 self.displayedBrewsH = [] self.displayedBrewsVMash = [] self.displayedBrewsVBoil = [] self.displayedBrewsVFerment = [] self.create_layout_viewData() self.setWindowTitle('Past Brew Viewer') self.activeColours = [] self.colours = ["red", "green", "blue", "black",\ "cyan", "magenta", "yellow", "gray"] # Function to create layout of New Brew window def create_layout_viewData(self): # Create scroll boxes for filtering data filterGroupBox = QGroupBox("Filter by:") # Date edit box lab_date = QLabel("Date:") self.dateEdit = QDateEdit() # Set range of possible dates, current date is max date self.maxDate = QDate().currentDate() self.minDate = QDate(2019, 1, 1) self.dateEdit.setDate(self.maxDate) self.dateEdit.setDateRange(self.minDate, self.maxDate) self.dateEdit.setCalendarPopup(1) self.dateEdit.setDisplayFormat("dd/MM/yy") #self.dateEdit2 = QLineEdit() self.dateEdit.dateChanged.connect(self.filter_date) dateHLayout = QHBoxLayout() dateHLayout.addWidget(lab_date) dateHLayout.addWidget(self.dateEdit) ### Text edit filters ### # Batch ID search lab_batch = QLabel("Batch ID:") self.edit_batchID = QLineEdit() self.edit_batchID.setPlaceholderText("Enter Batch ID") self.but_IDsearch = QPushButton("Go") self.but_IDsearch.setAutoDefault(0) self.but_IDsearch.clicked.connect(self.filter_batchID) self.edit_batchID.returnPressed.connect(self.filter_batchID) batchHLayout = QHBoxLayout() batchHLayout.addWidget(lab_batch) batchHLayout.addWidget(self.edit_batchID) batchHLayout.addWidget(self.but_IDsearch) # Recipe search lab_recipe = QLabel("Recipe:") self.lineEdit_recipe = QLineEdit() self.lineEdit_recipe.setPlaceholderText("Enter Recipe") self.lineEdit_recipe.textChanged.connect(self.filter_recipe) self.lineEdit_recipe.returnPressed.connect(self.filter_recipe) recipeHLayout = QHBoxLayout() recipeHLayout.addWidget(lab_recipe) recipeHLayout.addWidget(self.lineEdit_recipe) # Clear filters button self.but_clearFilter = QPushButton("Clear Filters") self.but_clearFilter.setAutoDefault(0) clearHLayout = QHBoxLayout() clearHLayout.addStretch(1) clearHLayout.addWidget(self.but_clearFilter) clearHLayout.addStretch(0) self.but_clearFilter.clicked.connect(self.clearFilters) # Filter groupbox layout #recipeHLayout.addWidget(self.recipeEdit) #recipeVLayout = QVBoxLayout() #recipeVLayout.addLayout(recipeHLayout) #recipeVLayout.addWidget(self.lineEdit_recipe) filterHLayout = QHBoxLayout() filterHLayout.addStretch(1) filterHLayout.addWidget(lab_date) filterHLayout.addWidget(self.dateEdit) filterHLayout.addStretch(1) #filterHLayout.addLayout(recipeVLayout) filterHLayout.addStretch(1) filterHLayout.addWidget(self.edit_batchID) filterHLayout.addWidget(self.but_IDsearch) filterHLayout.addStretch(1) #filterGroupBox.setLayout(filterHLayout) # Alternate - Filter vertical layout filterVLayout = QVBoxLayout() filterVLayout.addLayout(batchHLayout) filterVLayout.addLayout(recipeHLayout) filterVLayout.addLayout(dateHLayout) filterVLayout.addLayout(clearHLayout) filterGroupBox.setLayout(filterVLayout) # scrollHLayout = QHBoxLayout() # scrollHLayout.addWidget(filterGroupBox) # scrollHLayout.addStretch(1) # Create QTableView of brew data header = ['Brew ID', 'Recipe', 'Date'] self.model = MyTableModel( self.db.readFromTable("Brews", "id, Recipe, Date"), header, self) self.proxyModel = QSortFilterProxyModel(self) self.proxyModel.setFilterCaseSensitivity(Qt.CaseInsensitive) self.proxyModel.setSourceModel(self.model) self.dataTable = QTableView() self.dataTable.setModel(self.proxyModel) self.dataTable.setSortingEnabled(True) self.dataTable.setSelectionBehavior(1) # Create bottom buttons self.but_quit = QPushButton("Close") self.but_quit.setAutoDefault(0) self.but_view = QPushButton("Display Brew") self.but_view.setAutoDefault(0) quitHLayout = QHBoxLayout() quitHLayout.addStretch(0) quitHLayout.addWidget(self.but_quit) quitHLayout.addStretch(3) quitHLayout.addWidget(self.but_view) quitHLayout.addStretch(0) # Main vertical layout for left area lWidget = QWidget() vLayoutL = QVBoxLayout(lWidget) vLayoutL.addWidget(filterGroupBox) vLayoutL.addWidget(self.dataTable) vLayoutL.addLayout(quitHLayout) # Widget for displayed brews - Widget allows fixed height to be set displayedWidget = QWidget() #displayedWidget.setFixedHeight(180) # h Layout to add groupboxes of displayed brews - added to in viewButtonClicked slot self.hLayoutDisplayed = QHBoxLayout() self.hLayoutDisplayed.addStretch(1) self.hLayoutDisplayed.setSizeConstraint(5) displayedWidget.setLayout(self.hLayoutDisplayed) # Scroll area for horizontal displayed brews hScrollArea = QScrollArea() hScrollArea.setWidget(displayedWidget) hScrollArea.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOn) hScrollArea.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) #hScrollArea.setFixedHeight(120) hScrollArea.setMinimumHeight(20) # Main v layout for displayed brews widget displayTitle = QLabel("Displayed Brews:") displayTitle.setMaximumHeight(20) hDisplayedWidget = QWidget() self.vLayoutDisplayed = QVBoxLayout(hDisplayedWidget) self.vLayoutDisplayed.addWidget(displayTitle) self.vLayoutDisplayed.addWidget(hScrollArea) # Main vertical layout for right area rWidget = QWidget() rSplitter = QSplitter() rSplitter.setOrientation(Qt.Orientation(2)) #vLayoutR = QVBoxLayout(rWidget) self.tabs = QTabWidget() self.tabMash = MashTab() self.tabBoil = BoilTab() self.tabFerment = FermentTab() self.tabs.resize(100, 1000) self.tabs.setMinimumSize(400, 100) self.tabs.addTab(self.tabMash, "Mash") self.tabs.addTab(self.tabBoil, "Boil") self.tabs.addTab(self.tabFerment, "Ferment") #vLayoutR.addLayout(self.vLayoutDisplayed) #vLayoutR.addWidget(self.tabs) rSplitter.addWidget(hDisplayedWidget) rSplitter.addWidget(self.tabs) # Main layout for whole window - splitter so widget sizes are adjustable mainLayout = QHBoxLayout() mainSplitter = QSplitter() mainSplitter.addWidget(lWidget) mainSplitter.addWidget(rSplitter) mainLayout.addWidget(mainSplitter) mainSplitter.setSizes([260, 740]) self.setLayout(mainLayout) #self.showFullScreen() self.setGeometry(0, 0, 1500, 1000) self.but_view.clicked.connect(self.viewButtonClicked) self.but_view.clicked.connect(self.viewButtonClickedTabs) self.but_quit.clicked.connect(self.quitButtonClicked) def quitButtonClicked(self): self.close() self.closeSignal.emit() # Slot for adding selected brew to widget def viewButtonClicked(self): self.brewInfo = [] # array to place brew info: ID, Recipe, Date self.displayNo = self.displayNo + 1 # Add brew info to array based on selected row index = (self.dataTable.selectionModel().currentIndex()) for i in range(3): self.brewInfo.append( QLabel(str(index.sibling(index.row(), i).data()))) # Choose colour to use for this displayed brew self.colourToUse = self.chooseColour() # Create group box with all brew info displayed and Remove button brewGroupBox = QGroupBox(str(self.displayNo)) brewGroupBox.setObjectName("ColouredGroupBox") brewGroupBox.setStyleSheet( "QGroupBox#ColouredGroupBox { border: 2px solid %s;}" % self.colourToUse) brewForm = QFormLayout() brewForm.addRow(QLabel('Brew ID:'), self.brewInfo[0]) brewForm.addRow(QLabel('Recipe:'), self.brewInfo[1]) brewForm.addRow(QLabel('Date:'), self.brewInfo[2]) removeButHLayout = QHBoxLayout() removeButHLayout.addStretch(1) self.but_Remove = QPushButton("Remove") removeButHLayout.addWidget(self.but_Remove) brewForm.addRow(removeButHLayout) brewGroupBox.setLayout(brewForm) # Add group box to layout - use insert so that stretch stays on right side self.hLayoutDisplayed.insertWidget(self.displayNo - 1, brewGroupBox) self.displayedBrewsH.append( brewGroupBox) # Add groupbox to array of displayed brews # Signal to connect remove brew button. Lambda function used to pass argument of specific # brew to be removed self.but_Remove.clicked.connect( lambda: self.removeBrewClickedMash(brewGroupBox)) self.but_Remove.clicked.connect( lambda: self.removeBrewClickedBoil(brewGroupBox)) self.but_Remove.clicked.connect( lambda: self.removeBrewClickedFerment(brewGroupBox)) self.but_Remove.clicked.connect( lambda: self.removeBrewClicked(brewGroupBox)) # Slot for adding brew info to each of the process tabs def viewButtonClickedTabs(self): batchID = self.brewInfo[0].text() # Query database to get recipe data for the brew selected to view # brewInfo[0].text() gives brew ID for selected brew from table sql = f"SELECT * FROM Brews WHERE id = '{self.brewInfo[0].text()}'" query = self.db.custom(sql) self.recipedata = {} self.recipedata['batchID'] = query[0][0] self.recipedata['recipeName'] = query[0][1] self.recipedata['recipeDate'] = query[0][2] self.recipedata['mashTemp'] = query[0][3] self.recipedata['mashTime'] = query[0][4] self.recipedata['boilTemp'] = query[0][5] self.recipedata['boilTime'] = query[0][6] self.recipedata['hop1'] = (query[0][7], query[0][8]) self.recipedata['hop2'] = (query[0][9], query[0][10]) self.recipedata['hop3'] = (query[0][11], query[0][12]) self.recipedata['hop4'] = (query[0][13], query[0][14]) self.recipedata['fermenttemp'] = query[0][15] # Create groupboxes for each of the process tabs to fill with brew info mashGroupBox = QGroupBox(str(self.displayNo)) mashGroupBox.setObjectName("MashColouredGroupBox") mashGroupBox.setStyleSheet( "QGroupBox#MashColouredGroupBox { border: 2px solid %s;}" % self.colourToUse) mashFormLayout = QFormLayout() mashFormLayout.addRow( QLabel(f"Temp ({DEGREES}C): {self.recipedata['mashTemp']}")) mashFormLayout.addRow( QLabel(f"Time (mins): {self.recipedata['mashTime']}")) mashGroupBox.setLayout(mashFormLayout) self.tabMash.mashVLayout.insertWidget(self.displayNo - 1, mashGroupBox) self.displayedBrewsVMash.append(mashGroupBox) boilGroupBox = QGroupBox(str(self.displayNo)) boilGroupBox.setObjectName("BoilColouredGroupBox") boilGroupBox.setStyleSheet( "QGroupBox#BoilColouredGroupBox { border: 2px solid %s;}" % self.colourToUse) boilFormLayout = QFormLayout() boilFormLayout.addRow( QLabel(f"Temp ({DEGREES}C):{self.recipedata['boilTemp']}")) boilFormLayout.addRow( QLabel(f"Time (mins): {self.recipedata['boilTime']}")) boilFormLayout.addRow(QLabel(f"Hop 1: {self.recipedata['hop1'][0]}")) boilFormLayout.addRow( QLabel(f"Time (mins): {self.recipedata['hop1'][1]}")) boilFormLayout.addRow(QLabel(f"Hop 2: {self.recipedata['hop2'][0]}")) boilFormLayout.addRow( QLabel(f"Time (mins): {self.recipedata['hop2'][1]}")) boilFormLayout.addRow(QLabel(f"Hop 3: {self.recipedata['hop3'][0]}")) boilFormLayout.addRow( QLabel(f"Time (mins): {self.recipedata['hop3'][1]}")) boilFormLayout.addRow(QLabel(f"Hop 4: {self.recipedata['hop4'][0]}")) boilFormLayout.addRow( QLabel(f"Time (mins): {self.recipedata['hop4'][1]}")) boilGroupBox.setLayout(boilFormLayout) self.tabBoil.boilVLayout.insertWidget(self.displayNo - 1, boilGroupBox) self.displayedBrewsVBoil.append(boilGroupBox) fermentGroupBox = QGroupBox(str(self.displayNo)) fermentGroupBox.setObjectName("FermentColouredGroupBox") fermentGroupBox.setStyleSheet( "QGroupBox#FermentColouredGroupBox { border: 2px solid %s;}" % self.colourToUse) fermentFormLayout = QFormLayout() fermentFormLayout.addRow( QLabel(f"Temp ({DEGREES}C): {self.recipedata['fermenttemp']}")) #fermentFormLayout.addRow(QLabel('Time (mins):')) fermentGroupBox.setLayout(fermentFormLayout) self.tabFerment.fermentVLayout.insertWidget(self.displayNo - 1, fermentGroupBox) self.displayedBrewsVFerment.append(fermentGroupBox) ### PUTTING DATA ONTO GRAPHS ### # Query database to get data to plot on graphs sqlMash = f"SELECT TimeStamp, Temp FROM Mash WHERE BatchID = '{batchID}'" sqlBoil = f"SELECT TimeStamp, Temp FROM BoilMonitor WHERE BatchID = '{batchID}'" sqlFermentTemp = f"SELECT TimeStamp, Temp FROM Ferment WHERE BatchID = '{batchID}'" sqlFermentSG = f"SELECT TimeStamp, Sg FROM Ferment WHERE BatchID = '{batchID}'" mashDataX, mashDataY = self.createData(sqlMash) boilDataX, boilDataY = self.createData(sqlBoil) fermentTempDataX, fermentTempDataY = self.createData(sqlFermentTemp) fermentSGDataX, fermentSGDataY = self.createData(sqlFermentSG) # Create and add curves to each of the plots self.tabMash.graph.createCurve(mashDataX, mashDataY, self.colourToUse) self.tabBoil.graph.createCurve(boilDataX, boilDataY, self.colourToUse) self.tabFerment.tempGraph.createCurve(fermentTempDataX[1:], fermentTempDataY[1:], self.colourToUse) self.tabFerment.gravGraph.createCurve(fermentSGDataX[1:], fermentSGDataY[1:], self.colourToUse) # Function to choose first available colour def chooseColour(self): # Loop through dictionary, checking if self.colours[j] appear for colours in self.colours: # If it does appear, continue to next colour if colours not in self.activeColours: # If it doesn't appear, add colour to dictionary and return colour self.activeColours.append(colours) return colours # Get data from database using sql query def createData(self, sql): timestamps = [] tempdat = [] try: for data in self.db.custom(sql): timestamps.append(data[0]) tempdat.append(data[1]) startTime = timestamps[0] for i in range(len(timestamps)): timestamps[i] = (timestamps[i] - startTime).seconds except IndexError: return [0], [0] return timestamps, tempdat # Slot for removing group boxes from horizontal tab def removeBrewClicked(self, brewToRemove): brewArrayPos = self.displayedBrewsH.index(brewToRemove) self.hLayoutDisplayed.removeWidget( brewToRemove) # remove widget from layout brewToRemove.setParent(None) # remove parent so widget dissappears self.displayNo = self.displayNo - 1 self.displayedBrewsH.remove( brewToRemove) # remove brew from array of displayed brews i = 0 # Loop to renumber the remaining displayed groupboxes using the array for i in range(len(self.displayedBrewsH)): self.displayedBrewsH[i].setTitle(str(i + 1)) del self.activeColours[brewArrayPos] # Slot for removing group boxes of brew info in mash tab def removeBrewClickedMash(self, brewToRemove): # Obtain position in array of displayed brews of brew to remove brewArrayPos = self.displayedBrewsH.index(brewToRemove) # Use position to remove widget from layout self.tabMash.mashVLayout.takeAt(brewArrayPos) # Use position to remove parent self.displayedBrewsVMash[brewArrayPos].setParent(None) # Use position to delete from vertical array del self.displayedBrewsVMash[brewArrayPos] # Renumber groupboxes in vertical display for i in range(len(self.displayedBrewsVMash)): self.displayedBrewsVMash[i].setTitle(str(i + 1)) # Remove curve from graph self.tabMash.graph.removeCurve(brewArrayPos) # Slot for removing group boxes of brew info in boil tab def removeBrewClickedBoil(self, brewToRemove): # Obtain position in array of displayed brews of brew to remove brewArrayPos = self.displayedBrewsH.index(brewToRemove) # Use position to remove widget from layout self.tabBoil.boilVLayout.takeAt(brewArrayPos) # Use position to remove parent self.displayedBrewsVBoil[brewArrayPos].setParent(None) # Use position to delete from vertical array del self.displayedBrewsVBoil[brewArrayPos] # Renumber groupboxes in vertical display for i in range(len(self.displayedBrewsVBoil)): self.displayedBrewsVBoil[i].setTitle(str(i + 1)) self.tabBoil.graph.removeCurve(brewArrayPos) # Slot for removing group boxes of brew info in ferment tab def removeBrewClickedFerment(self, brewToRemove): # Obtain position in array of displayed brews of brew to remove brewArrayPos = self.displayedBrewsH.index(brewToRemove) # Use position to remove widget from layout self.tabFerment.fermentVLayout.takeAt(brewArrayPos) # Use position to remove parent self.displayedBrewsVFerment[brewArrayPos].setParent(None) # Use position to delete from vertical array del self.displayedBrewsVFerment[brewArrayPos] # Renumber groupboxes in vertical display for i in range(len(self.displayedBrewsVFerment)): self.displayedBrewsVFerment[i].setTitle(str(i + 1)) self.tabFerment.tempGraph.removeCurve(brewArrayPos) self.tabFerment.gravGraph.removeCurve(brewArrayPos) # Slot for filtering by Batch IDdisplayed def filter_batchID(self): self.lineEdit_recipe.clear() self.proxyModel.setFilterRegExp(self.edit_batchID.text()) self.proxyModel.setFilterKeyColumn(0) # Slot for filtering by Recipe def filter_recipe(self): self.edit_batchID.clear() self.proxyModel.setFilterRegExp(self.lineEdit_recipe.text()) self.proxyModel.setFilterKeyColumn(1) # Slot for filtering by date def filter_date(self): self.lineEdit_recipe.clear() self.edit_batchID.clear() self.proxyModel.setFilterRegExp( self.dateEdit.date().toString("dd/MM/yy")) self.proxyModel.setFilterKeyColumn(2) # Slot for clearing all filters def clearFilters(self): self.dateEdit.setDate(self.maxDate) self.proxyModel.setFilterRegExp('') self.proxyModel.setFilterKeyColumn(0) self.proxyModel.setFilterKeyColumn(1) self.proxyModel.setFilterKeyColumn(2) self.lineEdit_recipe.clear() self.edit_batchID.clear()
class FormWidget(FormPage): instanceAssigned = pyqtSignal(str) instanceDeleted = pyqtSignal(str) instanceSaved = pyqtSignal(str) exception = pyqtSignal(str) def __init__(self, parent = None, buttons = FormButtons.EditButtons): super(FormWidget, self).__init__(parent) self.buildButtonBox(buttons) self.tabs = None self._tabs = {} def buildButtonBox(self, buttons): buttonWidget = QGroupBox() self.buttonbox = QHBoxLayout(buttonWidget) if buttons & FormButtons.DeleteButton: self.deletebutton = QPushButton("Delete", self) self.deletebutton.clicked.connect(self.deleteInstance) self.buttonbox.addWidget(self.deletebutton) self.buttonbox.addStretch(1) if buttons & FormButtons.ResetButton: self.resetbutton = QPushButton("Reset", self) self.resetbutton.clicked.connect(self.setInstance) self.buttonbox.addWidget(self.resetbutton) if buttons & FormButtons.SaveButton: self.savebutton = QPushButton("Save", self) self.savebutton.clicked.connect(self.save) self.buttonbox.addWidget(self.savebutton) self.layout().addWidget(buttonWidget) def addWidgetToButtonBox(self, widget, *args): self.buttonbox.insertWidget(0, widget, *args) def addTab(self, widget, title): if self.tabs is None: self.tabs = QTabWidget(self) self.tabs.currentChanged[int].connect(self.tabChanged) # Remove stretch at the bottom: self._removeStretch() self.vbox.addWidget(self.tabs, 1) if isinstance(widget, FormPage): self.form.addSubLayout(widget.form) self.tabs.addTab(widget, title) self._tabs[title] = widget return widget def count(self): return self.tabs and self.tabs.count() def setTab(self, tab): if self.tabs and tab <= self.tabs.count(): self.tabs.setCurrentIndex(tab) def tabChanged(self, ix): w = self.tabs.currentWidget() if hasattr(w, "selected"): w.selected() def save(self): try: self.form.retrieve(self.instance()) if hasattr(self, "retrieve") and callable(self.retrieve): self.retrieve(self.instance()) self.instanceSaved.emit(str(self.instance.key())) self.statusMessage.emit("Saved") except: self.exception.emit("Save failed...") self.setInstance() def instance(self): return self._instance def setInstance(self, instance = None): if instance: self._instance = instance self.form.apply(self.instance()) if hasattr(self, "assign") and callable(self.assign): self.assign(self.instance()) self.instanceAssigned.emit(str(self.instance().key())) def confirmDelete(self): return QMessageBox.warning(self, "Are you sure?", "Are you sure you want to delete this?", QMessageBox.Cancel | QMessageBox.Ok, QMessageBox.Cancel) == QMessageBox.Ok def onDelete(self): try: with gripe.db.Tx.begin(): key = str(self.instance().key()) if grumble.model.delete(self.instance()): self.instanceDeleted.emit(key) except: traceback.print_exc() self.exception.emit("Delete failed...") def deleteInstance(self): if self.instance() and self.confirmDelete(): self.onDelete()
class MainWindow(QMainWindow): # signal to be emitted when device/channel is changed _sig_entry_form_changed = pyqtSignal(object) def __init__(self, parent=None): super(MainWindow, self).__init__(parent) self.ui = Ui_MainWindow() self.ui.setupUi(self) # aliases self._statusbar = self.ui.statusBar self._messagelog = self.ui.txtMessageLog self._overview = self.ui.fmOverview self._plots = self.ui.fmPlots self._gbpinnedplot = self.ui.gbPinnedPlot self._tabview = self.ui.tabMain self._btnload = self.ui.btnLoad self._btnsave = self.ui.btnSave self._btnsaveas = self.ui.btnSaveAs self._btnquit = self.ui.btnQuit self._btnabout = self.ui.btnAbout # set up pinned plot self._pinnedplot = DateTimePlotWidget() self._gbpinnedplot.layout().itemAt(0).widget().deleteLater() self._gbpinnedplot.layout().insertWidget(0, self._pinnedplot) # Icons self._icon_path = os.path.join( os.path.abspath(os.path.dirname(__file__)), "images/icons") self._btnquit.setIcon( QIcon(QPixmap(os.path.join(self._icon_path, 'process-stop.png')))) self._btnload.setIcon( QIcon(QPixmap(os.path.join(self._icon_path, 'document-open.png')))) self._btnsave.setIcon( QIcon(QPixmap(os.path.join(self._icon_path, 'document-save.png')))) self._btnsaveas.setIcon( QIcon(QPixmap(os.path.join(self._icon_path, 'document-save.png')))) self._btnabout.setIcon( QIcon(QPixmap(os.path.join(self._icon_path, 'help-browser.png')))) self.ui.btnExpand.setIcon( QIcon(QPixmap(os.path.join(self._icon_path, 'list-add.png')))) self.ui.btnCollapse.setIcon( QIcon(QPixmap(os.path.join(self._icon_path, 'list-remove.png')))) # About dialog connection self._btnabout.triggered.connect(self.show_AboutDialog) self.ui.action_about.triggered.connect(self.show_AboutDialog) # Help will open the README.md self.ui.action_help.triggered.connect(self.show_readme) # tab changes self._current_tab = 'main' self._tabview.currentChanged.connect(self.tab_changed) # set up containers self._overview_layout = QHBoxLayout() self._overview.setLayout(self._overview_layout) self._overview_layout.addStretch() self._plot_layout = QGridLayout() self._plots.setLayout(self._plot_layout) # settings page self.ui.treeDevices.setHeaderLabels(['Label', 'Type']) self.ui.treeDevices.currentItemChanged.connect( self.on_settings_row_changed) self.ui.btnExpand.clicked.connect(self.ui.treeDevices.expandAll) self.ui.btnCollapse.clicked.connect(self.ui.treeDevices.collapseAll) self._devvbox = QVBoxLayout() self.ui.fmDeviceSettings.setLayout(self._devvbox) # local copies of data self._settings_devices = {} def apply_settings(self, settings): self.resize(settings['window-width'], settings['window-height']) self.ui.splitter_horizontal.setSizes( [settings['split-main-first'], settings['split-main-second']]) self.ui.splitSettings.setSizes([ settings['split-settings-first'], settings['split-settings-second'] ]) self.move(settings['window-pos-x'], settings['window-pos-y']) def current_settings(self): return { 'split-main-first': self.ui.splitter_horizontal.sizes()[0], 'split-main-second': self.ui.splitter_horizontal.sizes()[1], 'split-settings-first': self.ui.splitSettings.sizes()[0], 'split-settings-second': self.ui.splitSettings.sizes()[1], 'window-maximize-state': True if self.windowState == Qt.WindowMaximized else False, 'window-pos-x': self.pos().x(), 'window-pos-y': self.pos().y(), 'window-width': self.frameSize().width(), 'window-height': self.frameSize().height(), } @property def current_tab(self): return self._current_tab def tab_changed(self): tabName = self._tabview.tabText(self._tabview.currentIndex()) if tabName == 'Overview': self._current_tab = 'main' elif tabName == 'Devices': self._current_tab = 'devices' elif tabName == 'Plotting': self._current_tab = 'plots' # ---- Tab Update Functions ---- def update_overview(self, devices): self.clearLayout(self._overview_layout) order_devlist = sorted([x for _, x in devices.items()], key=lambda y: y.overview_order) for device in order_devlist: if 'overview' in device.pages: self._overview_layout.insertWidget(0, device._overview_widget) self._overview_layout.addStretch() def update_plots(self, devices, plotted_channels): """ Draw the plotted channels, as specified by the PlotChooseDialog """ self.clearLayout(self._plot_layout) row = 0 col = 0 for device_name, device in devices.items(): for channel_name, channel in device.channels.items(): if channel in plotted_channels: self._plot_layout.addWidget(channel._plot_widget, row, col) row += 1 if row == 2: row = 0 col += 1 def update_device_settings(self, devices): """ Populates the treeview on the devices tab """ self.clearLayout(self._devvbox) self.ui.treeDevices.clear() for device_name, device in devices.items(): devrow = QTreeWidgetItem(self.ui.treeDevices) devrow.setText(0, device.label) devrow.setText(1, 'Device') self._settings_devices[device.name] = { 'device': device, 'row': devrow, 'channels': {} } for chname, ch in reversed( sorted(device.channels.items(), key=lambda x: x[1].display_order)): chrow = QTreeWidgetItem(devrow) chrow.setText(0, ch.label) chrow.setText(1, 'Channel') self._settings_devices[device.name]['channels'][ch.name] = { 'channel': ch, 'row': chrow } newchrow = QTreeWidgetItem(devrow) newchrow.setText(0, '[Add a new Channel]') #newchrow.setText(1, 'Channel') newdevrow = QTreeWidgetItem(self.ui.treeDevices) newdevrow.setText(0, '[Add a new Device]') #newdevrow.setText(1, 'Device') self.ui.treeDevices.expandAll() def update_procedures(self, procedures): # Add procedures to the procedures tab self.clearLayout(self.ui.vboxProcedures) for procedure_name, procedure in procedures.items(): self.ui.vboxProcedures.addWidget(procedure.widget) self.ui.vboxProcedures.addStretch() # ---- Settings page functions ---- def on_settings_row_changed(self, item): if item == None: return # if clause fixes mysterious floating entry forms.... if self._devvbox.count(): widget = self._devvbox.itemAt(0).widget() widget.hide() self.clearLayout(self._devvbox) obj = None parent = None for device_name, device_data in self._settings_devices.items(): # are we editing an existing device/channel? if device_data['row'] == item: obj = device_data['device'] break else: for channel_name, channel_data in device_data[ 'channels'].items(): if channel_data['row'] == item: obj = channel_data['channel'] parent = device_data['device'] break if (obj, parent) == (None, None): # adding a new device or channel if 'channel' in item.text(0).lower(): for device_name, device_data in self._settings_devices.items(): if device_data['row'] == item.parent(): parent = device_data['device'] break if obj is None: if parent is None: obj = Device(label='New Device') else: obj = Channel(label='New Channel') obj.parent_device = parent obj.reset_entry_form() self._devvbox.addWidget(obj.entry_form) obj.entry_form.show() self._sig_entry_form_changed.emit(obj) # ---- Misc functions --- def show_AboutDialog(self): _aboutdialog = AboutDialog() _aboutdialog.exec_() @staticmethod def show_readme(): readme_url = "https://github.com/DanielWinklehner/pycontrolsystem" wb.open(readme_url, new=2) def show_ErrorDialog(self, error_message='Error'): _errordialog = ErrorDialog(error_message) _errordialog.exec_() def set_polling_rate(self, text): self.ui.lblServPoll.setText('Server polling rate: ' + text + ' Hz') def status_message(self, text): self._statusbar.showMessage(text) self._messagelog.append( time.strftime('[%Y-%m-%d %H:%M:%S] ', time.localtime()) + text) @staticmethod def clearLayout(layout): """ Removes all widgets from a QLayout. Does not delete the widget """ while layout.count(): child = layout.takeAt(0) if child.widget(): child.widget().setParent(None) elif child.layout(): self.clearLayout(child)
def __init__(self, HomeTab): super().__init__() parent_horizontal_layout = QHBoxLayout(HomeTab) menu_vlayout = QVBoxLayout() frames_parent_vlayout = QVBoxLayout() frames_container = QFrame() frames_container.setLayout(frames_parent_vlayout) # Create scrollArea widget inside centralwidget self.scrollArea = QtWidgets.QScrollArea(frames_container) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.scrollArea.sizePolicy().hasHeightForWidth()) # Set size policy for scrollArea widget self.scrollArea.setSizePolicy(sizePolicy) self.scrollArea.setMinimumSize(QtCore.QSize(767, 620)) self.scrollArea.setMaximumSize(QtCore.QSize(767, 620)) self.scrollArea.setHorizontalScrollBarPolicy( QtCore.Qt.ScrollBarAlwaysOff) self.scrollArea.setWidgetResizable(False) self.scrollArea.setObjectName("scrollArea") # Create scrollAreaWidgetContents widget self.scrollAreaWidgetContents = QtWidgets.QWidget() self.scrollAreaWidgetContents.setGeometry( QtCore.QRect(0, -176, 771, 900)) # Create size policy for scrollAreaWidgetContents sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.scrollAreaWidgetContents.sizePolicy().hasHeightForWidth()) # Set scrollAreaWidgetContents size policy self.scrollAreaWidgetContents.setSizePolicy(sizePolicy) self.scrollAreaWidgetContents.setMinimumSize(QtCore.QSize(771, 900)) self.scrollAreaWidgetContents.setMaximumSize(QtCore.QSize(771, 900)) self.scrollAreaWidgetContents.setObjectName("scrollAreaWidgetContents") menu_frame = QFrame() menu_frame.setFixedWidth(130) self.home_btn = QPushButton("Home") self.home_btn.setStyleSheet( "background: #14ffec; font-family: Verdana, Geneva, sans-serif; font-weight: bold;" ) self.home_btn.clicked.connect(self.showHome) self.register_btn = QPushButton("Registration") self.register_btn.setStyleSheet( "QPushButton { background: #14ffec; font-family: Verdana, Geneva, sans-serif; font-weight: bold; }" "QPushButton:pressed { background: #f6c90e; }") self.member_profiles_btn = QPushButton("Member Profiles") self.member_profiles_btn.setStyleSheet( "background: #14ffec; font-family: Verdana, Geneva, sans-serif; font-weight: bold;" ) #self.member_profiles_btn.clicked.connect(self.showMemberProfiles) self.units_btn = QPushButton("Units") self.units_btn.setStyleSheet( "background: #14ffec; font-family: Verdana, Geneva, sans-serif; font-weight: bold;" ) self.units_btn.clicked.connect(self.showUnits) self.files_btn = QPushButton("Files") self.files_btn.setStyleSheet( "background: #14ffec; font-family: Verdana, Geneva, sans-serif; font-weight: bold;" ) self.files_btn.clicked.connect(self.showFiles) self.accounts_btn = QPushButton("Accounts") self.accounts_btn.setStyleSheet( "background: #14ffec; font-family: Verdana, Geneva, sans-serif; font-weight: bold;" ) self.accounts_btn.clicked.connect(self.showAccounts) self.share_capital_btn = QPushButton("Share Capital") self.share_capital_btn.setStyleSheet( "background: #14ffec; font-family: Verdana, Geneva, sans-serif; font-weight: bold;" ) self.share_capital_btn.clicked.connect(self.showShareCapital) menu_vlayout.addWidget(self.home_btn) menu_vlayout.addWidget(self.units_btn) menu_vlayout.addWidget(self.files_btn) menu_vlayout.addWidget(self.accounts_btn) menu_vlayout.addWidget(self.share_capital_btn) menu_vlayout.addWidget(self.register_btn) menu_vlayout.addWidget(self.member_profiles_btn) v_layout1 = QVBoxLayout() toolbar_Hlayout = QHBoxLayout() toolbar_Hlayout.setSpacing(0) toolbar_Hlayout.addStretch() toolbar_Hlayout.setContentsMargins(2, 1, 2, 1) sortby_label = QLabel("Sort by:") sortby_label.setStyleSheet( "background: #e8e4e1; color: #1b1b2f; border: none; font-family: Verdana, Geneva, sans-serif; padding-left: 0.5px;" ) self.comboBox = QComboBox() self.comboBox.setStyleSheet( "background: white; border: none; font-family: Verdana, Geneva, sans-serif;" ) self.comboBox.addItem("Date") self.comboBox.addItem("Name") self.comboBox.setFixedWidth(57) search_label = QLabel("Search:") search_label.setStyleSheet( "background: #e8e4e1; color: #1b1b2f; border: none; font-family: Verdana, Geneva, sans-serif; padding-left: 0.5px;" ) self.search_input = QLineEdit() self.search_input.setStyleSheet( "background: white; border: none; font-family: Verdana, Geneva, sans-serif;" ) self.search_input.setFixedWidth(150) toolbar_Hlayout.addWidget(search_label) toolbar_Hlayout.addWidget(self.search_input) toolbar_Hlayout.insertWidget(0, self.comboBox) toolbar_Hlayout.insertWidget(0, sortby_label) self.listwidget = QListWidget() self.listwidget.setSpacing(1) #self.listwidget.setStyleSheet("QListWidget {font-family: Verdana, Geneva, sans-serif;}" "QListWidget.item:hover {color: white; background: blue;}") self.listwidget.setFixedHeight(600) label_Hlayout = QHBoxLayout() label_Hlayout.setSpacing(1) label_Hlayout.setContentsMargins(2, 1, 2, 0) no_label, name_label, address_label, date_label = QLabel('No'), QLabel( 'Name'), QLabel('Address'), QLabel('Date') no_label.setStyleSheet( "background: white; color: blue; font-family: Verdana, Geneva, sans-serif; font-weight: bold; padding-top: 3px; padding-bottom: 3px; border-left: none; border-right: none; border-top: none;" ) name_label.setStyleSheet( "background: white; color: blue; font-family: Verdana, Geneva, sans-serif; font-weight: bold; padding-left: 65px; padding-right: 60px; padding-top: 3px; padding-bottom: 3px; border-left: none; border-right: none; border-top: none;" ) address_label.setStyleSheet( "background: white; color: blue; font-family: Verdana, Geneva, sans-serif; font-weight: bold; padding-left: 145px; padding-right: 150px; padding-top: 3px; padding-bottom: 3px; border-left: none; border-right: none; border-top: none;" ) date_label.setStyleSheet( "background: white; color: blue; font-family: Verdana, Geneva, sans-serif; font-weight: bold; border-left: none; padding-top: 3px; padding-bottom: 3px; padding-right: 15px; padding-left: 11px; border-right: none; border-top: none;" ) label_Hlayout.addWidget(no_label, 0) label_Hlayout.addWidget(name_label, 1) label_Hlayout.addWidget(address_label, 2) label_Hlayout.addWidget(date_label, 0) v_layout1.addLayout(toolbar_Hlayout) v_layout1.addLayout(label_Hlayout) v_layout1.addWidget(self.listwidget) v_layout1.setContentsMargins(0, 0, 0, 0) v_layout1.setSpacing(0) v_layout2 = QVBoxLayout() units_label = QLabel("Units") units_label.setStyleSheet("border: 1px solid red;") v_layout2.addWidget(units_label) v_layout3 = QVBoxLayout() files_label = QLabel("Files") files_label.setStyleSheet("border: 1px solid red;") v_layout3.addWidget(files_label) v_layout4 = QVBoxLayout() accounts_label = QLabel("Accounts") accounts_label.setStyleSheet("border: 1px solid red;") v_layout4.addWidget(accounts_label) v_layout5 = QVBoxLayout() share_capitals_label = QLabel("Share Capitals") share_capitals_label.setStyleSheet("border: 1px solid red;") v_layout5.addWidget(share_capitals_label) self.frame1 = QFrame() self.frame1.setLayout(v_layout1) self.frame2 = QFrame() self.frame2.setStyleSheet("border: 1px solid blue;") self.frame2.setLayout(v_layout2) self.frame3 = QFrame() self.frame3.setStyleSheet("border: 1px solid blue;") self.frame3.setLayout(v_layout3) self.frame4 = QFrame() self.frame4.setStyleSheet("border: 1px solid blue;") self.frame4.setLayout(v_layout4) self.frame5 = QFrame() self.frame5.setStyleSheet("border: 1px solid blue;") self.frame5.setLayout(v_layout5) frames_parent_vlayout.addWidget(self.frame1) frames_parent_vlayout.addWidget(self.frame2) frames_parent_vlayout.addWidget(self.frame3) frames_parent_vlayout.addWidget(self.frame4) frames_parent_vlayout.addWidget(self.frame5) self.showHome() menu_frame.setLayout(menu_vlayout) menu_frame.setStyleSheet("background: #252525;") menu_vlayout.setContentsMargins(5, 5, 5, 0) parent_horizontal_layout.addWidget(menu_frame) parent_horizontal_layout.addWidget(frames_container) frames_parent_vlayout.addStretch() frames_parent_vlayout.setContentsMargins(0, 0, 0, 0) frames_container.setContentsMargins(0, 0, 0, 0) frames_container.setStyleSheet("background: #978d58;") menu_vlayout.addStretch() parent_horizontal_layout.setContentsMargins(0, 0, 0, 0) parent_horizontal_layout.setSpacing(0) #parent_horizontal_layout.addStretch() self.scrollArea.setWidget(self.scrollAreaWidgetContents)
class Invite(QWidget): """ An invite is a small notification being displayed in the bottom right corner of the window. It fades in and out, and is used to invite an user to jump to a certain location. Some other uses might be added later. """ def __init__(self, plugin, parent=None): super(Invite, self).__init__(parent) self._plugin = plugin self._time = 0 self.setWindowFlags(Qt.FramelessWindowHint | Qt.Tool | Qt.WindowStaysOnTopHint) self.setAttribute(Qt.WA_ShowWithoutActivating) self.setAttribute(Qt.WA_TranslucentBackground) self._icon = QLabel() self._icon.setAutoFillBackground(False) self._icon.setAttribute(Qt.WA_TranslucentBackground) self._text = QLabel() self._text.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter) self._layout = QHBoxLayout() self._layout.addWidget(self._text) self.setLayout(self._layout) # Fade in and out animation self._popup_opacity = 0.0 self._animation = QPropertyAnimation() self._animation.setTargetObject(self) self._animation.setPropertyName("popup_opacity") self._animation.finished.connect(self.hide) # Timer used to auto-close the window self._timer = QTimer() self._timer.timeout.connect(self.hide_animation) self._callback = None self._triggered = False @property def time(self): return self._time @time.setter def time(self, time): self._time = time @property def text(self): return self._text.text() @text.setter def text(self, text): self._text.setText(text) self.adjustSize() @property def icon(self): return self._icon.pixmap() @icon.setter def icon(self, pixmap): # Resize the given pixmap pixmap_height = self._text.sizeHint().height() self._icon.setPixmap( pixmap.scaled( pixmap_height, pixmap_height, Qt.KeepAspectRatio, Qt.SmoothTransformation, )) self._layout.insertWidget(0, self._icon) @property def callback(self): return self._callback @callback.setter def callback(self, callback): self._callback = callback @property def triggered(self): return self._triggered @triggered.setter def triggered(self, triggered): self._triggered = triggered def paintEvent(self, event): # noqa: N802 """We override the painting event to draw the invite ourselves.""" painter = QPainter(self) painter.setRenderHint(QPainter.Antialiasing) rect = QRect(self.rect()) # Draw the border painter.setBrush(QBrush(QColor(122, 122, 122))) painter.setPen(Qt.NoPen) painter.drawRect(rect) rect.setX(rect.x() + 1) rect.setY(rect.y() + 1) rect.setWidth(rect.width() - 1) rect.setHeight(rect.height() - 1) # Draw the background painter.setBrush(QBrush(QColor(255, 255, 225))) painter.setPen(Qt.NoPen) painter.drawRect(rect) def mouseReleaseEvent(self, event): # noqa: N802 """ This function is called when the user clicks the invite. It triggers the callback function is it has been specified, and hides the invite. """ if self._callback: self._callback() self._triggered = True self._popup_opacity = 0.0 self.hide() def show(self): """Shows the invite to user. It triggers a fade in effect.""" self._plugin.logger.debug("Showing invite %s" % self.text) self.setWindowOpacity(0.0) self._animation.setDuration(500) self._animation.setStartValue(0.0) self._animation.setEndValue(1.0) # Map the notification to the bottom right corner pos = QPoint(self.parent().width() - 25, self.parent().height() - 50) pos = self.parent().mapToGlobal(pos) self.setGeometry( pos.x() - self.width(), pos.y() - self.height(), self.width(), self.height(), ) super(Invite, self).show() self._animation.start() self._timer.start(3500) def hide(self): """Hides the invite only if it is fully transparent.""" if self._popup_opacity == 0.0: self._plugin.interface.widget.refresh() super(Invite, self).hide() def hide_animation(self): """Hides the invite. It triggers the fade out animation.""" self._timer.stop() self._animation.setDuration(500) self._animation.setStartValue(1.0) self._animation.setEndValue(0.0) self._animation.start() @pyqtProperty(float) def popup_opacity(self): return self._popup_opacity @popup_opacity.setter def popup_opacity(self, opacity): self._popup_opacity = opacity self.setWindowOpacity(opacity)
class LineEdit(QLineEdit): inactiveText = QtDynamicProperty('inactiveText', unicode) widgetSpacing = QtDynamicProperty('widgetSpacing', int) def __init__(self, parent=None, contents=u""): super(LineEdit, self).__init__(contents, parent) box_direction = QBoxLayout.RightToLeft if self.isRightToLeft() else QBoxLayout.LeftToRight self.inactiveText = u"" self.left_widget = SideWidget(self) self.left_widget.resize(0, 0) self.left_layout = QHBoxLayout(self.left_widget) self.left_layout.setContentsMargins(0, 0, 0, 0) self.left_layout.setDirection(box_direction) self.left_layout.setSizeConstraint(QLayout.SetFixedSize) self.right_widget = SideWidget(self) self.right_widget.resize(0, 0) self.right_layout = QHBoxLayout(self.right_widget) self.right_layout.setContentsMargins(0, 0, 0, 0) self.right_layout.setDirection(box_direction) self.right_layout.addItem(QSpacerItem(0, 0, QSizePolicy.Expanding, QSizePolicy.Minimum)) self.widgetSpacing = 2 self.left_widget.sizeHintChanged.connect(self._update_text_margins) self.right_widget.sizeHintChanged.connect(self._update_text_margins) @property def left_margin(self): return self.left_widget.sizeHint().width() + 2*self.left_layout.spacing() @property def right_margin(self): return self.right_widget.sizeHint().width() + 2*self.right_layout.spacing() def _update_text_margins(self): self.setTextMargins(self.left_margin, 0, self.right_margin, 0) self._update_side_widget_locations() def _update_side_widget_locations(self): option = QStyleOptionFrame() self.initStyleOption(option) spacing = self.right_layout.spacing() text_rect = self.style().subElementRect(QStyle.SE_LineEditContents, option, self) text_rect.adjust(spacing, 0, -spacing, 0) mid_height = text_rect.center().y() + 1 - (text_rect.height() % 2) # need -1 correction for odd heights -Dan if self.left_layout.count() > 0: left_height = mid_height - self.left_widget.height()/2 left_width = self.left_widget.width() if left_width == 0: left_height = mid_height - self.left_widget.sizeHint().height()/2 self.left_widget.move(text_rect.x(), left_height) text_rect.setX(self.left_margin) text_rect.setY(mid_height - self.right_widget.sizeHint().height()/2.0) text_rect.setHeight(self.right_widget.sizeHint().height()) self.right_widget.setGeometry(text_rect) def event(self, event): event_type = event.type() if event_type == QEvent.LayoutDirectionChange: box_direction = QBoxLayout.RightToLeft if self.isRightToLeft() else QBoxLayout.LeftToRight self.left_layout.setDirection(box_direction) self.right_layout.setDirection(box_direction) elif event_type == QEvent.DynamicPropertyChange: property_name = event.propertyName() if property_name == 'widgetSpacing': self.left_layout.setSpacing(self.widgetSpacing) self.right_layout.setSpacing(self.widgetSpacing) self._update_text_margins() elif property_name == 'inactiveText': self.update() return QLineEdit.event(self, event) def resizeEvent(self, event): self._update_side_widget_locations() QLineEdit.resizeEvent(self, event) def paintEvent(self, event): QLineEdit.paintEvent(self, event) if not self.hasFocus() and not self.text() and self.inactiveText: options = QStyleOptionFrame() self.initStyleOption(options) text_rect = self.style().subElementRect(QStyle.SE_LineEditContents, options, self) text_rect.adjust(self.left_margin+2, 0, -self.right_margin, 0) painter = QPainter(self) painter.setPen(self.palette().brush(QPalette.Disabled, QPalette.Text).color()) painter.drawText(text_rect, Qt.AlignLeft | Qt.AlignVCenter, self.inactiveText) def addHeadWidget(self, widget): if self.isRightToLeft(): self.right_layout.insertWidget(1, widget) else: self.left_layout.addWidget(widget) def addTailWidget(self, widget): if self.isRightToLeft(): self.left_layout.addWidget(widget) else: self.right_layout.insertWidget(1, widget) def removeWidget(self, widget): self.left_layout.removeWidget(widget) self.right_layout.removeWidget(widget) widget.hide()
class E5LineEdit(QLineEdit): """ Class implementing a line edit widget showing some inactive text. """ LeftSide = 0 RightSide = 1 def __init__(self, parent=None, inactiveText=""): """ Constructor @param parent reference to the parent widget (QWidget) @param inactiveText text to be shown on inactivity (string) """ super(E5LineEdit, self).__init__(parent) self.setMinimumHeight(22) if qVersion() < "4.7.0": self.__inactiveText = inactiveText else: self.setPlaceholderText(inactiveText) self.__mainLayout = QHBoxLayout(self) self.__mainLayout.setContentsMargins(0, 0, 0, 0) self.__mainLayout.setSpacing(0) self.__leftMargin = 0 self.__leftWidget = E5LineEditSideWidget(self) self.__leftWidget.resize(0, 0) self.__leftLayout = QHBoxLayout(self.__leftWidget) self.__leftLayout.setContentsMargins(0, 0, 2, 0) if QApplication.isRightToLeft(): self.__leftLayout.setDirection(QBoxLayout.RightToLeft) else: self.__leftLayout.setDirection(QBoxLayout.LeftToRight) self.__leftLayout.setSizeConstraint(QLayout.SetFixedSize) self.__rightWidget = E5LineEditSideWidget(self) self.__rightWidget.resize(0, 0) self.__rightLayout = QHBoxLayout(self.__rightWidget) self.__rightLayout.setContentsMargins(0, 0, 2, 0) if self.isRightToLeft(): self.__rightLayout.setDirection(QBoxLayout.RightToLeft) else: self.__rightLayout.setDirection(QBoxLayout.LeftToRight) horizontalSpacer = QSpacerItem( 0, 0, QSizePolicy.Expanding, QSizePolicy.Minimum) self.__mainLayout.addWidget( self.__leftWidget, 0, Qt.AlignVCenter | Qt.AlignLeft) self.__mainLayout.addItem(horizontalSpacer) self.__mainLayout.addWidget( self.__rightWidget, 0, Qt.AlignVCenter | Qt.AlignRight) if self.isRightToLeft(): self.__mainLayout.setDirection(QBoxLayout.RightToLeft) else: self.__mainLayout.setDirection(QBoxLayout.LeftToRight) self.setWidgetSpacing(3) self.__leftWidget.sizeHintChanged.connect(self._updateTextMargins) self.__rightWidget.sizeHintChanged.connect(self._updateTextMargins) def setLeftMargin(self, margin): """ Public method to set the left margin. @param margin left margin in pixel (integer) """ self.__leftMargin = margin def leftMargin(self): """ Public method to get the size of the left margin. @return left margin in pixel (integer) """ return self.__leftMargin def event(self, evt): """ Public method to handle events. @param evt reference to the event (QEvent) @return flag indicating, whether the event was recognized (boolean) """ if evt.type() == QEvent.LayoutDirectionChange: if self.isRightToLeft(): self.__mainLayout.setDirection(QBoxLayout.RightToLeft) self.__leftLayout.setDirection(QBoxLayout.RightToLeft) self.__rightLayout.setDirection(QBoxLayout.RightToLeft) else: self.__mainLayout.setDirection(QBoxLayout.LeftToRight) self.__leftLayout.setDirection(QBoxLayout.LeftToRight) self.__rightLayout.setDirection(QBoxLayout.LeftToRight) return QLineEdit.event(self, evt) def paintEvent(self, evt): """ Protected method handling a paint event. @param evt reference to the paint event (QPaintEvent) """ super(E5LineEdit, self).paintEvent(evt) if qVersion() < "4.7.0": if not self.text() and \ self.__inactiveText and \ not self.hasFocus(): panel = QStyleOptionFrame() self.initStyleOption(panel) textRect = self.style().subElementRect( QStyle.SE_LineEditContents, panel, self) textRect.adjust(2, 0, 0, 0) left = self.textMargin(self.LeftSide) right = self.textMargin(self.RightSide) textRect.adjust(left, 0, -right, 0) painter = QPainter(self) painter.setPen(self.palette().brush( QPalette.Disabled, QPalette.Text).color()) painter.drawText( textRect, Qt.AlignLeft | Qt.AlignVCenter, self.__inactiveText) def _updateTextMargins(self): """ Protected slot to update the text margins. """ if self.__leftMargin == 0: left = self.__leftWidget.sizeHint().width() else: left = self.__leftMargin right = self.__rightWidget.sizeHint().width() top = 0 bottom = 0 self.setTextMargins(left, top, right, bottom) def addWidget(self, widget, position): """ Public method to add a widget to a side. @param widget reference to the widget to add (QWidget) @param position position to add to (E5LineEdit.LeftSide, E5LineEdit.RightSide) """ if widget is None: return if self.isRightToLeft(): if position == self.LeftSide: position = self.RightSide else: position = self.LeftSide if position == self.LeftSide: self.__leftLayout.addWidget(widget) else: self.__rightLayout.insertWidget(1, widget) def removeWidget(self, widget): """ Public method to remove a widget from a side. @param widget reference to the widget to remove (QWidget) """ if widget is None: return self.__leftLayout.removeWidget(widget) self.__rightLayout.removeWidget(widget) widget.hide() def widgetSpacing(self): """ Public method to get the side widget spacing. @return side widget spacing (integer) """ return self.__leftLayout.spacing() def setWidgetSpacing(self, spacing): """ Public method to set the side widget spacing. @param spacing side widget spacing (integer) """ self.__leftLayout.setSpacing(spacing) self.__rightLayout.setSpacing(spacing) self._updateTextMargins() def textMargin(self, position): """ Public method to get the text margin for a side. @param position side to get margin for (E5LineEdit.LeftSide, E5LineEdit.RightSide) @return text margin (integer) """ spacing = self.__rightLayout.spacing() w = 0 if position == self.LeftSide: w = self.__leftWidget.sizeHint().width() else: w = self.__rightWidget.sizeHint().width() if w == 0: return 0 return w + spacing * 2 def inactiveText(self): """ Public method to get the inactive text. @return inactive text (string) """ if qVersion() < "4.7.0": return self.__inactiveText else: return self.placeholderText() def setInactiveText(self, inactiveText): """ Public method to set the inactive text. @param inactiveText text to be shown on inactivity (string) """ if qVersion() < "4.7.0": self.__inactiveText = inactiveText self.update() else: self.setPlaceholderText(inactiveText)
class EstimhabW(StatModUseful): """ The Estimhab class provides the graphical interface for the version of the Estimhab model written in HABBY. The Estimhab model is described elsewhere. EstimhabW() just loads the data for Estimhab given by the user. """ save_signal_estimhab = pyqtSignal() """ PyQtsignal to save the Estimhab data. """ def __init__(self, path_prj, name_prj): super().__init__() self.tab_name = "estimhab" self.tab_position = 7 self.model_type = "Estimhab" self.eq50 = QLineEdit() self.esub = QLineEdit() self.path_prj = path_prj self.name_prj = name_prj self.path_bio_estimhab = os.path.join(self.path_bio, 'estimhab') self.total_lineedit_number = 1 self.init_iu() self.process_manager = MyProcessManager( "estimhab_plot") # SC (Suitability Curve) self.read_estimhab_dict() self.fill_input_data() self.fill_fish_name() self.check_if_ready_to_compute() self.eq1.textChanged.connect(self.check_if_ready_to_compute) self.eq2.textChanged.connect(self.check_if_ready_to_compute) self.ew1.textChanged.connect(self.check_if_ready_to_compute) self.ew2.textChanged.connect(self.check_if_ready_to_compute) self.eh1.textChanged.connect(self.check_if_ready_to_compute) self.eh2.textChanged.connect(self.check_if_ready_to_compute) self.eq50.textChanged.connect(self.check_if_ready_to_compute) self.eqmin.textChanged.connect(self.check_if_ready_to_compute) self.eqmax.textChanged.connect(self.check_if_ready_to_compute) self.esub.textChanged.connect(self.check_if_ready_to_compute) self.selected_aquatic_animal_qtablewidget.model().rowsInserted.connect( self.check_if_ready_to_compute) self.selected_aquatic_animal_qtablewidget.model().rowsRemoved.connect( self.check_if_ready_to_compute) def init_iu(self): """ This function is used to initialized an instance of the EstimhabW() class. It is called by __init__(). **Technical comments and walk-through** First we looked if some data for Estimhab was saved before by an user. If yes, we will fill the GUI with the information saved before. Estimhab information is saved in hdf5 file format and the path/name of the hdf5 file is saved in the xml project file. So we open the xml project file and look if the name of an hdf5 file was saved for Estimhab. If yes, the hdf5 file is read. The format of hdf5 file is relatively simple. Each input data for Estimhab has its own dataset (qmes, hmes, wmes, q50, qrange, and substrate). Then, we have a list of string which are a code for the fish species which were analyzed. All the data contained in hdf5 file is loaded into variable. The different label are written on the graphical interface. Then, two QListWidget are modified. The first list contains all the fish species on which HABBY has info (see XML Estimhab format for more info). The second list is the fish selected by the user on which Estimhab will be run. Here, we link these lists with two functions so that the user can select/deselect fish using the mouse. The function name are add_fish() and remove_fish(). Then, we fill the first list. HABBY look up all file of xml type in the “Path_bio” folder (the one indicated in the xml project file under the attribute “Path_bio”). The name are them modified so that the only the name of species appears (and not the full path). We set the layout with all the different QLineEdit where the user can write the needed data. Estimhab model is saved using a function situated in MainWindows_1.py (frankly, I am not so sure why I did put the save function there, but anyway). So the save button just send a signal to MainWindows here, which save the data. """ available_model_label = QLabel(self.tr('Available')) selected_model_label = QLabel(self.tr('Selected')) self.lineedit_width = 50 self.spacer_width = 50 # input q1_layout = QHBoxLayout() q1_layout.addWidget(QLabel('Q1 [m<sup>3</sup>/s]')) q1_layout.addWidget(self.eq1) q1_layout.addItem(QSpacerItem(self.spacer_width, 1)) self.eq1.setFixedWidth(self.lineedit_width) q2_layout = QHBoxLayout() q2_layout.addWidget(QLabel('Q2 [m<sup>3</sup>/s]')) q2_layout.addWidget(self.eq2) q2_layout.addItem(QSpacerItem(self.spacer_width, 1)) self.eq2.setFixedWidth(self.lineedit_width) w1_layout = QHBoxLayout() w1_layout.addWidget(QLabel(self.tr("Width1 [m]"))) w1_layout.addWidget(self.ew1) w1_layout.addItem(QSpacerItem(self.spacer_width, 1)) self.ew1.setFixedWidth(self.lineedit_width) w2_layout = QHBoxLayout() w2_layout.addWidget(QLabel(self.tr("Width2 [m]"))) w2_layout.addWidget(self.ew2) w2_layout.addItem(QSpacerItem(self.spacer_width, 1)) self.ew2.setFixedWidth(self.lineedit_width) h1_layout = QHBoxLayout() h1_layout.addWidget(QLabel(self.tr("Height1 [m]"))) h1_layout.addWidget(self.eh1) self.eh1.setFixedWidth(self.lineedit_width) h2_layout = QHBoxLayout() h2_layout.addWidget(QLabel(self.tr("Height2 [m]"))) h2_layout.addWidget(self.eh2) self.eh2.setFixedWidth(self.lineedit_width) q50_layout = QHBoxLayout() q50_layout.addWidget(QLabel('Qmedian/Q50 [m<sup>3</sup>/s]')) q50_layout.addWidget(self.eq50) q50_layout.addItem(QSpacerItem(self.spacer_width, 1)) self.eq50.setFixedWidth(self.lineedit_width) sub_layout = QHBoxLayout() sub_layout.addWidget(QLabel(self.tr('Mean substrate size [m]'))) sub_layout.addWidget(self.esub) sub_layout.addItem(QSpacerItem(self.spacer_width, 1)) self.esub.setFixedWidth(self.lineedit_width) # output q1out_layout = QHBoxLayout() q1out_layout.addWidget(QLabel(self.tr("Qmin [m<sup>3</sup>/s]"))) q1out_layout.addWidget(self.eqmin) q1out_layout.addItem(QSpacerItem(self.spacer_width, 1)) self.eqmin.setFixedWidth(self.lineedit_width) q2out_layout = QHBoxLayout() q2out_layout.addWidget(QLabel(self.tr("Qmax [m<sup>3</sup>/s]"))) q2out_layout.addWidget(self.eqmax) q2out_layout.addItem(QSpacerItem(self.spacer_width, 1)) self.eqmax.setFixedWidth(self.lineedit_width) self.q2target_layout = QHBoxLayout() self.q2target_layout.addWidget( QLabel(self.tr("Qtarget [m<sup>3</sup>/s]"))) self.q2target_layout.addWidget(self.add_qtarget_button) self.q2target_layout.addWidget(self.remove_qtarget_button) self.add_qtarget_button.clicked.connect(self.add_new_qtarget) self.remove_qtarget_button.clicked.connect(self.remove_one_qtarget) # create lists with the possible fishes self.list_f.setSelectionMode(QAbstractItemView.ExtendedSelection) self.list_f.setDragDropMode(QAbstractItemView.DragDrop) self.list_f.setDefaultDropAction(Qt.MoveAction) self.list_f.setAcceptDrops(True) self.list_f.setSortingEnabled(True) self.selected_aquatic_animal_qtablewidget.setSelectionMode( QAbstractItemView.ExtendedSelection) self.selected_aquatic_animal_qtablewidget.setDragDropMode( QAbstractItemView.DragDrop) self.selected_aquatic_animal_qtablewidget.setDefaultDropAction( Qt.MoveAction) self.selected_aquatic_animal_qtablewidget.setAcceptDrops(True) self.selected_aquatic_animal_qtablewidget.setSortingEnabled(True) # insist on white background color (for linux, mac) self.setAutoFillBackground(True) p = self.palette() p.setColor(self.backgroundRole(), Qt.white) self.setPalette(p) # send model self.run_stop_button = QPushButton(self.tr('Run Estimhab'), self) self.run_stop_button.clicked.connect(self.run_estmihab) change_button_color(self.run_stop_button, "#47B5E6") self.run_stop_button.setEnabled(False) # empty frame scrolable content_widget = QFrame() # hydraulic_data_group hydraulic_data_group = QGroupBox(self.tr('Hydraulic data input')) hydraulic_data_group.setToolTip( self.tr("Double click to reset the input data group.")) hydraulic_data_layout = QGridLayout(hydraulic_data_group) hydraulic_data_layout.addLayout(q1_layout, 0, 0) hydraulic_data_layout.addLayout(w1_layout, 0, 1) hydraulic_data_layout.addLayout(h1_layout, 0, 2) hydraulic_data_layout.addLayout(q2_layout, 1, 0) hydraulic_data_layout.addLayout(w2_layout, 1, 1) hydraulic_data_layout.addLayout(h2_layout, 1, 2) hydraulic_data_layout.addLayout(q50_layout, 2, 0) hydraulic_data_layout.addLayout(sub_layout, 2, 1) hydraulic_data_group.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum) self.doubleclick_input_group = DoubleClicOutputGroup() hydraulic_data_group.installEventFilter(self.doubleclick_input_group) self.doubleclick_input_group.double_clic_signal.connect( self.reset_hydraulic_data_input_group) # hydraulic_data_output_group hydraulic_data_output_group = QGroupBox( self.tr('Desired hydraulic output data')) hydraulic_data_output_group.setToolTip( self.tr("Double click to reset the outpout data group.")) hydraulic_data_layout = QGridLayout(hydraulic_data_output_group) hydraulic_data_layout.addLayout(q1out_layout, 0, 0) hydraulic_data_layout.addLayout(q2out_layout, 0, 1) hydraulic_data_layout.addLayout(self.q2target_layout, 0, 2) hydraulic_data_output_group.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum) self.doubleclick_output_group = DoubleClicOutputGroup() hydraulic_data_output_group.installEventFilter( self.doubleclick_output_group) self.doubleclick_output_group.double_clic_signal.connect( self.reset_hydraulic_data_output_group) # models_group models_group = QGroupBox(self.tr('Biological models')) models_layout = QGridLayout(models_group) models_layout.addWidget(available_model_label, 0, 0) models_layout.addWidget(selected_model_label, 0, 1) models_layout.addWidget(self.list_f, 1, 0) models_layout.addWidget(self.selected_aquatic_animal_qtablewidget, 1, 1) models_layout.addWidget(self.run_stop_button, 2, 1) self.doubleclick_models_group = DoubleClicOutputGroup() models_group.installEventFilter(self.doubleclick_models_group) self.doubleclick_models_group.double_clic_signal.connect( self.reset_models_group) # gereral_layout self.layout3 = QVBoxLayout(content_widget) self.layout3.addWidget(hydraulic_data_group, Qt.AlignLeft) self.layout3.addWidget(hydraulic_data_output_group) self.layout3.addWidget(models_group) # self.setLayout(self.layout3) self.setWidgetResizable(True) self.setFrameShape(QFrame.NoFrame) self.setWidget(content_widget) def add_new_qtarget(self): # count existing number of lineedit total_widget_number = self.q2target_layout.count() self.total_lineedit_number = total_widget_number - 2 # - first : qlabel and plus and moins button + New lineedit lineedit_name = 'new_qtarget' + str(self.total_lineedit_number) setattr(self, lineedit_name, QLineEdit()) getattr(self, lineedit_name).setFixedWidth(self.lineedit_width) self.target_lineedit_list.append(getattr(self, lineedit_name)) self.q2target_layout.insertWidget(total_widget_number - 2, getattr(self, lineedit_name)) def remove_one_qtarget(self): # count existing number of lineedit total_widget_number = self.q2target_layout.count() self.total_lineedit_number = total_widget_number - 3 # - first : qlabel and plus and moins button - New lineedit if self.total_lineedit_number > 0: self.target_lineedit_list.pop(-1) self.q2target_layout.itemAt(total_widget_number - 3).widget().setParent(None) self.total_lineedit_number = self.total_lineedit_number - 1 def reset_hydraulic_data_input_group(self): # remove txt in lineedit self.eq1.setText("") self.eq2.setText("") self.ew1.setText("") self.ew2.setText("") self.eh1.setText("") self.eh2.setText("") self.eq50.setText("") self.esub.setText("") def reset_hydraulic_data_output_group(self): # remove txt in lineedit self.eqmin.setText("") self.eqmax.setText("") # remove lineedits qtarget for i in reversed(range(1, self.q2target_layout.count() - 1)): self.q2target_layout.itemAt(i).widget().setParent(None) self.total_lineedit_number = self.total_lineedit_number - 1 self.target_lineedit_list = [] def reset_models_group(self): if self.selected_aquatic_animal_qtablewidget.count() > 0: self.selected_aquatic_animal_qtablewidget.clear() self.fill_fish_name() def read_estimhab_dict(self): """ This function opens the json data created by estimhab """ # load_project_properties self.estimhab_dict = load_specific_properties(self.path_prj, [self.model_type])[0] def fill_fish_name(self): """ This function reads all latin fish name from the xml files which are contained in the biological directory related to estimhab and fill GUI fish names """ all_xmlfile = glob.glob(os.path.join(self.path_bio_estimhab, r'*.xml')) if self.estimhab_dict: selected_fish = self.estimhab_dict["fish_list"] else: selected_fish = [] for f in all_xmlfile: # open xml try: try: docxml = ET.parse(f) root = docxml.getroot() except IOError: self.send_log.emit( self.tr("Warning: ") + self.tr("The .habby project file ") + f + self.tr(" could not be open.\n")) return except ET.ParseError: self.send_log.emit( self.tr("Warning: ") + self.tr("The .habby project file ") + f + self.tr(" is not well-formed.\n")) return # find fish name fish_name = root.find(".//LatinName") # None is null for python 3 if fish_name is not None: fish_name = fish_name.text.strip() # find fish stage stage = root.find(".//estimhab/stage") # None is null for python 3 if stage is not None: stage = stage.text.strip() if stage != 'all_stage': fish_name += ' ' + stage # check if not selected if fish_name not in selected_fish: # add to the list item = QListWidgetItem(fish_name) item.setData(1, f) self.list_f.addItem(item) else: # add to the list item2 = QListWidgetItem(fish_name) item2.setData(1, f) self.selected_aquatic_animal_qtablewidget.addItem(item2) def fill_input_data(self): if self.estimhab_dict: # input data self.eq1.setText(str(self.estimhab_dict["q"][0])) self.eq2.setText(str(self.estimhab_dict["q"][1])) self.eh1.setText(str(self.estimhab_dict["h"][0])) self.eh2.setText(str(self.estimhab_dict["h"][1])) self.ew1.setText(str(self.estimhab_dict["w"][0])) self.ew2.setText(str(self.estimhab_dict["w"][1])) self.eq50.setText(str(self.estimhab_dict["q50"])) self.eqmin.setText(str(self.estimhab_dict["qrange"][0])) self.eqmax.setText(str(self.estimhab_dict["qrange"][1])) self.esub.setText(str(self.estimhab_dict["substrate"])) # qtarg if len(self.estimhab_dict["qtarg"]) > 0: while self.total_lineedit_number != len( self.estimhab_dict["qtarg"]): self.add_new_qtarget() for qtarg_num, qtarg_value in enumerate( self.estimhab_dict["qtarg"][1:]): getattr(self, 'new_qtarget' + str(qtarg_num + 2)).setText( str(qtarg_value)) def check_if_ready_to_compute(self): all_string_selection = (self.eq1.text(), self.eq2.text(), self.ew1.text(), self.ew2.text(), self.eh1.text(), self.eh2.text(), self.eq50.text(), self.eqmin.text(), self.eqmax.text(), self.esub.text()) # minimum one fish and string in input lineedits to enable run_stop_button if self.selected_aquatic_animal_qtablewidget.count( ) > 0 and "" not in all_string_selection: self.run_stop_button.setEnabled(True) else: self.run_stop_button.setEnabled(False) def change_folder(self): """ A small method to change the folder which indicates where is the biological data """ # user find new path self.path_bio_estimhab = QFileDialog.getExistingDirectory( self, self.tr("Open Directory"), os.getenv('HOME')) # update list self.list_f.clear() all_file = glob.glob(os.path.join(self.path_bio_estimhab, r'*.xml')) # make it look nicer for i in range(0, len(all_file)): all_file[i] = all_file[i].replace(self.path_bio_estimhab, "") all_file[i] = all_file[i].replace("\\", "") all_file[i] = all_file[i].replace(".xml", "") item = QListWidgetItem(all_file[i]) # add them to the menu self.list_f.addItem(item) def run_estmihab(self): """ A function to execute Estimhab by calling the estimhab function. **Technical comment** This is the function making the link between the GUI and the source code proper. The source code for Estimhab is in src/Estimhab.py. This function loads in memory the data given in the graphical interface and call sthe Estimhab model. The data could be written by the user now or it could be data which was saved in the hdf5 file before and loaded when HABBY was open (and the init function called). We check that all necessary data is present and that the data given makes sense (e.g.,the minimum discharge should not be bigger than the maximal discharge, the data should be a float, etc.). We then remove the duplicate fish species (in case the user select one specie twice) and the Estimhab model is called. The log is then written (see the paragraph on the log for more information). Next, the figures created by Estimmhab are shown. As there is only a short number of outputs for Estimhab, we create a figure in all cases (it could be changed by adding a checkbox on the GUI like in the Telemac or other hydrological class). """ # prepare data try: q = [ float(self.eq1.text().replace(",", ".")), float(self.eq2.text().replace(",", ".")) ] w = [ float(self.ew1.text().replace(",", ".")), float(self.ew2.text().replace(",", ".")) ] h = [ float(self.eh1.text().replace(",", ".")), float(self.eh2.text().replace(",", ".")) ] q50 = float(self.eq50.text().replace(",", ".")) qrange = [ float(self.eqmin.text().replace(",", ".")), float(self.eqmax.text().replace(",", ".")) ] qtarget_values_list = [] for qtarg_lineedit in self.target_lineedit_list: if qtarg_lineedit.text(): qtarget_values_list.append( float(qtarg_lineedit.text().replace(",", "."))) substrate = float(self.esub.text().replace(",", ".")) except ValueError: self.send_log.emit('Error: ' + self.tr( 'Some data are empty or not float. Cannot run Estimhab')) return # get the list of xml file fish_list = [] fish_name2 = [] for i in range(0, self.selected_aquatic_animal_qtablewidget.count()): fish_item = self.selected_aquatic_animal_qtablewidget.item(i) fish_item_str = fish_item.text() fish_list.append(os.path.basename(fish_item.data(1))) fish_name2.append(fish_item_str) # check internal logic if not fish_list: self.send_log.emit( 'Error: ' + self.tr('No fish selected. Cannot run Estimhab.')) return if qrange[0] >= qrange[1]: self.send_log.emit('Error: ' + self.tr( 'Minimum discharge bigger or equal to max discharge. Cannot run Estimhab.' )) return if qtarget_values_list: for qtarg in qtarget_values_list: if qtarg < qrange[0] or qtarg > qrange[1]: self.send_log.emit('Error: ' + self.tr( 'Target discharge is not between Qmin and Qmax. Cannot run Estimhab.' )) return if q[0] == q[1]: self.send_log.emit( 'Error: ' + self.tr('Estimhab needs two differents measured discharges.')) return if h[0] == h[1]: self.send_log.emit( 'Error: ' + self.tr('Estimhab needs two different measured height.')) return if w[0] == w[1]: self.send_log.emit( 'Error: ' + self.tr('Estimhab needs two different measured width.')) return if (q[0] > q[1] and h[0] < h[1]) or (q[0] > q[1] and w[0] < w[1]) or (q[1] > q[0] and h[1] < h[0]) \ or (q[1] > q[0] and w[1] < w[0]): self.send_log.emit( 'Error: ' + self.tr('Discharge, width, and height data are not coherent.')) return if q[0] <= 0 or q[1] <= 0 or w[0] <= 0 or w[1] <= 0 or h[0] <= 0 or h[1] <= 0 or qrange[0] <= 0 or qrange[1] <= 0 \ or substrate <= 0 or q50 <= 0: self.send_log.emit('Error: ' + self.tr( 'Negative or zero data found. Could not run Estimhab.')) return if substrate > 3: self.send_log.emit( 'Error: ' + self.tr('Substrate is too large. Could not run Estimhab.')) return self.send_log.emit(self.tr('# Computing: Estimhab...')) # check if the discharge range is realistic with the result self.qall = [q[0], q[1], qrange[0], qrange[1], q50] self.check_all_q() # run and save project_properties = load_project_properties(self.path_prj) sys.stdout = mystdout = StringIO() self.estimhab_dict = dict(q=q, w=w, h=h, q50=q50, qrange=qrange, qtarg=qtarget_values_list, substrate=substrate, path_bio=self.path_bio_estimhab, xml_list=fish_list, fish_list=fish_name2) # change_specific_properties change_specific_properties(self.path_prj, ["Estimhab"], [self.estimhab_dict]) # process state = Value("d", 0) self.p = Process(target=estimhab_mod.estimhab_process, args=(self.estimhab_dict, project_properties, self.path_prj, state), name="Estimhab") self.p.start() self.p.join() # plot plot_attr = lambda: None plot_attr.name_hdf5 = self.name_prj + '_ESTIMHAB' + '.hab' plot_attr.nb_plot = 1 self.process_manager.set_estimhab_plot_mode( self.path_prj, plot_attr, load_project_properties(self.path_prj)) self.process_manager.start() # log info str_found = mystdout.getvalue() str_found = str_found.split('\n') for i in range(0, len(str_found)): if len(str_found[i]) > 1: self.send_log.emit(str_found[i]) self.send_log.emit( self. tr("Estimhab computation done. Figure and text files created in output project folder." )) self.send_log.emit("py data = [" + str(q) + ',' + str(w) + ',' + str(h) + ',' + str(q50) + ',' + str(substrate) + ']') self.send_log.emit("py qrange =[" + str(qrange[0]) + ',' + str(qrange[1]) + ']') self.send_log.emit( "py path1= os.path.join(os.path.dirname(path_bio),'" + self.path_bio_estimhab + "')") fish_list_str = "py fish_list = [" for i in range(0, len(fish_list)): fish_list_str += "'" + fish_list[i] + "'," fish_list_str = fish_list_str[:-1] + ']' self.send_log.emit(fish_list_str) self.send_log.emit( "py [OSI, WUA] = estimhab.estimhab(data[0], data[1], data[2], data[3] ," " qrange, data[4], path1, fish_list, '.', True, {}, '.')\n")