class collapsibleGroupBox2(QWidget): def __init__(self, parent=None, title=None): QWidget.__init__(self, parent) self.frame = QFrame(self) self.button = QPushButton("Toggle", self) self.button.setCheckable(True) self.button.setChecked(True) self.switched = False self.vPolicy = None self.button.setStyleSheet(style.collapsibleGroupBoxButton()) if title: self.setTitle(title) def resizeEvent(self, event): if not self.switched: self.switchLayout() return QWidget.resizeEvent(self, event) def switchLayout(self): self.frame.setLayout(self.layout()) self.wLayout = QVBoxLayout(self) self.wLayout.setContentsMargins(0, 0, 0, 0) self.wLayout.setSpacing(0) self.wLayout.addWidget(self.button) self.wLayout.addWidget(self.frame) self.button.toggled.connect(self.setExpanded) self.frame.layout().setContentsMargins(0, 0, 0, 4) self.frame.layout().setSpacing(0) self.switched = True self.vPolicy = self.sizePolicy().verticalPolicy() self.parent().layout().setAlignment(Qt.AlignTop) self.setExpanded(self.button.isChecked()) def setFlat(self, val): if val: self.frame.setFrameShape(QFrame.NoFrame) def setCheckable(self, val): pass def setTitle(self, title): self.button.setText(title) def setExpanded(self, val): self.frame.setVisible(val) if val: self.setSizePolicy(QSizePolicy.Preferred, self.vPolicy) else: self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Maximum) def saveState(self): return self.button.isChecked() def restoreState(self, val): self.button.setChecked(val)
class collapsibleGroupBox2(QWidget): def __init__(self, parent=None, title=None): QWidget.__init__(self, parent) self.frame = QFrame(self) self.button = QPushButton("Toggle", self) self.button.setCheckable(True) self.button.setChecked(True) self.switched = False self.vPolicy = None # self.button.setStyleSheet("background-color: lightBlue;") self.button.setStyleSheet(style.collapsibleGroupBoxButton()) if title: self.setTitle(title) def resizeEvent(self, event): if not self.switched: self.switchLayout() return QWidget.resizeEvent(self, event) def switchLayout(self): self.frame.setLayout(self.layout()) self.wLayout = QVBoxLayout(self) self.wLayout.setContentsMargins(0, 0, 0, 0) self.wLayout.setSpacing(0) self.wLayout.addWidget(self.button) self.wLayout.addWidget(self.frame) self.button.toggled.connect(self.setExpanded) self.frame.layout().setContentsMargins(0, 0, 0, 4) self.frame.layout().setSpacing(0) self.switched = True self.vPolicy = self.sizePolicy().verticalPolicy() self.parent().layout().setAlignment(Qt.AlignTop) self.setExpanded(self.button.isChecked()) def setFlat(self, val): if val: self.frame.setFrameShape(QFrame.NoFrame) def setCheckable(self, val): pass def setTitle(self, title): self.button.setText(title) def setExpanded(self, val): self.frame.setVisible(val) if val: self.setSizePolicy(QSizePolicy.Preferred, self.vPolicy) else: self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Maximum) def saveState(self): return self.button.isChecked() def restoreState(self, val): self.button.setChecked(val)
def openWebUI(self): self.device_username = self.settings.value("device_username", "", str) self.device_password = self.settings.value("device_password", "", str) if self.device and self.device.p.get('IPAddress'): url = QUrl("http://{}:{}@{}".format( self.device_username, \ urllib.parse.quote(self.device_password),self.device.p['IPAddress'])) try: webui = QWebEngineView() webui.load(url) frm_webui = QFrame() frm_webui.setWindowTitle("WebUI [{}]".format( self.device.p['FriendlyName1'])) frm_webui.setFrameShape(QFrame.StyledPanel) frm_webui.setLayout(VLayout(0)) frm_webui.layout().addWidget(webui) frm_webui.destroyed.connect(self.updateMDI) self.mdi.addSubWindow(frm_webui) self.mdi.setViewMode(QMdiArea.TabbedView) frm_webui.setWindowState(Qt.WindowMaximized) except NameError: QDesktopServices.openUrl(url)
def add_select_button(self, frame: QFrame): # Create the select button c = QToolButton() c.setIconSize(QSize(32, 32)) c.setText("Find") self.on_style_select_button(c) c.clicked.connect(self.on_show_selection_menu) frame.layout().insertWidget(0, c)
def _createTopBar(self): topbar = QFrame() topbar.setFixedHeight(50) topbar.setLayout(QHBoxLayout()) topbar.setContentsMargins(10, 0, 0, 0) topbar.setSizePolicy(QSizePolicy(1, 0)) topbar.setObjectName("topbar") self.titleText = QLabel("Produtos") topbar.layout().addWidget(self.titleText) return topbar
def __init__(self, item, item_list, viewer): super().__init__(item, viewer) self.setObjectName("LootWidget") self.item = item self.item_list = item_list self.viewer = viewer name_frame = QFrame() name_frame.setContentsMargins(0, 0, 0, 0) name_frame.setLayout(QHBoxLayout()) self.name_label = NameLabel(item.name) self.name_label.setObjectName("LootWidget_name") name_frame.layout().addWidget(self.name_label) name_frame.layout().addStretch(1) self.reroll_button = RerollButton() self.reroll_button.clicked.connect(self.reroll_item) self.button_bar = QFrame() self.button_bar.setContentsMargins(0, 0, 0, 0) self.button_bar.setLayout(QHBoxLayout()) self.button_bar.setHidden(True) self.type_dropdown = QComboBox() self.type_dropdown.addItem("Any") self.type_dropdown.addItem("Weapon") self.type_dropdown.addItem("Armor") self.type_dropdown.addItems(self.item_list.unique_attr("type")) if self.item.type in ["Staff", "Melee", "Ranged", "Rod"]: self.type_dropdown.setCurrentText("Weapon") elif self.item.type in ["Light Armor, Medium Armor, Heavy Armor"]: self.type_dropdown.setCurrentText("Armor") else: self.type_dropdown.setCurrentText(self.item.type) self.rarity_dropdown = QComboBox() self.rarity_dropdown.addItems(self.item_list.unique_attr("rarity")) self.rarity_dropdown.setCurrentText(self.item.rarity) self.button_bar.layout().addWidget(self.type_dropdown) self.button_bar.layout().addWidget(self.rarity_dropdown) self.button_bar.layout().addStretch(1) self.button_bar.layout().addWidget(self.reroll_button) self.setLayout(QVBoxLayout()) QSpacerItem(50, 10) self.layout().setContentsMargins(20, 5, 10, 10) self.layout().addWidget(name_frame) self.layout().addWidget(self.button_bar) self.setFrameShape(QFrame.Box)
def __init__(self, body): super().__init__() self.body = body self.setLayout(QHBoxLayout()) leftCol = QFrame() leftCol.setLayout(QVBoxLayout()) leftCol.layout().setAlignment(Qt.AlignTop) leftCol.setStyleSheet("QFrame{background:#ddd;}") delBtn = QPushButton("X") delBtn.clicked.connect(self.deleteMe) leftCol.layout().addWidget(delBtn) self.layout().addWidget(leftCol) self.layout().addWidget(body)
def __init__(self, parent=None): super().__init__(parent) self.model = None self.transactionsTableView = QTableView() headerBox = QFrame() headerBox.setLayout(QHBoxLayout()) headerBox.layout().setContentsMargins(0, 0, 0, 0) headerBox.layout().addWidget(QLabel("Transactions")) newTransactionButton = QPushButton(QIcon.fromTheme("list-add"), '', self) newTransactionButton.setFixedSize(20, 20) newTransactionButton.clicked.connect(self.addEntry) delTransactionButton = QPushButton(QIcon.fromTheme("list-remove"), '', self) delTransactionButton.setFixedSize(20, 20) delTransactionButton.clicked.connect(self.removeEntry) headerBox.layout().addWidget(newTransactionButton) headerBox.layout().addWidget(delTransactionButton) headerBox.layout().addStretch() boxLayout = QVBoxLayout(self) boxLayout.addWidget(headerBox) boxLayout.addWidget(self.transactionsTableView)
def __init__(self, step: WorkflowStep, index: int, steps_view=None, enable_button: bool = False): super().__init__() if step is None: raise ValueError("step") self.step = step self.name = step.name self.index = index self.form_rows: List[FormRow] = list() self.button = QPushButton(f"Run {step.name}") self.button.clicked.connect( lambda: steps_view.btn_run_clicked(self.index)) self.steps_view = steps_view layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) self.setLayout(layout) if step.function.parameters is None: label_no_param = QLabel("No parameters needed") label_no_param.setAlignment(Qt.AlignCenter) label_no_param.setContentsMargins(0, 0, 6, 0) self.form_rows.append(FormRow("", label_no_param)) else: for param_name, param_data in step.function.parameters.items(): default_values = step.parameter_values[param_name] self._add_param_rows(param_name, param_data, default_values) buttons = QFrame() buttons.setStyleSheet("border: none;") buttons.setLayout(QHBoxLayout()) buttons.layout().addWidget(self.button) if not enable_button: self.button.setDisabled(True) box_contents = QVBoxLayout() box_contents.addLayout(Form(self.form_rows, (11, 5, 5, 5))) box_contents.addWidget(buttons) step_name = f"<span>{step.step_number}. {step.name}</span>" box = CollapsibleBox(step_name, box_contents, self) layout.addWidget(box)
class Example(QMainWindow): def __init__(self): super().__init__() self.count_of_buttons = 1 self.centralWidget = QWidget() self.setCentralWidget(self.centralWidget) self.setGeometry(700, 200, 500, 400) self.button_add = QPushButton('Add') self.button_add.clicked.connect(self.add_button) self.button_clear = QPushButton('Clear') # +++ self.button_clear.clicked.connect(self.clear_button) # +++ self.layout_buttons = QVBoxLayout() self.layout_buttons.addStretch() self.frame = QFrame() self.frame.setMinimumSize(200, 200) self.frame.setFrameStyle(QFrame.Box) self.frame.setLayout(self.layout_buttons) self.main_layout = QGridLayout(self.centralWidget) # QGridLayout self.main_layout.addWidget(self.button_add, 0, 0) self.main_layout.addWidget(self.button_clear, 0, 1) # +++ self.main_layout.addWidget(self.frame, 1, 0, 1, 2) # +++ def add_button(self): # - self.main_layout.addWidget(self.frame) self.button = QPushButton(f"Кнопка № {self.count_of_buttons}") self.button.clicked.connect( lambda ch, btn=self.button: self.pressed_btn(btn)) self.count_of_buttons += 1 self.layout_buttons.insertWidget(0, self.button) def pressed_btn(self, btn): print(f"кнопка нажата: {btn.text()}") # +++ vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv def clear_button(self): widgets = self.frame.layout().count() if widgets > 1: for i in range(widgets - 1): widget = self.frame.layout().itemAt(0).widget() self.frame.layout().removeWidget(widget) widget.hide()
class ImageDialog(QDialog): def __init__(self, image_path, parent=None): super(ImageDialog, self).__init__(parent) position = self.cursor().pos() position.setX(position.x()) position.setY(position.y()) self.move(position) #self.setWindowOpacity(0.8) self.setLayout(QVBoxLayout()) self.layout().setContentsMargins(0, 0, 0, 0) self.widget = QFrame() self.widget.setStyleSheet(''' QFrame{ border-style: outset; border-width: 1px; border-radius: 5px; } ''') self.widget.setLayout(QVBoxLayout()) self.widget.layout().setContentsMargins(0, 0, 0, 0) self.setMouseTracking(True) self.setWindowFlags(QtCore.Qt.Popup | QtCore.Qt.WindowStaysOnTopHint | QtCore.Qt.FramelessWindowHint | QtCore.Qt.X11BypassWindowManagerHint) self.qgraphics_view = ImageDialogViewer() self.qgraphics_view.pixmap = QPixmap(image_path) self.widget.layout().addWidget(self.qgraphics_view) self.layout().addWidget(self.widget) def setMouseTracking(self, flag): def set_mouse_tracking(parent): for child in parent.findChildren(QtCore.QObject): try: child.setMouseTracking(flag) except: pass set_mouse_tracking(child) QWidget.setMouseTracking(self, flag) set_mouse_tracking(self) def mouseMoveEvent(self, event: QMouseEvent) -> None: # print('mouseMoveEvent: x=%d, y=%d' % (event.x(), event.y())) if not self.rect().contains(event.pos()): self.close()
def _init_search_field(self): search_field_frame = QFrame() search_field_layout = QHBoxLayout() search_field_layout.addWidget(VSep(self)) self._search_field = SearchField(self) self._search_field.setIcon(qtawesome.icon("fa.search")) self._search_field.setPlaceholderText("Search codec") self._search_field.textChanged.connect(lambda text: {}) search_field_layout.addWidget(self._search_field) self._search_field_button = QToolButton() self._search_field_button.setIcon(qtawesome.icon("fa.arrow-circle-right")) self._search_field_button.setContentsMargins(0, 0, 0, 0) search_field_layout.addWidget(self._search_field_button) search_field_frame.setLayout(search_field_layout) search_field_frame.setContentsMargins(5, 0, 5, 5) # left, top, right, bottom search_field_frame.layout().setContentsMargins(0, 0, 0, 0) return search_field_frame
def openWebUI(self): if self.device and self.device.p.get('IPAddress'): url = QUrl("http://{}".format(self.device.p['IPAddress'])) try: webui = QWebEngineView() webui.load(url) frm_webui = QFrame() frm_webui.setWindowTitle("WebUI [{}]".format(self.device.p['FriendlyName1'])) frm_webui.setFrameShape(QFrame.StyledPanel) frm_webui.setLayout(VLayout(0)) frm_webui.layout().addWidget(webui) frm_webui.destroyed.connect(self.updateMDI) self.mdi.addSubWindow(frm_webui) self.mdi.setViewMode(QMdiArea.TabbedView) frm_webui.setWindowState(Qt.WindowMaximized) except NameError: QDesktopServices.openUrl(QUrl("http://{}".format(self.device.p['IPAddress'])))
class BookmarkWidget(QFrame): SPELL_TAB = 0 DICE_TAB = 1 def __init__(self, monster_table, monster_viewer, spell_table, spell_viewer): super().__init__() self.monster_table = monster_table self.spell_table = spell_table self.monster_viewer = monster_viewer self.spell_viewer = spell_viewer self.bookmark_frame = QFrame() self.bookmark_frame.setMaximumHeight(300) self.button_bar = QFrame() self.button_bar.setLayout(QHBoxLayout()) bookmark_layout = QHBoxLayout() self.setLayout(QVBoxLayout()) self.layout().setContentsMargins(0, 0, 0, 0) self.setSizePolicy(QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Maximum)) self.spell_bookmark = LinkedSpellTable(self.spell_table, self.spell_viewer) self.monster_bookmark = LinkedMonsterTable(self.monster_table, self.monster_viewer) self.monster_tabWidget = QTabWidget() self.monster_tabWidget.addTab(self.monster_bookmark, "Monster") self.spell_tabWidget = QTabWidget() self.spell_tabWidget.addTab(self.spell_bookmark, "Spell") self.dice_bookmark = QFrame() dice_layout = QVBoxLayout() for i in range(5): dice_layout.addWidget(DiceBox().frame) dice_help_button = QPushButton("Help") dice_help_button.clicked.connect(self.dice_instructions) dice_layout.addWidget(dice_help_button) self.dice_bookmark.setLayout(dice_layout) self.spell_tabWidget.addTab(self.dice_bookmark, "Dice") bookmark_layout.addWidget(self.monster_tabWidget) bookmark_layout.addWidget(self.spell_tabWidget) self.bookmark_frame.setLayout(bookmark_layout) self.clear_bookmark_button = QPushButton("Clear Bookmark") self.toggle_bookmark_button = QPushButton("Toggle Bookmark") self.button_bar.layout().setContentsMargins(0, 0, 0, 0) self.button_bar.layout().addWidget(self.clear_bookmark_button) self.button_bar.layout().addWidget(self.toggle_bookmark_button) self.layout().addWidget(self.bookmark_frame) self.layout().addWidget(self.button_bar) self.bookmark_frame.setHidden(True) self.hidden = True def dice_instructions(self): sNexus.printSignal.emit("\nEither input diceroll in format xdy+z, AttackBonus|DamageRoll, or" " AttackBonus, DamageRoll\nExample: 1d20+6\n5|2d6+3\n5, 2d6+3\n") def toggle_hide(self): self.hidden = not self.hidden self.bookmark_frame.setHidden(self.hidden)
def __init__(self, item_list, viewer): self.challenge_rating_combo_box = QComboBox() self.challenge_rating_combo_box.addItems([ self.challengeRatingLow, self.challengeRatingMidLow, self.challengeRatingMidHigh, self.challengeRatingHigh ]) super().__init__() self.viewer = viewer rollButton = QPushButton("Roll") rollButton.clicked.connect(self.roll_loot) srd_label = QLabel("Only SRD") self.srd_checkbox = QCheckBox() bottom_bar = QFrame() bottom_bar.setLayout(QHBoxLayout()) bottom_bar.layout().addStretch(1) bottom_bar.layout().addWidget(srd_label) bottom_bar.layout().addWidget(self.srd_checkbox) bottom_bar.layout().addWidget(self.challenge_rating_combo_box) bottom_bar.layout().addWidget(rollButton) self.layout().addWidget(bottom_bar) self.item_list = item_list sNexus.treasureHoardDeselectSignal.connect(self.deselectAll)
class RainyHub(QMainWindow): def __init__(self): super().__init__() self._setup_ui() # self._setup_menu() self.bind_signals() self._display_ui() self.setStyleSheet(open(os.path.join("styles", "default.css")).read()) self.db_path = os.path.join(os.getcwd(), "RainyDB") def _setup_ui(self): self.window_frame = QFrame() self.window_frame.setLayout(QHBoxLayout()) # self.window_frame.layout().setContentsMargins(0, 0, 0, 0) self.dm_button = DMButton() self.bg_button = BGButton() self.window_frame.layout().addWidget(self.dm_button) self.window_frame.layout().addWidget(self.bg_button) self.setFixedSize(600, 300) self.setWindowTitle("RainyHub") def bind_signals(self): self.dm_button.clicked.connect(self.open_rainydm) self.bg_button.clicked.connect(self.open_rainybg) def _display_ui(self): self.setCentralWidget(self.window_frame) def open_rainydm(self): os.chdir("RainyDM") self.rainy_dm = DMTool(self.db_path) self.close() self.rainy_dm.show() def open_rainybg(self): os.chdir("RainyBG") self.rainy_bag = RainyBG(self.db_path) self.close() self.rainy_bag.show()
def value(self, value): ControlBase.label.fset(self, value) for item in range(self.form.count(), -1, -1): self.form.removeItem(item) for item in value: if isinstance(item, tuple): widget = QFrame(self.form) layout = QVBoxLayout() if conf.PYFORMS_USE_QT5: layout.setContentsMargins(0, 0, 0, 0) else: layout.setMargin(0) widget.setLayout(layout) for e in item[1]: if isinstance(e, tuple): hwidget = QFrame(self.form) hlayout = QHBoxLayout() if conf.PYFORMS_USE_QT5: hlayout.setContentsMargins(0, 0, 0, 0) else: hlayout.setMargin(0) hwidget.setLayout(hlayout) for ee in e: hlayout.addWidget(ee.form) widget.layout().addWidget(hwidget) else: widget.layout().addWidget(e.form) self.form.addItem(widget, item[0]) else: self.form.addItem(item.form, item.label)
def create_other_player_layout(self, name): # Creates a dice mat, a cup, a name label and the place to write what # bids the player made layout = QVBoxLayout() player_name = QLabel(name) scroll = QScrollArea() scroll.setMaximumHeight(90) scroll.setWidgetResizable(True) # CRITICAL inner = QFrame(scroll) inner.setLayout(QVBoxLayout()) scroll.setWidget(inner) # CRITICAL for i in range(50): inner.layout().addWidget(QLabel(f"{i} threes")) layout.addWidget(player_name) layout.addWidget(scroll) frame = QFrame() frame.setLayout(layout) return frame
class VerticallyCompressedWidget(QWidget): def __init__(self, parent=None): super().__init__(parent=parent) self.setLayout(QVBoxLayout()) self._contentsFrame = QFrame(parent=self) spacer = QSpacerItem(0, 0, QSizePolicy.Expanding, QSizePolicy.Expanding) self.layout().addWidget(self._contentsFrame) self.layout().addItem(spacer) self.layout = self._layout # override methods self.setLayout = self._setLayout def _layout(self) -> QLayout: return self._contentsFrame.layout() def _setLayout(self, layout: QLayout): self._contentsFrame.setLayout(layout)
def __init__(self, parent: QWidget): super().__init__("Results", parent=parent) self.setStyleSheet( "QDockWidget > QWidget { border: 1px solid lightgray; }") self.setObjectName('ResultsTableDock') self._widget = QWidget() self._widget.setLayout(QGridLayout()) self._table = ResultsTable() checkBoxFrame = QFrame(parent=self) checkBoxFrame.setLayout(QVBoxLayout()) checkBoxFrame.layout().setContentsMargins(1, 1, 1, 1) checkBoxFrame.layout().setSpacing(1) self._checkBoxes = [] for i, (name, (default, settingsName, compilerClass, tooltip)) in enumerate(self._table.columns.items()): c = QCheckBox(name) c.setCheckState(2) if default else c.setCheckState(0) c.stateChanged.connect( lambda state, j=i: self._table.setColumnHidden(j, state == 0)) checkBoxFrame.layout().addWidget(c) self._checkBoxes.append(c) self._roiNameEdit = QLineEdit('.*', self._widget) self._roiNameEdit.setToolTip( "ROIs matching this RegEx pattern will be compiled.") self._analysisNameEdit = QLineEdit('.*', self._widget) self._analysisNameEdit.setToolTip( "Analyses matching this RegEx pattern will be compiled.") self._compileButton = QPushButton("Compile") self._compMan = CompilationManager(self.window()) self._compileButton.released.connect(self._compMan.run) self._compMan.compilationDone.connect(self._handleCompilationResults) scroll = QScrollArea() scroll.setWidget(checkBoxFrame) scroll.verticalScrollBar().setStyleSheet( "QScrollBar:horizontal { height: 10px; }") scroll.setMaximumWidth(checkBoxFrame.width() + 10) scroll.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) scroll.horizontalScrollBar().setEnabled(False) sidebar = QWidget() l = QGridLayout() l.addWidget(scroll, 0, 0, 1, 2) l.addWidget(QLabel('Analysis:'), 1, 0, 1, 1) l.addWidget(self._analysisNameEdit, 1, 1, 1, 1) l.addWidget(QLabel("Roi:"), 2, 0, 1, 1) l.addWidget(self._roiNameEdit, 2, 1, 1, 1) l.addWidget(self._compileButton, 3, 0, 1, 2) sidebar.setLayout(l) sidebar.setMaximumWidth(scroll.width() + 10) self._widget.layout().addWidget(sidebar, 0, 0) self._widget.layout().addWidget(self._table, 0, 1) self.setWidget(self._widget)
def createTopBar(self): topbar = QFrame() topbar.setFixedHeight(50) topbar.setObjectName("topbar") topbar.setLayout(QHBoxLayout()) topbar.layout().setAlignment(Qt.AlignLeft) """buttonClose = loader.buttonIcon("close", 35, 35) buttonClose.setObjectName("buttonClose") buttonClose.setFixedSize(35, 35) buttonClose.setFlat(True) buttonClose.clicked.connect(self.close) """ self._title = QLabel() topbar.layout().addWidget(self._title) topbar.layout().addStretch() #topbar.layout().addWidget(buttonClose) return topbar
class GridCard(QFrame, QObject): def __init__(self, parent=None, debug=False, with_actions=True, with_title=True): super(GridCard, self).__init__(parent) self._buttons = [] self._content_layout = QVBoxLayout() self._content_layout.setContentsMargins(0, 0, 0, 0) self._content_layout.setSpacing(0) self.setLayout(self._content_layout) self.setFixedHeight(150) self._title_widget = QLabel() self._title_widget.setStyleSheet("QLabel { font-size : 14px;}") self._title_widget.setAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter) self._title_widget.setScaledContents(True) self._sub_title_widget = QLabel() self._sub_title_widget.setStyleSheet("QLabel { font-size : 10px;}") self._sub_title_widget.setAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter) self._sub_title_widget.setScaledContents(True) self._body_widget = None # layouts self._body_frame = QFrame() self._body_frame.setMinimumWidth(150) self._body_frame.setObjectName("image_container") self._body_frame.setLayout(QVBoxLayout()) self._body_frame.layout().setContentsMargins(2, 2, 2, 2) self._body_frame.layout().setAlignment(QtCore.Qt.AlignHCenter) size_policy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) size_policy.setVerticalStretch(4) self._body_frame.setSizePolicy(size_policy) self._title_frame = QFrame() self._title_frame.setLayout(QVBoxLayout()) self._title_frame.layout().setContentsMargins(2, 2, 2, 2) self._title_frame.layout().addWidget(self._title_widget) self._title_frame.layout().addWidget(self._sub_title_widget) size_policy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) size_policy.setVerticalStretch(1) self._title_frame.setSizePolicy(size_policy) self._actions_frame = QFrame() self._actions_frame.setLayout(QHBoxLayout()) self._actions_frame.setFixedHeight(30) self._actions_frame.layout().setContentsMargins(0, 0, 0, 0) size_policy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) size_policy.setVerticalStretch(2) self._actions_frame.setSizePolicy(size_policy) # self._body_frame.setSizePolicy(QSizePolicy.Minimum,QSizePolicy.Ex) # self._title_frame.setSizePolicy(QSizePolicy.Minimum,QSizePolicy.Minimum) # self._actions_frame.setSizePolicy(QSizePolicy.Minimum,QSizePolicy.Minimum) self._content_layout.addWidget(self._body_frame) if with_title: self._content_layout.addWidget(self._title_frame) if with_actions: self._content_layout.addWidget(self._actions_frame, alignment=QtCore.Qt.AlignCenter) if debug: self.setFrameStyle(QFrame.Box) self._body_frame.setFrameStyle(QFrame.Box) self._title_frame.setFrameStyle(QFrame.Box) self._actions_frame.setFrameStyle(QFrame.Box) def add_buttons(self, args: typing.Any): if isinstance(args, QPushButton): self._actions_frame.layout().addWidget(args) else: for btn in args: if isinstance(btn, QPushButton): self._actions_frame.layout().addWidget(btn) @property def body(self) -> QWidget: return self._body_widget @body.setter def body(self, value): self._body_widget = value self._body_frame.layout().addWidget(value) @property def body_frame(self): return self._body_frame @property def label(self): return self._title_widget @label.setter def label(self, value): self._title_widget.setText(value) @property def label2(self): return self._sub_title_widget @label2.setter def label2(self, value): self._sub_title_widget.setText(value) @property def buttons(self): return self._buttons @buttons.setter def buttons(self, value): self._buttons = value for btn in self._buttons: self._actions_frame.layout().addWidget(btn)
class HeaderFrame(QFrame): """ Clickable frame with title. """ class AbstractHeaderFrameItem(QFrame): def __init__(self, parent): super(__class__, self).__init__(parent) self.setContentsMargins(0, 0, 0, 0) self.setLayout(QHBoxLayout()) self.layout().setContentsMargins(0, 0, 0, 0) self.layout().setSpacing(0) def clearLayout(self): while self.layout().count(): child = self.layout().takeAt(0) if child.widget(): child.widget().deleteLater() def setCentralWidget(self, widget: QWidget): self.clearLayout() self.layout().addWidget(widget) def refresh(self): pass class VSepItem(AbstractHeaderFrameItem): def __init__(self, parent): super(__class__, self).__init__(parent) self.setCentralWidget(VSep(self)) class HSpacerItem(AbstractHeaderFrameItem): def __init__(self, parent): super(__class__, self).__init__(parent) self.setCentralWidget(HSpacer(self)) arrowClicked = pyqtSignal() upButtonClicked = pyqtSignal() downButtonClicked = pyqtSignal() configButtonClicked = pyqtSignal() closeButtonClicked = pyqtSignal() def __init__(self, parent: 'ui.widget.CollapsibleFrame'): super(__class__, self).__init__(parent) self._parent = parent self.setMinimumHeight(26) self.setMaximumHeight(26) self.indicateError(False) self._hlayout = QHBoxLayout() self._hlayout.setContentsMargins(0, 0, 0, 0) self._hlayout.setSpacing(0) self._arrow = None self._hlayout.addWidget(self._init_header_frame(parent.isCollapsed())) self.setLayout(self._hlayout) def _init_header_frame(self, collapsed: bool): self._frm_header = QFrame(self) self._frm_header.setMinimumHeight(24) self._frm_header.setMaximumHeight(24) frm_header_layout = QHBoxLayout(self) frm_header_layout.setContentsMargins(0, 0, 0, 0) frm_header_layout.addWidget(self._init_arrow(collapsed)) background_color = self._frm_header.palette().color(self._frm_header.backgroundRole()) self._frm_header.setStyleSheet("QFrame { border:0px; background:" + background_color.darker(96).name() + "; }") self._frm_header.setLayout(frm_header_layout) return self._frm_header def _init_arrow(self, collapsed): self._arrow = CollapsibleFrame.Arrow(self, collapsed=collapsed) self._arrow.setMinimumWidth(24) self._arrow.setMaximumWidth(24) self._arrow.setStyleSheet("border:0px") return self._arrow def addWidget(self, header_item: AbstractHeaderFrameItem): self._frm_header.layout().addWidget(header_item) def refresh(self): """ Refreshes the header model (e.g. line-count, character-count, etc.). """ for i in range(self._frm_header.layout().count()): widget = self._frm_header.layout().itemAt(i).widget() if isinstance(widget, CollapsibleFrame.HeaderFrame.AbstractHeaderFrameItem): widget.refresh() def indicateError(self, status: bool): """ Indicates an error by painting the title-border red. Otherweise black. """ if status: self.setStyleSheet("QFrame { border:1px solid red; }") else: self.setStyleSheet("QFrame { border:1px solid rgb(41, 41, 41); }") def mouseReleaseEvent(self, event): if self.underMouse() and event.button() == QtCore.Qt.LeftButton: # The arrow (or something non-button like) was clicked self.arrowClicked.emit() return super(CollapsibleFrame.HeaderFrame, self).mousePressEvent(event)
class GuiSessionViewer(QSplitter): def __init__(self, config, querylist, mainwin, owner, debug=True): QSplitter.__init__(self, mainwin) self.debug = debug self.conf = config self.sql = querylist self.window = mainwin self.owner = owner self.liststore = None self.MYSQL_INNODB = 2 self.PGSQL = 3 self.SQLITE = 4 self.fig = None self.canvas = None self.ax = None self.graphBox = None # create new db connection to avoid conflicts with other threads self.db = Database.Database(self.conf, sql=self.sql) self.cursor = self.db.cursor settings = {} settings.update(self.conf.get_db_parameters()) settings.update(self.conf.get_import_parameters()) settings.update(self.conf.get_default_paths()) # text used on screen stored here so that it can be configured self.filterText = { 'handhead': _('Hand Breakdown for all levels listed above') } filters_display = { "Heroes": True, "Sites": True, "Games": True, "Currencies": True, "Limits": True, "LimitSep": True, "LimitType": True, "Type": False, "Seats": True, "SeatSep": False, "Dates": True, "Groups": False, "GroupsAll": False, "Button1": True, "Button2": False } self.filters = Filters.Filters(self.db, display=filters_display) self.filters.registerButton1Name("_Refresh") self.filters.registerButton1Callback(self.refreshStats) scroll = QScrollArea() scroll.setWidget(self.filters) # ToDo: store in config # ToDo: create popup to adjust column config # columns to display, keys match column name returned by sql, values in tuple are: # is column displayed, column heading, xalignment, formatting self.columns = [(1.0, "SID"), (1.0, "Hands"), (0.5, "Start"), (0.5, "End"), (1.0, "Rate"), (1.0, "Open"), (1.0, "Close"), (1.0, "Low"), (1.0, "High"), (1.0, "Range"), (1.0, "Profit")] self.detailFilters = [] # the data used to enhance the sql select self.stats_frame = QFrame() self.stats_frame.setLayout(QVBoxLayout()) self.view = None heading = QLabel(self.filterText['handhead']) heading.setAlignment(Qt.AlignCenter) self.stats_frame.layout().addWidget(heading) self.main_vbox = QSplitter(Qt.Vertical) self.graphBox = QFrame() self.graphBox.setLayout(QVBoxLayout()) self.addWidget(scroll) self.addWidget(self.main_vbox) self.setStretchFactor(0, 0) self.setStretchFactor(1, 1) self.main_vbox.addWidget(self.graphBox) self.main_vbox.addWidget(self.stats_frame) def refreshStats(self, checkState): if self.view: self.stats_frame.layout().removeWidget(self.view) self.view.setParent(None) self.fillStatsFrame(self.stats_frame) def fillStatsFrame(self, frame): sites = self.filters.getSites() heroes = self.filters.getHeroes() siteids = self.filters.getSiteIds() games = self.filters.getGames() currencies = self.filters.getCurrencies() limits = self.filters.getLimits() seats = self.filters.getSeats() sitenos = [] playerids = [] # Which sites are selected? for site in sites: sitenos.append(siteids[site]) _hname = Charset.to_utf8(heroes[site]) result = self.db.get_player_id(self.conf, site, _hname) if result is not None: playerids.append(result) if not sitenos: #Should probably pop up here. print _("No sites selected - defaulting to PokerStars") sitenos = [2] if not games: print _("No games found") return if not currencies: print _("No currencies found") return if not playerids: print _("No player ids found") return if not limits: print _("No limits found") return self.createStatsPane(frame, playerids, sitenos, games, currencies, limits, seats) def createStatsPane(self, frame, playerids, sitenos, games, currencies, limits, seats): starttime = time() (results, quotes) = self.generateDatasets(playerids, sitenos, games, currencies, limits, seats) if DEBUG: for x in quotes: print "start %s\tend %s \thigh %s\tlow %s" % (x[1], x[2], x[3], x[4]) self.generateGraph(quotes) self.addTable(frame, results) self.db.rollback() print _("Stats page displayed in %4.2f seconds") % (time() - starttime) def generateDatasets(self, playerids, sitenos, games, currencies, limits, seats): if (DEBUG): print "DEBUG: Starting generateDatasets" THRESHOLD = 1800 # Min # of secs between consecutive hands before being considered a new session PADDING = 5 # Additional time in minutes to add to a session, session startup, shutdown etc # Get a list of timestamps and profits q = self.sql.query['sessionStats'] start_date, end_date = self.filters.getDates() q = q.replace("<datestest>", " BETWEEN '" + start_date + "' AND '" + end_date + "'") for m in self.filters.display.items(): if m[0] == 'Games' and m[1]: if len(games) > 0: gametest = str(tuple(games)) gametest = gametest.replace("L", "") gametest = gametest.replace(",)", ")") gametest = gametest.replace("u'", "'") gametest = "AND gt.category in %s" % gametest else: gametest = "AND gt.category IS NULL" q = q.replace("<game_test>", gametest) limittest = self.filters.get_limits_where_clause(limits) q = q.replace("<limit_test>", limittest) currencytest = str(tuple(currencies)) currencytest = currencytest.replace(",)", ")") currencytest = currencytest.replace("u'", "'") currencytest = "AND gt.currency in %s" % currencytest q = q.replace("<currency_test>", currencytest) if seats: q = q.replace( '<seats_test>', 'AND h.seats BETWEEN ' + str(seats['from']) + ' AND ' + str(seats['to'])) else: q = q.replace('<seats_test>', 'AND h.seats BETWEEN 0 AND 100') nametest = str(tuple(playerids)) nametest = nametest.replace("L", "") nametest = nametest.replace(",)", ")") q = q.replace("<player_test>", nametest) q = q.replace("<ampersand_s>", "%s") if DEBUG: hands = [ (u'10000', 10), (u'10000', 20), (u'10000', 30), (u'20000', -10), (u'20000', -20), (u'20000', -30), (u'30000', 40), (u'40000', 0), (u'50000', -40), (u'60000', 10), (u'60000', 30), (u'60000', -20), (u'70000', -20), (u'70000', 10), (u'70000', 30), (u'80000', -10), (u'80000', -30), (u'80000', 20), (u'90000', 20), (u'90000', -10), (u'90000', -30), (u'100000', 30), (u'100000', -50), (u'100000', 30), (u'110000', -20), (u'110000', 50), (u'110000', -20), (u'120000', -30), (u'120000', 50), (u'120000', -30), (u'130000', 20), (u'130000', -50), (u'130000', 20), (u'140000', 40), (u'140000', -40), (u'150000', -40), (u'150000', 40), (u'160000', -40), (u'160000', 80), (u'160000', -40), ] else: self.db.cursor.execute(q) hands = self.db.cursor.fetchall() #fixme - nasty hack to ensure that the hands.insert() works # for mysql data. mysql returns tuples which can't be inserted # into so convert explicity to list. hands = list(hands) if (not hands): return ([], []) hands.insert(0, (hands[0][0], 0)) # Take that list and create an array of the time between hands times = map(lambda x: long(x[0]), hands) profits = map(lambda x: float(x[1]), hands) #print "DEBUG: times : %s" % times #print "DEBUG: profits: %s" % profits #print "DEBUG: len(times) %s" %(len(times)) diffs = diff( times ) # This array is the difference in starttime between consecutive hands diffs2 = append( diffs, THRESHOLD + 1 ) # Append an additional session to the end of the diffs, so the next line # includes an index into the last 'session' index = nonzero( diffs2 > THRESHOLD ) # This array represents the indexes into 'times' for start/end times of sessions # times[index[0][0]] is the end of the first session, #print "DEBUG: len(index[0]) %s" %(len(index[0])) if len(index[0]) > 0: #print "DEBUG: index[0][0] %s" %(index[0][0]) #print "DEBUG: index %s" %(index) pass else: index = [[0]] #print "DEBUG: index %s" %(index) #print "DEBUG: index[0][0] %s" %(index[0][0]) pass first_idx = 1 quotes = [] results = [] cum_sum = cumsum(profits) / 100 sid = 1 total_hands = 0 total_time = 0 global_open = None global_lwm = None global_hwm = None self.times = [] # Take all results and format them into a list for feeding into gui model. #print "DEBUG: range(len(index[0]): %s" % range(len(index[0])) for i in range(len(index[0])): last_idx = index[0][i] hds = last_idx - first_idx + 1 # Number of hands in session if hds > 0: stime = strftime("%d/%m/%Y %H:%M", localtime( times[first_idx])) # Formatted start time etime = strftime("%d/%m/%Y %H:%M", localtime( times[last_idx])) # Formatted end time self.times.append((times[first_idx] - PADDING * 60, times[last_idx] + PADDING * 60)) minutesplayed = (times[last_idx] - times[first_idx]) / 60 minutesplayed = minutesplayed + PADDING if minutesplayed == 0: minutesplayed = 1 hph = hds * 60 / minutesplayed # Hands per hour end_idx = last_idx + 1 won = sum(profits[first_idx:end_idx]) / 100.0 #print "DEBUG: profits[%s:%s]: %s" % (first_idx, end_idx, profits[first_idx:end_idx]) hwm = max(cum_sum[first_idx - 1:end_idx]) # include the opening balance, lwm = min(cum_sum[first_idx - 1:end_idx]) # before we win/lose first hand open = (sum(profits[:first_idx])) / 100 close = (sum(profits[:end_idx])) / 100 #print "DEBUG: range: (%s, %s) - (min, max): (%s, %s) - (open,close): (%s, %s)" %(first_idx, end_idx, lwm, hwm, open, close) total_hands = total_hands + hds total_time = total_time + minutesplayed if (global_lwm == None or global_lwm > lwm): global_lwm = lwm if (global_hwm == None or global_hwm < hwm): global_hwm = hwm if (global_open == None): global_open = open global_stime = stime results.append([ sid, hds, stime, etime, hph, "%.2f" % open, "%.2f" % close, "%.2f" % lwm, "%.2f" % hwm, "%.2f" % (hwm - lwm), "%.2f" % won ]) quotes.append((sid, open, close, hwm, lwm)) #print "DEBUG: Hands in session %4s: %4s Start: %s End: %s HPH: %s Profit: %s" %(sid, hds, stime, etime, hph, won) first_idx = end_idx sid = sid + 1 else: print "hds <= 0" global_close = close global_etime = etime results.append([''] * 11) results.append([ _("all"), total_hands, global_stime, global_etime, total_hands * 60 / total_time, "%.2f" % global_open, "%.2f" % global_close, "%.2f" % global_lwm, "%.2f" % global_hwm, "%.2f" % (global_hwm - global_lwm), "%.2f" % (global_close - global_open) ]) return (results, quotes) def clearGraphData(self): try: try: if self.canvas: self.graphBox.layout().removeWidget(self.canvas) self.canvas.setParent(None) except: pass if self.fig is not None: self.fig.clear() self.fig = Figure(figsize=(5, 4), dpi=100) if self.canvas is not None: self.canvas.destroy() self.canvas = FigureCanvas(self.fig) self.canvas.setParent(self) except: err = traceback.extract_tb(sys.exc_info()[2])[-1] print _("Error:") + " " + err[2] + "(" + str(err[1]) + "): " + str( sys.exc_info()[1]) raise def generateGraph(self, quotes): self.clearGraphData() self.ax = self.fig.add_subplot(111) self.ax.set_title(_("Session candlestick graph")) #Set axis labels and grid overlay properites self.ax.set_xlabel(_("Sessions"), fontsize=12) self.ax.set_ylabel("$", fontsize=12) self.ax.grid(color='g', linestyle=':', linewidth=0.2) candlestick_ochl(self.ax, quotes, width=0.50, colordown='r', colorup='g', alpha=1.00) self.graphBox.layout().addWidget(self.canvas) self.canvas.draw() def addTable(self, frame, results): colxalign, colheading = range(2) self.liststore = QStandardItemModel(0, len(self.columns)) self.liststore.setHorizontalHeaderLabels( [column[colheading] for column in self.columns]) for row in results: listrow = [QStandardItem(str(r)) for r in row] for item in listrow: item.setEditable(False) self.liststore.appendRow(listrow) self.view = QTableView() self.view.setModel(self.liststore) self.view.verticalHeader().hide() self.view.setSelectionBehavior(QTableView.SelectRows) frame.layout().addWidget(self.view) self.view.doubleClicked.connect(self.row_activated) def row_activated(self, index): if index.row() < len(self.times): replayer = None for tabobject in self.owner.threads: if isinstance(tabobject, GuiHandViewer.GuiHandViewer): replayer = tabobject self.owner.tab_hand_viewer(None) break if replayer is None: self.owner.tab_hand_viewer(None) for tabobject in self.owner.threads: if isinstance(tabobject, GuiHandViewer.GuiHandViewer): replayer = tabobject break # added the timezone offset ('+00:00') to make the db query work. Otherwise the hands # at the edges of the date range are not included. A better solution may be possible. # Optionally the end date in the call below, which is a Long gets a '+1'. reformat = lambda t: strftime("%Y-%m-%d %H:%M:%S+00:00", gmtime(t)) handids = replayer.get_hand_ids_from_date_range( reformat(self.times[index.row()][0]), reformat(self.times[index.row()][1])) replayer.reload_hands(handids)
class TabToolbar(QToolBar): """[summary] Args: _TabToolbar ([type]): [description] Returns: [type]: [description] """ Minimized = QtCore.pyqtSignal() Maximized = QtCore.pyqtSignal() SpecialTabClicked = QtCore.pyqtSignal() StyleChanged = QtCore.pyqtSignal() def __init__(self, parent: QWidget = None, group_maxheight: int = 75, group_rowcount: int = 3): super(TabToolbar, self).__init__(parent) style.register_default_styles() self.group_rowcount = group_rowcount self.group_maxheight = group_maxheight self.has_specialtab = False self.current_index = 0 self.ignore_styleevent = False self.is_shown = True self._is_minimized = False self.maxheight = QtWidgets.QWIDGETSIZE_MAX self._style = style.StyleParams() self.setObjectName("TabToolbar") # self.tempShowTimer = QtCore.QTimer() # self.tempShowTimer.setSingleShot(True) # self.tempShowTimer.setInterval(QApplication.doubleClickInterval()) self.setProperty("TabToolbar", QtCore.QVariant(True)) self.layout().setContentsMargins(0, 0, 0, 0) self.layout().setSpacing(0) self.setContentsMargins(0, 0, 0, 0) self.setFloatable(False) self.setMovable(False) self.setAllowedAreas(QtCore.Qt.TopToolBarArea) self.tabBar = QTabWidget(self) self.tabBar.setProperty("TTWidget", QtCore.QVariant(True)) self.tabBar.tabBar().setProperty("TTTab", QtCore.QVariant(True)) self.tabBarHandle = self.addWidget(self.tabBar) self.tabBar.setUsesScrollButtons(True) self.cornerActions = QFrame(self) self.cornerActions.setFrameShape(QFrame.NoFrame) self.cornerActions.setLineWidth(0) self.cornerActions.setContentsMargins(0, 0, 0, 0) self.cornerActions.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Minimum) self.cornerLayout = QHBoxLayout(self.cornerActions) self.cornerLayout.setContentsMargins(0, 0, 0, 0) self.cornerLayout.setSpacing(0) self.cornerLayout.setDirection(QBoxLayout.LeftToRight) self.cornerActions.setLayout(self.cornerLayout) self.hideAction = QAction(self) self.hideAction.setCheckable(True) self.hideAction.setText("▲") self.hideButton = QToolButton(self.tabBar) self.hideButton.setProperty("TTHide", QtCore.QVariant(True)) self.hideButton.setToolButtonStyle(QtCore.Qt.ToolButtonTextOnly) self.hideButton.setDefaultAction(self.hideAction) self.hideButton.setAutoRaise(True) self.hideButton.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.hideAction.triggered.connect(self._hideaction) self.tabBar.tabBarDoubleClicked.connect(self.hideAction.trigger) self.tabBar.tabBarClicked.connect(self.current_tabchanged) self.tabBar.currentChanged.connect(self.focus_changed) self.cornerLayout.addWidget(self.hideButton) self.tabBar.setCornerWidget(self.cornerActions) self.set_style(style.get_defaultstyle()) def _hideaction(self): # self.tempShowTimer.start() self._is_minimized = self.hideAction.isChecked() self.hideAction.setText("▼" if self._is_minimized else "▲") self.hide_at(self.tabBar.currentIndex()) if self._is_minimized: self.Minimized.emit() else: self.Maximized.emit() def event(self, event: QtCore.QEvent): if event.type( ) == QtCore.QEvent.StyleChange and not self.ignore_styleevent: # TODO: Validatre if we need a timer stylename = (self._style.objectName() if self._style else style.get_defaultstyle()) self.set_style(stylename) return super(TabToolbar, self).event(event) def focus_changed(self, old: QWidget = None, now: QWidget = None): if now and now != self: if self.isMinimized() and self.is_shown: parent = now while parent: parent = parent.parent() if parent == self: return self.hide_at(self.current_index) def rowcount(self): return self.group_rowcount def group_maxheight(self): return self.group_maxheight * style.get_scalefactor(self) def set_style(self, stylename: str): self.ignore_styleevent = True self._style = style.create_style(stylename) stylesheet = style.get_stylesheet(self._style) self.setStyleSheet(stylesheet) self.ignore_styleevent = False self.StyleChanged.emit() def get_style(self) -> str: if self._style: return self._style.objectName() return "" def add_corneraction(self, action: QAction): action_button = QToolButton(self.tabBar) action_button.setProperty("TTInternal", QtCore.QVariant(True)) action_button.setToolButtonStyle(QtCore.Qt.ToolButtonIconOnly) action_button.setDefaultAction(action) action_button.setAutoRaise(True) action_button.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.cornerActions.layout().addWidget(action_button) def set_specialtabenabled(self, enabled: bool): self.has_specialtab = enabled self.tabBar.tabBar().setProperty("TTSpecial", QtCore.QVariant(enabled)) if enabled and self.tabBar.count() > 0: self.tabBar.setCurrentIndex(1) def hide_action(self) -> QAction: return self.hideAction def tab_clicked(self, index: int): if self.tempShowTimer.isActive() or (index == 0 and self.has_specialtab): return if self._is_minimized: if self.is_shown and index != self.current_index: return self._is_minimized = self.is_shown self.hide_at(index) self._is_minimized = True def current_tabchanged(self, index: int): QtCore.QSignalBlocker(self.tabBar) if index == 0 and self.has_specialtab: self.tabBar.setCurrentIndex(self.current_index) self.SpecialTabClicked.emit() else: self.current_index = index @property def current_tab(self) -> int: return self.current_index def set_currenttab(self, index: int): self.tabBar.setCurrentIndex(index) def hide_at(self, index: int): if self._is_minimized: minheight = self.tabBar.tabBar().height() + 2 self.tabBar.setMaximumHeight(minheight) self.tabBar.setMinimumHeight(minheight) self.setMaximumHeight(minheight) self.setMinimumHeight(minheight) self.is_shown = False else: self.tabBar.setCurrentIndex(index) if not self.is_shown: self.tabBar.setMaximumHeight(self.maxheight) self.tabBar.setMinimumHeight(self.maxheight) self.setMaximumHeight(self.maxheight) self.setMinimumHeight(self.maxheight) self.tabBar.adjustSize() self.setFocus() self.is_shown = True def hide_tab(self, index: int): page = self.sender() QtCore.QSignalBlocker(page) for i in range(self.tabBar.count()): if self.tabBar.widget(i) == page: self.tabBar.removeTab(i) return self.current_index = self.tabBar.currentIndex() @QtCore.pyqtSlot(int) def _adjustverticalsize(self, vsize: int): self.maxheight = vsize + self.tabBar.tabBar().height() + 6 self.setMaximumHeight(self.maxheight) self.setMinimumHeight(self.maxheight) def adjust_verticalsize(self, vsize: int): QtCore.QTimer.singleShot(0, lambda: self._adjustverticalsize(vsize) ) # type: ignore[attr-defined] # self._AdjustVerticleSize(vSize) def show_tab(self, index: int): tab_page = self.sender() QtCore.QSignalBlocker(tab_page) self.tabBar.insertTab(index, tab_page, tab_page.objectName()) self.current_index = self.tabBar.currentIndex() def add_page(self, page_name: str) -> page.Page: tab_page = page.Page(self.tabBar.count(), page_name) QtCore.QSignalBlocker(tab_page) tab_page.Hiding.connect(self.hide_tab) tab_page.Showing.connect(self.show_tab) self.tabBar.addTab(tab_page, page_name) return tab_page
class _UIElement(QObject): """ Универсальный класс элемента интерфейса. Создан, потому что изначальные классы PyQt требуют значительной доработки и к тому же недостаточно интуитивны, легко запутаться в Qt-овском нагромождении из виджетов, лэйаутов, item объектов, прочей шелухи. Плюсы этого класса: позволяет отображать подпись над элементом, удобно включать/выключать функциональность, сразу ставит виджет в центр (а не в край, чтоб потом его ещё ровнять) убирает ненужные отступы между элементами все последующие элементы наследуются от него """ def __init__(self, widget: QWidget = None, layout: QLayout = None, description: str = None, disable: bool = False, stroked: bool = False): """ :param widget: класс Qt Widget, который будет отображаться как элемент интерфейса :param layout: класс слоя, который будет основанием, на котором будет лежать виджет :param description: описание или название, котороые при наличии будет отображаться над виджетом :param disable: опция отключения виджета при создании. По умолчанию False, т.е. виджет включён """ super().__init__() self._inner_widget = widget self._layout = QVBoxLayout() if layout is None else layout self._layout.setContentsMargins(2, 2, 2, 2) if description is not None or layout is not None: self._out_widget = QFrame() if stroked: self._out_widget.setFrameStyle(QFrame.Plain) if description is not None: self._layout.addWidget(QLabel(description), alignment=Qt.AlignCenter) if widget is not None: self._layout.addWidget(widget) self._out_widget.setLayout(self._layout) elif widget is not None: self._out_widget = widget widget.setDisabled(disable) def toggle_element(self, state=None): if self._inner_widget is not None: self._inner_widget.setDisabled(self._inner_widget.isEnabled() if state is None else not state) def set_max_width(self, width): self._out_widget.setMaximumWidth(width) def set_max_height(self, height): self._out_widget.setMaximumHeight(height) def set_fixed_width(self, width): self._out_widget.setFixedWidth(width) def set_fixed_height(self, height): self._out_widget.setFixedHeight(height) def set_min_width(self, width): self._out_widget.setMinimumWidth(width) def set_min_height(self, height): self._out_widget.setMinimumHeight(height) def __layout__(self) -> QLayout: return self._out_widget.layout() def __widget__(self) -> QWidget: return self._out_widget
class CorpusGeneralWidget(QWidget): def __init__(self, parent): super(CorpusGeneralWidget, self).__init__(parent) self.setLayout(QVBoxLayout()) self.layout().addWidget(QLabel("Corpus Information", self)) self.w_corpus = QFrame(self) self.w_corpus.setWindowTitle("Corpus Information") self.w_corpus.setLayout(QVBoxLayout()) self.layout().addWidget(self.w_corpus) self.lt_actions = QHBoxLayout(self) self.layout().addWidget(QLabel("Template", self)) self.layout().addItem(self.lt_actions) self.layout().addWidget(QLabel("Project Information", self)) self.w_movie = CorpusMovieWidget(self) self.layout().addWidget(self.w_movie) self.w_name = QWidget(self) self.w_name.setLayout(QHBoxLayout()) self.w_name.layout().addWidget(QLabel("Corpus Name")) self.textEdit_Name = QLineEdit(self.w_name) self.textEdit_Name.editingFinished.connect(self.on_name_changed) self.w_name.layout().addWidget(self.textEdit_Name) self.w_corpus.layout().addWidget(self.w_name) self.templateStack = QStackedWidget(self) self.templateStack.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Maximum) self.lt_actions.addWidget(self.templateStack) self.template_widget_manage = QWidget(self.templateStack) self.template_widget_edit = QWidget(self.templateStack) self.templateStack.addWidget(self.template_widget_manage) self.templateStack.addWidget(self.template_widget_edit) self.template_widget_manage.setLayout(QGridLayout()) self.template_widget_edit.setLayout(QGridLayout()) self.btn_EditTemplate = QPushButton("Edit Template", self) self.template_widget_manage.layout().addWidget(self.btn_EditTemplate, 0, 0) self.btn_ImportTemplate = QPushButton("Import Template", self) self.template_widget_manage.layout().addWidget(self.btn_ImportTemplate, 0, 1) self.btn_SaveTemplate = QPushButton("Save Template", self) self.template_widget_edit.layout().addWidget(self.btn_SaveTemplate, 0, 0) self.btn_CloseTemplate = QPushButton("Close Template", self) self.template_widget_edit.layout().addWidget(self.btn_CloseTemplate, 0, 1) self.layout().addItem( QSpacerItem(1, 1, QSizePolicy.Preferred, QSizePolicy.Expanding)) self.corpus = None self.setEnabled(False) def on_corpus_loaded(self, corpus): self.corpus = corpus if self.corpus is not None: self.textEdit_Name.setText(self.corpus.name) self.setEnabled(True) else: self.setEnabled(False) def on_name_changed(self): if self.corpus is not None: if self.textEdit_Name.text() != "": self.corpus.name = self.textEdit_Name.text() self.corpus.save() def on_project_changed(self, project): self.w_movie.set_project(project)
class VideoDialog(QDialog): def __init__(self, video_path, parent=None): super(VideoDialog, self).__init__(parent) #self.setWindowFlags(QtCore.Qt.FramelessWindowHint) self.resize(400, 400) position = self.cursor().pos() position.setX(position.x()) position.setY(position.y()) self.move(position) #self.setWindowOpacity(0.9) self.setLayout(QVBoxLayout()) self.layout().setContentsMargins(0, 0, 0, 0) self.widget = QFrame() self.widget.setStyleSheet(''' QFrame{ border-style: outset; border-width: 1px; /*border-radius: 10px;*/ border-color: #B94129; } ''') #self.widget.setFrameStyle(QFrame.Box) self.widget.setLayout(QVBoxLayout()) self.widget.layout().setContentsMargins(10, 10, 10, 10) self.video_player = VideoPlayer() self.video_player.source = video_path self.video_player.play() self.video_player.signals.video_position_changed_signal.connect( self.video_position_changed) duration = self.video_player.total_duration self.video_duration_slider = QSlider(orientation=QtCore.Qt.Horizontal) self.video_duration_slider.setRange(0, duration) self.video_duration_slider.setTickInterval(5) self.video_duration_slider.sliderMoved.connect( self.slider_changed_handler) #self.video_duration_slider.setTickPosition(QSlider.TicksBelow) self.setMouseTracking(True) self.setWindowFlags(QtCore.Qt.Popup | QtCore.Qt.WindowStaysOnTopHint | QtCore.Qt.FramelessWindowHint | QtCore.Qt.X11BypassWindowManagerHint) #self.setAttribute(QtCore.Qt.WA_TranslucentBackground) self.widget.layout().addWidget(self.video_player) self.widget.layout().addWidget(self.video_duration_slider) self.layout().addWidget(self.widget) def setMouseTracking(self, flag): def set_mouse_tracking(parent): for child in parent.findChildren(QtCore.QObject): try: child.setMouseTracking(flag) except: pass set_mouse_tracking(child) QWidget.setMouseTracking(self, flag) set_mouse_tracking(self) def slider_changed_handler(self, change): self.video_player.go_to(change) def video_position_changed(self, current, total): self.video_duration_slider.setValue(current) def mouseMoveEvent(self, event: QMouseEvent) -> None: # print('mouseMoveEvent: x=%d, y=%d' % (event.x(), event.y())) if not self.rect().contains(event.pos()): self.close()
def __init__(self): super(memoryDump, self).__init__() if memoryDump.memoryDumpFrame == None: memoryDump.memoryDumpFrame = QFrame() Font = QtGui.QFont("Arial", 15, QtGui.QFont.Bold) Font.setBold(True) Title = QtWidgets.QLabel(self) Title.setText("EEPROM Memory Dump") Title.setAlignment(Qt.AlignCenter) Title.setFont(Font) Title.setAlignment(Qt.AlignCenter) Title.setGeometry(QRect(290, 40, 191, 31)) self.refreshButton = QtWidgets.QPushButton("Refresh") self.refreshButton.clicked.connect(lambda: self.submitClicked) self.memoryAddressLabel = QLabel(self) self.memoryAddressLabel.setText('Memory Address:') self.line = QLineEdit(self) self.line.move(80, 20) self.line.resize(100, 32) self.memoryAddressLabel.move(20, 20) submitbutton = QPushButton('Submit', self) submitbutton.clicked.connect(self.submitClicked) submitbutton.resize(100, 32) submitbutton.move(80, 60) self.inputFrame = QFrame() self.inputFrame.layout = QHBoxLayout() self.inputFrame.layout.addWidget(self.memoryAddressLabel) self.inputFrame.layout.addWidget(self.line) self.inputFrame.layout.addWidget(submitbutton) self.inputFrame.layout.addWidget(self.refreshButton, 0, Qt.AlignRight) self.inputFrame.setLayout(self.inputFrame.layout) headerfont = QtGui.QFont() headerfont.setFamily("Arial Black") headerfont.setPointSize(11) memoryDump.tableWidget = QTableWidget() memoryDump.tableWidget.setRowCount(2) memoryDump.tableWidget.setColumnCount(3) memoryDump.map = {} memoryDump.tableWidget.setItem(0, 0, QTableWidgetItem("Address ")) memoryDump.tableWidget.setItem(0, 1, QTableWidgetItem("Hex Values")) memoryDump.tableWidget.setItem(0, 2, QTableWidgetItem("Text Values")) tableview = QtWidgets.QTableView() tableview.setAlternatingRowColors(True) tableview.horizontalHeader().setSectionResizeMode( QtWidgets.QHeaderView.Stretch) memoryDump.tableWidget.resizeColumnsToContents() simulatorFrame = QFrame() simulatorFrame.layout = QVBoxLayout() simulatorFrame.layout.addWidget(Title) simulatorFrame.layout.addWidget(self.inputFrame) simulatorFrame.layout.addWidget(memoryDump.tableWidget) simulatorFrame.setFrameShadow(simulatorFrame.Raised) simulatorFrame.setLayout(simulatorFrame.layout) memoryDump.memoryDumpFrame.setFrameShape(QFrame.StyledPanel) memoryDump.memoryDumpFrame.layout = QHBoxLayout() memoryDump.memoryDumpFrame.layout.addWidget(simulatorFrame) memoryDump.memoryDumpFrame.setLayout( memoryDump.memoryDumpFrame.layout)
class XNova_MainWindow(QWidget): STATE_NOT_AUTHED = 0 STATE_AUTHED = 1 def __init__(self, parent=None): super(XNova_MainWindow, self).__init__(parent, Qt.Window) # state vars self.config_store_dir = './cache' self.cfg = configparser.ConfigParser() self.cfg.read('config/net.ini', encoding='utf-8') self.state = self.STATE_NOT_AUTHED self.login_email = '' self.cookies_dict = {} self._hidden_to_tray = False # # init UI self.setWindowIcon(QIcon(':/i/xnova_logo_64.png')) self.setWindowTitle('XNova Commander') # main layouts self._layout = QVBoxLayout() self._layout.setContentsMargins(0, 2, 0, 0) self._layout.setSpacing(3) self.setLayout(self._layout) self._horizontal_layout = QHBoxLayout() self._horizontal_layout.setContentsMargins(0, 0, 0, 0) self._horizontal_layout.setSpacing(6) # flights frame self._fr_flights = QFrame(self) self._fr_flights.setMinimumHeight(22) self._fr_flights.setFrameShape(QFrame.NoFrame) self._fr_flights.setFrameShadow(QFrame.Plain) # planets bar scrollarea self._sa_planets = QScrollArea(self) self._sa_planets.setMinimumWidth(125) self._sa_planets.setMaximumWidth(125) self._sa_planets.setFrameShape(QFrame.NoFrame) self._sa_planets.setFrameShadow(QFrame.Plain) self._sa_planets.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn) self._sa_planets.setWidgetResizable(True) self._panel_planets = QWidget(self._sa_planets) self._layout_pp = QVBoxLayout() self._panel_planets.setLayout(self._layout_pp) self._lbl_planets = QLabel(self.tr('Planets:'), self._panel_planets) self._lbl_planets.setMaximumHeight(32) self._layout_pp.addWidget(self._lbl_planets) self._layout_pp.addStretch() self._sa_planets.setWidget(self._panel_planets) # # tab widget self._tabwidget = XTabWidget(self) self._tabwidget.enableButtonAdd(False) self._tabwidget.tabCloseRequested.connect(self.on_tab_close_requested) self._tabwidget.addClicked.connect(self.on_tab_add_clicked) # # create status bar self._statusbar = XNCStatusBar(self) self.set_status_message(self.tr('Not connected: Log in!')) # # tab widget pages self.login_widget = None self.flights_widget = None self.overview_widget = None self.imperium_widget = None # # settings widget self.settings_widget = SettingsWidget(self) self.settings_widget.settings_changed.connect(self.on_settings_changed) self.settings_widget.hide() # # finalize layouts self._horizontal_layout.addWidget(self._sa_planets) self._horizontal_layout.addWidget(self._tabwidget) self._layout.addWidget(self._fr_flights) self._layout.addLayout(self._horizontal_layout) self._layout.addWidget(self._statusbar) # # system tray icon self.tray_icon = None show_tray_icon = False if 'tray' in self.cfg: if (self.cfg['tray']['icon_usage'] == 'show') or \ (self.cfg['tray']['icon_usage'] == 'show_min'): self.create_tray_icon() # # try to restore last window size ssz = self.load_cfg_val('main_size') if ssz is not None: self.resize(ssz[0], ssz[1]) # # world initialization self.world = XNovaWorld_instance() self.world_timer = QTimer(self) self.world_timer.timeout.connect(self.on_world_timer) # overrides QWidget.closeEvent # cleanup just before the window close def closeEvent(self, close_event: QCloseEvent): logger.debug('closing') if self.tray_icon is not None: self.tray_icon.hide() self.tray_icon = None if self.world_timer.isActive(): self.world_timer.stop() self.world.script_command = 'stop' # also stop possible running scripts if self.world.isRunning(): self.world.quit() logger.debug('waiting for world thread to stop (5 sec)...') wait_res = self.world.wait(5000) if not wait_res: logger.warn('wait failed, last chance, terminating!') self.world.terminate() # store window size ssz = (self.width(), self.height()) self.store_cfg_val('main_size', ssz) # accept the event close_event.accept() def showEvent(self, evt: QShowEvent): super(XNova_MainWindow, self).showEvent(evt) self._hidden_to_tray = False def changeEvent(self, evt: QEvent): super(XNova_MainWindow, self).changeEvent(evt) if evt.type() == QEvent.WindowStateChange: if not isinstance(evt, QWindowStateChangeEvent): return # make sure we only do this for minimize events if (evt.oldState() != Qt.WindowMinimized) and self.isMinimized(): # we were minimized! explicitly hide settings widget # if it is open, otherwise it will be lost forever :( if self.settings_widget is not None: if self.settings_widget.isVisible(): self.settings_widget.hide() # should we minimize to tray? if self.cfg['tray']['icon_usage'] == 'show_min': if not self._hidden_to_tray: self._hidden_to_tray = True self.hide() def create_tray_icon(self): if QSystemTrayIcon.isSystemTrayAvailable(): logger.debug('System tray icon is available, showing') self.tray_icon = QSystemTrayIcon(QIcon(':/i/xnova_logo_32.png'), self) self.tray_icon.setToolTip(self.tr('XNova Commander')) self.tray_icon.activated.connect(self.on_tray_icon_activated) self.tray_icon.show() else: self.tray_icon = None def hide_tray_icon(self): if self.tray_icon is not None: self.tray_icon.hide() self.tray_icon.deleteLater() self.tray_icon = None def set_tray_tooltip(self, tip: str): if self.tray_icon is not None: self.tray_icon.setToolTip(tip) def set_status_message(self, msg: str): self._statusbar.set_status(msg) def store_cfg_val(self, category: str, value): pickle_filename = '{0}/{1}.dat'.format(self.config_store_dir, category) try: cache_dir = pathlib.Path(self.config_store_dir) if not cache_dir.exists(): cache_dir.mkdir() with open(pickle_filename, 'wb') as f: pickle.dump(value, f) except pickle.PickleError as pe: pass except IOError as ioe: pass def load_cfg_val(self, category: str, default_value=None): value = None pickle_filename = '{0}/{1}.dat'.format(self.config_store_dir, category) try: with open(pickle_filename, 'rb') as f: value = pickle.load(f) if value is None: value = default_value except pickle.PickleError as pe: pass except IOError as ioe: pass return value @pyqtSlot() def on_settings_changed(self): self.cfg.read('config/net.ini', encoding='utf-8') # maybe show/hide tray icon now? show_tray_icon = False if 'tray' in self.cfg: icon_usage = self.cfg['tray']['icon_usage'] if (icon_usage == 'show') or (icon_usage == 'show_min'): show_tray_icon = True # show if needs show and hidden, or hide if shown and needs to hide if show_tray_icon and (self.tray_icon is None): logger.debug('settings changed, showing tray icon') self.create_tray_icon() elif (not show_tray_icon) and (self.tray_icon is not None): logger.debug('settings changed, hiding tray icon') self.hide_tray_icon() # also notify world about changed config! self.world.reload_config() def add_tab(self, widget: QWidget, title: str, closeable: bool = True) -> int: tab_index = self._tabwidget.addTab(widget, title, closeable) return tab_index def remove_tab(self, index: int): self._tabwidget.removeTab(index) # called by main application object just after main window creation # to show login widget and begin login process def begin_login(self): # create flights widget self.flights_widget = FlightsWidget(self._fr_flights) self.flights_widget.load_ui() install_layout_for_widget(self._fr_flights, Qt.Vertical, margins=(1, 1, 1, 1), spacing=1) self._fr_flights.layout().addWidget(self.flights_widget) self.flights_widget.set_online_state(False) self.flights_widget.requestShowSettings.connect(self.on_show_settings) # create and show login widget as first tab self.login_widget = LoginWidget(self._tabwidget) self.login_widget.load_ui() self.login_widget.loginError.connect(self.on_login_error) self.login_widget.loginOk.connect(self.on_login_ok) self.login_widget.show() self.add_tab(self.login_widget, self.tr('Login'), closeable=False) # self.test_setup_planets_panel() # self.test_planet_tab() def setup_planets_panel(self, planets: list): layout = self._panel_planets.layout() layout.setSpacing(0) remove_trailing_spacer_from_layout(layout) # remove all previous planet widgets from planets panel if layout.count() > 0: for i in range(layout.count()-1, -1, -1): li = layout.itemAt(i) if li is not None: wi = li.widget() if wi is not None: if isinstance(wi, PlanetSidebarWidget): layout.removeWidget(wi) wi.close() wi.deleteLater() # fix possible mem leak del wi for pl in planets: pw = PlanetSidebarWidget(self._panel_planets) pw.setPlanet(pl) layout.addWidget(pw) pw.show() # connections from each planet bar widget pw.requestOpenGalaxy.connect(self.on_request_open_galaxy_tab) pw.requestOpenPlanet.connect(self.on_request_open_planet_tab) append_trailing_spacer_to_layout(layout) def update_planets_panel(self): """ Calls QWidget.update() on every PlanetBarWidget embedded in ui.panel_planets, causing repaint """ layout = self._panel_planets.layout() if layout.count() > 0: for i in range(layout.count()): li = layout.itemAt(i) if li is not None: wi = li.widget() if wi is not None: if isinstance(wi, PlanetSidebarWidget): wi.update() def add_tab_for_planet(self, planet: XNPlanet): # construct planet widget and setup signals/slots plw = PlanetWidget(self._tabwidget) plw.requestOpenGalaxy.connect(self.on_request_open_galaxy_tab) plw.setPlanet(planet) # construct tab title tab_title = '{0} {1}'.format(planet.name, planet.coords.coords_str()) # add tab and make it current tab_index = self.add_tab(plw, tab_title, closeable=True) self._tabwidget.setCurrentIndex(tab_index) self._tabwidget.tabBar().setTabIcon(tab_index, QIcon(':/i/planet_32.png')) return tab_index def add_tab_for_galaxy(self, coords: XNCoords = None): gw = GalaxyWidget(self._tabwidget) tab_title = '{0}'.format(self.tr('Galaxy')) if coords is not None: tab_title = '{0} {1}'.format(self.tr('Galaxy'), coords.coords_str()) gw.setCoords(coords.galaxy, coords.system) idx = self.add_tab(gw, tab_title, closeable=True) self._tabwidget.setCurrentIndex(idx) self._tabwidget.tabBar().setTabIcon(idx, QIcon(':/i/galaxy_32.png')) @pyqtSlot(int) def on_tab_close_requested(self, idx: int): # logger.debug('tab close requested: {0}'.format(idx)) if idx <= 1: # cannot close overview or imperium tabs return self.remove_tab(idx) @pyqtSlot() def on_tab_add_clicked(self): pos = QCursor.pos() planets = self.world.get_planets() # logger.debug('tab bar add clicked, cursor pos = ({0}, {1})'.format(pos.x(), pos.y())) menu = QMenu(self) # galaxy view galaxy_action = QAction(menu) galaxy_action.setText(self.tr('Add galaxy view')) galaxy_action.setData(QVariant('galaxy')) menu.addAction(galaxy_action) # planets menu.addSection(self.tr('-- Planet tabs: --')) for planet in planets: action = QAction(menu) action.setText('{0} {1}'.format(planet.name, planet.coords.coords_str())) action.setData(QVariant(planet.planet_id)) menu.addAction(action) action_ret = menu.exec(pos) if action_ret is not None: # logger.debug('selected action data = {0}'.format(str(action_ret.data()))) if action_ret == galaxy_action: logger.debug('action_ret == galaxy_action') self.add_tab_for_galaxy() return # else consider this is planet widget planet_id = int(action_ret.data()) self.on_request_open_planet_tab(planet_id) @pyqtSlot(str) def on_login_error(self, errstr): logger.error('Login error: {0}'.format(errstr)) self.state = self.STATE_NOT_AUTHED self.set_status_message(self.tr('Login error: {0}').format(errstr)) QMessageBox.critical(self, self.tr('Login error:'), errstr) @pyqtSlot(str, dict) def on_login_ok(self, login_email, cookies_dict): # logger.debug('Login OK, login: {0}, cookies: {1}'.format(login_email, str(cookies_dict))) # save login data: email, cookies self.state = self.STATE_AUTHED self.set_status_message(self.tr('Login OK, loading world')) self.login_email = login_email self.cookies_dict = cookies_dict # # destroy login widget and remove its tab self.remove_tab(0) self.login_widget.close() self.login_widget.deleteLater() self.login_widget = None # # create overview widget and add it as first tab self.overview_widget = OverviewWidget(self._tabwidget) self.overview_widget.load_ui() self.add_tab(self.overview_widget, self.tr('Overview'), closeable=False) self.overview_widget.show() self.overview_widget.setEnabled(False) # # create 2nd tab - Imperium self.imperium_widget = ImperiumWidget(self._tabwidget) self.add_tab(self.imperium_widget, self.tr('Imperium'), closeable=False) self.imperium_widget.setEnabled(False) # # initialize XNova world updater self.world.initialize(cookies_dict) self.world.set_login_email(self.login_email) # connect signals from world self.world.world_load_progress.connect(self.on_world_load_progress) self.world.world_load_complete.connect(self.on_world_load_complete) self.world.net_request_started.connect(self.on_net_request_started) self.world.net_request_finished.connect(self.on_net_request_finished) self.world.flight_arrived.connect(self.on_flight_arrived) self.world.build_complete.connect(self.on_building_complete) self.world.loaded_overview.connect(self.on_loaded_overview) self.world.loaded_imperium.connect(self.on_loaded_imperium) self.world.loaded_planet.connect(self.on_loaded_planet) self.world.start() @pyqtSlot(str, int) def on_world_load_progress(self, comment: str, progress: int): self._statusbar.set_world_load_progress(comment, progress) @pyqtSlot() def on_world_load_complete(self): logger.debug('main: on_world_load_complete()') # enable adding new tabs self._tabwidget.enableButtonAdd(True) # update statusbar self._statusbar.set_world_load_progress('', -1) # turn off progress display self.set_status_message(self.tr('World loaded.')) # update account info if self.overview_widget is not None: self.overview_widget.setEnabled(True) self.overview_widget.update_account_info() self.overview_widget.update_builds() # update flying fleets self.flights_widget.set_online_state(True) self.flights_widget.update_flights() # update planets planets = self.world.get_planets() self.setup_planets_panel(planets) if self.imperium_widget is not None: self.imperium_widget.setEnabled(True) self.imperium_widget.update_planets() # update statusbar self._statusbar.update_online_players_count() # update tray tooltip, add account name self.set_tray_tooltip(self.tr('XNova Commander') + ' - ' + self.world.get_account_info().login) # set timer to do every-second world recalculation self.world_timer.setInterval(1000) self.world_timer.setSingleShot(False) self.world_timer.start() @pyqtSlot() def on_loaded_overview(self): logger.debug('on_loaded_overview') # A lot of things are updated when overview is loaded # * Account information and stats if self.overview_widget is not None: self.overview_widget.update_account_info() # * flights will be updated every second anyway in on_world_timer(), so no need to call # self.flights_widget.update_flights() # * messages count also, is updated with flights # * current planet may have changed self.update_planets_panel() # * server time is updated also self._statusbar.update_online_players_count() @pyqtSlot() def on_loaded_imperium(self): logger.debug('on_loaded_imperium') # need to update imperium widget if self.imperium_widget is not None: self.imperium_widget.update_planets() # The important note here is that imperium update is the only place where # the planets list is read, so number of planets, their names, etc may change here # Also, imperium update OVERWRITES full planets array, so, all prev # references to planets in all GUI elements must be invalidated, because # they will point to unused, outdated planets planets = self.world.get_planets() # re-create planets sidebar self.setup_planets_panel(planets) # update all builds in overview widget if self.overview_widget: self.overview_widget.update_builds() # update all planet tabs with new planet references cnt = self._tabwidget.count() if cnt > 2: for index in range(2, cnt): tab_page = self._tabwidget.tabWidget(index) if tab_page is not None: try: tab_type = tab_page.get_tab_type() if tab_type == 'planet': tab_planet = tab_page.planet() new_planet = self.world.get_planet(tab_planet.planet_id) tab_page.setPlanet(new_planet) except AttributeError: # not all pages may have method get_tab_type() pass @pyqtSlot(int) def on_loaded_planet(self, planet_id: int): logger.debug('Got signal on_loaded_planet({0}), updating overview ' 'widget and planets panel'.format(planet_id)) if self.overview_widget: self.overview_widget.update_builds() self.update_planets_panel() # update also planet tab, if any planet = self.world.get_planet(planet_id) if planet is not None: tab_idx = self.find_tab_for_planet(planet_id) if tab_idx != -1: tab_widget = self._tabwidget.tabWidget(tab_idx) if isinstance(tab_widget, PlanetWidget): logger.debug('Updating planet tab #{}'.format(tab_idx)) tab_widget.setPlanet(planet) @pyqtSlot() def on_world_timer(self): if self.world: self.world.world_tick() self.update_planets_panel() if self.flights_widget: self.flights_widget.update_flights() if self.overview_widget: self.overview_widget.update_builds() if self.imperium_widget: self.imperium_widget.update_planet_resources() @pyqtSlot() def on_net_request_started(self): self._statusbar.set_loading_status(True) @pyqtSlot() def on_net_request_finished(self): self._statusbar.set_loading_status(False) @pyqtSlot(int) def on_tray_icon_activated(self, reason): # QSystemTrayIcon::Unknown 0 Unknown reason # QSystemTrayIcon::Context 1 The context menu for the system tray entry was requested # QSystemTrayIcon::DoubleClick 2 The system tray entry was double clicked # QSystemTrayIcon::Trigger 3 The system tray entry was clicked # QSystemTrayIcon::MiddleClick 4 The system tray entry was clicked with the middle mouse button if reason == QSystemTrayIcon.Trigger: # left-click self.setWindowState((self.windowState() & ~Qt.WindowMinimized) | Qt.WindowActive) self.show() return def show_tray_message(self, title, message, icon_type=None, timeout_ms=None): """ Shows message from system tray icon, if system supports it. If no support, this is just a no-op :param title: message title :param message: message text :param icon_type: one of: QSystemTrayIcon.NoIcon 0 No icon is shown. QSystemTrayIcon.Information 1 An information icon is shown. QSystemTrayIcon.Warning 2 A standard warning icon is shown. QSystemTrayIcon.Critical 3 A critical warning icon is shown """ if self.tray_icon is None: return if self.tray_icon.supportsMessages(): if icon_type is None: icon_type = QSystemTrayIcon.Information if timeout_ms is None: timeout_ms = 10000 self.tray_icon.showMessage(title, message, icon_type, timeout_ms) else: logger.info('This system does not support tray icon messages.') @pyqtSlot() def on_show_settings(self): if self.settings_widget is not None: self.settings_widget.show() self.settings_widget.showNormal() @pyqtSlot(XNFlight) def on_flight_arrived(self, fl: XNFlight): logger.debug('main: flight arrival: {0}'.format(fl)) mis_str = flight_mission_for_humans(fl.mission) if fl.direction == 'return': mis_str += ' ' + self.tr('return') short_fleet_info = self.tr('{0} {1} => {2}, {3} ship(s)').format( mis_str, fl.src, fl.dst, len(fl.ships)) self.show_tray_message(self.tr('XNova: Fleet arrived'), short_fleet_info) @pyqtSlot(XNPlanet, XNPlanetBuildingItem) def on_building_complete(self, planet: XNPlanet, bitem: XNPlanetBuildingItem): logger.debug('main: build complete: on planet {0}: {1}'.format( planet.name, str(bitem))) # update also planet tab, if any if isinstance(planet, XNPlanet): tab_idx = self.find_tab_for_planet(planet.planet_id) if tab_idx != -1: tab_widget = self._tabwidget.tabWidget(tab_idx) if isinstance(tab_widget, PlanetWidget): logger.debug('Updating planet tab #{}'.format(tab_idx)) tab_widget.setPlanet(planet) # construct message to show in tray if bitem.is_shipyard_item: binfo_str = '{0} x {1}'.format(bitem.quantity, bitem.name) else: binfo_str = self.tr('{0} lv.{1}').format(bitem.name, bitem.level) msg = self.tr('{0} has built {1}').format(planet.name, binfo_str) self.show_tray_message(self.tr('XNova: Building complete'), msg) @pyqtSlot(XNCoords) def on_request_open_galaxy_tab(self, coords: XNCoords): tab_index = self.find_tab_for_galaxy(coords.galaxy, coords.system) if tab_index == -1: # create new tab for these coords self.add_tab_for_galaxy(coords) return # else switch to that tab self._tabwidget.setCurrentIndex(tab_index) @pyqtSlot(int) def on_request_open_planet_tab(self, planet_id: int): tab_index = self.find_tab_for_planet(planet_id) if tab_index == -1: # create new tab for planet planet = self.world.get_planet(planet_id) if planet is not None: self.add_tab_for_planet(planet) return # else switch to that tab self._tabwidget.setCurrentIndex(tab_index) def find_tab_for_planet(self, planet_id: int) -> int: """ Finds tab index where specified planet is already opened :param planet_id: planet id to search for :return: tab index, or -1 if not found """ cnt = self._tabwidget.count() if cnt < 3: return -1 # only overview and imperium tabs are present for index in range(2, cnt): tab_page = self._tabwidget.tabWidget(index) if tab_page is not None: try: tab_type = tab_page.get_tab_type() if tab_type == 'planet': tab_planet = tab_page.planet() if tab_planet.planet_id == planet_id: # we have found tab index where this planet is already opened return index except AttributeError: # not all pages may have method get_tab_type() pass return -1 def find_tab_for_galaxy(self, galaxy: int, system: int) -> int: """ Finds tab index where specified galaxy view is already opened :param galaxy: galaxy target coordinate :param system: system target coordinate :return: tab index, or -1 if not found """ cnt = self._tabwidget.count() if cnt < 3: return -1 # only overview and imperium tabs are present for index in range(2, cnt): tab_page = self._tabwidget.tabWidget(index) if tab_page is not None: try: tab_type = tab_page.get_tab_type() if tab_type == 'galaxy': coords = tab_page.coords() if (coords[0] == galaxy) and (coords[1] == system): # we have found galaxy tab index where this place is already opened return index except AttributeError: # not all pages may have method get_tab_type() pass return -1 def test_setup_planets_panel(self): """ Testing only - add 'fictive' planets to test planets panel without loading data :return: None """ pl1 = XNPlanet('Arnon', XNCoords(1, 7, 6)) pl1.pic_url = 'skins/default/planeten/small/s_normaltempplanet08.jpg' pl1.fields_busy = 90 pl1.fields_total = 167 pl1.is_current = True pl2 = XNPlanet('Safizon', XNCoords(1, 232, 7)) pl2.pic_url = 'skins/default/planeten/small/s_dschjungelplanet05.jpg' pl2.fields_busy = 84 pl2.fields_total = 207 pl2.is_current = False test_planets = [pl1, pl2] self.setup_planets_panel(test_planets) def test_planet_tab(self): """ Testing only - add 'fictive' planet tab to test UI without loading world :return: """ # construct planet pl1 = XNPlanet('Arnon', coords=XNCoords(1, 7, 6), planet_id=12345) pl1.pic_url = 'skins/default/planeten/small/s_normaltempplanet08.jpg' pl1.fields_busy = 90 pl1.fields_total = 167 pl1.is_current = True pl1.res_current.met = 10000000 pl1.res_current.cry = 50000 pl1.res_current.deit = 250000000 # 250 mil pl1.res_per_hour.met = 60000 pl1.res_per_hour.cry = 30000 pl1.res_per_hour.deit = 15000 pl1.res_max_silos.met = 6000000 pl1.res_max_silos.cry = 3000000 pl1.res_max_silos.deit = 1000000 pl1.energy.energy_left = 10 pl1.energy.energy_total = 1962 pl1.energy.charge_percent = 92 # planet building item bitem = XNPlanetBuildingItem() bitem.gid = 1 bitem.name = 'Рудник металла' bitem.level = 29 bitem.remove_link = '' bitem.build_link = '?set=buildings&cmd=insert&building={0}'.format(bitem.gid) bitem.seconds_total = 23746 bitem.cost_met = 7670042 bitem.cost_cry = 1917510 bitem.is_building_item = True # second bitem bitem2 = XNPlanetBuildingItem() bitem2.gid = 2 bitem2.name = 'Рудник кристалла' bitem2.level = 26 bitem2.remove_link = '' bitem2.build_link = '?set=buildings&cmd=insert&building={0}'.format(bitem2.gid) bitem2.seconds_total = 13746 bitem2.cost_met = 9735556 bitem2.cost_cry = 4667778 bitem2.is_building_item = True bitem2.is_downgrade = True bitem2.seconds_left = bitem2.seconds_total // 2 bitem2.calc_end_time() # add bitems pl1.buildings_items = [bitem, bitem2] # add self.add_tab_for_planet(pl1)
def __init__(self, compare_frame_controller: CompareFrameController, generator_tab_controller: GeneratorTabController, project_manager: ProjectManager, parent): super().__init__(parent) self.project_manager = project_manager self.compare_frame_controller = compare_frame_controller self.generator_tab_controller = generator_tab_controller self.proto_analyzer = compare_frame_controller.proto_analyzer self.simulator_config = SimulatorConfiguration(self.project_manager) self.sim_expression_parser = SimulatorExpressionParser(self.simulator_config) SimulatorItem.simulator_config = self.simulator_config SimulatorItem.expression_parser = self.sim_expression_parser self.ui = Ui_SimulatorTab() self.ui.setupUi(self) util.set_splitter_stylesheet(self.ui.splitter) util.set_splitter_stylesheet(self.ui.splitterLeftRight) self.ui.splitter.setSizes([self.width() / 0.7, self.width() / 0.3]) self.ui.treeProtocols.setHeaderHidden(True) self.tree_model = self.generator_tab_controller.tree_model self.ui.treeProtocols.setModel(self.tree_model) self.participant_table_model = ParticipantTableModel(project_manager.participants) self.ui.tableViewParticipants.setModel(self.participant_table_model) self.participant_table_model.update() self.simulator_message_field_model = SimulatorMessageFieldModel(self) self.ui.tblViewFieldValues.setModel(self.simulator_message_field_model) self.ui.tblViewFieldValues.setItemDelegateForColumn(1, ComboBoxDelegate(ProtocolLabel.DISPLAY_FORMATS, parent=self.ui.tblViewFieldValues)) self.ui.tblViewFieldValues.setItemDelegateForColumn(2, ComboBoxDelegate(SimulatorProtocolLabel.VALUE_TYPES, parent=self.ui.tblViewFieldValues)) self.ui.tblViewFieldValues.setItemDelegateForColumn(3, ProtocolValueDelegate(controller=self, parent=self.ui.tblViewFieldValues)) self.project_manager.reload_field_types() self.update_field_name_column() self.simulator_message_table_model = SimulatorMessageTableModel(self.project_manager, self) self.ui.tblViewMessage.setModel(self.simulator_message_table_model) self.ui.ruleCondLineEdit.setValidator(RuleExpressionValidator(self.sim_expression_parser, is_formula=False)) self.completer_model = QStringListModel([]) self.ui.ruleCondLineEdit.setCompleter(QCompleter(self.completer_model, self.ui.ruleCondLineEdit)) self.ui.ruleCondLineEdit.setToolTip(self.sim_expression_parser.rule_condition_help) self.simulator_scene = SimulatorScene(mode=0, simulator_config=self.simulator_config) self.simulator_scene.tree_root_item = compare_frame_controller.proto_tree_model.rootItem self.ui.gvSimulator.setScene(self.simulator_scene) self.ui.gvSimulator.setAlignment(Qt.AlignLeft | Qt.AlignTop) self.ui.gvSimulator.proto_analyzer = compare_frame_controller.proto_analyzer self.__active_item = None self.ui.listViewSimulate.setModel(SimulatorParticipantListModel(self.simulator_config)) self.ui.spinBoxNRepeat.setValue(self.project_manager.simulator_num_repeat) self.ui.spinBoxTimeout.setValue(self.project_manager.simulator_timeout_ms) self.ui.spinBoxRetries.setValue(self.project_manager.simulator_retries) self.ui.comboBoxError.setCurrentIndex(self.project_manager.simulator_error_handling_index) # place save/load button at corner of tab widget frame = QFrame(parent=self) frame.setLayout(QHBoxLayout()) frame.setFrameStyle(frame.NoFrame) self.ui.btnSave = QToolButton(self.ui.tab) self.ui.btnSave.setIcon(QIcon.fromTheme("document-save")) frame.layout().addWidget(self.ui.btnSave) self.ui.btnLoad = QToolButton(self.ui.tab) self.ui.btnLoad.setIcon(QIcon.fromTheme("document-open")) frame.layout().addWidget(self.ui.btnLoad) frame.layout().setContentsMargins(0, 0, 0, 0) self.ui.tabWidget.setCornerWidget(frame) self.ui.splitterLeftRight.setSizes([0.2 * self.width(), 0.8 * self.width()]) self.create_connects()
def __init__(self, compare_frame_controller: CompareFrameController, generator_tab_controller: GeneratorTabController, project_manager: ProjectManager, parent): super().__init__(parent) self.project_manager = project_manager self.compare_frame_controller = compare_frame_controller self.generator_tab_controller = generator_tab_controller self.proto_analyzer = compare_frame_controller.proto_analyzer self.simulator_config = SimulatorConfiguration(self.project_manager) self.sim_expression_parser = SimulatorExpressionParser( self.simulator_config) SimulatorItem.simulator_config = self.simulator_config SimulatorItem.expression_parser = self.sim_expression_parser self.ui = Ui_SimulatorTab() self.ui.setupUi(self) util.set_splitter_stylesheet(self.ui.splitter) util.set_splitter_stylesheet(self.ui.splitterLeftRight) self.ui.splitter.setSizes([self.width() / 0.7, self.width() / 0.3]) self.ui.treeProtocols.setHeaderHidden(True) self.tree_model = self.generator_tab_controller.tree_model self.ui.treeProtocols.setModel(self.tree_model) self.participant_table_model = ParticipantTableModel( project_manager.participants) self.ui.tableViewParticipants.setModel(self.participant_table_model) self.participant_table_model.update() self.simulator_message_field_model = SimulatorMessageFieldModel(self) self.ui.tblViewFieldValues.setModel(self.simulator_message_field_model) self.ui.tblViewFieldValues.setItemDelegateForColumn( 1, ComboBoxDelegate(ProtocolLabel.DISPLAY_FORMATS, parent=self.ui.tblViewFieldValues)) self.ui.tblViewFieldValues.setItemDelegateForColumn( 2, ComboBoxDelegate(SimulatorProtocolLabel.VALUE_TYPES, parent=self.ui.tblViewFieldValues)) self.ui.tblViewFieldValues.setItemDelegateForColumn( 3, ProtocolValueDelegate(controller=self, parent=self.ui.tblViewFieldValues)) self.project_manager.reload_field_types() self.update_field_name_column() self.simulator_message_table_model = SimulatorMessageTableModel( self.project_manager, self) self.ui.tblViewMessage.setModel(self.simulator_message_table_model) self.ui.ruleCondLineEdit.setValidator( RuleExpressionValidator(self.sim_expression_parser, is_formula=False)) self.completer_model = QStringListModel([]) self.ui.ruleCondLineEdit.setCompleter( QCompleter(self.completer_model, self.ui.ruleCondLineEdit)) self.ui.ruleCondLineEdit.setToolTip( self.sim_expression_parser.rule_condition_help) self.simulator_scene = SimulatorScene( mode=0, simulator_config=self.simulator_config) self.simulator_scene.tree_root_item = compare_frame_controller.proto_tree_model.rootItem self.ui.gvSimulator.setScene(self.simulator_scene) self.ui.gvSimulator.setAlignment(Qt.AlignLeft | Qt.AlignTop) self.ui.gvSimulator.proto_analyzer = compare_frame_controller.proto_analyzer self.__active_item = None self.ui.listViewSimulate.setModel( SimulatorParticipantListModel(self.simulator_config)) self.ui.spinBoxNRepeat.setValue( self.project_manager.simulator_num_repeat) self.ui.spinBoxTimeout.setValue( self.project_manager.simulator_timeout_ms) self.ui.spinBoxRetries.setValue(self.project_manager.simulator_retries) self.ui.comboBoxError.setCurrentIndex( self.project_manager.simulator_error_handling_index) # place save/load button at corner of tab widget frame = QFrame(parent=self) frame.setLayout(QHBoxLayout()) frame.setFrameStyle(frame.NoFrame) self.ui.btnSave = QToolButton(self.ui.tab) self.ui.btnSave.setIcon(QIcon.fromTheme("document-save")) frame.layout().addWidget(self.ui.btnSave) self.ui.btnLoad = QToolButton(self.ui.tab) self.ui.btnLoad.setIcon(QIcon.fromTheme("document-open")) frame.layout().addWidget(self.ui.btnLoad) frame.layout().setContentsMargins(0, 0, 0, 0) self.ui.tabWidget.setCornerWidget(frame) self.ui.splitterLeftRight.setSizes( [0.2 * self.width(), 0.8 * self.width()]) self.create_connects()
class QCollapsibleFrame(QWidget): def __init__(self, title, parent): super().__init__(parent) self.widget = None self.headerWidget = None self.contentsFrame = None self.mainLayout = QVBoxLayout() self.mainLayout.setSpacing(0) self.mainLayout.setContentsMargins(2, 2, 2, 2) self.setLayout(self.mainLayout) self.SetHeaderWidget(CCollapsibleFrameHeader(title, self)) def getWidget(self): return self.widget def getDragHandler(self): return self.headerWidget.collapseButton def setWidget(self, widget): if not self.contentsFrame: self.contentsFrame = QFrame(self) frameLayout = QVBoxLayout() frameLayout.setSpacing(0) frameLayout.setContentsMargins(2, 2, 2, 2) self.contentsFrame.setLayout(frameLayout) self.layout().addWidget(self.contentsFrame) mainLayout = self.contentsFrame.layout() if self.widget: mainLayout.removeWidget(self.widget) self.widget.deleteLater() self.widget = widget if self.widget: mainLayout.addWidget(self.widget) self.contentsFrame.setHidden(self.headerWidget.bCollapsed) def setClosable(self, closable): self.headerWidget.setClosable(closable) def closable(self): return self.headerWidget.closable() def setTitle(self, title): self.headerWidget.setTitle(title) def collapsed(self): return self.headerWidget.bCollapsed def setCollapsed(self, bCollapsed): self.headerWidget.setCollapsed(bCollapsed) def setCollapsedStateChangeCallback(self, callback): self.headerWidget.onCollapsedStateChanged = callback def paintEvent(self, e): opt = QStyleOption() opt.initFrom(self) painter = QPainter(self) self.style().drawPrimitive(QStyle.PE_Widget, opt, painter, self) def setHearderWidget(self, header): self.headerWidget = header self.layout().addWidget(header) self.headerWidget.closeButton.clicked.connect(self.onCloseRequested) def onCloseRequested(self): self.closeRequested(self) def closeRequested(self, caller): pass