class IPFSHashExplorerStack(GalacteekTab): """ Organizes IPFSHashExplorerWidgets with a QStackedWidget """ def __init__(self, gWindow, hashRef, maxItems=16, parent=None): super(IPFSHashExplorerStack, self).__init__(gWindow) self.rootHash = hashRef self.maxItems = maxItems self.stack = QStackedWidget(self) self.exLayout = QVBoxLayout() self.exLayout.addWidget(self.stack) self.vLayout.addLayout(self.exLayout) if self.rootHash: self.viewHash(self.rootHash) def tabDestroyedPost(self): self.stack.setParent(None) self.stack.deleteLater() @property def itemsCount(self): return self.stack.count() def viewHash(self, hashRef, addClose=False, autoOpenFolders=False, parentView=None): view = IPFSHashExplorerWidget(hashRef, parent=self, addClose=addClose, showCidLabel=True, autoOpenFolders=autoOpenFolders, parentView=parentView) view.closeRequest.connect(partialEnsure(self.remove, view)) view.directoryOpenRequest.connect(lambda nView, cid: self.viewHash( cid, addClose=True, parentView=nView)) self.stack.insertWidget(self.stack.count(), view) self.stack.setCurrentWidget(view) view.reFocus() return True async def remove(self, view): try: view.cancelTasks() self.stack.removeWidget(view) except: pass async def onClose(self): for idx in range(self.stack.count()): widget = self.stack.widget(idx) await widget.cleanup() return True
class PyMultiPageWidget(QWidget): def __init__(self, parent=None): super(PyMultiPageWidget, self).__init__(parent) self.value = 0 self.bt = QPushButton('switch', self) self.bt.clicked.connect(self.switch) self.stackWidget = QStackedWidget() self.layout = QVBoxLayout() self.layout.addWidget(self.bt) self.layout.addWidget(self.stackWidget) self.setLayout(self.layout) self.resize(500, 400) def switch(self): self.value = 1 - self.value bt = QPushButton('new') if self.value: self.addPage(bt) else: self.removePage(0) @pyqtSlot(QWidget) def addPage(self, page): index = self.stackWidget.count() page.setParent(self.stackWidget) self.stackWidget.insertWidget(index, page) @pyqtSlot(int) def removePage(self, index): widget = self.stackWidget.widget(index) self.stackWidget.removeWidget(widget)
class DynaFrame(QFrame): """ The DynaFrame class wraps one custom widget which can be replaced with another during runtime. """ def __init__(self, parent, widget: QWidget=None): super(__class__, self).__init__(parent) layout = QHBoxLayout() self._stk_widget = QStackedWidget() if widget is not None: self.setWidget(widget) layout.addWidget(self._stk_widget) self.setLayout(layout) def setWidget(self, widget: QWidget): """ Replaces the currently present widget (if any) with the specified one. """ self._clear_stack_widget() self._stk_widget.addWidget(widget) def getWidget(self) -> QWidget: """ Returns the widget wrapped in the frame. """ return self._stk_widget.widget(0) def _clear_stack_widget(self): """ Clears all widgets which were added to the stack. """ count = self._stk_widget.count() assert 0 <= count <= 1 for index in range(0, count): widget = self._stk_widget.widget(index) self._stk_widget.removeWidget(widget)
class IPFSHashExplorerStack(GalacteekTab): """ Organizes IPFSHashExplorerWidgets with a QStackedWidget """ def __init__(self, gWindow, hashRef, maxItems=16, parent=None): super(IPFSHashExplorerStack, self).__init__(gWindow) self.rootHash = hashRef self.maxItems = maxItems self.stack = QStackedWidget(self) self.exLayout = QVBoxLayout() self.exLayout.addWidget(self.stack) self.vLayout.addLayout(self.exLayout) if self.rootHash: self.viewHash(self.rootHash) @property def itemsCount(self): return self.stack.count() def viewHash(self, hashRef, addClose=False, autoOpenFolders=False): view = IPFSHashExplorerWidget(hashRef, parent=self, addClose=addClose, showCidLabel=True, autoOpenFolders=autoOpenFolders) view.closeRequest.connect(functools.partial(self.remove, view)) view.directoryOpenRequest.connect( lambda cid: self.viewHash(cid, addClose=True)) self.stack.insertWidget(self.stack.count(), view) self.stack.setCurrentWidget(view) view.reFocus() return True def remove(self, view): try: self.stack.removeWidget(view) except: pass
class StackedPage(QWidget): def __init__(self, parent=None): super(StackedPage, self).__init__(parent) layout = QHBoxLayout(self) leftpane = QVBoxLayout() self.buttongroup = QButtonGroup(self) self.buttongroup.setExclusive(True) self.groupbox = QGroupBox(self) self.groupbox.setMinimumWidth(200) QVBoxLayout(self.groupbox) leftpane.addWidget(self.groupbox) leftpane.addStretch(1) layout.addLayout(leftpane) self.rightpane = QStackedWidget(self) layout.addWidget(self.rightpane) self.buttongroup.buttonClicked[int].connect( self.rightpane.setCurrentIndex) self.rightpane.currentChanged[int].connect(self.activate) def addPage(self, buttontext, widget): button = QPushButton(buttontext) button.setCheckable(True) button.setChecked(self.rightpane.count() == 0) self.buttongroup.addButton(button, self.rightpane.count()) self.groupbox.layout().addWidget(button) self.rightpane.addWidget(widget) def pages(self): return [ self.rightpane.widget(i) for i in range(self.rightpane.count()) ] def activate(self, ix): page = self.rightpane.currentWidget() if hasattr(page, "activate") and callable(page.activate): page.activate() def showEvent(self, *args, **kwargs): self.activate(0) return QWidget.showEvent(self, *args, **kwargs)
class MainView(QMainWindow): def __init__(self): super(MainView, self).__init__() self.setWindowTitle('Jira Helper') self.window = QStackedWidget() # Create the main widget for the page main_window_widget = QWidget() main_window_layout = QGridLayout() main_window_widget.setLayout(main_window_layout) menu_widget = QWidget() menu_layout = QHBoxLayout() menu_widget.setLayout(menu_layout) main_window_layout.addWidget(menu_widget, 0, 0) main_window_layout.addWidget(self.window, 1, 0) self.setCentralWidget(main_window_widget) # Create a settings button self.settings_submit_button = QPushButton() self.settings_submit_button.setText("Settings") menu_layout.addWidget(self.settings_submit_button) # Create date time datetime_font = QFont("Times", 30) self.date = QLabel() self.date.setFont(datetime_font) self.time = QLabel() self.time.setFont(datetime_font) menu_layout.addWidget(self.date) menu_layout.addWidget(self.time) # Create a clean queue button self.clean_queue_button = QPushButton() self.clean_queue_button.setText("Clean Queue") self.clean_queue_button.setCheckable(True) menu_layout.addWidget(self.clean_queue_button) def update_datetime(self): '''Connected to a Qtimer to periodically update the date and time''' Qdate = QDate.currentDate() Qtime = QTime.currentTime() self.date.setText(Qdate.toString(Qt.DefaultLocaleLongDate)) self.time.setText(Qtime.toString(Qt.DefaultLocaleLongDate)) def transition_page(self): '''Connected to a Qtimer to periodically transition through the widgets stacked on self.window''' index_Id = self.window.currentIndex() if index_Id < self.window.count() - 1: self.window.setCurrentIndex(index_Id + 1) else: self.window.setCurrentIndex(0)
class Timer(Base): def __init__(self, observer: Observer, name: str, foreground_color="#ffffff", font_name=""): super().__init__(name, foreground_color, font_name) self.main_layout = QGridLayout() self.central_widget = QStackedWidget() self.central_widget.insertWidget(0, TimerOverview(observer, "TimerOverview", self, foreground_color)) self.central_widget.insertWidget(1, AddTimer(observer, "AddTimer", self, foreground_color)) self.central_widget.setCurrentIndex(0) self.main_layout.addWidget(self.central_widget) self.setLayout(self.main_layout) def pass_timer(self, timer_config: dict): self.central_widget.widget(0).add_timer(timer_config) def switch_central_widget(self): max_value = self.central_widget.count() index = self.central_widget.currentIndex() self.central_widget.setCurrentIndex((index + 1) % max_value)
class MainWindow(QMainWindow): def __init__(self, app, dtos): super(MainWindow, self).__init__() self.app = app # pass app just in case need to do some events self.twitter_ids = list(dtos.keys()) dbgp("twitter_ids:{} => {}".format(dtos.keys(), self.twitter_ids)) self.stack_widget = QStackedWidget() for twitter_id in dtos: profile_elem, activities = dtos[twitter_id] self.stack_widget.addWidget(MainWidget(twitter_id, activities)) self.stack_widget.keyPressEvent = self.changePage self.init_ui() def init_ui(self): self.layout = QVBoxLayout() self.layout.addWidget(self.stack_widget) self.setCentralWidget(self.stack_widget) self.setWindowTitle(window_title) def changePage(self, event): if event.key() == ord(next_page_key): next_index = (self.stack_widget.currentIndex() + 1) % self.stack_widget.count() dbgp("Changing to show the content of twitter_id :{}:".format(self.twitter_ids[next_index])) self.stack_widget.setCurrentIndex((next_index))
class TableWidget(QWidget): def __init__(self): super(TableWidget, self).__init__() vbox = QVBoxLayout(self) vbox.setContentsMargins(0, 0, 0, 0) self.relations = {} # Stack self.stacked = QStackedWidget() vbox.addWidget(self.stacked) def count(self): return self.stacked.count() def remove_table(self, index): widget = self.stacked.widget(index) self.stacked.removeWidget(widget) del widget def current_table(self): return self.stacked.currentWidget() def remove_relation(self, name): del self.relations[name] def add_relation(self, name, rela): if self.relations.get(name, None) is None: self.relations[name] = rela return True return False def update_table(self, data): current_table = self.current_table() model = current_table.model() # Clear content model.clear() # Add new header and content model.setHorizontalHeaderLabels(data.header) for row_count, row in enumerate(data.content): for col_count, data in enumerate(row): item = QStandardItem(data) item.setFlags(item.flags() & ~Qt.ItemIsEditable) # item.setSelectable(False) model.setItem(row_count, col_count, item) def add_table(self, rela, name): """ Add new table from New Relation Dialog """ # Create table table = self.create_table(rela) self.add_relation(name, rela) self.stacked.addWidget(table) def create_table(self, rela): table = custom_table.Table() model = QStandardItemModel() table.setModel(model) model.setHorizontalHeaderLabels(rela.header) for row_count, row in enumerate(rela.content): for col_count, data in enumerate(row): item = QStandardItem(data) item.setFlags(item.flags() & ~Qt.ItemIsEditable) model.setItem(row_count, col_count, item) return table
class TabBarWindow(TabWindow): """Implementation which uses a separate QTabBar and QStackedWidget. The Tab bar is placed next to the menu bar to save real estate.""" def __init__(self, app, **kwargs): super().__init__(app, **kwargs) def _setupUi(self): self.setWindowTitle(self.app.NAME) self.resize(640, 480) self.tabBar = QTabBar() self.verticalLayout = QVBoxLayout() self.verticalLayout.setContentsMargins(0, 0, 0, 0) self._setupActions() self._setupMenu() self.centralWidget = QWidget(self) self.setCentralWidget(self.centralWidget) self.stackedWidget = QStackedWidget() self.centralWidget.setLayout(self.verticalLayout) self.horizontalLayout = QHBoxLayout() self.horizontalLayout.addWidget(self.menubar, 0, Qt.AlignTop) self.horizontalLayout.addWidget(self.tabBar, 0, Qt.AlignTop) self.verticalLayout.addLayout(self.horizontalLayout) self.verticalLayout.addWidget(self.stackedWidget) self.tabBar.currentChanged.connect(self.showWidget) self.tabBar.tabCloseRequested.connect(self.onTabCloseRequested) self.stackedWidget.currentChanged.connect(self.updateMenuBar) self.stackedWidget.widgetRemoved.connect(self.onRemovedWidget) self.tabBar.setTabsClosable(True) self.restoreGeometry() def addTab(self, page, title, switch=True): stack_index = self.stackedWidget.insertWidget(-1, page) tab_index = self.tabBar.addTab(title) if isinstance(page, DirectoriesDialog): self.tabBar.setTabButton(tab_index, QTabBar.RightSide, None) if switch: # switch to the added tab immediately upon creation self.setTabIndex(tab_index) self.stackedWidget.setCurrentWidget(page) return stack_index @pyqtSlot(int) def showWidget(self, index): if index >= 0 and index <= self.stackedWidget.count() - 1: self.stackedWidget.setCurrentIndex(index) # if not self.tabBar.isTabVisible(index): self.setTabVisible(index, True) def indexOfWidget(self, widget): # Warning: this may return -1 if widget is not a child of stackedwidget return self.stackedWidget.indexOf(widget) def setCurrentIndex(self, tab_index): # The signal will handle switching the stackwidget's widget self.setTabIndex(tab_index) # self.stackedWidget.setCurrentWidget(self.stackedWidget.widget(tab_index)) @pyqtSlot(int) def setTabIndex(self, index): if index is None: return self.tabBar.setCurrentIndex(index) def setTabVisible(self, index, value): return self.tabBar.setTabVisible(index, value) @pyqtSlot(int) def onRemovedWidget(self, index): self.removeTab(index) @pyqtSlot(int) def removeTab(self, index): # No need to remove the widget here: # self.stackedWidget.removeWidget(self.stackedWidget.widget(index)) return self.tabBar.removeTab(index) @pyqtSlot(int) def removeWidget(self, widget): return self.stackedWidget.removeWidget(widget) def isTabVisible(self, index): return self.tabBar.isTabVisible(index) def getCurrentIndex(self): return self.stackedWidget.currentIndex() def getWidgetAtIndex(self, index): return self.stackedWidget.widget(index) def getCount(self): return self.stackedWidget.count() @pyqtSlot() def toggleTabBar(self): value = self.sender().isChecked() self.actionToggleTabs.setChecked(value) self.tabBar.setVisible(value) @pyqtSlot(int) def onTabCloseRequested(self, index): current_widget = self.getWidgetAtIndex(index) if isinstance(current_widget, DirectoriesDialog): # On MacOS, the tab has a close button even though we explicitely # set it to None in order to hide it. This should prevent # the "Directories" tab from closing by mistake. return current_widget.close() self.stackedWidget.removeWidget(current_widget)
class ExecTA(ElementMaster): pixmap_path = 'images/ExecTA.png' child_pos = (True, False) def __init__(self, row, column): self.row = row self.column = column ta_str = 'MA' ta_index = 0 ta_config = (3, ) log_state = False self.config = (ta_str, ta_index, ta_config, log_state) super().__init__(self.row, self.column, QPixmap(self.pixmap_path), True, self.config) super().edit_sig.connect(self.edit) logging.debug('ExecTA called at row {}, column {}'.format(row, column)) self.addFunction(TAFunction) def __setstate__(self, state): logging.debug('__setstate__() called ExecTA') self.row, self.column, self.config = state super().__init__(self.row, self.column, QPixmap(self.pixmap_path), True, self.config) super().edit_sig.connect(self.edit) self.addFunction(TAFunction) def __getstate__(self): logging.debug('__getstate__() called ExecTA') return (self.row, self.column, self.config) def openEditor(self): logging.debug('openEditor() called ExecTA') def edit(self): logging.debug('edit() called ExecTA') ta_str, ta_index, ta_config, log_state = self.config self.basic_ta_layout = QVBoxLayout() self.confirm_button = QPushButton(QC.translate('', 'Ok')) self.interval_txt = QLabel() self.interval_txt.setText(QC.translate('', 'Choose technical analysis function')) # https://github.com/sammchardy/python-binance/blob/master/binance/client.py self.selectTA = QComboBox() self.selectTA.addItem(QC.translate('', 'Moving Average'), QVariant('MA')) self.selectTA.addItem(QC.translate('', 'Exponential Moving Average'), QVariant('EMA')) self.selectTA.addItem(QC.translate('', 'Stochastic Oscillator %K'), QVariant('STOK')) self.selectTA.addItem(QC.translate('', 'Stochastic Oscillator %D'), QVariant('STO')) self.selectTA.addItem(QC.translate('', 'Relative Strenght Index'), QVariant('RSI')) """ self.selectTA.addItem(QC.translate('', 'Momentum'), QVariant('MOM')) self.selectTA.addItem(QC.translate('', 'Rate of Change'), QVariant('ROC')) self.selectTA.addItem(QC.translate('', 'Average True Range'), QVariant('ATR')) self.selectTA.addItem(QC.translate('', 'Bollinger Bands'), QVariant('BBANDS')) self.selectTA.addItem(QC.translate('', 'Pivot Points, Support and Resitances'), QVariant('PPSR')) self.selectTA.addItem(QC.translate('', 'Trix'), QVariant('TRIX')) self.selectTA.addItem(QC.translate('', 'Average Directional Movement Index'), QVariant('ADX')) self.selectTA.addItem(QC.translate('', 'MACD, MACD Signal and MACD diffrence'), QVariant('MACD')) self.selectTA.addItem(QC.translate('', 'Mass Index'), QVariant('MI')) self.selectTA.addItem(QC.translate('', 'Vortex Indikator'), QVariant('VORTEX')) self.selectTA.addItem(QC.translate('', 'KST Oscillator'), QVariant('KST')) self.selectTA.addItem(QC.translate('', 'True Strenght Index'), QVariant('TSI')) self.selectTA.addItem(QC.translate('', 'Accumulation/Distribution'), QVariant('ACCDIST')) self.selectTA.addItem(QC.translate('', 'Chaikin Oscillator'), QVariant('CHAI')) self.selectTA.addItem(QC.translate('', 'Money Flow Index and Ratio'), QVariant('MFI')) self.selectTA.addItem(QC.translate('', 'On Balance Volume'), QVariant('OBV')) self.selectTA.addItem(QC.translate('', 'Force Index'), QVariant('FI')) self.selectTA.addItem(QC.translate('', 'Ease of Movement'), QVariant('EOM')) self.selectTA.addItem(QC.translate('', 'Commodity Channel Index'), QVariant('CCI')) """ self.selectTA.setCurrentIndex(ta_index) self.variable_box = QStackedWidget() self.maInput() self.emaInput() self.stokInput() self.stoInput() self.rsiInput() self.loadLastConfig() logging.debug('edit() - {} elements in QStackedWidget'.format(self.variable_box.count())) self.link_line = QWidget() self.link_line_layout = QHBoxLayout(self.link_line) self.link_txt = QLabel() self.link_txt.setText(QC.translate('', 'Find information about technical analysis on')) self.link = QLabel() self.link.setText('<a href="https://www.investopedia.com/walkthrough/forex/">Investopedia</a>') self.link.setTextFormat(Qt.RichText) self.link.setTextInteractionFlags(Qt.TextBrowserInteraction) self.link.setOpenExternalLinks(True) self.link_line_layout.addWidget(self.link_txt) self.link_line_layout.addWidget(self.link) self.link_line_layout.addStretch(1) # hier logging option einfügen self.log_line = QWidget() self.ask_for_logging = QLabel() self.ask_for_logging.setText(QC.translate('', 'Log output?')) self.log_checkbox = QCheckBox() self.log_line_layout = QHBoxLayout(self.log_line) self.log_line_layout.addWidget(self.ask_for_logging) self.log_line_layout.addWidget(self.log_checkbox) self.log_line_layout.addStretch(1) if log_state: self.log_checkbox.setChecked(True) self.basic_ta_edit = ElementEditor(self) self.basic_ta_edit.setWindowTitle(QC.translate('', 'Edit TA function')) # signals and slots self.confirm_button.clicked.connect(self.basic_ta_edit.closeEvent) self.basic_ta_edit.window_closed.connect(self.edit_done) self.selectTA.currentIndexChanged.connect(self.indexChanged) self.basic_ta_layout.addWidget(self.interval_txt) self.basic_ta_layout.addWidget(self.selectTA) self.basic_ta_layout.addWidget(self.variable_box) self.basic_ta_layout.addStretch(1) self.basic_ta_layout.addWidget(self.log_line) self.basic_ta_layout.addWidget(self.link_line) self.basic_ta_layout.addWidget(self.confirm_button) self.basic_ta_edit.setLayout(self.basic_ta_layout) self.basic_ta_edit.show() def loadLastConfig(self): ta_str, ta_index, ta_config, log_state = self.config logging.debug('loadLastConfig() called with ta_str = {}'.format(ta_str)) self.variable_box.setCurrentIndex(ta_index) if ta_str == 'MA': self.ma_range_input.setText(str(ta_config[0])) elif ta_str == 'EMA': self.ema_range_input.setText(str(ta_config[0])) elif ta_str == 'STO': self.sto_range_input.setText(str(ta_config[0])) elif ta_str == 'RSI': self.rsi_range_input.setText(str(ta_config[0])) def maInput(self): self.ma_input = QWidget() self.ma_layout = QHBoxLayout(self.ma_input) self.ma_range_txt = QLabel() self.ma_range_txt.setText(QC.translate('', 'Enter time range MA')) self.ma_range_input = QLineEdit() self.ma_range_input.setValidator(QIntValidator(1, 999)) self.ma_range_input.setPlaceholderText(QC.translate('', 'Default value: 3')) self.ma_layout.addWidget(self.ma_range_txt) self.ma_layout.addWidget(self.ma_range_input) self.variable_box.addWidget(self.ma_input) def emaInput(self): self.ema_input = QWidget() self.ema_layout = QHBoxLayout(self.ema_input) self.ema_range_txt = QLabel() self.ema_range_txt.setText(QC.translate('', 'Enter time range EMA')) self.ema_range_input = QLineEdit() self.ema_range_input.setValidator(QIntValidator(1, 999)) self.ema_range_input.setPlaceholderText(QC.translate('', 'Default value: 3')) self.ema_layout.addWidget(self.ema_range_txt) self.ema_layout.addWidget(self.ema_range_input) self.variable_box.addWidget(self.ema_input) def stokInput(self): self.stok_input = QWidget() self.stok_layout = QHBoxLayout(self.stok_input) self.variable_box.addWidget(self.stok_input) def stoInput(self): self.sto_input = QWidget() self.sto_layout = QHBoxLayout(self.sto_input) self.sto_range_txt = QLabel() self.sto_range_txt.setText(QC.translate('', 'Enter MA period')) self.sto_range_input = QLineEdit() self.sto_range_input.setValidator(QIntValidator(1, 999)) self.sto_range_input.setPlaceholderText(QC.translate('', 'Default value: 3')) self.sto_layout.addWidget(self.sto_range_txt) self.sto_layout.addWidget(self.sto_range_input) self.variable_box.addWidget(self.sto_input) def rsiInput(self): self.rsi_input = QWidget() self.rsi_layout = QHBoxLayout(self.rsi_input) self.rsi_range_txt = QLabel() self.rsi_range_txt.setText(QC.translate('', 'Enter periods')) self.rsi_range_input = QLineEdit() self.rsi_range_input.setValidator(QIntValidator(1, 999)) self.rsi_range_input.setPlaceholderText(QC.translate('', 'Default value: 3')) self.rsi_layout.addWidget(self.rsi_range_txt) self.rsi_layout.addWidget(self.rsi_range_input) self.variable_box.addWidget(self.rsi_input) def indexChanged(self, event): current_index = event logging.debug('indexChanged() called {}'.format(current_index)) self.variable_box.setCurrentIndex(current_index) if current_index == 0: logging.debug('Moving Average selected - {}'.format(self.selectTA.currentData())) elif current_index == 1: logging.debug('Exponential Moving Average selected') def edit_done(self): logging.debug('edit_done() called ExecTA') if self.selectTA.currentData() == 'MA': period = self.ma_range_input.text() if period == '': ta_config = (3, ) else: ta_config = (int(period), ) logging.debug('edit_done() - Moving Average selected - {}'.format(ta_config)) elif self.selectTA.currentData() == 'EMA': period = self.ema_range_input.text() if period == '': ta_config = (3, ) else: ta_config = (int(period), ) logging.debug('edit_done() - Exponential Moving Average selected - {}'.format(ta_config)) elif self.selectTA.currentData() == 'STO': period = self.sto_range_input.text() if period == '': ta_config = (3, ) else: ta_config = (int(period), ) logging.debug('edit_done() - Stochastic Oscillator %D or EMA or RSI selected - {}'.format(ta_config)) elif self.selectTA.currentData() == 'RSI': period = self.rsi_range_input.text() if period == '': ta_config = (3, ) else: ta_config = (int(period), ) logging.debug('edit_done() - Relative Strenght Index selected - {}'.format(ta_config)) else: ta_config = None ta_str = self.selectTA.currentData() ta_index = self.selectTA.currentIndex() log_state = self.log_checkbox.isChecked() self.config = (ta_str, ta_index, ta_config, log_state) self.addFunction(TAFunction)
class FontWindow(BaseWindow): def __init__(self, font, parent=None): super().__init__(parent) self._font = None self._infoWindow = None self._featuresWindow = None self._groupsWindow = None self._kerningWindow = None self._metricsWindow = None self.toolBar = ToolBar(self) self.toolBar.setTools(t() for t in QApplication.instance().drawingTools()) self.glyphCellView = GlyphCellView(self) self.glyphCellView.glyphActivated.connect(self.openGlyphTab) self.glyphCellView.glyphsDropped.connect(self._orderChanged) self.glyphCellView.selectionChanged.connect(self._selectionChanged) self.glyphCellView.setAcceptDrops(True) self.glyphCellView.setCellRepresentationName("TruFont.GlyphCell") self.glyphCellView.setFrameShape(self.glyphCellView.NoFrame) self.glyphCellView.setFocus() self.tabWidget = TabWidget(self) self.tabWidget.setAutoHide(True) self.tabWidget.setHeroFirstTab(True) self.tabWidget.addTab(self.tr("Font")) self.stackWidget = QStackedWidget(self) self.stackWidget.addWidget(self.glyphCellView) self.tabWidget.currentTabChanged.connect(self._tabChanged) self.tabWidget.tabRemoved.connect( lambda index: self.stackWidget.removeWidget(self.stackWidget.widget(index)) ) self.stackWidget.currentChanged.connect(self._widgetChanged) self.propertiesView = PropertiesView(font, self) self.propertiesView.hide() self.statusBar = StatusBar(self) self.statusBar.setMinimumSize(32) self.statusBar.setMaximumSize(128) self.statusBar.sizeChanged.connect(self._sizeChanged) self.setFont_(font) app = QApplication.instance() app.dispatcher.addObserver( self, "_drawingToolRegistered", "drawingToolRegistered" ) app.dispatcher.addObserver( self, "_drawingToolUnregistered", "drawingToolUnregistered" ) app.dispatcher.addObserver( self, "_glyphViewGlyphsChanged", "glyphViewGlyphsChanged" ) layout = QHBoxLayout(self) layout.addWidget(self.toolBar) vLayout = QVBoxLayout() vLayout.addWidget(self.tabWidget) pageWidget = PageWidget() pageWidget.addWidget(self.stackWidget) pageWidget.addWidget(self.statusBar) vLayout.addWidget(pageWidget) layout.addLayout(vLayout) layout.addWidget(self.propertiesView) layout.setContentsMargins(0, 2, 0, 0) layout.setSpacing(2) elements = [ ("Ctrl+D", self.deselect), (platformSpecific.closeKeySequence(), self.closeGlyphTab), # XXX: does this really not warrant widget focus? (QKeySequence.Delete, self.delete), ("Shift+" + QKeySequence(QKeySequence.Delete).toString(), self.delete), ("Z", lambda: self.zoom(1)), ("X", lambda: self.zoom(-1)), ] e = platformSpecific.altDeleteSequence() if e is not None: elements.append((e, self.delete)) e = platformSpecific.altRedoSequence() if e is not None: elements.append((e, self.redo)) for keys, callback in elements: shortcut = QShortcut(QKeySequence(keys), self) shortcut.activated.connect(callback) self.installEventFilter(PreviewEventFilter(self)) self.readSettings() self.propertiesView.activeLayerModified.connect(self._activeLayerModified) self.statusBar.sizeChanged.connect(self.writeSettings) def readSettings(self): geometry = settings.fontWindowGeometry() if geometry: self.restoreGeometry(geometry) cellSize = settings.glyphCellSize() self.statusBar.setSize(cellSize) hidden = settings.propertiesHidden() if not hidden: self.properties() def writeSettings(self): settings.setFontWindowGeometry(self.saveGeometry()) settings.setGlyphCellSize(self.glyphCellView.cellSize()[0]) settings.setPropertiesHidden(self.propertiesView.isHidden()) def menuBar(self): return self.layout().menuBar() def setMenuBar(self, menuBar): self.layout().setMenuBar(menuBar) def setupMenu(self, menuBar): app = QApplication.instance() fileMenu = menuBar.fetchMenu(Entries.File) fileMenu.fetchAction(Entries.File_New) fileMenu.fetchAction(Entries.File_Open) fileMenu.fetchMenu(Entries.File_Open_Recent) if not platformSpecific.mergeOpenAndImport(): fileMenu.fetchAction(Entries.File_Import) fileMenu.addSeparator() fileMenu.fetchAction(Entries.File_Save, self.saveFile) fileMenu.fetchAction(Entries.File_Save_As, self.saveFileAs) fileMenu.fetchAction(Entries.File_Save_All) fileMenu.fetchAction(Entries.File_Reload, self.reloadFile) fileMenu.addSeparator() fileMenu.fetchAction(Entries.File_Export, self.exportFile) fileMenu.fetchAction(Entries.File_Exit) editMenu = menuBar.fetchMenu(Entries.Edit) self._undoAction = editMenu.fetchAction(Entries.Edit_Undo, self.undo) self._redoAction = editMenu.fetchAction(Entries.Edit_Redo, self.redo) editMenu.addSeparator() cut = editMenu.fetchAction(Entries.Edit_Cut, self.cut) copy = editMenu.fetchAction(Entries.Edit_Copy, self.copy) copyComponent = editMenu.fetchAction( Entries.Edit_Copy_As_Component, self.copyAsComponent ) paste = editMenu.fetchAction(Entries.Edit_Paste, self.paste) self._clipboardActions = (cut, copy, copyComponent, paste) editMenu.fetchAction(Entries.Edit_Select_All, self.selectAll) # editMenu.fetchAction(Entries.Edit_Deselect, self.deselect) editMenu.fetchAction(Entries.Edit_Find, self.findGlyph) editMenu.addSeparator() editMenu.fetchAction(Entries.Edit_Settings) viewMenu = menuBar.fetchMenu(Entries.View) viewMenu.fetchAction(Entries.View_Zoom_In, lambda: self.zoom(1)) viewMenu.fetchAction(Entries.View_Zoom_Out, lambda: self.zoom(-1)) viewMenu.fetchAction(Entries.View_Reset_Zoom, self.resetZoom) viewMenu.addSeparator() viewMenu.fetchAction(Entries.View_Next_Tab, lambda: self.tabOffset(1)) viewMenu.fetchAction(Entries.View_Previous_Tab, lambda: self.tabOffset(-1)) viewMenu.fetchAction(Entries.View_Next_Glyph, lambda: self.glyphOffset(1)) viewMenu.fetchAction(Entries.View_Previous_Glyph, lambda: self.glyphOffset(-1)) viewMenu.fetchAction(Entries.View_Layer_Up, lambda: self.layerOffset(-1)) viewMenu.fetchAction(Entries.View_Layer_Down, lambda: self.layerOffset(1)) viewMenu.addSeparator() viewMenu.fetchAction(Entries.View_Show_Points) viewMenu.fetchAction(Entries.View_Show_Metrics) viewMenu.fetchAction(Entries.View_Show_Images) viewMenu.fetchAction(Entries.View_Show_Guidelines) fontMenu = menuBar.fetchMenu(Entries.Font) fontMenu.fetchAction(Entries.Font_Font_Info, self.fontInfo) fontMenu.fetchAction(Entries.Font_Font_Features, self.fontFeatures) fontMenu.addSeparator() fontMenu.fetchAction(Entries.Font_Add_Glyphs, self.addGlyphs) fontMenu.fetchAction(Entries.Font_Sort, self.sortGlyphs) # glyphMenu = menuBar.fetchMenu(self.tr("&Glyph")) # self._layerAction = glyphMenu.fetchAction( # self.tr("&Layer Actions…"), self.layerActions, "L") menuBar.fetchMenu(Entries.Scripts) windowMenu = menuBar.fetchMenu(Entries.Window) windowMenu.fetchAction(Entries.Window_Groups, self.groups) windowMenu.fetchAction(Entries.Window_Kerning, self.kerning) windowMenu.fetchAction(Entries.Window_Metrics, self.metrics) windowMenu.fetchAction(Entries.Window_Scripting) windowMenu.fetchAction(Entries.Window_Properties, self.properties) windowMenu.addSeparator() action = windowMenu.fetchAction(Entries.Window_Output) action.setEnabled(app.outputWindow is not None) helpMenu = menuBar.fetchMenu(Entries.Help) helpMenu.fetchAction(Entries.Help_Documentation) helpMenu.fetchAction(Entries.Help_Report_An_Issue) helpMenu.addSeparator() helpMenu.fetchAction(Entries.Help_About) self._updateGlyphActions() # -------------- # Custom methods # -------------- def font_(self): return self._font def setFont_(self, font): if self._font is not None: self._font.removeObserver(self, "Font.Changed") self._font.removeObserver(self, "Font.GlyphOrderChanged") self._font.removeObserver(self, "Font.SortDescriptorChanged") self._font = font self.setWindowTitle(self.fontTitle()) if font is None: return self._updateGlyphsFromGlyphOrder() font.addObserver(self, "_fontChanged", "Font.Changed") font.addObserver(self, "_glyphOrderChanged", "Font.GlyphOrderChanged") font.addObserver(self, "_sortDescriptorChanged", "Font.SortDescriptorChanged") def fontTitle(self): if self._font is None: return None path = self._font.path or self._font.binaryPath if path is not None: return os.path.basename(path.rstrip(os.sep)) return self.tr("Untitled") def isGlyphTab(self): return bool(self.stackWidget.currentIndex()) def openGlyphTab(self, glyph): # if a tab with this glyph exists already, switch to it for index in range(self.stackWidget.count()): if not index: continue view = self.stackWidget.widget(index) if list(view.glyphs()) == [glyph]: self.tabWidget.setCurrentTab(index) return # spawn widget = GlyphCanvasView(self) widget.setInputNames([glyph.name]) widget.activeGlyphChanged.connect(self._selectionChanged) widget.glyphNamesChanged.connect(self._namesChanged) widget.pointSizeModified.connect(self.statusBar.setSize) widget.toolModified.connect(self.toolBar.setCurrentTool) # add self.tabWidget.addTab(_textForGlyphs([glyph])) self.stackWidget.addWidget(widget) # activate self.tabWidget.setCurrentTab(-1) def closeGlyphTab(self): index = self.stackWidget.currentIndex() if index: self.tabWidget.removeTab(index) def maybeSaveBeforeExit(self): if self._font.dirty: ret = CloseMessageBox.getCloseDocument(self, self.fontTitle()) if ret == QMessageBox.Save: self.saveFile() return True elif ret == QMessageBox.Discard: return True return False return True # ------------- # Notifications # ------------- # app def _drawingToolRegistered(self, notification): toolClass = notification.data["tool"] index = self.stackWidget.currentIndex() parent = self.stackWidget.currentWidget() if index else None self.toolBar.addTool(toolClass(parent=parent)) def _drawingToolUnregistered(self, notification): toolClass = notification.data["tool"] for tool in self.toolBar.tools(): if isinstance(tool, toolClass): self.toolBar.removeTool(tool) return raise ValueError(f"couldn't find tool to unregister: {toolClass}") def _glyphViewGlyphsChanged(self, notification): self._updateGlyphActions() # widgets def _activeLayerModified(self): if self.isGlyphTab(): widget = self.stackWidget.currentWidget() index = self.sender().currentIndex().row() layers = self._font.layers layer = layers[layers.layerOrder[index]] currentGlyph = widget.activeGlyph() # XXX: adjust TLayer.get and use it if currentGlyph.name in layer: glyph = layer[currentGlyph.name] else: glyph = layer.newGlyph(currentGlyph.name) widget.setActiveGlyph(glyph) def _namesChanged(self): sender = self.sender() index = self.stackWidget.indexOf(sender) self.tabWidget.setTabName(index, _textForGlyphs(sender.glyphs())) def _sizeChanged(self): size = self.statusBar.size() if self.isGlyphTab(): widget = self.stackWidget.currentWidget() widget.setPointSize(size) else: self.glyphCellView.setCellSize(size) def _tabChanged(self, index): self.statusBar.setShouldPropagateSize(not index) # we need to hide, then setParent, then show self.stackWidget.currentWidget().hide() newWidget = self.stackWidget.widget(index) if index: for tool in self.toolBar.tools(): tool.setParent(newWidget) self.stackWidget.setCurrentIndex(index) newWidget.setFocus(Qt.OtherFocusReason) def _toolChanged(self, tool): widget = self.stackWidget.currentWidget() ok = widget.setCurrentTool(tool) # the glyph view NAKed the change (in mouseDown) # set back the current tool in the toolbar if not ok: self.toolBar.setCurrentTool(widget.currentTool()) def _widgetChanged(self, index): # update current glyph self._updateCurrentGlyph() # update undo/redo self._updateGlyphActions() # update slider if self.isGlyphTab(): lo, hi, unit = 0, 900000, " pt" widget = self.stackWidget.currentWidget() size = widget.pointSize() else: lo, hi, unit = 32, 128, None size = self.glyphCellView.cellSize()[0] self.statusBar.setMinimumSize(lo) self.statusBar.setMaximumSize(hi) self.statusBar.setSize(size) self.statusBar.setUnit(unit) self.statusBar.setTextVisible(not self.isGlyphTab()) # update and connect setCurrentTool try: self.toolBar.currentToolChanged.disconnect() except TypeError: pass if not index: return widget = self.stackWidget.currentWidget() widget.setCurrentTool(self.toolBar.currentTool()) self.toolBar.currentToolChanged.connect(self._toolChanged) def _orderChanged(self): # TODO: reimplement when we start showing glyph subsets glyphs = self.glyphCellView.glyphs() self._font.glyphOrder = [glyph.name for glyph in glyphs] def _selectionChanged(self): if self.isGlyphTab(): activeGlyph = self.stackWidget.currentWidget().activeGlyph() else: activeGlyph = self.glyphCellView.lastSelectedGlyph() # selection text # TODO: this should probably be internal to the label selection = self.glyphCellView.selection() if selection is not None: count = len(selection) if count == 1: glyph = self.glyphCellView.glyphsForIndexes(selection)[0] text = "%s " % glyph.name else: text = "" if count: text = self.tr(f"{text}(%n selected)", n=count) else: text = "" self.statusBar.setText(text) # currentGlyph app = QApplication.instance() app.setCurrentGlyph(activeGlyph) # actions self._updateGlyphActions() # defcon def _fontChanged(self, notification): font = notification.object self.setWindowModified(font.dirty) def _glyphOrderChanged(self, notification): self._updateGlyphsFromGlyphOrder() def _updateGlyphsFromGlyphOrder(self): font = self._font glyphOrder = font.glyphOrder if glyphOrder: glyphCount = 0 glyphs = [] for glyphName in glyphOrder: if glyphName in font: glyph = font[glyphName] glyphCount += 1 else: glyph = font.get(glyphName, asTemplate=True) glyphs.append(glyph) if glyphCount < len(font): # if some glyphs in the font are not present in the glyph # order, loop again to add them at the end for glyph in font: if glyph not in glyphs: glyphs.append(glyph) font.disableNotifications(observer=self) font.glyphOrder = [glyph.name for glyph in glyphs] font.enableNotifications(observer=self) else: glyphs = list(font) font.disableNotifications(observer=self) font.glyphOrder = [glyph.name for glyph in glyphs] font.enableNotifications(observer=self) self.glyphCellView.setGlyphs(glyphs) def _sortDescriptorChanged(self, notification): font = notification.object descriptors = notification.data["newValue"] if descriptors is None: return if descriptors[0]["type"] == "glyphSet": glyphNames = descriptors[0]["glyphs"] else: glyphNames = font.unicodeData.sortGlyphNames(font.keys(), descriptors) font.glyphOrder = glyphNames # ------------ # Menu methods # ------------ # File def saveFile(self, path=None, ufoFormatVersion=3): if path is None and self._font.path is None: self.saveFileAs() else: if path is None: path = self._font.path self._font.save(path, ufoFormatVersion) def saveFileAs(self): fileFormats = OrderedDict( [ (self.tr("UFO Font version 3 {}").format("(*.ufo)"), 3), (self.tr("UFO Font version 2 {}").format("(*.ufo)"), 2), ] ) state = settings.saveFileDialogState() path = self._font.path or self._font.binaryPath if path: directory = os.path.dirname(path) else: directory = ( None if state else QStandardPaths.standardLocations(QStandardPaths.DocumentsLocation)[ 0 ] ) # TODO: switch to directory dlg on platforms that need it dialog = QFileDialog( self, self.tr("Save File"), directory, ";;".join(fileFormats.keys()) ) if state: dialog.restoreState(state) dialog.setAcceptMode(QFileDialog.AcceptSave) if directory: dialog.setDirectory(directory) ok = dialog.exec_() settings.setSaveFileDialogState(dialog.saveState()) if ok: nameFilter = dialog.selectedNameFilter() path = dialog.selectedFiles()[0] if not os.path.basename(path).endswith(".ufo"): path += ".ufo" self.saveFile(path, fileFormats[nameFilter]) app = QApplication.instance() app.setCurrentFile(self._font.path) self.setWindowTitle(self.fontTitle()) # return ok def reloadFile(self): font = self._font path = font.path or font.binaryPath if not font.dirty or path is None: return if not ReloadMessageBox.getReloadDocument(self, self.fontTitle()): return if font.path is not None: font.reloadInfo() font.reloadKerning() font.reloadGroups() font.reloadFeatures() font.reloadLib() font.reloadGlyphs(font.keys()) font.dirty = False else: # TODO: we should do this in-place font_ = font.__class__().new() font_.extract(font.binaryPath) self.setFont_(font_) def exportFile(self): params, ok = ExportDialog.getExportParameters(self, self._font) if not ok: return baseName = params["baseName"] directory = params["exportDirectory"] compression = set(map(str.lower, params["compression"])) for format in map(str.lower, params["formats"]): fileName = f"{baseName}.{format}" path = os.path.join(directory, fileName) try: self._font.export(path, format, compression=compression) except Exception as e: msg = ( self.tr("This font’s feature file contains an error.") if isinstance(e, FeatureLibError) else None ) errorReports.showCriticalException(e, message=msg) # Edit def undo(self): widget = self.stackWidget.currentWidget() if self.isGlyphTab(): glyph = widget.activeGlyph() else: glyph = widget.lastSelectedGlyph() glyph.undo() def redo(self): widget = self.stackWidget.currentWidget() if self.isGlyphTab(): glyph = widget.activeGlyph() else: glyph = widget.lastSelectedGlyph() glyph.redo() def cut(self): self.copy() widget = self.stackWidget.currentWidget() if self.isGlyphTab(): glyph = widget.activeGlyph() deleteUISelection(glyph) else: glyphs = widget.glyphs() for index in widget.selection(): glyph = glyphs[index] glyph.clear() def copy(self): font = self._font widget = self.stackWidget.currentWidget() clipboard = QApplication.clipboard() mimeData = QMimeData() if self.isGlyphTab(): glyph = widget.activeGlyph() copyGlyph = glyph.getRepresentation("TruFont.FilterSelection") packGlyphs = (copyGlyph,) else: glyphs = self.glyphCellView.glyphs() packGlyphs = ( glyphs[index] for index in sorted(self.glyphCellView.selection()) ) svgGlyphs = [] pickled = [] for i, glyph in enumerate(packGlyphs): pickled.append(glyph.serialize(blacklist=("name", "unicodes"))) pen = SVGPathPen(font) glyph.draw(pen) col = i % 5 row = i // 5 g = '<g transform="matrix(1,0,0,-1,{:f},{:f})"><path d="{}"/></g>'.format( font.info.unitsPerEm * col, font.info.unitsPerEm * row, pen.getCommands(), ) svgGlyphs.append(g) mimeData.setData("application/x-trufont-glyph-data", pickle.dumps(pickled)) svg = """\ <?xml version="1.0" standalone="no"?> <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"> <svg version="1.0" xmlns="http://www.w3.org/2000/svg"> %s </svg> """ % "\n".join( svgGlyphs ) mimeData.setData("image/svg+xml", svg.encode("utf-8")) clipboard.setMimeData(mimeData) def copyAsComponent(self): if self.isGlyphTab(): pass else: glyphs = self.glyphCellView.glyphs() pickled = [] for index in self.glyphCellView.selection(): glyph = glyphs[index] componentGlyph = glyph.__class__() componentGlyph.width = glyph.width component = componentGlyph.instantiateComponent() component.baseGlyph = glyph.name pickled.append(componentGlyph.serialize()) clipboard = QApplication.clipboard() mimeData = QMimeData() mimeData.setData("application/x-trufont-glyph-data", pickle.dumps(pickled)) clipboard.setMimeData(mimeData) def paste(self): isGlyphTab = self.isGlyphTab() widget = self.stackWidget.currentWidget() if isGlyphTab: glyphs = (widget.activeGlyph(),) else: selection = self.glyphCellView.selection() glyphs = widget.glyphsForIndexes(selection) clipboard = QApplication.clipboard() mimeData = clipboard.mimeData() if mimeData.hasFormat("application/x-trufont-glyph-data"): data = pickle.loads(mimeData.data("application/x-trufont-glyph-data")) if len(data) == len(glyphs): for pickled, glyph in zip(data, glyphs): if isGlyphTab: pasteGlyph = glyph.__class__() pasteGlyph.deserialize(pickled) # TODO: if we serialize selected state, we don't need # to do this pasteGlyph.selected = True if ( len(pasteGlyph) or len(pasteGlyph.components) or len(pasteGlyph.anchors) ): glyph.beginUndoGroup() glyph.holdNotifications() count = len(glyph) pen = glyph.getPointPen() # contours, components pasteGlyph.drawPoints(pen) for contour in glyph[count:]: contour.selected = True # anchors for anchor in pasteGlyph.anchors: glyph.appendAnchor(dict(anchor)) # guidelines for guideline in pasteGlyph.guidelines: glyph.appendGuideline(dict(guideline)) glyph.releaseHeldNotifications() glyph.endUndoGroup() else: glyph.deserialize(pickled) return if mimeData.hasFormat("image/svg+xml"): if len(glyphs) == 1: glyph = glyphs[0] try: svgPath = SVGPath.fromstring(mimeData.data("image/svg+xml")) except Exception: pass else: glyph.beginUndoGroup() if not isGlyphTab: glyph.clear() svgPath.draw(glyph.getPen()) glyph.endUndoGroup() return if mimeData.hasText(): if len(glyphs) == 1: glyph = glyphs[0] otherGlyph = glyph.__class__() text = mimeData.text() try: readGlyphFromString(text, otherGlyph, otherGlyph.getPointPen()) except Exception: try: svgPath = SVGPath.fromstring(text) svgPath.draw(otherGlyph.getPen()) except Exception: return glyph.beginUndoGroup() if not isGlyphTab: glyph.clear() otherGlyph.drawPoints(glyph.getPointPen()) glyph.endUndoGroup() def selectAll(self): widget = self.stackWidget.currentWidget() if self.isGlyphTab(): glyph = widget.activeGlyph() if glyph.selected: for anchor in glyph.anchors: anchor.selected = True for component in glyph.components: component.selected = True else: glyph.selected = True else: widget.selectAll() def deselect(self): widget = self.stackWidget.currentWidget() if self.isGlyphTab(): glyph = widget.activeGlyph() for anchor in glyph.anchors: anchor.selected = False for component in glyph.components: component.selected = False glyph.selected = False else: widget.setSelection(set()) def delete(self): modifiers = QApplication.keyboardModifiers() widget = self.stackWidget.currentWidget() if self.isGlyphTab(): glyph = widget.activeGlyph() # TODO: fuse more the two methods, they're similar and delete is # Cut except not putting in the clipboard if modifiers & Qt.AltModifier: deleteUISelection(glyph) else: preserveShape = not modifiers & Qt.ShiftModifier removeUIGlyphElements(glyph, preserveShape) else: erase = modifiers & Qt.ShiftModifier if self._proceedWithDeletion(erase): glyphs = widget.glyphsForIndexes(widget.selection()) for glyph in glyphs: font = glyph.font for layer in font.layers: if glyph.name in layer: defaultLayer = layer[glyph.name] == glyph if defaultLayer and not erase: # TODO: clear in glyph.template setter? glyph.clear() glyph.template = True else: del layer[glyph.name] def findGlyph(self): widget = self.stackWidget.currentWidget() if self.isGlyphTab(): glyph = widget.activeGlyph() newGlyph, ok = FindDialog.getNewGlyph(self, glyph) if ok and newGlyph is not None: widget.setActiveGlyph(newGlyph) else: pass # XXX # View def zoom(self, step): if self.isGlyphTab(): widget = self.stackWidget.currentWidget() newScale = widget.scale() * pow(1.2, step) widget.zoom(newScale) self.statusBar.setSize(widget.pointSize()) else: value = self.statusBar.size() newValue = value + 10 * step self.statusBar.setSize(newValue) def resetZoom(self): widget = self.stackWidget.currentWidget() if self.isGlyphTab(): widget.fitScaleBBox() else: settings.removeGlyphCellSize() cellSize = settings.glyphCellSize() self.statusBar.setSize(cellSize) def tabOffset(self, value): tab = self.tabWidget.currentTab() newTab = (tab + value) % len(self.tabWidget.tabs()) self.tabWidget.setCurrentTab(newTab) def glyphOffset(self, value): widget = self.stackWidget.currentWidget() if self.isGlyphTab(): currentGlyph = widget.activeGlyph() font = currentGlyph.font glyphOrder = font.glyphOrder # should be enforced in fontView already if not (glyphOrder and len(glyphOrder)): return index = glyphOrder.index(currentGlyph.name) newIndex = (index + value) % len(glyphOrder) glyph = font[glyphOrder[newIndex]] widget.setActiveGlyph(glyph) else: lastSelectedCell = widget.lastSelectedCell() if lastSelectedCell is None: return newIndex = lastSelectedCell + value if newIndex < 0 or newIndex >= len(widget.glyphs()): return widget.setSelection({newIndex}) def layerOffset(self, value): widget = self.stackWidget.currentWidget() if self.isGlyphTab(): currentGlyph = widget.activeGlyph() layerSet, layer = currentGlyph.layerSet, currentGlyph.layer if None in (layerSet, layer): return index = layerSet.layerOrder.index(layer.name) newIndex = (index + value) % len(layerSet) layer_ = layerSet[layerSet.layerOrder[newIndex]] if layer_ == layer: return # XXX: fix get # glyph = layer_.get(currentGlyph.name) if currentGlyph.name in layer_: glyph = layer_[currentGlyph.name] else: glyph = layer_.newGlyph(currentGlyph.name) widget.setActiveGlyph(glyph) # Font def fontInfo(self): # If a window is already opened, bring it to the front, else spawn one. # TODO: see about using widget.setAttribute(Qt.WA_DeleteOnClose) # otherwise it seems we're just leaking memory after each close... # (both raise_ and show allocate memory instead of using the hidden # widget it seems) if self._infoWindow is not None and self._infoWindow.isVisible(): self._infoWindow.raise_() else: self._infoWindow = FontInfoWindow(self._font, self) self._infoWindow.show() def fontFeatures(self): # TODO: see up here if self._featuresWindow is not None and self._featuresWindow.isVisible(): self._featuresWindow.raise_() else: self._featuresWindow = FontFeaturesWindow(self._font, self) self._featuresWindow.show() def addGlyphs(self): glyphs = self.glyphCellView.glyphs() newGlyphNames, params, ok = AddGlyphsDialog.getNewGlyphNames(self, glyphs) if ok: sortFont = params.pop("sortFont") for name in newGlyphNames: glyph = self._font.get(name, **params) if glyph is not None: glyphs.append(glyph) self.glyphCellView.setGlyphs(glyphs) if sortFont: # TODO: when the user add chars from a glyphSet and no others, # should we try to sort according to that glyphSet? # The above would probably warrant some rearchitecturing. # kick-in the sort mechanism self._font.sortDescriptor = self._font.sortDescriptor def sortGlyphs(self): sortDescriptor, ok = SortDialog.getDescriptor(self, self._font.sortDescriptor) if ok: self._font.sortDescriptor = sortDescriptor # Window def groups(self): # TODO: see up here if self._groupsWindow is not None and self._groupsWindow.isVisible(): self._groupsWindow.raise_() else: self._groupsWindow = GroupsWindow(self._font, self) self._groupsWindow.show() def kerning(self): # TODO: see up here if self._kerningWindow is not None and self._kerningWindow.isVisible(): self._kerningWindow.raise_() else: self._kerningWindow = KerningWindow(self._font, self) self._kerningWindow.show() def metrics(self): # TODO: see up here if self._metricsWindow is not None and self._metricsWindow.isVisible(): self._metricsWindow.raise_() else: self._metricsWindow = MetricsWindow(self._font) # XXX: need proper, fast windowForFont API! self._metricsWindow._fontWindow = self self.destroyed.connect(self._metricsWindow.close) self._metricsWindow.show() # TODO: default string kicks-in on the window before this. Figure out # how to make a clean interface selection = self.glyphCellView.selection() if selection: glyphs = self.glyphCellView.glyphsForIndexes(selection) self._metricsWindow.setGlyphs(glyphs) def properties(self): shouldBeVisible = self.propertiesView.isHidden() self.propertiesView.setVisible(shouldBeVisible) self.writeSettings() # update methods def _setGlyphPreview(self, value): index = self.stackWidget.currentIndex() if index: widget = self.stackWidget.currentWidget() widget.setPreviewEnabled(value) def _updateCurrentGlyph(self): # TODO: refactor this pattern... widget = self.stackWidget.currentWidget() if self.isGlyphTab(): glyph = widget.activeGlyph() else: glyph = widget.lastSelectedGlyph() if glyph is not None: app = QApplication.instance() app.setCurrentGlyph(glyph) def _updateGlyphActions(self): if not hasattr(self, "_undoAction"): return widget = self.stackWidget.currentWidget() if self.isGlyphTab(): currentGlyph = widget.activeGlyph() else: currentGlyph = widget.lastSelectedGlyph() # disconnect eventual signal of previous glyph objects = ((self._undoAction, self.undo), (self._redoAction, self.redo)) for action, slot in objects: try: action.disconnect() except TypeError: pass action.triggered.connect(slot) # now update status if currentGlyph is None: self._undoAction.setEnabled(False) self._redoAction.setEnabled(False) else: undoManager = currentGlyph.undoManager self._undoAction.setEnabled(currentGlyph.canUndo()) undoManager.canUndoChanged.connect(self._undoAction.setEnabled) self._redoAction.setEnabled(currentGlyph.canRedo()) undoManager.canRedoChanged.connect(self._redoAction.setEnabled) # and other actions for action in self._clipboardActions: action.setEnabled(currentGlyph is not None) # helper def _proceedWithDeletion(self, erase=False): if not self.glyphCellView.selection(): return tr = self.tr("Delete") if erase else self.tr("Clear") text = self.tr("Do you want to %s selected glyphs?") % tr.lower() closeDialog = QMessageBox( QMessageBox.Question, "", self.tr("%s glyphs") % tr, QMessageBox.Yes | QMessageBox.No, self, ) closeDialog.setInformativeText(text) closeDialog.setModal(True) ret = closeDialog.exec_() if ret == QMessageBox.Yes: return True return False # ---------- # Qt methods # ---------- def setWindowTitle(self, title): if platformSpecific.appNameInTitle(): title += " – TruFont" super().setWindowTitle(f"[*]{title}") def sizeHint(self): return QSize(1270, 800) def moveEvent(self, event): self.writeSettings() resizeEvent = moveEvent def showEvent(self, event): app = QApplication.instance() data = dict(font=self._font, window=self) app.postNotification("fontWindowWillOpen", data) super().showEvent(event) app.postNotification("fontWindowOpened", data) def closeEvent(self, event): ok = self.maybeSaveBeforeExit() if ok: app = QApplication.instance() data = dict(font=self._font, window=self) app.postNotification("fontWindowWillClose", data) self._font.removeObserver(self, "Font.Changed") app = QApplication.instance() app.dispatcher.removeObserver(self, "drawingToolRegistered") app.dispatcher.removeObserver(self, "drawingToolUnregistered") app.dispatcher.removeObserver(self, "glyphViewGlyphsChanged") event.accept() else: event.ignore() def event(self, event): if event.type() == QEvent.WindowActivate: app = QApplication.instance() app.setCurrentFontWindow(self) self._updateCurrentGlyph() return super().event(event) def paintEvent(self, event): painter = QPainter(self) painter.fillRect(event.rect(), QColor(212, 212, 212))
class ParamsByType(QWidget, MooseWidget): """ Has a QComboBox for the different allowed types. On switching type a new ParamsByGroup is shown. """ needBlockList = pyqtSignal(list) blockRenamed = pyqtSignal(object, str) changed = pyqtSignal() def __init__(self, block, **kwds): """ Constructor. Input: block[BlockInfo]: The block to show. """ super(ParamsByType, self).__init__(**kwds) self.block = block self.combo = QComboBox() self.types = [] self.type_params_map = {} self.table_stack = QStackedWidget() self.type_table_map = {} for t in sorted(self.block.types.keys()): self.types.append(t) params_list = [] for p in self.block.parameters_list: params_list.append(self.block.parameters[p]) t_block = self.block.types[t] for p in t_block.parameters_list: params_list.append(t_block.parameters[p]) self.type_params_map[t] = params_list self.combo.addItems(sorted(self.block.types.keys())) self.combo.currentTextChanged.connect(self.setBlockType) self.top_layout = WidgetUtils.addLayout(vertical=True) self.top_layout.addWidget(self.combo) self.top_layout.addWidget(self.table_stack) self.setLayout(self.top_layout) self.user_params = [] self.setDefaultBlockType() self.setup() def _syncUserParams(self, current, to): """ Sync user added parameters that are on the main block into each type ParamsByGroup. Input: current[ParamsByGroup]: The current group parameter table to[ParamsByGroup]: The new group parameter table """ ct = current.findTable("Main") tot = to.findTable("Main") if not ct or not tot or ct == tot: return # first remove user params in tot tot.removeUserParams() params = ct.getUserParams() tot.addUserParams(params) idx = ct.findRow("Name") if idx >= 0: name = ct.item(idx, 1).text() idx = tot.findRow("Name") if idx >= 0: tot.item(idx, 1).setText(name) def currentType(self): return self.combo.currentText() def save(self): """ Look at the user params in self.block.parameters. update the type tables Save type on block """ t = self.getTable() if t: t.save() self.block.setBlockType(self.combo.currentText()) def reset(self): t = self.getTable() t.reset() def getOrCreateTypeTable(self, type_name): """ Gets the table for the type name or create it if it doesn't exist. Input: type_name[str]: Name of the type Return: ParamsByGroup: The parameters corresponding to the type """ t = self.type_table_map.get(type_name) if t: return t t = ParamsByGroup(self.block, self.type_params_map.get(type_name, self.block.orderedParameters())) t.needBlockList.connect(self.needBlockList) t.blockRenamed.connect(self.blockRenamed) t.changed.connect(self.changed) self.type_table_map[type_name] = t self.table_stack.addWidget(t) return t def setDefaultBlockType(self): param = self.block.getParamInfo("type") if param and param.value: self.setBlockType(param.value) elif self.block.types: self.setBlockType(sorted(self.block.types.keys())[0]) def setBlockType(self, type_name): if type_name not in self.block.types: return t = self.getOrCreateTypeTable(type_name) t.updateWatchers() self.combo.blockSignals(True) self.combo.setCurrentText(type_name) self.combo.blockSignals(False) t.updateType(type_name) current = self.table_stack.currentWidget() self._syncUserParams(current, t) self.table_stack.setCurrentWidget(t) self.changed.emit() def addUserParam(self, param): t = self.table_stack.currentWidget() t.addUserParam(param) def setWatchedBlockList(self, path, children): for i in range(self.table_stack.count()): t = self.table_stack.widget(i) t.setWatchedBlockList(path, children) def updateWatchers(self): for i in range(self.table_stack.count()): t = self.table_stack.widget(i) t.updateWatchers() def getTable(self): return self.table_stack.currentWidget() def paramValue(self, name): for i in range(self.table_stack.count()): t = self.table_stack.widget(i) if t.paramValue(name): return t.paramValue(name)
class GstMediaSettings(SettingsSection): Name = 'Media Settings' def __init__(self, size, cue=None, parent=None): super().__init__(size, cue=cue, parent=parent) self._pipe = '' self._conf = {} self._check = False self.glayout = QGridLayout(self) self.listWidget = QListWidget(self) self.glayout.addWidget(self.listWidget, 0, 0) self.pipeButton = QPushButton('Change Pipe', self) self.glayout.addWidget(self.pipeButton, 1, 0) self.elements = QStackedWidget(self) self.glayout.addWidget(self.elements, 0, 1, 2, 1) self.glayout.setColumnStretch(0, 2) self.glayout.setColumnStretch(1, 5) self.listWidget.currentItemChanged.connect(self.__change_page) self.pipeButton.clicked.connect(self.__edit_pipe) def set_configuration(self, conf): # Get the media section of the cue configuration if conf is not None: conf = conf.get('media', {}) # Activate the layout, so we can get the right widgets size self.glayout.activate() # Create a local copy of the configuration self._conf = deepcopy(conf) # Create the widgets sections = sections_by_element_name() for element in conf.get('pipe', '').split('!'): widget = sections.get(element) if widget is not None: widget = widget(self.elements.size(), element, self) widget.set_configuration(self._conf['elements']) self.elements.addWidget(widget) item = QListWidgetItem(widget.NAME) self.listWidget.addItem(item) self.listWidget.setCurrentRow(0) def get_configuration(self): conf = {'elements': {}} for el in self.elements.children(): if isinstance(el, SettingsSection): conf['elements'].update(el.get_configuration()) # If in check mode the pipeline is not returned if not self._check: conf['pipe'] = self._conf['pipe'] return {'media': conf} def enable_check(self, enable): self._check = enable for element in self.elements.children(): if isinstance(element, SettingsSection): element.enable_check(enable) def __change_page(self, current, previous): if not current: current = previous self.elements.setCurrentIndex(self.listWidget.row(current)) def __edit_pipe(self): # Backup the settings self._conf.update(self.get_configuration()['media']) # Show the dialog dialog = GstPipeEdit(self._conf.get('pipe', ''), parent=self) if dialog.exec_() == dialog.Accepted: # Reset the view for _ in range(self.elements.count()): self.elements.removeWidget(self.elements.widget(0)) self.listWidget.clear() # Reload with the new pipeline self._conf['pipe'] = dialog.get_pipe() self.set_configuration({'media': self._conf}) self.enable_check(self._check)
class TableWidget(QSplitter): def __init__(self): super(TableWidget, self).__init__() # vbox = QVBoxLayout(self) # vbox.setContentsMargins(0, 0, 0, 0) self._tabs = QTabWidget() self._tabs.setAutoFillBackground(True) p = self._tabs.palette() p.setColor(p.Window, QColor("white")) self._tabs.setPalette(p) self._other_tab = QTabWidget() self._other_tab.setAutoFillBackground(True) self._other_tab.setPalette(p) self.addWidget(self._tabs) self.addWidget(self._other_tab) self.setSizes([1, 1]) self._other_tab.hide() self.relations = {} # Stack self.stacked = QStackedWidget() self._tabs.addTab(self.stacked, "Workspace") self.stacked_result = QStackedWidget() self._tabs.addTab(self.stacked_result, self.tr("Resultados")) btn_split = QToolButton() btn_split.setToolTip(self.tr("Click para dividir la pantalla")) btn_split.setAutoRaise(True) btn_split.setIcon(QIcon(":img/split")) self._tabs.setCornerWidget(btn_split) btn_split.clicked.connect(self._split) btn_split = QToolButton() btn_split.setToolTip(self.tr("Click para juntar las pantallas")) btn_split.setAutoRaise(True) btn_split.setIcon(QIcon(":img/split")) btn_split.clicked.connect(self._unsplit) self._other_tab.setCornerWidget(btn_split) # self.setContextMenuPolicy(Qt.CustomContextMenu) # self.customContextMenuRequested.connect(self._show_menu) lateral_widget = Pireal.get_service("lateral_widget") lateral_widget.resultClicked.connect(self._on_result_list_clicked) lateral_widget.resultSelectionChanged.connect( lambda index: self.stacked_result.setCurrentIndex(index)) # lateral_widget.newRowsRequested.connect(self._insert_rows) def insert_rows(self, tuplas): current_view = self.current_table() if current_view is not None: model = current_view.model() for tupla in tuplas: model.insertRow(model.rowCount(), tupla) current_view.adjust_columns() def _on_result_list_clicked(self, index): self.stacked_result.setCurrentIndex(index) if not self._other_tab.isVisible(): self._tabs.setCurrentIndex(1) def _unsplit(self): self._other_tab.hide() result_widget = self._other_tab.widget(0) self._tabs.addTab(result_widget, self.tr("Resultados")) self._tabs.cornerWidget().show() def _split(self): result_widget = self._tabs.widget(1) self._other_tab.addTab(result_widget, self.tr("Resultados")) self._other_tab.show() self.setSizes([1, 1]) self._tabs.cornerWidget().hide() self.setOrientation(Qt.Horizontal) def _show_menu(self, position): menu = QMenu(self) if self.count() > 0: add_tuple_action = menu.addAction(self.tr("Agregar Tupla")) add_col_action = menu.addAction(self.tr("Add Column")) add_tuple_action.triggered.connect(self.add_tuple) add_col_action.triggered.connect(self.add_column) menu.addSeparator() add_relation_action = menu.addAction(self.tr("Create new Relation")) add_relation_action.triggered.connect(self.__new_relation) menu.exec_(self.mapToGlobal(position)) def __new_relation(self): central_service = Pireal.get_service("central") central_service.create_new_relation() def count(self): return self.stacked.count() def remove_table(self, index): widget = self.stacked.widget(index) self.stacked.removeWidget(widget) del widget def current_table(self): return self.stacked.currentWidget() def remove_relation(self, name): del self.relations[name] def add_relation(self, name, rela): if self.relations.get(name, None) is None: self.relations[name] = rela return True return False def add_table(self, rela, name, table): """ Add new table from New Relation Dialog """ self.add_relation(name, rela) self.stacked.addWidget(table) def add_tuple(self): current_view = self.current_table() if current_view is not None: model = current_view.model() model.insertRow(model.rowCount()) def add_column(self): current_view = self.current_table() if current_view is not None: model = current_view.model() model.insertColumn(model.columnCount()) def delete_tuple(self): current_view = self.current_table() if current_view is not None: model = current_view.model() selection = current_view.selectionModel() if selection.hasSelection(): selection = selection.selection() rows = set([index.row() for index in selection.indexes()]) rows = sorted(list(rows)) previous = -1 i = len(rows) - 1 while i >= 0: current = rows[i] if current != previous: model.removeRow(current) i -= 1 def delete_column(self): """ Elimina la/las columnas seleccionadas """ current_view = self.current_table() if current_view is not None: model = current_view.model() selection = current_view.selectionModel() if selection.hasSelection(): selection = selection.selection() columns = set( [index.column() for index in selection.indexes()]) columns = sorted(list(columns)) previous = -1 i = len(columns) - 1 while i >= 0: current = columns[i] if current != previous: model.removeColumn(current) i -= 1 def create_table(self, rela, editable=True): """ Se crea la vista y el modelo """ _view = view.View() _model = model.Model(rela) if not editable: _model.editable = False _view.setModel(_model) _view.setItemDelegate(delegate.Delegate()) _view.setHorizontalHeader(view.Header()) return _view
class CentralWidget(QWidget): # This signals is used by notificator databaseSaved = pyqtSignal('QString') querySaved = pyqtSignal('QString') databaseConected = pyqtSignal('QString') def __init__(self): QWidget.__init__(self) box = QVBoxLayout(self) box.setContentsMargins(0, 0, 0, 0) box.setSpacing(0) self.stacked = QStackedWidget() box.addWidget(self.stacked) self.created = False # Acá cacheo la última carpeta accedida self.__last_open_folder = None if CONFIG.get("lastOpenFolder") is not None: self.__last_open_folder = CONFIG.get("lastOpenFolder") self.__recent_dbs = [] if CONFIG.get("recentFiles"): self.__recent_dbs = CONFIG.get("recentFiles") Pireal.load_service("central", self) esc_short = QShortcut(QKeySequence(Qt.Key_Escape), self) esc_short.activated.connect(self._hide_search) def _hide_search(self): query_container = self.get_active_db().query_container if query_container is not None: query_container.set_editor_focus() @property def recent_databases(self): return self.__recent_dbs @recent_databases.setter def recent_databases(self, database_file): recent_files = CONFIG.get("recentFiles") if database_file in recent_files: recent_files.remove(database_file) recent_files.insert(0, database_file) self.__recent_dbs = recent_files @property def last_open_folder(self): return self.__last_open_folder def rdb_to_pdb(self): from src.gui import rdb_pdb_tool dialog = rdb_pdb_tool.RDBPDBTool(self) dialog.exec_() def create_database(self): """Show a wizard widget to create a new database, only have one database open at time.""" if self.created: return self.__say_about_one_db_at_time() dialog = new_database_dialog.NewDatabaseDialog(self) dialog.created.connect(self.__on_wizard_finished) dialog.show() def __on_wizard_finished(self, *data): """This slot execute when wizard to create a database is finished""" pireal = Pireal.get_service("pireal") if data: db_name, location, fname = data # Create a new data base container db_container = database_container.DatabaseContainer() # Associate the file name with the PFile object pfile_object = pfile.File(fname) # Associate PFile object with data base container # and add widget to stacked db_container.pfile = pfile_object self.add_widget(db_container) # Set window title pireal.change_title(file_manager.get_basename(fname)) # Enable db actions pireal.set_enabled_db_actions(True) pireal.set_enabled_relation_actions(True) self.created = True logger.debug("La base de datos ha sido creada con éxito") def __say_about_one_db_at_time(self): logger.info("Una base de datos a la vez") QMessageBox.information( self, self.tr("Información"), self.tr("Una base de datos a la vez por " "favor.")) def open_database(self, filename='', remember=True): """ This function opens a database and set this on the UI """ if self.created: return self.__say_about_one_db_at_time() # If not filename provide, then open dialog to select if not filename: if self.__last_open_folder is None: directory = os.path.expanduser("~") else: directory = self.__last_open_folder filter_ = settings.SUPPORTED_FILES.split(';;')[0] filename, _ = QFileDialog.getOpenFileName( self, self.tr("Abrir Base de " "Datos"), directory, filter_) # If is canceled, return if not filename: return # If filename provide try: logger.debug("Intentando abrir el archivo {}".format(filename)) # Read pdb file pfile_object = pfile.File(filename) db_data = pfile_object.read() # Create a dict to manipulate data more easy db_data = self.__sanitize_data(db_data) except Exception as reason: QMessageBox.information(self, self.tr("El archivo no se puede abrir"), reason.__str__()) logger.debug("Error al abrir el archivo {0}: '{1}'".format( filename, reason.__str__())) return # Create a database container widget db_container = database_container.DatabaseContainer() try: db_container.create_database(db_data) except Exception as reason: QMessageBox.information(self, self.tr("Error"), str(reason)) logger.debug("Error al crear la base de datos: {}".format( reason.__str__())) return # Set the PFile object to the new database db_container.pfile = pfile_object # Add data base container to stacked self.add_widget(db_container) # Database name db_name = file_manager.get_basename(filename) # Update title with the new database name, and enable some actions pireal = Pireal.get_service("pireal") self.databaseConected.emit(self.tr("Conectado a: {}".format(db_name))) pireal.set_enabled_db_actions(True) pireal.set_enabled_relation_actions(True) if remember: # Add to recent databases self.recent_databases = filename # Remember the folder self.__last_open_folder = file_manager.get_path(filename) self.created = True def open_query(self, filename='', remember=True): if not filename: if self.__last_open_folder is None: directory = os.path.expanduser("~") else: directory = self.__last_open_folder filter_ = settings.SUPPORTED_FILES.split(';;')[1] filename, _ = QFileDialog.getOpenFileName( self, self.tr("Abrir Consulta"), directory, filter_) if not filename: return # Si @filename no es False # Cacheo la carpeta accedida if remember: self.__last_open_folder = file_manager.get_path(filename) # FIXME: mejorar éste y new_query self.new_query(filename) def save_query(self, editor=None): db = self.get_active_db() fname = db.save_query(editor) if fname: self.querySaved.emit(self.tr( "Consulta guardada: {}".format(fname))) def save_query_as(self): pass def __sanitize_data(self, data): """ Este método convierte el contenido de la base de datos a un diccionario para un mejor manejo despues """ # FIXME: controlar cuando al final de la línea hay una coma from collections import defaultdict data_dict = defaultdict(list) for line_count, line in enumerate(data.splitlines()): # Ignore blank lines if not line.strip(): continue if line.startswith("@"): # Header de una relación tpoint = line.find(":") if tpoint == -1: raise Exception( "Error de sintáxis en la línea {}".format(line_count + 1)) table_name, line = line.split(":") table_name = table_name[1:].strip() table_dict = {} table_dict["name"] = table_name table_dict["header"] = list(map(str.strip, line.split(","))) table_dict["tuples"] = set() else: # Tuplas de la relación for l in csv.reader([line]): tupla = tuple(map(str.strip, l)) table_dict["tuples"].add(tupla) if not table_dict["tuples"]: data_dict["tables"].append(table_dict) return data_dict def remove_last_widget(self): """ Remove last widget from stacked """ widget = self.stacked.widget(self.stacked.count() - 1) self.stacked.removeWidget(widget) def close_database(self): """ Close the database and return to the main widget """ db = self.get_active_db() query_container = db.query_container if db.modified: msgbox = QMessageBox(self) msgbox.setIcon(QMessageBox.Question) msgbox.setWindowTitle(self.tr("Guardar cambios?")) msgbox.setText( self.tr("La base de datos <b>{}</b> ha sido " "modificada.<br>Quiere guardar los " "cambios?".format(db.dbname()))) cancel_btn = msgbox.addButton(self.tr("Cancelar"), QMessageBox.RejectRole) msgbox.addButton(self.tr("No"), QMessageBox.NoRole) yes_btn = msgbox.addButton(self.tr("Si"), QMessageBox.YesRole) msgbox.exec_() r = msgbox.clickedButton() if r == cancel_btn: return if r == yes_btn: self.save_database() # Check if editor is modified query_widget = query_container.currentWidget() if query_widget is not None: weditor = query_widget.get_editor() if weditor is not None: # TODO: duplicate code, see tab widget if weditor.modified: msgbox = QMessageBox(self) msgbox.setIcon(QMessageBox.Question) msgbox.setWindowTitle(self.tr("Archivo modificado")) msgbox.setText( self.tr("El archivo <b>{}</b> tiene cambios" " no guardados. Quiere " "mantenerlos?".format(weditor.name))) cancel_btn = msgbox.addButton(self.tr("Cancelar"), QMessageBox.RejectRole) msgbox.addButton(self.tr("No"), QMessageBox.NoRole) yes_btn = msgbox.addButton(self.tr("Si"), QMessageBox.YesRole) msgbox.exec_() r = msgbox.clickedButton() if r == cancel_btn: return if r == yes_btn: self.save_query(weditor) self.stacked.removeWidget(db) pireal = Pireal.get_service("pireal") pireal.set_enabled_db_actions(False) pireal.set_enabled_relation_actions(False) pireal.set_enabled_query_actions(False) pireal.set_enabled_editor_actions(False) pireal.change_title() # Título en la ventana principal 'Pireal' self.created = False del db def new_query(self, filename=''): pireal = Pireal.get_service("pireal") db_container = self.get_active_db() db_container.new_query(filename) # Enable editor actions # FIXME: refactoring pireal.set_enabled_query_actions(True) zoom_in_action = Pireal.get_action("zoom_in") zoom_in_action.setEnabled(True) zoom_out_action = Pireal.get_action("zoom_out") zoom_out_action.setEnabled(True) paste_action = Pireal.get_action("paste_action") paste_action.setEnabled(True) comment_action = Pireal.get_action("comment") comment_action.setEnabled(True) uncomment_action = Pireal.get_action("uncomment") uncomment_action.setEnabled(True) search_action = Pireal.get_action("search") search_action.setEnabled(True) def execute_queries(self): db_container = self.get_active_db() db_container.execute_queries() def execute_selection(self): db_container = self.get_active_db() db_container.execute_selection() def save_database(self): db = self.get_active_db() if not db.modified: return # Get relations dict relations = db.table_widget.relations # Generate content content = file_manager.generate_database(relations) db.pfile.save(data=content) filename = db.pfile.filename # Emit signal self.databaseSaved.emit( self.tr("Base de datos guardada: {}".format(filename))) db.modified = False def save_database_as(self): filter = settings.SUPPORTED_FILES.split(';;')[0] filename, _ = QFileDialog.getSaveFileName( self, self.tr("Guardar Base de " "Datos como..."), settings.PIREAL_DATABASES, filter) if not filename: return db = self.get_active_db() # Get relations relations = db.table_widget.relations # Content content = file_manager.generate_database(relations) # Si no se provee la extensión, le agrego if not os.path.splitext(filename)[1]: filename += '.pdb' db.pfile.save(content, filename) self.databaseSaved.emit( self.tr("Base de datos guardada: {}".format(db.pfile.filename))) db.modified = False def remove_relation(self): db = self.get_active_db() if db.delete_relation(): db.modified = True def create_new_relation(self): def create_relation(relation, relation_name): db = self.get_active_db() lateral = Pireal.get_service("lateral_widget") table = db.create_table(relation, relation_name) db.table_widget.add_table(relation, relation_name, table) lateral.relation_list.add_item(relation_name, relation.cardinality(), relation.degree()) db.modified = True dialog = new_relation_dialog.NewRelationDialog(self) dialog.created.connect(create_relation) dialog.show() def load_relation(self, filename=''): """ Load Relation file """ if not filename: if self.__last_open_folder is None: directory = os.path.expanduser("~") else: directory = self.__last_open_folder msg = self.tr("Abrir Relación") filter_ = settings.SUPPORTED_FILES.split(';;')[-1] filenames = QFileDialog.getOpenFileNames(self, msg, directory, filter_)[0] if not filenames: return # Save folder self.__last_open_folder = file_manager.get_path(filenames[0]) db_container = self.get_active_db() if db_container.load_relation(filenames): db_container.modified = True def add_start_page(self): """ This function adds the Start Page to the stacked widget """ sp = start_page.StartPage() self.add_widget(sp) def show_settings(self): """ Show settings dialog on stacked """ # preferences_dialog = preferences.Preferences(self) # if isinstance(self.widget(1), preferences.Preferences): # self.widget(1).close() # else: # self.stacked.insertWidget(1, preferences_dialog) # self.stacked.setCurrentIndex(1) # # Connect the closed signal # preferences_dialog.settingsClosed.connect(self._settings_closed) # TODO: para la próxima versión pass def widget(self, index): """ Returns the widget at the given index """ return self.stacked.widget(index) def add_widget(self, widget): """ Appends and show the given widget to the Stacked """ index = self.stacked.addWidget(widget) self.stacked.setCurrentIndex(index) def _settings_closed(self): self.stacked.removeWidget(self.widget(1)) self.stacked.setCurrentWidget(self.stacked.currentWidget()) def get_active_db(self): """ Return an instance of DatabaseContainer widget if the stacked contains an DatabaseContainer in last index or None if it's not an instance of DatabaseContainer """ index = self.stacked.count() - 1 widget = self.widget(index) if isinstance(widget, database_container.DatabaseContainer): return widget return None def get_unsaved_queries(self): query_container = self.get_active_db().query_container return query_container.get_unsaved_queries() def undo_action(self): query_container = self.get_active_db().query_container query_container.undo() def redo_action(self): query_container = self.get_active_db().query_container query_container.redo() def cut_action(self): query_container = self.get_active_db().query_container query_container.cut() def copy_action(self): query_container = self.get_active_db().query_container query_container.copy() def paste_action(self): query_container = self.get_active_db().query_container query_container.paste() def zoom_in(self): query_container = self.get_active_db().query_container query_container.zoom_in() def zoom_out(self): query_container = self.get_active_db().query_container query_container.zoom_out() def comment(self): query_container = self.get_active_db().query_container query_container.comment() def uncomment(self): query_container = self.get_active_db().query_container query_container.uncomment() def add_tuple(self): lateral = Pireal.get_service("lateral_widget") if lateral.relation_list.has_item() == 0: return # rname = lateral.relation_list.item_text(lateral.relation_list.row()) rname = lateral.relation_list.current_text() from src.gui.dialogs.edit_relation_dialog import EditRelationDialog dialog = EditRelationDialog(rname, self) tw = self.get_active_db().table_widget dialog.sendData.connect(tw.insert_rows) dialog.show() def add_column(self): tw = self.get_active_db().table_widget tw.add_column() def delete_tuple(self): lateral = Pireal.get_service("lateral_widget") if lateral.relation_list.has_item() == 0: return r = QMessageBox.question( self, self.tr("Eliminar tupla/s"), self.tr("Seguro que quiere eliminar las tuplas seleccionadas?"), QMessageBox.Yes | QMessageBox.Cancel) if r == QMessageBox.Cancel: return tw = self.get_active_db().table_widget tw.delete_tuple() def delete_column(self): tw = self.get_active_db().table_widget tw.delete_column() def search(self): query_container = self.get_active_db().query_container query_container.search()
class QWatson(QWidget, QWatsonImportMixin, QWatsonProjectMixin, QWatsonActivityMixin): def __init__(self, config_dir=None, parent=None): super(QWatson, self).__init__(parent) self.setWindowIcon(icons.get_icon('master')) self.setWindowTitle(__namever__) self.setMinimumWidth(300) self.setWindowFlags(Qt.Window | Qt.WindowMinimizeButtonHint | Qt.WindowCloseButtonHint) if platform.system() == 'Windows': import ctypes myappid = __namever__ ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID( myappid) config_dir = (config_dir or os.environ.get('QWATSON_DIR') or click.get_app_dir('QWatson')) self.client = Watson(config_dir=config_dir) self.model = WatsonTableModel(self.client) self.setup_activity_overview() self.setup() if self.client.is_started: self.add_new_project(self.client.current['project']) self.stop_watson(tags=['error'], message="last session not closed correctly.") self.set_settings_from_index(-1) # ---- Setup layout def setup(self): """Setup the main widget.""" # Setup the stack widget. self.stackwidget = QStackedWidget() self.setup_activity_tracker() self.setup_datetime_input_dialog() self.setup_close_dialog() self.setup_del_project_dialog() self.setup_merge_project_dialog() self.setup_import_dialog() # Setup the main layout of the widget layout = QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(self.stackwidget) def setup_close_dialog(self): """ Setup a dialog that is shown when closing QWatson while and activity is being tracked. """ self.close_dial = CloseDialog(parent=self) self.close_dial.register_dialog_to(self) def setup_datetime_input_dialog(self): """ Setup the dialog to ask the user to enter a datetime value for the starting time of the activity. """ self.datetime_input_dial = DateTimeInputDialog(parent=self) self.datetime_input_dial.register_dialog_to(self) # ---- Main interface def setup_activity_tracker(self): """Setup the widget used to start, track, and stop new activity.""" stopwatch = self.setup_stopwatch() managers = self.setup_watson_managers() statusbar = self.setup_statusbar() tracker = QWidget() layout = QVBoxLayout(tracker) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) layout.addWidget(stopwatch) layout.addWidget(managers) layout.addWidget(statusbar) layout.setStretch(1, 100) self.stackwidget.addWidget(tracker) # ---- Project, Tags and Comment def setup_watson_managers(self): """ Setup the embedded dialog to setup the current activity parameters. """ project_manager = self.setup_project_manager() self.tag_manager = TagLineEdit() self.tag_manager.setPlaceholderText("Tags (comma separated)") self.comment_manager = QLineEdit() self.comment_manager.setPlaceholderText("Comment") # ---- Setup the layout managers = ColoredFrame('light') layout = QGridLayout(managers) layout.setContentsMargins(5, 5, 5, 5) layout.addWidget(project_manager, 0, 1) layout.addWidget(self.tag_manager, 1, 1) layout.addWidget(self.comment_manager, 2, 1) layout.addWidget(QLabel('project :'), 0, 0) layout.addWidget(QLabel('tags :'), 1, 0) layout.addWidget(QLabel('comment :'), 2, 0) return managers def set_settings_from_index(self, index): """ Load the settings in the manager from the data of the frame saved at index. """ if index is not None: try: frame = self.client.frames[index] self.project_manager.blockSignals(True) self.project_manager.setCurrentProject(frame.project) self.project_manager.blockSignals(False) self.tag_manager.blockSignals(True) self.tag_manager.set_tags(frame.tags) self.tag_manager.blockSignals(False) self.comment_manager.blockSignals(True) self.comment_manager.setText(frame.message) self.comment_manager.blockSignals(False) except IndexError: print("IndexError: list index out of range") # ---- Bottom Toolbar def setup_statusbar(self): """Setup the toolbar located at the bottom of the main widget.""" self.btn_report = QToolButtonSmall('note') self.btn_report.clicked.connect(self.overview_widg.show) self.btn_report.setToolTip("<b>Activity Overview</b><br><br>" "Open the activity overview window.") self.round_time_btn = DropDownToolButton(style='text_only') self.round_time_btn.addItems(list(ROUNDMIN.keys())) self.round_time_btn.setCurrentIndex(1) self.round_time_btn.setToolTip( "<b>Round Start and Stop</b><br><br>" "Round start and stop times to the nearest" " multiple of the selected factor.") self.btn_startfrom = DropDownToolButton(style='text_only') self.btn_startfrom.addItems( ['start from now', 'start from last', 'start from other']) self.btn_startfrom.setCurrentIndex(0) self.btn_startfrom.setToolTip( "<b>Start From</b><br><br>" "Set whether the current activity starts" " from the current time (now)," " from the stop time of the last logged activity (last)," " or from a user defined time (other).") # Setup the layout of the statusbar statusbar = ToolBarWidget('window') statusbar.setSpacing(0) statusbar.addWidget(self.round_time_btn) statusbar.addWidget(self.btn_startfrom) statusbar.addStretch(100) statusbar.addWidget(self.btn_report) statusbar.setSizePolicy( QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)) return statusbar def roundTo(self): """ Return the start and stop rounding time factor, in minutes, that corresponds to the option selected in the round_time_btn. """ return ROUNDMIN[self.round_time_btn.text()] def startFrom(self): """ Return the mode to use to determine at what reference time the activity must refer to calculate its elapsed time. """ return STARTFROM[self.btn_startfrom.text()] # ---- Stackwidget handlers def addWidget(self, widget): """ Add a widget to the stackwidget and return the index where the widget was added. """ self.stackwidget.addWidget(widget) return self.stackwidget.count() - 1 def removeWidget(self, widget): """Remove a widget from the stackwidget.""" self.stackwidget.removeWidget(widget) def currentIndex(self): """Return the current index of the stackwidget.""" return self.stackwidget.currentIndex() def setCurrentIndex(self, index): """Set the current index of the stackwidget.""" self.stackwidget.setCurrentIndex(index) # ---- Stop, Start, and Cancel def setup_stopwatch(self): """ Setup the widget that contains a button to start/stop Watson and a digital clock that shows the elapsed amount of time since Watson was started. """ self.stopwatch = StopWatchWidget() self.stopwatch.sig_btn_start_clicked.connect(self.start_watson) self.stopwatch.sig_btn_stop_clicked.connect(self.stop_watson) self.stopwatch.sig_btn_cancel_clicked.connect(self.cancel_watson) return self.stopwatch def start_watson(self, start_time=None): """Start monitoring a new activity with the Watson client.""" if isinstance(start_time, arrow.Arrow): self.btn_startfrom.setEnabled(False) self.stopwatch.start(start_time) self.client.start(self.currentProject()) self.client._current['start'] = start_time else: frames = self.client.frames if self.startFrom() == 'now': self.start_watson(arrow.now()) elif self.startFrom() == 'last' and len(frames) > 0: self.start_watson(min(frames[-1].stop, arrow.now())) else: self.datetime_input_dial.show() def cancel_watson(self): """Cancel the Watson client if it is running and reset the UI.""" self.btn_startfrom.setEnabled(True) self.stopwatch.cancel() if self.client.is_started: self.client.cancel() def stop_watson(self, message=None, project=None, tags=None, round_to=None): """Stop Watson and update the table model.""" self.btn_startfrom.setEnabled(True) self.stopwatch.stop() self.client._current['message'] = \ self.comment_manager.text() if message is None else message self.client._current['project'] = \ self.currentProject() if project is None else project self.client._current['tags'] = \ self.tag_manager.tags if tags is None else tags self.model.beginInsertRows(QModelIndex(), len(self.client.frames), len(self.client.frames)) self.client.stop() # Round the start and stop times of the last added frame. round_frame_at(self.client, -1, self.roundTo() if round_to is None else round_to) self.client.save() self.model.endInsertRows() def closeEvent(self, event): """Qt method override.""" if self.client.is_started: self.close_dial.show() event.ignore() else: self.overview_widg.close() self.client.save() event.accept() print("QWatson is closed.\n")
class MainWindow(QMainWindow): def __init__(self, resolution: QRect, *args, **kwargs): super(MainWindow, self).__init__(*args, **kwargs) self.setWindowTitle("") self.screens_config = [] self.current_screen = 0 self.sub_screens = {} self.button = {} self.subscreen_stacked_widget = QStackedWidget() self.number_of_subs = 0 self.main_stack_widget = QStackedWidget() self.main_widget = QWidget() self.main_layout = QGridLayout() self.gui_element_builder = GuiElementsBuilder() self.gui_button_builder = GuiButtonBuilder() self.gui_subscreen_builder = GuiSubscreenBuilder() self.resolution = resolution self.alarm_observer = AlarmObserver(self) self.alarm_widget = Alarm(self.alarm_observer, "Alarm_Widget", "#550055", self) def set_current_subscreen(self): source_button = self.sender() for i in range(0, self.number_of_subs): # print(source_button.text(), self.subscreen_stacked_widget.widget(i).get_name()) if self.subscreen_stacked_widget.widget( i).get_name() == source_button.text(): self.subscreen_stacked_widget.setCurrentIndex(i) def toggle_main_widget(self, index: int): max_value = self.main_stack_widget.count() self.main_stack_widget.setCurrentIndex(index % max_value) def close_main_window(self): self.close() def update_from_subscreen(self, msg: dict) -> None: print(msg) self.alarm_widget.set_alarm_text(msg) self.toggle_main_widget(1) def init_with_config(self, config: dict): self.screens_config = config # Set Title title = str(config['main']['name']) self.setWindowTitle(title) # Set window flag flags = Qt.CustomizeWindowHint # Small Frame # flags = Qt.FramelessWindowHint # No Frame self.setWindowFlags(flags) # Set Resolution ###################################################### # via config window_width = config['main']["resolution"][0] window_height = config['main']["resolution"][1] # via given screen geometry # window_width = self.resolution.width() # window_height = self.resolution.height() # Set Resolution End ################################################## button_width = config['main']["button-size"][0] button_height = config['main']["button-size"][1] front_color = config['main']["front_color"] background_color = config['main']["background_color"] font = config['main']["font"] self.number_of_subs = len(config['sub']) self.gui_element_builder.set_font(font) self.setFixedSize(window_width, window_height) self.main_widget.setLayout(self.main_layout) self.main_widget.setStyleSheet("background-color:" + background_color) vbox_menu = QVBoxLayout() vbox_menu.setSizeConstraint(QLayout.SetFixedSize) vbox_menu.addWidget( self.gui_element_builder.get_svg_widget(Gui_Element.TOP_LEFT_SHORT, 100, 191, front_color)) button_list_widget = QListWidget() vbox_menu.addWidget(button_list_widget) vbox_menu.addWidget( self.gui_element_builder.get_svg_widget( Gui_Element.BOTTOM_LEFT_SHORT, 100, 191, front_color)) # Header ################################################################# self.main_layout.addWidget( self.gui_element_builder.get_svg_widget(Gui_Element.BUTTON, 33, 712, front_color), 0, 1, 1, 1, Qt.AlignTop) self.main_layout.addWidget( self.gui_element_builder.get_svg_widget(Gui_Element.BUTTON, 33, 52, front_color), 0, 3, Qt.AlignTop) self.main_layout.addWidget( self.gui_element_builder.get_svg_widget(Gui_Element.END_RIGHT, 33, 33, front_color), 0, 4, Qt.AlignTop) # Header - END ########################################################### # Menu self.main_layout.addLayout(vbox_menu, 0, 0, 4, 1) # Central Window self.main_layout.addWidget(self.subscreen_stacked_widget, 1, 1, 2, 4) # Footer ################################################################# self.main_layout.addWidget( self.gui_element_builder.get_svg_widget(Gui_Element.BUTTON, 33, 712, front_color), 3, 1, Qt.AlignBottom) # Add Exit Button exit_button = QPushButton("EXIT") exit_button.setFont(QFont(font, 20, QFont.Bold)) exit_button.setFixedSize(52, 33) exit_button.setStyleSheet("background:#ff0000; border:1px solid " + front_color + ";") exit_button.clicked.connect(lambda state: self.close()) self.main_layout.addWidget(exit_button, 3, 3, Qt.AlignBottom) self.main_layout.addWidget( self.gui_element_builder.get_svg_widget(Gui_Element.END_RIGHT, 33, 33, front_color, font), 3, 4, Qt.AlignBottom) # Footer - END ########################################################### # button_ListWidget.setVerticalScrolllayout(QAbstractItemView.ScrollMode.ScrollPerItem) button_list_widget.setStyleSheet("QListWidget{background:" + background_color + "; border: 0px solid " + front_color + ";}") # Erstellen der linken Button-Leiste ############## button_width = button_width * window_width / 100 button_height = button_height * window_height / 100 button_size = QSize(button_width, button_height) for i in range(0, self.number_of_subs): sub_button_list_item = QListWidgetItem(button_list_widget) placeholder_list_item = QListWidgetItem(button_list_widget) placeholder_list_item.setSizeHint(QSize(button_width, 4)) placeholder_list_item.setBackground(QColor(background_color)) flag = placeholder_list_item.flags() & Qt.ItemIsUserCheckable placeholder_list_item.setFlags(flag) # Widgets ################################################################################################## self.subscreen_stacked_widget.insertWidget( i, self.gui_subscreen_builder.init_with_config( self.screens_config['sub'][i], self.alarm_observer)) # Buttons ################################################################################################## self.gui_button_builder.set_color( self.screens_config['sub'][i]["Background"]) self.gui_button_builder.set_size(button_height, button_width) self.button[i] = self.gui_button_builder.create_button( self.screens_config["sub"][i]["name"], Gui_Element.BUTTON_TEXT) sub_button_list_item.setSizeHint(button_size) button_list_widget.addItem(placeholder_list_item) button_list_widget.addItem(sub_button_list_item) button_list_widget.setItemWidget(sub_button_list_item, self.button[i]) # signals ################################################################################################## self.button[i].clicked.connect( lambda widget=self.subscreen_stacked_widget.widget( i): self.set_current_subscreen()) # button_list_widget.setMaximumWidth(1000) button_list_widget.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) button_list_widget.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) button_list_widget.setMaximumWidth( button_list_widget.sizeHintForColumn(0)) ############################################# self.subscreen_stacked_widget.setCurrentIndex(0) self.main_stack_widget.insertWidget(0, self.main_widget) self.main_stack_widget.insertWidget(1, self.alarm_widget) self.main_stack_widget.setCurrentIndex(0) self.setCentralWidget(self.main_stack_widget)
class LabelStack(QWidget): """Manager of the label stack Attributes ---------- children : list Widgets created directly under in the 'hierarchy' mode_teste : bool Control of testing mode stack : QStackedWidget Stack of widgets of each type of label Methods ------- display(self, i) Changes currently displayed label widget """ def __init__(self, mode_teste): super().__init__() self.children = [] self.mode_teste = mode_teste main_layout = QVBoxLayout() # ---------- label = QLabel() label.setText("Choisir le type d'etiquette") label.setAlignment(Qt.AlignCenter) stack_options = QListWidget() stack_options.insertItem(0, 'Mondial Relay') stack_options.insertItem(1, 'Lettre suivie') stack_options.insertItem(2, 'Colissimo') vbox = QVBoxLayout() vbox.addWidget(label) vbox.addWidget(stack_options) # ---------- stack_mondial = MondialRelay(self.mode_teste) stack_lettre = LettreSuivie(self.mode_teste) stack_colissimo = Colissimo(self.mode_teste) self.stack = QStackedWidget() self.stack.addWidget(stack_mondial) self.stack.addWidget(stack_lettre) self.stack.addWidget(stack_colissimo) for i in range(self.stack.count()): self.children.append(self.stack.widget(i)) # ---------- hbox = QHBoxLayout() hbox.addLayout(vbox) hbox.addWidget(self.stack) main_layout.addLayout(hbox) # ---------- self.setLayout(main_layout) stack_options.currentRowChanged.connect(self.display) # ----------- Actions --------------------- def display(self, i): self.stack.setCurrentIndex(i)
class QueryWidget(QWidget): editorModified = pyqtSignal(bool) def __init__(self): super(QueryWidget, self).__init__() box = QVBoxLayout(self) box.setContentsMargins(0, 0, 0, 0) self._vsplitter = QSplitter(Qt.Vertical) self._hsplitter = QSplitter(Qt.Horizontal) self._result_list = lateral_widget.LateralWidget() self._result_list.header().hide() self._hsplitter.addWidget(self._result_list) self._stack_tables = QStackedWidget() self._hsplitter.addWidget(self._stack_tables) self.relations = {} self._query_editor = editor.Editor() # Editor connections self._query_editor.customContextMenuRequested.connect( self.__show_context_menu) self._query_editor.modificationChanged[bool].connect( self.__editor_modified) self._query_editor.undoAvailable[bool].connect( self.__on_undo_available) self._query_editor.redoAvailable[bool].connect( self.__on_redo_available) self._query_editor.copyAvailable[bool].connect( self.__on_copy_available) self._vsplitter.addWidget(self._query_editor) self._vsplitter.addWidget(self._hsplitter) box.addWidget(self._vsplitter) # Connections self._result_list.itemClicked.connect( lambda index: self._stack_tables.setCurrentIndex( self._result_list.row())) self._result_list.itemDoubleClicked.connect( self.show_relation) def __show_context_menu(self, point): popup_menu = self._query_editor.createStandardContextMenu() undock_editor = QAction(self.tr("Undock"), self) popup_menu.insertAction(popup_menu.actions()[0], undock_editor) popup_menu.insertSeparator(popup_menu.actions()[1]) undock_editor.triggered.connect(self.__undock_editor) popup_menu.exec_(self.mapToGlobal(point)) def __undock_editor(self): new_editor = editor.Editor() actual_doc = self._query_editor.document() new_editor.setDocument(actual_doc) new_editor.resize(900, 400) # Set text cursor tc = self._query_editor.textCursor() new_editor.setTextCursor(tc) # Set title db = Pireal.get_service("central").get_active_db() qc = db.query_container new_editor.setWindowTitle(qc.tab_text(qc.current_index())) new_editor.show() def __on_undo_available(self, value): """ Change state of undo action """ pireal = Pireal.get_service("pireal") action = pireal.get_action("undo_action") action.setEnabled(value) def __on_redo_available(self, value): """ Change state of redo action """ pireal = Pireal.get_service("pireal") action = pireal.get_action("redo_action") action.setEnabled(value) def __on_copy_available(self, value): """ Change states of cut and copy action """ cut_action = Pireal.get_action("cut_action") cut_action.setEnabled(value) copy_action = Pireal.get_action("copy_action") copy_action.setEnabled(value) def show_relation(self, item): central_widget = Pireal.get_service("central") table_widget = central_widget.get_active_db().table_widget rela = self.relations[item.name] dialog = QDialog(self) dialog.resize(700, 500) dialog.setWindowTitle(item.name) box = QVBoxLayout(dialog) box.setContentsMargins(5, 5, 5, 5) table = table_widget.create_table(rela) box.addWidget(table) hbox = QHBoxLayout() btn = QPushButton(self.tr("Ok")) btn.clicked.connect(dialog.close) hbox.addStretch() hbox.addWidget(btn) box.addLayout(hbox) dialog.show() def save_sizes(self): """ Save sizes of Splitters """ qsettings = QSettings(settings.SETTINGS_PATH, QSettings.IniFormat) qsettings.setValue('hsplitter_query_sizes', self._hsplitter.saveState()) qsettings.setValue('vsplitter_query_sizes', self._vsplitter.saveState()) def get_editor(self): return self._query_editor def __editor_modified(self, modified): self.editorModified.emit(modified) def showEvent(self, event): super(QueryWidget, self).showEvent(event) self._hsplitter.setSizes([1, self.width() / 3]) def clear_results(self): self._result_list.clear_items() i = self._stack_tables.count() while i >= 0: widget = self._stack_tables.widget(i) self._stack_tables.removeWidget(widget) if widget is not None: widget.deleteLater() i -= 1 def add_table(self, rela, rname): wtable = custom_table.Table() # Model model = QStandardItemModel() wtable.setModel(model) model.setHorizontalHeaderLabels(rela.header) for data in rela.content: nrow = model.rowCount() # wtable.insertRow(nrow) for col, text in enumerate(data): item = QStandardItem(text) item.setFlags(item.flags() & ~Qt.ItemIsEditable) model.setItem(nrow, col, item) index = self._stack_tables.addWidget(wtable) self._stack_tables.setCurrentIndex(index) self._result_list.add_item(rname, rela.count())
class PageManagerView(QFrame): def __init__(self, parent=None): super().__init__(parent) self.switch = QStackedWidget(self) label_view_page = self.add_page(LabelView()) send_email_page = self.add_page(SendEmailPageView()) contacts_page = self.add_page(ContactsPageView()) settings_page = self.add_page(OptionsPageView()) email_viewer_page = self.add_page(EmailViewerPageView()) self.sidebar = Sidebar(self) personal_wid = self.sidebar.add_item('Personal', ':images/personal_image.png') social_wid = self.sidebar.add_item('Social', ':images/social_image.png') updates_wid = self.sidebar.add_item('Updates', ':images/updates_image.png') promotions_wid = self.sidebar.add_item('Promotions', ':images/promotions_image.png') forums_wid = self.sidebar.add_item('Forums', ':images/forums_image.png') categories_label_ids = [ GMAIL_LABEL_PERSONAL, GMAIL_LABEL_SOCIAL, GMAIL_LABEL_UPDATES, GMAIL_LABEL_PROMOTIONS, GMAIL_LABEL_FORUMS ] group_id = self.sidebar.add_group('Other Labels') sent_wid = self.sidebar.add_item_to_group(group_id, 'Sent', ':images/sent_image.png') unread_wid = self.sidebar.add_item_to_group(group_id, 'Unread', ':images/unread_image.png') important_wid = self.sidebar.add_item_to_group(group_id, 'Important', ':images/important_image.png') starred_wid = self.sidebar.add_item_to_group(group_id, 'Starred', ':images/starred_image.png') trash_wid = self.sidebar.add_item_to_group(group_id, 'Trash', ':images/trash_image.png') spam_wid = self.sidebar.add_item_to_group(group_id, 'Spam', ':images/spam_image.png') other_labels_label_ids = [ GMAIL_LABEL_SENT, GMAIL_LABEL_UNREAD, GMAIL_LABEL_IMPORTANT, GMAIL_LABEL_STARRED, GMAIL_LABEL_TRASH, GMAIL_LABEL_SPAM ] self.item_id_to_label_id = {} self.label_id_to_item_id = {} send_email_wid = self.sidebar.add_item('Send Email', ':images/send_email_image.png') contacts_wid = self.sidebar.add_item('Contacts', ':images/contacts_image.png') self.sidebar.add_stretch() settings_wid = self.sidebar.add_item('Settings', ':images/options_button.png') def item_pressed_handler(widget_id, switch, item_id_to_label_id): if widget_id > settings_wid: # User defined label switch.setCurrentIndex(label_view_page) EmailEventChannel.publish( 'show_label', label_id=item_id_to_label_id[widget_id]) elif personal_wid <= widget_id <= forums_wid: switch.setCurrentIndex(label_view_page) EmailEventChannel.publish('show_label', label_id=categories_label_ids[widget_id]) elif sent_wid <= widget_id <= spam_wid: switch.setCurrentIndex(label_view_page) EmailEventChannel.publish( 'show_label', label_id=other_labels_label_ids[widget_id - group_id - 1]) elif widget_id == send_email_wid: switch.setCurrentIndex(send_email_page) elif widget_id == contacts_wid: switch.setCurrentIndex(contacts_page) elif widget_id == settings_wid: switch.setCurrentIndex(settings_page) self.item_pressed_handler = item_pressed_handler self.sidebar.on_item_pressed.connect(self.handle_item_pressed) def index_changed_handler(page_index, sidebar): if page_index == send_email_page: sidebar.select_item(send_email_wid) elif page_index == contacts_page: sidebar.select_item(contacts_wid) elif page_index == settings_page: sidebar.select_item(settings_wid) elif page_index == email_viewer_page: sidebar.select_item(None) else: assert False self.index_changed_handler = index_changed_handler main_layout = QHBoxLayout() main_layout.addWidget(self.sidebar) main_layout.addWidget(self.switch) main_layout.setContentsMargins(0, 0, 0, 0) self.setLayout(main_layout) EmailEventChannel.subscribe( 'email_response', lambda **kwargs: self.change_to_index(email_viewer_page)) ContactEventChannel.subscribe( 'contact_picked', lambda **kwargs: self.change_to_index(send_email_page)) ContactEventChannel.subscribe( 'pick_contact', lambda **kwargs: self.change_to_index(contacts_page)) ShortcutEventChannel.subscribe('personal', lambda **kwargs: self.show_label(personal_wid)) ShortcutEventChannel.subscribe('social', lambda **kwargs: self.show_label(social_wid)) ShortcutEventChannel.subscribe('updates', lambda **kwargs: self.show_label(updates_wid)) ShortcutEventChannel.subscribe('promotions', lambda **kwargs: self.show_label(promotions_wid)) ShortcutEventChannel.subscribe('forums', lambda **kwargs: self.show_label(forums_wid)) ShortcutEventChannel.subscribe('sent', lambda **kwargs: self.show_label(sent_wid)) ShortcutEventChannel.subscribe('unread', lambda **kwargs: self.show_label(unread_wid)) ShortcutEventChannel.subscribe('important', lambda **kwargs: self.show_label(important_wid)) ShortcutEventChannel.subscribe('starred', lambda **kwargs: self.show_label(starred_wid)) ShortcutEventChannel.subscribe('trash', lambda **kwargs: self.show_label(trash_wid)) ShortcutEventChannel.subscribe( 'spam', lambda **kwargs: self.show_label(spam_wid)) ShortcutEventChannel.subscribe('send_email', lambda **kwargs: self.show_label(send_email_wid)) ShortcutEventChannel.subscribe('contacts', lambda **kwargs: self.show_label(contacts_wid)) ShortcutEventChannel.subscribe('settings', lambda **kwargs: self.show_label(settings_wid)) self.other_labels_group_id = group_id EmailEventChannel.subscribe('labels_sync', self.handle_labels_sync) EmailEventChannel.publish('labels_request') def handle_item_pressed(self, widget_id): self.item_pressed_handler(widget_id, self.switch, self.item_id_to_label_id) def add_page(self, page): self.switch.addWidget(page) return self.switch.count() - 1 def add_page_switch_rule(self, page_idx, event_channel, topic): event_channel.subscribe(topic, lambda **kwargs: self.change_to_index(page_idx)) def change_to_index(self, page_idx): self.switch.setCurrentIndex(page_idx) self.index_changed_handler(page_idx, self.sidebar) def show_label(self, widget_id): self.item_pressed_handler(widget_id, self.switch, self.item_id_to_label_id) self.sidebar.select_item(widget_id) def handle_labels_sync(self, labels, error=''): assert not error if 'all' in labels: for l in labels['all']: lbl_obj = Label(*l) if lbl_obj.type == USER_LABEL: item_id = self.sidebar.add_item_to_group( self.other_labels_group_id, lbl_obj.name, ':images/label_image.png') self.item_id_to_label_id[item_id] = lbl_obj.id self.label_id_to_item_id[lbl_obj.id] = item_id else: for l in labels['added']: lbl_obj = Label(*l) if lbl_obj.type == USER_LABEL: item_id = self.sidebar.add_item_to_group( self.other_labels_group_id, lbl_obj.name, ':images/label_image.png') self.item_id_to_label_id[item_id] = lbl_obj.id self.label_id_to_item_id[lbl_obj.id] = item_id for l in labels['modified']: lbl_obj = Label(*l) if lbl_obj.type == USER_LABEL: item_id = self.label_id_to_item_id[lbl_obj.id] self.sidebar.change_item_name(item_id, lbl_obj.name) for l in labels['deleted']: lbl_obj = Label(*l) if lbl_obj.type == USER_LABEL: item_id = self.label_id_to_item_id[lbl_obj.id] self.sidebar.remove_item(item_id, self.other_labels_group_id)
class CentralWidget(QWidget): # This signals is used by notificator databaseSaved = pyqtSignal('QString') querySaved = pyqtSignal('QString') def __init__(self): QWidget.__init__(self) box = QVBoxLayout(self) box.setContentsMargins(0, 0, 0, 0) box.setSpacing(0) self.stacked = QStackedWidget() box.addWidget(self.stacked) self.created = False self.__last_open_folder = None self.__recent_dbs = [] if PSetting.RECENT_DBS: self.__recent_dbs = PSetting.RECENT_DBS Pireal.load_service("central", self) @property def recent_databases(self): return self.__recent_dbs @recent_databases.setter def recent_databases(self, database_file): if database_file in PSetting.RECENT_DBS: PSetting.RECENT_DBS.remove(database_file) PSetting.RECENT_DBS.insert(0, database_file) self.__recent_dbs = PSetting.RECENT_DBS def create_database(self): """ Show a wizard widget to create a new database, only have one database open at time. """ if self.created: QMessageBox.information(self, self.tr("Information"), self.tr("You may only have one database" " open at time.")) DEBUG("Ya existe una base de datos abierta") return wizard = database_wizard.DatabaseWizard(self) wizard.wizardFinished.connect( self.__on_wizard_finished) # Hide menubar and toolbar pireal = Pireal.get_service("pireal") pireal.show_hide_menubar() pireal.show_hide_toolbar() # Add wizard widget to stacked self.add_widget(wizard) def __on_wizard_finished(self, data, wizard_widget): """ This slot execute when wizard to create a database is finished """ pireal = Pireal.get_service("pireal") if not data: # If it's canceled, remove wizard widget and return to Start Page self.remove_last_widget() else: # Create a new data base container db_container = database_container.DatabaseContainer() # Associate the file name with the PFile object pfile_object = pfile.File(data['filename']) # Associate PFile object with data base container # and add widget to stacked db_container.pfile = pfile_object self.add_widget(db_container) # Remove wizard self.stacked.removeWidget(wizard_widget) # Set window title pireal.change_title(file_manager.get_basename(data['filename'])) # Enable db actions pireal.set_enabled_db_actions(True) pireal.set_enabled_relation_actions(True) self.created = True DEBUG("Base de datos creada correctamente: '{}'".format( data['filename'])) # If data or not, show menubar and toolbar again pireal.show_hide_menubar() pireal.show_hide_toolbar() def open_database(self, filename=''): """ This function opens a database and set this on the UI """ # If not filename provide, then open dialog to select if self.created: QMessageBox.information(self, self.tr("Information"), self.tr("You may only have one database" " open at time.")) DEBUG("Ya existe una base de datos abierta") return if not filename: if self.__last_open_folder is None: directory = os.path.expanduser("~") else: directory = self.__last_open_folder filter_ = settings.SUPPORTED_FILES.split(';;')[0] filename, _ = QFileDialog.getOpenFileName(self, self.tr("Open Database"), directory, filter_) # If is canceled, return if not filename: return # Remember the folder self.__last_open_folder = file_manager.get_path(filename) DEBUG("Abriendo la base de datos: '{}'".format(filename)) # If filename provide try: # Read pdb file pfile_object = pfile.File(filename) db_data = pfile_object.read() # Create a dict to manipulate data more easy db_data = self.__sanitize_data(db_data) except Exception as reason: QMessageBox.information(self, self.tr("The file couldn't be open"), str(reason)) CRITICAL("Error al intentar abrir el archivo: {}".format(reason)) return # Create a database container widget db_container = database_container.DatabaseContainer() try: db_container.create_database(db_data) except Exception as reason: QMessageBox.information(self, self.tr("Error"), str(reason)) CRITICAL("Error al crear la base de datos: {}".format(reason)) return # Set the PFile object to the new database db_container.pfile = pfile_object # Add data base container to stacked self.add_widget(db_container) # Database name db_name = file_manager.get_basename(filename) # Update title with the new database name, and enable some actions pireal = Pireal.get_service("pireal") pireal.change_title(db_name) pireal.set_enabled_db_actions(True) pireal.set_enabled_relation_actions(True) # Add to recent databases self.recent_databases = filename self.created = True def open_query(self): filter_ = settings.SUPPORTED_FILES.split(';;')[1] filename, _ = QFileDialog.getOpenFileName(self, self.tr("Open Query"), os.path.expanduser("~"), filter_) if not filename: return # FIXME: mejorar éste y new_query self.new_query(filename) def save_query(self, editor=None): db = self.get_active_db() fname = db.save_query(editor) if fname: self.querySaved.emit(self.tr("Query saved: {}".format(fname))) def save_query_as(self): pass def __sanitize_data(self, data): """ This function converts the data into a dictionary for better handling then. The argument 'data' is the content of the database. """ # FIXME: controlar cuando al final de la línea hay una coma data_dict = {'tables': []} for line_count, line in enumerate(data.splitlines()): # Ignore blank lines if not line: continue if line.startswith('@'): # This line is a header tpoint = line.find(':') if tpoint == -1: raise Exception("Invalid syntax at line {}".format( line_count + 1)) table_name, line = line.split(':') table_name = table_name[1:].strip() table_dict = {} table_dict['name'] = table_name table_dict['header'] = line.split(',') table_dict['tuples'] = [] else: for l in csv.reader([line]): # Remove spaces l = list(map(str.strip, l)) # FIXME: this is necesary? if table_dict['name'] == table_name: table_dict['tuples'].append(l) if not table_dict['tuples']: data_dict['tables'].append(table_dict) return data_dict def remove_last_widget(self): """ Remove last widget from stacked """ widget = self.stacked.widget(self.stacked.count() - 1) self.stacked.removeWidget(widget) def close_database(self): """ Close the database and return to the main widget """ db = self.get_active_db() query_container = db.query_container if db.modified: msgbox = QMessageBox(self) msgbox.setIcon(QMessageBox.Question) msgbox.setWindowTitle(self.tr("Save Changes?")) msgbox.setText(self.tr("The <b>{}</b> database has ben" " modified.<br>Do you want save " "your changes?".format( db.dbname()))) cancel_btn = msgbox.addButton(self.tr("Cancel"), QMessageBox.RejectRole) msgbox.addButton(self.tr("No"), QMessageBox.NoRole) yes_btn = msgbox.addButton(self.tr("Yes"), QMessageBox.YesRole) msgbox.exec_() r = msgbox.clickedButton() if r == cancel_btn: return if r == yes_btn: self.save_database() # Check if editor is modified query_widget = query_container.currentWidget() if query_widget is not None: weditor = query_widget.get_editor() if weditor is not None: # TODO: duplicate code, see tab widget if weditor.modified: msgbox = QMessageBox(self) msgbox.setIcon(QMessageBox.Question) msgbox.setWindowTitle(self.tr("File modified")) msgbox.setText(self.tr("The file <b>{}</b> has unsaved " "changes. You want to keep " "them?".format( weditor.name))) cancel_btn = msgbox.addButton(self.tr("Cancel"), QMessageBox.RejectRole) msgbox.addButton(self.tr("No"), QMessageBox.NoRole) yes_btn = msgbox.addButton(self.tr("Yes"), QMessageBox.YesRole) msgbox.exec_() r = msgbox.clickedButton() if r == cancel_btn: return if r == yes_btn: self.save_query(weditor) self.stacked.removeWidget(db) pireal = Pireal.get_service("pireal") pireal.set_enabled_db_actions(False) pireal.set_enabled_relation_actions(False) pireal.set_enabled_query_actions(False) pireal.set_enabled_editor_actions(False) self.created = False DEBUG("Se cerró la base de datos: '{}'".format(db.dbname())) del db def new_query(self, filename=''): pireal = Pireal.get_service("pireal") db_container = self.get_active_db() db_container.new_query(filename) # Enable editor actions # FIXME: refactoring pireal.set_enabled_query_actions(True) zoom_in_action = Pireal.get_action("zoom_in") zoom_in_action.setEnabled(True) zoom_out_action = Pireal.get_action("zoom_out") zoom_out_action.setEnabled(True) paste_action = Pireal.get_action("paste_action") paste_action.setEnabled(True) comment_action = Pireal.get_action("comment") comment_action.setEnabled(True) uncomment_action = Pireal.get_action("uncomment") uncomment_action.setEnabled(True) def execute_queries(self): db_container = self.get_active_db() db_container.execute_queries() def execute_selection(self): db_container = self.get_active_db() db_container.execute_selection() def save_database(self): db = self.get_active_db() if not db.modified: return # Get relations dict relations = db.table_widget.relations # Generate content content = file_manager.generate_database(relations) db.pfile.save(content=content) filename = db.pfile.filename # Emit signal self.databaseSaved.emit( self.tr("Database saved: {}".format(filename))) db.modified = False def save_database_as(self): filter = settings.SUPPORTED_FILES.split(';;')[0] filename, _ = QFileDialog.getSaveFileName(self, self.tr("Save Database As"), settings.PIREAL_PROJECTS, filter) if not filename: return db = self.get_active_db() # Get relations relations = db.table_widget.relations # Content content = file_manager.generate_database(relations) db.pfile.save(content, filename) self.databaseSaved.emit( self.tr("Database saved: {}".format(db.pfile.filename))) db.modified = False def remove_relation(self): db = self.get_active_db() if db.delete_relation(): db.modified = True def create_new_relation(self): data = new_relation_dialog.create_relation() if data is not None: db = self.get_active_db() rela, rela_name = data # Add table db.table_widget.add_table(rela, rela_name) # Add item to lateral widget db.lateral_widget.add_item(rela_name, rela.count()) # Set modified db db.modified = True def edit_relation(self): db = self.get_active_db() lateral = db.lateral_widget selected_items = lateral.selectedItems() if selected_items: selected_relation = selected_items[0].text(0) relation_text = selected_relation.split()[0].strip() rela = db.table_widget.relations[relation_text] data = edit_relation_dialog.edit_relation(rela) if data is not None: # Update table db.table_widget.update_table(data) # Update relation db.table_widget.relations[relation_text] = data # Set modified db db.modified = True lateral.update_item(data.count()) def load_relation(self, filename=''): """ Load Relation file """ if not filename: if self.__last_open_folder is None: directory = os.path.expanduser("~") else: directory = self.__last_open_folder msg = self.tr("Open Relation File") filter_ = settings.SUPPORTED_FILES.split(';;')[-1] filenames = QFileDialog.getOpenFileNames(self, msg, directory, filter_)[0] if not filenames: return # Save folder self.__last_open_folder = file_manager.get_path(filenames[0]) db_container = self.get_active_db() if db_container.load_relation(filenames): db_container.modified = True def add_start_page(self): """ This function adds the Start Page to the stacked widget """ sp = start_page.StartPage() self.add_widget(sp) def show_settings(self): """ Show settings dialog on stacked """ preferences_dialog = preferences.Preferences(self) if isinstance(self.widget(1), preferences.Preferences): self.widget(1).close() else: self.stacked.insertWidget(1, preferences_dialog) self.stacked.setCurrentIndex(1) # Connect the closed signal preferences_dialog.settingsClosed.connect(self._settings_closed) def widget(self, index): """ Returns the widget at the given index """ return self.stacked.widget(index) def add_widget(self, widget): """ Appends and show the given widget to the Stacked """ index = self.stacked.addWidget(widget) self.stacked.setCurrentIndex(index) def _settings_closed(self): self.stacked.removeWidget(self.widget(1)) self.stacked.setCurrentWidget(self.stacked.currentWidget()) def get_active_db(self): """ Return an instance of DatabaseContainer widget if the stacked contains an DatabaseContainer in last index or None if it's not an instance of DatabaseContainer """ index = self.stacked.count() - 1 widget = self.widget(index) if isinstance(widget, database_container.DatabaseContainer): return widget return None def get_unsaved_queries(self): query_container = self.get_active_db().query_container return query_container.get_unsaved_queries() def undo_action(self): query_container = self.get_active_db().query_container query_container.undo() def redo_action(self): query_container = self.get_active_db().query_container query_container.redo() def cut_action(self): query_container = self.get_active_db().query_container query_container.cut() def copy_action(self): query_container = self.get_active_db().query_container query_container.copy() def paste_action(self): query_container = self.get_active_db().query_container query_container.paste() def zoom_in(self): query_container = self.get_active_db().query_container query_container.zoom_in() def zoom_out(self): query_container = self.get_active_db().query_container query_container.zoom_out() def comment(self): query_container = self.get_active_db().query_container query_container.comment() def uncomment(self): query_container = self.get_active_db().query_container query_container.uncomment()
class MainWindow(QWidget): def __init__(self, title="Raven"): super().__init__() # self.window_width = 350 # self.window_height = 600 # self.setFixedSize(self.window_width, self.window_height) self.showMaximized() self.setWindowTitle(title) self.init_ui() def init_ui(self): bottom_bar = BottomWidget(self) bottom_bar.home_click_listener.connect(self.on_home_click) bottom_bar.logout_click_listener.connect(self.on_logout_click) self.fragment = QStackedWidget() layout = QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) layout.addWidget(self.fragment) bottom_bar.on_back_press_listener.connect(self.on_back_press) layout.addWidget(bottom_bar) self.setLayout(layout) # self.add_screen(LoginWidget("Login Screen")) # self.add_screen(CameraWidget()) self.add_screen(AttendenceOptionWidget()) def add_screen(self, screen: base_widget): screen.add_screen_listener.connect(self.add_screen) screen.on_back_press_listener.connect(self.on_back_press) self.fragment.addWidget(screen) self.fragment.setCurrentIndex(self.fragment.count() - 1) self.updateUi() def on_back_press(self): if self.fragment.count() > 1: widget = self.fragment.currentWidget() self.fragment.removeWidget(widget) widget.deleteLater() self.updateUi() def updateUi(self): widget: base_widget = self.fragment.currentWidget() self.setWindowTitle(widget.title) def on_home_click(self): self.remove_all_screen() def remove_all_screen(self): while self.fragment.count() > 1: widget = self.fragment.currentWidget() self.fragment.removeWidget(widget) widget.deleteLater() self.updateUi() def on_logout_click(self): print("On Logout Click")
class AppSettings(QDialog): SettingsWidgets = [] def __init__(self, conf, **kwargs): super().__init__(**kwargs) self.conf = conf self.setWindowTitle(translate('AppSettings', 'LiSP preferences')) self.setWindowModality(QtCore.Qt.ApplicationModal) self.setMaximumSize(635, 530) self.setMinimumSize(635, 530) self.resize(635, 530) self.listWidget = QListWidget(self) self.listWidget.setGeometry(QtCore.QRect(5, 10, 185, 470)) self.sections = QStackedWidget(self) self.sections.setGeometry(QtCore.QRect(200, 10, 430, 470)) for widget in self.SettingsWidgets: widget = widget(parent=self) widget.resize(430, 465) widget.load_settings(self.conf) self.listWidget.addItem(translate('SettingsPageName', widget.Name)) self.sections.addWidget(widget) if self.SettingsWidgets: self.listWidget.setCurrentRow(0) self.listWidget.currentItemChanged.connect(self._change_page) self.dialogButtons = QDialogButtonBox(self) self.dialogButtons.setGeometry(10, 495, 615, 30) self.dialogButtons.setStandardButtons(QDialogButtonBox.Cancel | QDialogButtonBox.Ok) self.dialogButtons.rejected.connect(self.reject) self.dialogButtons.accepted.connect(self.accept) def get_configuraton(self): conf = {} for n in range(self.sections.count()): widget = self.sections.widget(n) newconf = widget.get_settings() deep_update(conf, newconf) return conf @classmethod def register_settings_widget(cls, widget): if widget not in cls.SettingsWidgets: cls.SettingsWidgets.append(widget) @classmethod def unregister_settings_widget(cls, widget): if widget in cls.SettingsWidgets: cls.SettingsWidgets.remove(widget) def _change_page(self, current, previous): if not current: current = previous self.sections.setCurrentIndex(self.listWidget.row(current))
class AccountCreationPresenter(QWidget): """ Handles data-validation, saving, and transitions between frames """ should_load_main_app = pyqtSignal(Account) # Signal to reload LandingWindow def __init__(self, parent: QObject): super().__init__(parent) self.__layout_manager = QVBoxLayout(self) self.__slide_stack = QStackedWidget() # Stores the various frames for creating an account self.__proceed_btn = QPushButton("Get Started") # Changes to say next or finish, slide-depending # Connect events self.__proceed_btn.clicked.connect(self.__proceed_btn_was_clicked) self.__setup_ui() @QtCore.pyqtSlot() def __proceed_btn_was_clicked(self): curr_idx = self.__slide_stack.currentIndex() if curr_idx == 0 and self.__slide_stack.isHidden(): # Get Started, must show self.__slide_stack.show() self.__proceed_btn.setText("Next") elif self.__slide_stack.currentWidget().is_valid(): if curr_idx + 1 == self.__slide_stack.count() - 1: # Second to last slide\ self.__proceed_btn.setText("Finish") self.__slide_stack.setCurrentIndex(curr_idx + 1) elif curr_idx == self.__slide_stack.count() - 1: # Last slide # Save account data to global account = Account() for i in range(self.__slide_stack.count()): self.__slide_stack.widget(i).fill_account_details(account) write_to_data_file(DataType.USER, FileName.GLOBAL, account, False) # Port-forward, if approved self.hide() self.should_load_main_app.emit(account) else: self.__slide_stack.setCurrentIndex(curr_idx + 1) def __setup_ui(self): # Create frames and populate stack self.__slide_stack.addWidget(AccountDetailsFrame(self.__slide_stack)) self.__slide_stack.addWidget(NetworkDetailsFrame(self.__slide_stack)) self.__slide_stack.hide() # Set up welcome top-half content_frame = QFrame() content_frame.setFrameShape(QFrame.StyledPanel) content_frame.setFrameShadow(QFrame.Raised) content_frame.setObjectName("create-account-top") content_layout = QVBoxLayout(content_frame) # # Create labels welcome_lbl = QLabel('Welcome to UChat.') welcome_lbl.setObjectName('create-account-welcome') sub_lbl = QLabel('A secure and stateless, peer-to-peer messaging client') sub_lbl.setObjectName('sub-lbl') # # # Create separation line line = QFrame() line.setFixedHeight(1) line.setObjectName("line") line.setFrameShape(QFrame.HLine) line.setFrameShadow(QFrame.Sunken) # Configure proceed button self.__proceed_btn.setFixedSize(QSize(85, 35)) # # Layout content_layout.addWidget(welcome_lbl) content_layout.addWidget(sub_lbl) content_layout.addSpacing(10) content_layout.addWidget(line) content_layout.addWidget(self.__slide_stack) self.__layout_manager.addWidget(content_frame) self.__layout_manager.addSpacing(35) self.__layout_manager.addWidget(self.__proceed_btn) self.__layout_manager.addStretch()
class BottomPanel(QWidget): def __init__(self, app, parent=None): super().__init__(parent) self._app = app self._layout = QHBoxLayout(self) self.back_btn = ToolbarButton('⇦', self) self.forward_btn = ToolbarButton('⇨', self) self.magicbox = MagicBox(self._app) self._stack_switch = ToolbarButton('⁐', self) self._stacked_widget = QStackedWidget(self) self._stacked_widget.addWidget(self.magicbox) self._stack_switch.hide() self.status_line = StatusLine(self._app) self.settings_btn = ToolbarButton('⋮', self) self.settings_btn.setToolTip('配置') # initialize widgets self.status_line.add_item(StatusLineItem('plugin', PluginStatus(self._app))) self.status_line.add_item(StatusLineItem('notify', NotifyStatus(self._app))) self.back_btn.setEnabled(False) self.forward_btn.setEnabled(False) self.back_btn.clicked.connect(self._app.browser.back) self.forward_btn.clicked.connect(self._app.browser.forward) self._stack_switch.clicked.connect(self._show_next_stacked_widget) self._setup_ui() def _setup_ui(self): self.setObjectName('bottom_panel') self._layout.addSpacing(5) self._layout.addWidget(self.back_btn) self._layout.addSpacing(5) self._layout.addWidget(self.forward_btn) self._layout.addSpacing(80) self._layout.addWidget(self._stacked_widget) self._layout.addSpacing(10) self._layout.addWidget(self._stack_switch) # self._layout.addStretch(0) self._layout.addSpacing(80) self._layout.addWidget(self.status_line) self._layout.addWidget(self.settings_btn) # assume the magicbox height is about 30 h_margin, v_margin = 5, 10 height = self.magicbox.height() self.setFixedHeight(height + v_margin * 2 + 10) self._layout.setContentsMargins(h_margin, v_margin, h_margin, v_margin) self._layout.setSpacing(0) def _show_next_stacked_widget(self): current_index = self._stacked_widget.currentIndex() if current_index < self._stacked_widget.count() - 1: next_index = current_index + 1 else: next_index = 0 self._stacked_widget.setCurrentIndex(next_index) def add_stacked_widget(self, widget): """ .. versionadded:: 3.7.10 """ self._stacked_widget.addWidget(widget) if self._stacked_widget.count() > 1: self._stack_switch.show() def set_top_stacked_widget(self, widget): """ .. versionadded:: 3.7.10 """ self._stacked_widget.setCurrentWidget(widget) def clear_stacked_widget(self): """ .. versionadded:: 3.7.10 """ while self._stacked_widget.count() > 0: self._stacked_widget.removeWidget(self._stacked_widget.currentWidget()) self._stack_switch.hide() def show_and_focus_magicbox(self): """Show and focus magicbox .. versionadded:: 3.7.10 """ if self._stacked_widget.indexOf(self.magicbox) != -1: self.set_top_stacked_widget(self.magicbox) self.magicbox.setFocus()
class PyMultiPageWidget(QWidget): currentIndexChanged = pyqtSignal(int) pageTitleChanged = pyqtSignal(str) def __init__(self, parent=None): super(PyMultiPageWidget, self).__init__(parent) self.comboBox = QComboBox() # MAGIC # It is important that the combo box has an object name beginning # with '__qt__passive_', otherwise, it is inactive in the form editor # of the designer and you can't change the current page via the # combo box. # MAGIC self.comboBox.setObjectName('__qt__passive_comboBox') self.stackWidget = QStackedWidget() self.comboBox.activated.connect(self.setCurrentIndex) self.layout = QVBoxLayout() self.layout.addWidget(self.comboBox) self.layout.addWidget(self.stackWidget) self.setLayout(self.layout) def sizeHint(self): return QSize(200, 150) def count(self): return self.stackWidget.count() def widget(self, index): return self.stackWidget.widget(index) @pyqtSlot(QWidget) def addPage(self, page): self.insertPage(self.count(), page) @pyqtSlot(int, QWidget) def insertPage(self, index, page): page.setParent(self.stackWidget) self.stackWidget.insertWidget(index, page) title = page.windowTitle() if title == "": title = "Page %d" % (self.comboBox.count() + 1) page.setWindowTitle(title) self.comboBox.insertItem(index, title) @pyqtSlot(int) def removePage(self, index): widget = self.stackWidget.widget(index) self.stackWidget.removeWidget(widget) self.comboBox.removeItem(index) def getPageTitle(self): return self.stackWidget.currentWidget().windowTitle() @pyqtSlot(str) def setPageTitle(self, newTitle): self.comboBox.setItemText(self.getCurrentIndex(), newTitle) self.stackWidget.currentWidget().setWindowTitle(newTitle) self.pageTitleChanged.emit(newTitle) def getCurrentIndex(self): return self.stackWidget.currentIndex() @pyqtSlot(int) def setCurrentIndex(self, index): if index != self.getCurrentIndex(): self.stackWidget.setCurrentIndex(index) self.comboBox.setCurrentIndex(index) self.currentIndexChanged.emit(index) pageTitle = pyqtProperty(str, fget=getPageTitle, fset=setPageTitle, stored=False) currentIndex = pyqtProperty(int, fget=getCurrentIndex, fset=setCurrentIndex)
class Settings(QDialog): """Window showing the Settings/settings. Parameters ---------- parent : instance of QMainWindow the main window """ def __init__(self, parent): super().__init__(None, Qt.WindowSystemMenuHint | Qt.WindowTitleHint) self.parent = parent self.config = ConfigUtils(self.parent.update) self.setWindowTitle('Settings') self.create_settings() def create_settings(self): """Create the widget, organized in two parts. Notes ----- When you add widgets in config, remember to update show_settings too """ bbox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Apply | QDialogButtonBox.Cancel) self.idx_ok = bbox.button(QDialogButtonBox.Ok) self.idx_apply = bbox.button(QDialogButtonBox.Apply) self.idx_cancel = bbox.button(QDialogButtonBox.Cancel) bbox.clicked.connect(self.button_clicked) page_list = QListWidget() page_list.setSpacing(1) page_list.currentRowChanged.connect(self.change_widget) pages = ['General', 'Overview', 'Signals', 'Channels', 'Spectrum', 'Notes', 'Video'] for one_page in pages: page_list.addItem(one_page) self.stacked = QStackedWidget() self.stacked.addWidget(self.config) self.stacked.addWidget(self.parent.overview.config) self.stacked.addWidget(self.parent.traces.config) self.stacked.addWidget(self.parent.channels.config) self.stacked.addWidget(self.parent.spectrum.config) self.stacked.addWidget(self.parent.notes.config) self.stacked.addWidget(self.parent.video.config) hsplitter = QSplitter() hsplitter.addWidget(page_list) hsplitter.addWidget(self.stacked) btnlayout = QHBoxLayout() btnlayout.addStretch(1) btnlayout.addWidget(bbox) vlayout = QVBoxLayout() vlayout.addWidget(hsplitter) vlayout.addLayout(btnlayout) self.setLayout(vlayout) def change_widget(self, new_row): """Change the widget on the right side. Parameters ---------- new_row : int index of the widgets """ self.stacked.setCurrentIndex(new_row) def button_clicked(self, button): """Action when button was clicked. Parameters ---------- button : instance of QPushButton which button was pressed """ if button in (self.idx_ok, self.idx_apply): # loop over widgets, to see if they were modified for i_config in range(self.stacked.count()): one_config = self.stacked.widget(i_config) if one_config.modified: lg.debug('Settings for ' + one_config.widget + ' were modified') one_config.get_values() if self.parent.info.dataset is not None: one_config.update_widget() one_config.modified = False if button == self.idx_ok: self.accept() if button == self.idx_cancel: self.reject()
class SubTabWidget(QWidget): _tabChanged = pyqtSignal(int, name = "tabChanged") def __init__(self, subtitleData, videoWidget, parent = None): super(SubTabWidget, self).__init__(parent) self._subtitleData = subtitleData self.__initTabWidget(videoWidget) def __initTabWidget(self, videoWidget): settings = SubSettings() mainLayout = QVBoxLayout(self) mainLayout.setContentsMargins(0, 0, 0, 0) mainLayout.setSpacing(0) #TabBar self.tabBar = QTabBar(self) # Splitter (bookmarks + pages) self.splitter = QSplitter(self) self.splitter.setObjectName("sidebar_splitter") self._toolbox = ToolBox(self._subtitleData, self) self._toolbox.setObjectName("sidebar") self._toolbox.setMinimumWidth(100) self._toolbox.addTool(Details(self._subtitleData, self)) self._toolbox.addTool(Synchronizer(videoWidget, self._subtitleData, self)) self._toolbox.addTool(History(self)) self.rightWidget = QWidget() rightLayout = QGridLayout() rightLayout.setContentsMargins(0, 0, 0, 0) self.rightWidget.setLayout(rightLayout) self._mainTab = FileList(_("Subtitles"), self._subtitleData, self) self.pages = QStackedWidget(self) rightLayout.addWidget(self.pages, 0, 0) self.tabBar.addTab(self._mainTab.name) self.pages.addWidget(self._mainTab) self.splitter.addWidget(self._toolbox) self.splitter.addWidget(self.rightWidget) self.__drawSplitterHandle(1) # Setting widgets mainLayout.addWidget(self.tabBar) mainLayout.addWidget(self.splitter) # Widgets settings self.tabBar.setMovable(True) self.tabBar.setTabsClosable(True) self.tabBar.setExpanding(False) # Don't resize left panel if it's not needed leftWidgetIndex = self.splitter.indexOf(self._toolbox) rightWidgetIndex = self.splitter.indexOf(self.rightWidget) self.splitter.setStretchFactor(leftWidgetIndex, 0) self.splitter.setStretchFactor(rightWidgetIndex, 1) self.splitter.setCollapsible(leftWidgetIndex, False) self.splitter.setSizes([250]) # Some signals self.tabBar.currentChanged.connect(self.showTab) self.tabBar.tabCloseRequested.connect(self.closeTab) self.tabBar.tabMoved.connect(self.moveTab) self._mainTab.requestOpen.connect(self.openTab) self._mainTab.requestRemove.connect(self.removeFile) self.tabChanged.connect(lambda i: self._toolbox.setContentFor(self.tab(i))) self.setLayout(mainLayout) def __addTab(self, filePath): """Returns existing tab index. Creates a new one if it isn't opened and returns its index otherwise.""" for i in range(self.tabBar.count()): widget = self.pages.widget(i) if not widget.isStatic and filePath == widget.filePath: return i tab = SubtitleEditor(filePath, self._subtitleData, self) newIndex = self.tabBar.addTab(self._createTabName(tab.name, tab.history.isClean())) tab.history.cleanChanged.connect( lambda clean: self._cleanStateForFileChanged(filePath, clean)) self.pages.addWidget(tab) return newIndex def __drawSplitterHandle(self, index): splitterHandle = self.splitter.handle(index) splitterLayout = QVBoxLayout(splitterHandle) splitterLayout.setSpacing(0) splitterLayout.setContentsMargins(0, 0, 0, 0) line = QFrame(splitterHandle) line.setFrameShape(QFrame.HLine) line.setFrameShadow(QFrame.Sunken) splitterLayout.addWidget(line) splitterHandle.setLayout(splitterLayout) def _createTabName(self, name, cleanState): if cleanState is True: return name else: return "%s +" % name def _cleanStateForFileChanged(self, filePath, cleanState): page = self.tabByPath(filePath) if page is not None: for i in range(self.tabBar.count()): if self.tabBar.tabText(i)[:len(page.name)] == page.name: self.tabBar.setTabText(i, self._createTabName(page.name, cleanState)) return def saveWidgetState(self, settings): settings.setState(self.splitter, self.splitter.saveState()) settings.setHidden(self._toolbox, self._toolbox.isHidden()) def restoreWidgetState(self, settings): self.showPanel(not settings.getHidden(self._toolbox)) splitterState = settings.getState(self.splitter) if not splitterState.isEmpty(): self.splitter.restoreState(settings.getState(self.splitter)) @pyqtSlot(str, bool) def openTab(self, filePath, background=False): if self._subtitleData.fileExists(filePath): tabIndex = self.__addTab(filePath) if background is False: self.showTab(tabIndex) else: log.error(_("SubtitleEditor not created for %s!" % filePath)) @pyqtSlot(str) def removeFile(self, filePath): tab = self.tabByPath(filePath) command = RemoveFile(filePath) if tab is not None: index = self.pages.indexOf(tab) if self.closeTab(index): self._subtitleData.execute(command) else: self._subtitleData.execute(command) @pyqtSlot(int) def closeTab(self, index): tab = self.tab(index) if tab.canClose(): widgetToRemove = self.pages.widget(index) self.tabBar.removeTab(index) self.pages.removeWidget(widgetToRemove) widgetToRemove.deleteLater() return True return False def count(self): return self.tabBar.count() def currentIndex(self): return self.tabBar.currentIndex() def currentPage(self): return self.pages.currentWidget() @pyqtSlot(int, int) def moveTab(self, fromIndex, toIndex): fromWidget = self.pages.widget(fromIndex) toWidget = self.pages.widget(toIndex) if fromWidget.isStatic or toWidget.isStatic: self.tabBar.blockSignals(True) # signals would cause infinite recursion self.tabBar.moveTab(toIndex, fromIndex) self.tabBar.blockSignals(False) return else: self.pages.removeWidget(fromWidget) self.pages.removeWidget(toWidget) if fromIndex < toIndex: self.pages.insertWidget(fromIndex, toWidget) self.pages.insertWidget(toIndex, fromWidget) else: self.pages.insertWidget(toIndex, fromWidget) self.pages.insertWidget(fromIndex, toWidget) # Hack # Qt changes tabs during mouse drag and dropping. The next line is added # to prevent it. self.showTab(self.tabBar.currentIndex()) @pyqtSlot(int) def showTab(self, index): showWidget = self.pages.widget(index) if showWidget: self.pages.setCurrentWidget(showWidget) self.tabBar.blockSignals(True) self.tabBar.setCurrentIndex(index) self.tabBar.blockSignals(False) # Try to update current tab. showWidget.updateTab() self._tabChanged.emit(index) def showPanel(self, val): if val is True: self._toolbox.show() else: self._toolbox.hide() def togglePanel(self): if self._toolbox.isHidden(): self._toolbox.show() else: self._toolbox.hide() def tab(self, index): return self.pages.widget(index) def tabByPath(self, path): for i in range(self.pages.count()): page = self.tab(i) if not page.isStatic and page.filePath == path: return page return None @property def fileList(self): return self._mainTab
class MyApp(QWidget): def __init__(self): super().__init__() self.setWindowTitle('Tic-Tac-Toe') self.resize(2500, 1400) self.setStyleSheet(open("style.qss").read()) self.currentTranslator = None self.stack = QStackedWidget(self) self.stackDict = {} # the different windows to show self._mainWindow = MainWindow(self) self._subMenu = SubMenu(self) self._board = Board(self) self.stack.addWidget(self._mainWindow) self.stackDict["main"] = 0 self.stack.addWidget(self._subMenu) self.stackDict["sub"] = 1 self.stack.addWidget(self._board) self.stackDict["board"] = 2 # init the timer self.timer = QTimer() self.timer.setSingleShot(True) self.timer.timeout.connect(self.showWarning) self.timerWarning = QTimer() self.timerWarning.setSingleShot(True) self.timerWarning.timeout.connect(self.expiredWarning) hbox = QHBoxLayout(self) hbox.addWidget(self.stack) self.setLayout(hbox) def keyPressEvent(self, event): if event.key() == Qt.Key_Escape: self.close() QApplication.quit() """ def paintEvent(self, event):# set background_img painter = QPainter(self) painter.drawRect(self.rect()) pixmap = QPixmap("./data/images/background.jpg")#Change to the relative path of your own image painter.drawPixmap(self.rect(), pixmap) """ def mouseReleaseEvent(self, event): if self.stack.currentIndex() > 0: self.timer.start(IDLE_TIME) def display(self, name): index = self.stackDict[name] if index == 0: self.timer.stop() else: self.timer.start(IDLE_TIME) self.stack.setCurrentIndex(index) def changeLanguage(self, lang): if self.currentTranslator is not None: QCoreApplication.removeTranslator(self.currentTranslator) if lang == 'en': self.currentTranslator = None else: self.currentTranslator = QTranslator() self.currentTranslator.load('data/translations/mainform_' + lang) QCoreApplication.installTranslator(self.currentTranslator) for i in range(self.stack.count()): self.stack.widget(i).retranslateUi(self) def showWarning(self): self.msg = WarningDialog(self) self.timerWarning.start(WARNING_TIME) self.msg.exec_() def removeWarning(self): self.msg.close() self.timerWarning.stop() self.timer.start(IDLE_TIME) def expiredWarning(self): self.msg.close() self.display('main')
class PreferencesDialog(QDialog): def __init__(self, mainwindow): super(PreferencesDialog, self).__init__(mainwindow) self.setWindowModality(Qt.WindowModal) if mainwindow: self.addAction(mainwindow.actionCollection.help_whatsthis) layout = QVBoxLayout() layout.setSpacing(10) self.setLayout(layout) # listview to the left, stacked widget to the right top = QHBoxLayout() layout.addLayout(top) self.pagelist = QListWidget(self) self.stack = QStackedWidget(self) top.addWidget(self.pagelist, 0) top.addWidget(self.stack, 2) layout.addWidget(widgets.Separator(self)) b = self.buttons = QDialogButtonBox(self) b.setStandardButtons( QDialogButtonBox.Ok | QDialogButtonBox.Cancel | QDialogButtonBox.Apply | QDialogButtonBox.Reset | QDialogButtonBox.Help) layout.addWidget(b) b.accepted.connect(self.accept) b.rejected.connect(self.reject) b.button(QDialogButtonBox.Apply).clicked.connect(self.saveSettings) b.button(QDialogButtonBox.Reset).clicked.connect(self.loadSettings) b.button(QDialogButtonBox.Help).clicked.connect(self.showHelp) b.button(QDialogButtonBox.Help).setShortcut(QKeySequence.HelpContents) b.button(QDialogButtonBox.Apply).setEnabled(False) # fill the pagelist self.pagelist.setIconSize(QSize(32, 32)) self.pagelist.setSpacing(2) for item in pageorder(): self.pagelist.addItem(item()) self.pagelist.currentItemChanged.connect(self.slotCurrentItemChanged) app.translateUI(self, 100) # read our size and selected page qutil.saveDialogSize(self, "preferences/dialog/size", QSize(500, 300)) self.pagelist.setCurrentRow(_prefsindex) def translateUI(self): self.pagelist.setFixedWidth(self.pagelist.sizeHintForColumn(0) + 12) self.setWindowTitle(app.caption(_("Preferences"))) def done(self, result): if result and self.buttons.button(QDialogButtonBox.Apply).isEnabled(): self.saveSettings() # save our size and selected page global _prefsindex _prefsindex = self.pagelist.currentRow() super(PreferencesDialog, self).done(result) def pages(self): """Yields the settings pages that are already instantiated.""" for n in range(self.stack.count()): yield self.stack.widget(n) def showHelp(self): userguide.show(self.pagelist.currentItem().help) def loadSettings(self): """Loads the settings on reset.""" for page in self.pages(): page.loadSettings() page.hasChanges = False self.buttons.button(QDialogButtonBox.Apply).setEnabled(False) def saveSettings(self): """Saves the settings and applies them.""" for page in self.pages(): if page.hasChanges: page.saveSettings() page.hasChanges = False self.buttons.button(QDialogButtonBox.Apply).setEnabled(False) # emit the signal app.settingsChanged() def slotCurrentItemChanged(self, item): item.activate() def changed(self): """Call this to enable the Apply button.""" self.buttons.button(QDialogButtonBox.Apply).setEnabled(True)
class AppSettings(QDialog): SettingsWidgets = [] def __init__(self, conf, **kwargs): super().__init__(**kwargs) self.conf = conf self.setWindowTitle('LiSP preferences') self.setWindowModality(QtCore.Qt.ApplicationModal) self.setMaximumSize(635, 530) self.setMinimumSize(635, 530) self.resize(635, 530) self.listWidget = QListWidget(self) self.listWidget.setGeometry(QtCore.QRect(5, 10, 185, 470)) self.sections = QStackedWidget(self) self.sections.setGeometry(QtCore.QRect(200, 10, 430, 470)) for widget in self.SettingsWidgets: widget = widget(QtCore.QSize(430, 465), self) widget.set_configuration(self.conf) self.listWidget.addItem(widget.NAME) self.sections.addWidget(widget) if len(self.SettingsWidgets) > 0: self.listWidget.setCurrentRow(0) self.listWidget.currentItemChanged.connect(self._change_page) self.dialogButtons = QDialogButtonBox(self) self.dialogButtons.setGeometry(10, 495, 615, 30) self.dialogButtons.setStandardButtons(QDialogButtonBox.Cancel | QDialogButtonBox.Ok) self.dialogButtons.rejected.connect(self.reject) self.dialogButtons.accepted.connect(self.accept) def get_configuraton(self): conf = {} for n in range(self.sections.count()): widget = self.sections.widget(n) newconf = widget.get_configuration() deep_update(conf, newconf) return conf @classmethod def register_settings_widget(cls, widget): if widget not in cls.SettingsWidgets: cls.SettingsWidgets.append(widget) @classmethod def unregister_settings_widget(cls, widget): if widget not in cls.SettingsWidgets: cls.SettingsWidgets.remove(widget) def _change_page(self, current, previous): if not current: current = previous self.sections.setCurrentIndex(self.listWidget.row(current))
class BatchTrackingDialog(QDialog): """Dialog for batch processing videos.""" def __init__(self, root_folder, parent=None): super(BatchTrackingDialog, self).__init__(parent) self.setWindowFlags(Qt.WindowMinimizeButtonHint | Qt.WindowCloseButtonHint) self.setWindowTitle('Batch Processing Wizard') app_icon = QIcon(QPixmap(os.path.join(DIR, '..', 'icons', 'logo.png'))) self.setWindowIcon(app_icon) self.step_number = 0 self.video_settings = [] self.root_folder = root_folder self.layout = QGridLayout() # move all steps into a stacked layout. # this allows each layout to be shown individually, # while providing a container for all layouts. self.widgets = QStackedWidget() file_widget = StackedStepWidget(0, BatchFileSelector(self.root_folder, 0)) arena_widget = StackedStepWidget(1, BatchArenaSpecifier(1)) try: arena_widget.settings_widget.background_frame_ix.connect( self.update_progress_bar) except RuntimeError: print('Error while trying to connect the arena widget' + 'to the update_progress_bar fxn.') female_widget = StackedStepWidget(2, BatchFemaleSpecifier(2)) tight_threshold_widget = StackedStepWidget( 3, BatchTightThresholdSpecifier(3)) tight_threshold_widget.settings_widget.image_calc_progress.connect( self.update_progress_bar) loose_threshold_widget = StackedStepWidget( 4, BatchLooseThresholdSpecifier(4)) loose_threshold_widget.settings_widget.image_calc_progress.connect( self.update_progress_bar) tracking_widget = StackedStepWidget(5, BatchTrackingWidget(5)) tracking_widget.settings_widget.tracking_progress.connect( self.update_progress_bar) self.widgets.addWidget(file_widget) self.widgets.addWidget(arena_widget) self.widgets.addWidget(female_widget) self.widgets.addWidget(tight_threshold_widget) self.widgets.addWidget(loose_threshold_widget) self.widgets.addWidget(tracking_widget) # connect all widget signals to this class's update_settings # function. self.connect_widget_signals() self.layout.addWidget(self.widgets, 0, 0, 6, 6) # setup the next and previous buttons self.next_button = QPushButton('Next') self.next_button.setEnabled(False) previous_button = QPushButton('Previous') self.next_button.clicked.connect(self.step_forward) previous_button.clicked.connect(self.step_backward) self.layout.addWidget(previous_button, 6, 0, 1, 1) self.layout.addWidget(self.next_button, 6, 5, 1, 1) # setup the status bar interface self.set_status() self.layout.addWidget(self.status, 7, 0, 1, 6) self.setLayout(self.layout) self.resize(1000, 600) def connect_widget_signals(self): """Connects all widgets in the StackedLayout to self.update_settings.""" for i in xrange(self.widgets.count() + 1): # this call returns 0 if widget at index is not present. if self.widgets.widget(i): self.widgets.widget( i).settings_widget.all_settings_valid.connect( self.update_settings) def set_status(self): """Sets interface for status bar at bottom of dialog window.""" self.status = QStatusBar() self.status.setSizeGripEnabled(True) self.process_label = QLabel('Process') self.process_label.setFrameStyle(QFrame.StyledPanel | QFrame.Sunken) self.progress_bar = QProgressBar() self.status.addWidget(self.process_label) self.status.addPermanentWidget(self.progress_bar) def _step(self): """Updates layout currently being displayed in StackedLayout.""" self.widgets.setCurrentIndex(self.step_number) if self.widgets.currentWidget().settings_widget.check_settings_valid(): self.next_button.setEnabled(True) def step_forward(self): """Moves the StackedLayout forward by one index.""" if self.step_number < self.widgets.count() - 1: self.step_number += 1 self._step() def step_backward(self): """Moves the StackedLayout backward by one index.""" if self.step_number > 0: self.step_number -= 1 self._step() @pyqtSlot(bool, int, list) def update_settings(self, is_set, widget_ix, settings_list): """This function updates all of the video settings. It is a slot for the BatchSettingsWidget.all_settings_valid signal, and not only sets the video_settings of this class (TrackingDialog), but updates all of the settings in each of the widgets in the StackedLayout. """ if is_set: self.next_button.setEnabled(True) self.video_settings = settings_list for i in xrange(self.widgets.count() + 1): if self.widgets.widget(i): self.widgets.widget(i).settings_widget.update_settings( settings_list) else: self.next_button.setEnabled(False) @pyqtSlot(int, str) def update_progress_bar(self, progress, description): """Updates the progress bar based on actions happening in widgets within the StackedLayout.""" self.process_label.setText('Process -- {}'.format(description)) self.progress_bar.setValue(progress)
class Pager(QWidget): finished = pyqtSignal() changed = pyqtSignal(int) def __init__(self, parent=None): QWidget.__init__(self, parent=parent) lay = QVBoxLayout(self) self.stack = QStackedWidget() self.stack.currentChanged.connect(self.onChanged) self.nextButton = QPushButton("Next") self.nextButton.clicked.connect(self.onNext) self.finishButton = QPushButton("Finish") self.finishButton.clicked.connect(self.onFinish) self.previousButton = QPushButton("Previous") self.previousButton.clicked.connect(self.onPrevious) self.disableControls() self.btnLayout = QHBoxLayout() self.btnLayout.addWidget(self.previousButton) self.btnLayout.addWidget(self.nextButton) self.btnLayout.addWidget(self.finishButton) lay.addWidget(self.stack) lay.addLayout(self.btnLayout) # events @pyqtSlot() def onNext(self): index = self.stack.currentIndex() top = self.stack.count() - 1 index += 1 if index >= top: index = top self.stack.setCurrentIndex(index) @pyqtSlot() def onPrevious(self): index = self.stack.currentIndex() bottom = 0 index -= 1 if index <= bottom: index = bottom self.stack.setCurrentIndex(index) @pyqtSlot() def onFinish(self): self.disableFinish() self.disablePrevious() self.enableNext() self.finished.emit() self.stack.setCurrentIndex(0) @pyqtSlot(int) def onChanged(self, index): self.disableControls() self.changed.emit(index) widget = self.stack.widget(index) widget.onEnter() if widget.nextEnabled: self.enableNext() if widget.previousEnabled: self.enablePrevious() @pyqtSlot() def onPageFinished(self): self.disableControls() index = self.stack.currentIndex() top = self.stack.count() - 1 bottom = 0 self.enableNext() if index >= top: self.disableNext() self.enableFinish() self.enablePrevious() if index <= bottom: self.disablePrevious() # previous/next/finish controls def enableNext(self): self.nextButton.show() def enableFinish(self): self.finishButton.show() def enablePrevious(self): self.previousButton.show() def enableControls(self): self.enableNext() self.enableFinish() self.enablePrevious() def disableNext(self): self.nextButton.hide() def disableFinish(self): self.finishButton.hide() def disablePrevious(self): self.previousButton.hide() def disableControls(self): self.disableNext() self.disableFinish() self.disablePrevious() # for adding widgets and controlling page def addPage(self, pageWidget): pageWidget.setPager(self) self.stack.addWidget(pageWidget) pageWidget.finished.connect(self.onPageFinished) def getButtonHeight(self): return self.btnLayout.sizeHint().height() def clearPages(self): self.stack.clear() def setPageIndex(self, index): self.stack.setCurrentIndex(index)
class ParamsByType(QWidget, MooseWidget): """ Has a QComboBox for the different allowed types. On switching type a new ParamsByGroup is shown. """ needBlockList = pyqtSignal(list) blockRenamed = pyqtSignal(object, str) changed = pyqtSignal() def __init__(self, block, type_block_map, **kwds): """ Constructor. Input: block[BlockInfo]: The block to show. """ super(ParamsByType, self).__init__(**kwds) self.block = block self.combo = QComboBox() self.types = [] self.type_params_map = {} self.type_block_map = type_block_map self.table_stack = QStackedWidget() self.type_table_map = {} for t in sorted(self.block.types.keys()): self.types.append(t) params_list = [] for p in self.block.parameters_list: params_list.append(self.block.parameters[p]) t_block = self.block.types[t] for p in t_block.parameters_list: params_list.append(t_block.parameters[p]) self.type_params_map[t] = params_list self.combo.addItems(sorted(self.block.types.keys())) self.combo.currentTextChanged.connect(self.setBlockType) self.top_layout = WidgetUtils.addLayout(vertical=True) self.top_layout.addWidget(self.combo) self.top_layout.addWidget(self.table_stack) self.setLayout(self.top_layout) self.user_params = [] self.setDefaultBlockType() self.setup() def _syncUserParams(self, current, to): """ Sync user added parameters that are on the main block into each type ParamsByGroup. Input: current[ParamsByGroup]: The current group parameter table to[ParamsByGroup]: The new group parameter table """ ct = current.findTable("Main") tot = to.findTable("Main") if not ct or not tot or ct == tot: return tot.removeUserParams() params = ct.getUserParams() tot.addUserParams(params) to.syncParamsFrom(current) # Make sure the name parameter stays the same idx = ct.findRow("Name") if idx >= 0: name = ct.item(idx, 1).text() idx = tot.findRow("Name") if idx >= 0: tot.item(idx, 1).setText(name) def currentType(self): return self.combo.currentText() def save(self): """ Look at the user params in self.block.parameters. update the type tables Save type on block """ t = self.getTable() if t: t.save() self.block.setBlockType(self.combo.currentText()) def reset(self): t = self.getTable() t.reset() def getOrCreateTypeTable(self, type_name): """ Gets the table for the type name or create it if it doesn't exist. Input: type_name[str]: Name of the type Return: ParamsByGroup: The parameters corresponding to the type """ t = self.type_table_map.get(type_name) if t: return t t = ParamsByGroup(self.block, self.type_params_map.get(type_name, self.block.orderedParameters()), self.type_block_map) t.needBlockList.connect(self.needBlockList) t.blockRenamed.connect(self.blockRenamed) t.changed.connect(self.changed) self.type_table_map[type_name] = t self.table_stack.addWidget(t) return t def setDefaultBlockType(self): param = self.block.getParamInfo("type") if param and param.value: self.setBlockType(param.value) elif self.block.types: self.setBlockType(sorted(self.block.types.keys())[0]) def setBlockType(self, type_name): if type_name not in self.block.types: return t = self.getOrCreateTypeTable(type_name) t.updateWatchers() self.combo.blockSignals(True) self.combo.setCurrentText(type_name) self.combo.blockSignals(False) t.updateType(type_name) current = self.table_stack.currentWidget() self._syncUserParams(current, t) self.table_stack.setCurrentWidget(t) self.changed.emit() def addUserParam(self, param): t = self.table_stack.currentWidget() t.addUserParam(param) def setWatchedBlockList(self, path, children): for i in range(self.table_stack.count()): t = self.table_stack.widget(i) t.setWatchedBlockList(path, children) def updateWatchers(self): for i in range(self.table_stack.count()): t = self.table_stack.widget(i) t.updateWatchers() def getTable(self): return self.table_stack.currentWidget() def paramValue(self, name): for i in range(self.table_stack.count()): t = self.table_stack.widget(i) if t.paramValue(name): return t.paramValue(name)
class TilesetDock(QDockWidget): ## # Emitted when the current tile changed. ## currentTileChanged = pyqtSignal(list) ## # Emitted when the currently selected tiles changed. ## stampCaptured = pyqtSignal(TileStamp) ## # Emitted when files are dropped at the tileset dock. ## tilesetsDropped = pyqtSignal(QStringList) newTileset = pyqtSignal() ## # Constructor. ## def __init__(self, parent=None): super().__init__(parent) # Shared tileset references because the dock wants to add new tiles self.mTilesets = QVector() self.mCurrentTilesets = QMap() self.mMapDocument = None self.mTabBar = QTabBar() self.mViewStack = QStackedWidget() self.mToolBar = QToolBar() self.mCurrentTile = None self.mCurrentTiles = None self.mNewTileset = QAction(self) self.mImportTileset = QAction(self) self.mExportTileset = QAction(self) self.mPropertiesTileset = QAction(self) self.mDeleteTileset = QAction(self) self.mEditTerrain = QAction(self) self.mAddTiles = QAction(self) self.mRemoveTiles = QAction(self) self.mTilesetMenuButton = TilesetMenuButton(self) self.mTilesetMenu = QMenu(self) # opens on click of mTilesetMenu self.mTilesetActionGroup = QActionGroup(self) self.mTilesetMenuMapper = None # needed due to dynamic content self.mEmittingStampCaptured = False self.mSynchronizingSelection = False self.setObjectName("TilesetDock") self.mTabBar.setMovable(True) self.mTabBar.setUsesScrollButtons(True) self.mTabBar.currentChanged.connect(self.updateActions) self.mTabBar.tabMoved.connect(self.moveTileset) w = QWidget(self) horizontal = QHBoxLayout() horizontal.setSpacing(0) horizontal.addWidget(self.mTabBar) horizontal.addWidget(self.mTilesetMenuButton) vertical = QVBoxLayout(w) vertical.setSpacing(0) vertical.setContentsMargins(5, 5, 5, 5) vertical.addLayout(horizontal) vertical.addWidget(self.mViewStack) horizontal = QHBoxLayout() horizontal.setSpacing(0) horizontal.addWidget(self.mToolBar, 1) vertical.addLayout(horizontal) self.mNewTileset.setIcon(QIcon(":images/16x16/document-new.png")) self.mImportTileset.setIcon(QIcon(":images/16x16/document-import.png")) self.mExportTileset.setIcon(QIcon(":images/16x16/document-export.png")) self.mPropertiesTileset.setIcon( QIcon(":images/16x16/document-properties.png")) self.mDeleteTileset.setIcon(QIcon(":images/16x16/edit-delete.png")) self.mEditTerrain.setIcon(QIcon(":images/16x16/terrain.png")) self.mAddTiles.setIcon(QIcon(":images/16x16/add.png")) self.mRemoveTiles.setIcon(QIcon(":images/16x16/remove.png")) Utils.setThemeIcon(self.mNewTileset, "document-new") Utils.setThemeIcon(self.mImportTileset, "document-import") Utils.setThemeIcon(self.mExportTileset, "document-export") Utils.setThemeIcon(self.mPropertiesTileset, "document-properties") Utils.setThemeIcon(self.mDeleteTileset, "edit-delete") Utils.setThemeIcon(self.mAddTiles, "add") Utils.setThemeIcon(self.mRemoveTiles, "remove") self.mNewTileset.triggered.connect(self.newTileset) self.mImportTileset.triggered.connect(self.importTileset) self.mExportTileset.triggered.connect(self.exportTileset) self.mPropertiesTileset.triggered.connect(self.editTilesetProperties) self.mDeleteTileset.triggered.connect(self.removeTileset) self.mEditTerrain.triggered.connect(self.editTerrain) self.mAddTiles.triggered.connect(self.addTiles) self.mRemoveTiles.triggered.connect(self.removeTiles) self.mToolBar.addAction(self.mNewTileset) self.mToolBar.setIconSize(QSize(16, 16)) self.mToolBar.addAction(self.mImportTileset) self.mToolBar.addAction(self.mExportTileset) self.mToolBar.addAction(self.mPropertiesTileset) self.mToolBar.addAction(self.mDeleteTileset) self.mToolBar.addAction(self.mEditTerrain) self.mToolBar.addAction(self.mAddTiles) self.mToolBar.addAction(self.mRemoveTiles) self.mZoomable = Zoomable(self) self.mZoomComboBox = QComboBox() self.mZoomable.connectToComboBox(self.mZoomComboBox) horizontal.addWidget(self.mZoomComboBox) self.mViewStack.currentChanged.connect(self.updateCurrentTiles) TilesetManager.instance().tilesetChanged.connect(self.tilesetChanged) DocumentManager.instance().documentAboutToClose.connect( self.documentAboutToClose) self.mTilesetMenuButton.setMenu(self.mTilesetMenu) self.mTilesetMenu.aboutToShow.connect(self.refreshTilesetMenu) self.setWidget(w) self.retranslateUi() self.setAcceptDrops(True) self.updateActions() def __del__(self): del self.mCurrentTiles ## # Sets the map for which the tilesets should be displayed. ## def setMapDocument(self, mapDocument): if (self.mMapDocument == mapDocument): return # Hide while we update the tab bar, to avoid repeated layouting if sys.platform != 'darwin': self.widget().hide() self.setCurrentTiles(None) self.setCurrentTile(None) if (self.mMapDocument): # Remember the last visible tileset for this map tilesetName = self.mTabBar.tabText(self.mTabBar.currentIndex()) self.mCurrentTilesets.insert(self.mMapDocument, tilesetName) # Clear previous content while (self.mTabBar.count()): self.mTabBar.removeTab(0) while (self.mViewStack.count()): self.mViewStack.removeWidget(self.mViewStack.widget(0)) #self.mTilesets.clear() # Clear all connections to the previous document if (self.mMapDocument): self.mMapDocument.disconnect() self.mMapDocument = mapDocument if (self.mMapDocument): self.mTilesets = self.mMapDocument.map().tilesets() for tileset in self.mTilesets: view = TilesetView() view.setMapDocument(self.mMapDocument) view.setZoomable(self.mZoomable) self.mTabBar.addTab(tileset.name()) self.mViewStack.addWidget(view) self.mMapDocument.tilesetAdded.connect(self.tilesetAdded) self.mMapDocument.tilesetRemoved.connect(self.tilesetRemoved) self.mMapDocument.tilesetMoved.connect(self.tilesetMoved) self.mMapDocument.tilesetNameChanged.connect( self.tilesetNameChanged) self.mMapDocument.tilesetFileNameChanged.connect( self.updateActions) self.mMapDocument.tilesetChanged.connect(self.tilesetChanged) self.mMapDocument.tileAnimationChanged.connect( self.tileAnimationChanged) cacheName = self.mCurrentTilesets.take(self.mMapDocument) for i in range(self.mTabBar.count()): if (self.mTabBar.tabText(i) == cacheName): self.mTabBar.setCurrentIndex(i) break object = self.mMapDocument.currentObject() if object: if object.typeId() == Object.TileType: self.setCurrentTile(object) self.updateActions() if sys.platform != 'darwin': self.widget().show() ## # Synchronizes the selection with the given stamp. Ignored when the stamp is # changing because of a selection change in the TilesetDock. ## def selectTilesInStamp(self, stamp): if self.mEmittingStampCaptured: return processed = QSet() selections = QMap() for variation in stamp.variations(): tileLayer = variation.tileLayer() for cell in tileLayer: tile = cell.tile if tile: if (processed.contains(tile)): continue processed.insert(tile) # avoid spending time on duplicates tileset = tile.tileset() tilesetIndex = self.mTilesets.indexOf( tileset.sharedPointer()) if (tilesetIndex != -1): view = self.tilesetViewAt(tilesetIndex) if (not view.model()): # Lazily set up the model self.setupTilesetModel(view, tileset) model = view.tilesetModel() modelIndex = model.tileIndex(tile) selectionModel = view.selectionModel() _x = QItemSelection() _x.select(modelIndex, modelIndex) selections[selectionModel] = _x if (not selections.isEmpty()): self.mSynchronizingSelection = True # Mark captured tiles as selected for i in selections: selectionModel = i[0] selection = i[1] selectionModel.select(selection, QItemSelectionModel.SelectCurrent) # Show/edit properties of all captured tiles self.mMapDocument.setSelectedTiles(processed.toList()) # Update the current tile (useful for animation and collision editors) first = selections.first() selectionModel = first[0] selection = first[1] currentIndex = QModelIndex(selection.first().topLeft()) if (selectionModel.currentIndex() != currentIndex): selectionModel.setCurrentIndex(currentIndex, QItemSelectionModel.NoUpdate) else: self.currentChanged(currentIndex) self.mSynchronizingSelection = False def currentTilesetChanged(self): view = self.currentTilesetView() if view: s = view.selectionModel() if s: self.setCurrentTile(view.tilesetModel().tileAt( s.currentIndex())) ## # Returns the currently selected tile. ## def currentTile(self): return self.mCurrentTile def changeEvent(self, e): super().changeEvent(e) x = e.type() if x == QEvent.LanguageChange: self.retranslateUi() else: pass def dragEnterEvent(self, e): urls = e.mimeData().urls() if (not urls.isEmpty() and not urls.at(0).toLocalFile().isEmpty()): e.accept() def dropEvent(self, e): paths = QStringList() for url in e.mimeData().urls(): localFile = url.toLocalFile() if (not localFile.isEmpty()): paths.append(localFile) if (not paths.isEmpty()): self.tilesetsDropped.emit(paths) e.accept() def selectionChanged(self): self.updateActions() if not self.mSynchronizingSelection: self.updateCurrentTiles() def currentChanged(self, index): if (not index.isValid()): return model = index.model() self.setCurrentTile(model.tileAt(index)) def updateActions(self): external = False hasImageSource = False hasSelection = False view = None index = self.mTabBar.currentIndex() if (index > -1): view = self.tilesetViewAt(index) if (view): tileset = self.mTilesets.at(index) if (not view.model()): # Lazily set up the model self.setupTilesetModel(view, tileset) self.mViewStack.setCurrentIndex(index) external = tileset.isExternal() hasImageSource = tileset.imageSource() != '' hasSelection = view.selectionModel().hasSelection() tilesetIsDisplayed = view != None mapIsDisplayed = self.mMapDocument != None self.mNewTileset.setEnabled(mapIsDisplayed) self.mImportTileset.setEnabled(tilesetIsDisplayed and external) self.mExportTileset.setEnabled(tilesetIsDisplayed and not external) self.mPropertiesTileset.setEnabled(tilesetIsDisplayed and not external) self.mDeleteTileset.setEnabled(tilesetIsDisplayed) self.mEditTerrain.setEnabled(tilesetIsDisplayed and not external) self.mAddTiles.setEnabled(tilesetIsDisplayed and not hasImageSource and not external) self.mRemoveTiles.setEnabled(tilesetIsDisplayed and not hasImageSource and hasSelection and not external) def updateCurrentTiles(self): view = self.currentTilesetView() if (not view): return s = view.selectionModel() if (not s): return indexes = s.selection().indexes() if len(indexes) == 0: return first = indexes[0] minX = first.column() maxX = first.column() minY = first.row() maxY = first.row() for index in indexes: if minX > index.column(): minX = index.column() if maxX < index.column(): maxX = index.column() if minY > index.row(): minY = index.row() if maxY < index.row(): maxY = index.row() # Create a tile layer from the current selection tileLayer = TileLayer(QString(), 0, 0, maxX - minX + 1, maxY - minY + 1) model = view.tilesetModel() for index in indexes: tileLayer.setCell(index.column() - minX, index.row() - minY, Cell(model.tileAt(index))) self.setCurrentTiles(tileLayer) def indexPressed(self, index): view = self.currentTilesetView() tile = view.tilesetModel().tileAt(index) if tile: self.mMapDocument.setCurrentObject(tile) def tilesetAdded(self, index, tileset): view = TilesetView() view.setMapDocument(self.mMapDocument) view.setZoomable(self.mZoomable) self.mTilesets.insert(index, tileset.sharedPointer()) self.mTabBar.insertTab(index, tileset.name()) self.mViewStack.insertWidget(index, view) self.updateActions() def tilesetChanged(self, tileset): # Update the affected tileset model, if it exists index = indexOf(self.mTilesets, tileset) if (index < 0): return model = self.tilesetViewAt(index).tilesetModel() if model: model.tilesetChanged() def tilesetRemoved(self, tileset): # Delete the related tileset view index = indexOf(self.mTilesets, tileset) self.mTilesets.removeAt(index) self.mTabBar.removeTab(index) self.tilesetViewAt(index).close() # Make sure we don't reference this tileset anymore if (self.mCurrentTiles): # TODO: Don't clean unnecessarily (but first the concept of # "current brush" would need to be introduced) cleaned = self.mCurrentTiles.clone() cleaned.removeReferencesToTileset(tileset) self.setCurrentTiles(cleaned) if (self.mCurrentTile and self.mCurrentTile.tileset() == tileset): self.setCurrentTile(None) self.updateActions() def tilesetMoved(self, _from, to): self.mTilesets.insert(to, self.mTilesets.takeAt(_from)) # Move the related tileset views widget = self.mViewStack.widget(_from) self.mViewStack.removeWidget(widget) self.mViewStack.insertWidget(to, widget) self.mViewStack.setCurrentIndex(self.mTabBar.currentIndex()) # Update the titles of the affected tabs start = min(_from, to) end = max(_from, to) for i in range(start, end + 1): tileset = self.mTilesets.at(i) if (self.mTabBar.tabText(i) != tileset.name()): self.mTabBar.setTabText(i, tileset.name()) def tilesetNameChanged(self, tileset): index = indexOf(self.mTilesets, tileset) self.mTabBar.setTabText(index, tileset.name()) def tileAnimationChanged(self, tile): view = self.currentTilesetView() if view: model = view.tilesetModel() if model: model.tileChanged(tile) ## # Removes the currently selected tileset. ## def removeTileset(self, *args): l = len(args) if l == 0: currentIndex = self.mViewStack.currentIndex() if (currentIndex != -1): self.removeTileset(self.mViewStack.currentIndex()) elif l == 1: ## # Removes the tileset at the given index. Prompting the user when the tileset # is in use by the map. ## index = args[0] tileset = self.mTilesets.at(index).data() inUse = self.mMapDocument.map().isTilesetUsed(tileset) # If the tileset is in use, warn the user and confirm removal if (inUse): warning = QMessageBox( QMessageBox.Warning, self.tr("Remove Tileset"), self.tr("The tileset \"%s\" is still in use by the map!" % tileset.name()), QMessageBox.Yes | QMessageBox.No, self) warning.setDefaultButton(QMessageBox.Yes) warning.setInformativeText( self.tr("Remove this tileset and all references " "to the tiles in this tileset?")) if (warning.exec() != QMessageBox.Yes): return remove = RemoveTileset(self.mMapDocument, index, tileset) undoStack = self.mMapDocument.undoStack() if (inUse): # Remove references to tiles in this tileset from the current map def referencesTileset(cell): tile = cell.tile if tile: return tile.tileset() == tileset return False undoStack.beginMacro(remove.text()) removeTileReferences(self.mMapDocument, referencesTileset) undoStack.push(remove) if (inUse): undoStack.endMacro() def moveTileset(self, _from, to): command = MoveTileset(self.mMapDocument, _from, to) self.mMapDocument.undoStack().push(command) def editTilesetProperties(self): tileset = self.currentTileset() if (not tileset): return self.mMapDocument.setCurrentObject(tileset) self.mMapDocument.emitEditCurrentObject() def importTileset(self): tileset = self.currentTileset() if (not tileset): return command = SetTilesetFileName(self.mMapDocument, tileset, QString()) self.mMapDocument.undoStack().push(command) def exportTileset(self): tileset = self.currentTileset() if (not tileset): return tsxFilter = self.tr("Tiled tileset files (*.tsx)") helper = FormatHelper(FileFormat.ReadWrite, tsxFilter) prefs = preferences.Preferences.instance() suggestedFileName = prefs.lastPath( preferences.Preferences.ExternalTileset) suggestedFileName += '/' suggestedFileName += tileset.name() extension = ".tsx" if (not suggestedFileName.endswith(extension)): suggestedFileName += extension selectedFilter = tsxFilter fileName, _ = QFileDialog.getSaveFileName(self, self.tr("Export Tileset"), suggestedFileName, helper.filter(), selectedFilter) if fileName == '': return prefs.setLastPath(preferences.Preferences.ExternalTileset, QFileInfo(fileName).path()) tsxFormat = TsxTilesetFormat() format = helper.formatByNameFilter(selectedFilter) if not format: format = tsxFormat if format.write(tileset, fileName): command = SetTilesetFileName(self.mMapDocument, tileset, fileName) self.mMapDocument.undoStack().push(command) else: error = format.errorString() QMessageBox.critical(self.window(), self.tr("Export Tileset"), self.tr("Error saving tileset: %s" % error)) def editTerrain(self): tileset = self.currentTileset() if (not tileset): return editTerrainDialog = EditTerrainDialog(self.mMapDocument, tileset, self) editTerrainDialog.exec() def addTiles(self): tileset = self.currentTileset() if (not tileset): return prefs = preferences.Preferences.instance() startLocation = QFileInfo( prefs.lastPath(preferences.Preferences.ImageFile)).absolutePath() filter = Utils.readableImageFormatsFilter() files = QFileDialog.getOpenFileNames(self.window(), self.tr("Add Tiles"), startLocation, filter) tiles = QList() id = tileset.tileCount() for file in files: image = QPixmap(file) if (not image.isNull()): tiles.append(Tile(image, file, id, tileset)) id += 1 else: warning = QMessageBox(QMessageBox.Warning, self.tr("Add Tiles"), self.tr("Could not load \"%s\"!" % file), QMessageBox.Ignore | QMessageBox.Cancel, self.window()) warning.setDefaultButton(QMessageBox.Ignore) if (warning.exec() != QMessageBox.Ignore): tiles.clear() return if (tiles.isEmpty()): return prefs.setLastPath(preferences.Preferences.ImageFile, files.last()) self.mMapDocument.undoStack().push( AddTiles(self.mMapDocument, tileset, tiles)) def removeTiles(self): view = self.currentTilesetView() if (not view): return if (not view.selectionModel().hasSelection()): return indexes = view.selectionModel().selectedIndexes() model = view.tilesetModel() tileIds = RangeSet() tiles = QList() for index in indexes: tile = model.tileAt(index) if tile: tileIds.insert(tile.id()) tiles.append(tile) def matchesAnyTile(cell): tile = cell.tile if tile: return tiles.contains(tile) return False inUse = self.hasTileReferences(self.mMapDocument, matchesAnyTile) # If the tileset is in use, warn the user and confirm removal if (inUse): warning = QMessageBox( QMessageBox.Warning, self.tr("Remove Tiles"), self.tr("One or more of the tiles to be removed are " "still in use by the map!"), QMessageBox.Yes | QMessageBox.No, self) warning.setDefaultButton(QMessageBox.Yes) warning.setInformativeText( self.tr("Remove all references to these tiles?")) if (warning.exec() != QMessageBox.Yes): return undoStack = self.mMapDocument.undoStack() undoStack.beginMacro(self.tr("Remove Tiles")) removeTileReferences(self.mMapDocument, matchesAnyTile) # Iterate backwards over the ranges in order to keep the indexes valid firstRange = tileIds.begin() it = tileIds.end() if (it == firstRange): # no range return tileset = view.tilesetModel().tileset() while (it != firstRange): it -= 1 item = tileIds.item(it) length = item[1] - item[0] + 1 undoStack.push( RemoveTiles(self.mMapDocument, tileset, item[0], length)) undoStack.endMacro() # Clear the current tiles, will be referencing the removed tiles self.setCurrentTiles(None) self.setCurrentTile(None) def documentAboutToClose(self, mapDocument): self.mCurrentTilesets.remove(mapDocument) def refreshTilesetMenu(self): self.mTilesetMenu.clear() if (self.mTilesetMenuMapper): self.mTabBar.disconnect(self.mTilesetMenuMapper) del self.mTilesetMenuMapper self.mTilesetMenuMapper = QSignalMapper(self) self.mTilesetMenuMapper.mapped.connect(self.mTabBar.setCurrentIndex) currentIndex = self.mTabBar.currentIndex() for i in range(self.mTabBar.count()): action = QAction(self.mTabBar.tabText(i), self) action.setCheckable(True) self.mTilesetActionGroup.addAction(action) if (i == currentIndex): action.setChecked(True) self.mTilesetMenu.addAction(action) action.triggered.connect(self.mTilesetMenuMapper.map) self.mTilesetMenuMapper.setMapping(action, i) def setCurrentTile(self, tile): if (self.mCurrentTile == tile): return self.mCurrentTile = tile self.currentTileChanged.emit([tile]) if (tile): self.mMapDocument.setCurrentObject(tile) def setCurrentTiles(self, tiles): if (self.mCurrentTiles == tiles): return del self.mCurrentTiles self.mCurrentTiles = tiles # Set the selected tiles on the map document if (tiles): selectedTiles = QList() for y in range(tiles.height()): for x in range(tiles.width()): cell = tiles.cellAt(x, y) if (not cell.isEmpty()): selectedTiles.append(cell.tile) self.mMapDocument.setSelectedTiles(selectedTiles) # Create a tile stamp with these tiles map = self.mMapDocument.map() stamp = Map(map.orientation(), tiles.width(), tiles.height(), map.tileWidth(), map.tileHeight()) stamp.addLayer(tiles.clone()) stamp.addTilesets(tiles.usedTilesets()) self.mEmittingStampCaptured = True self.stampCaptured.emit(TileStamp(stamp)) self.mEmittingStampCaptured = False def retranslateUi(self): self.setWindowTitle(self.tr("Tilesets")) self.mNewTileset.setText(self.tr("New Tileset")) self.mImportTileset.setText(self.tr("Import Tileset")) self.mExportTileset.setText(self.tr("Export Tileset As...")) self.mPropertiesTileset.setText(self.tr("Tileset Properties")) self.mDeleteTileset.setText(self.tr("Remove Tileset")) self.mEditTerrain.setText(self.tr("Edit Terrain Information")) self.mAddTiles.setText(self.tr("Add Tiles")) self.mRemoveTiles.setText(self.tr("Remove Tiles")) def currentTileset(self): index = self.mTabBar.currentIndex() if (index == -1): return None return self.mTilesets.at(index) def currentTilesetView(self): return self.mViewStack.currentWidget() def tilesetViewAt(self, index): return self.mViewStack.widget(index) def setupTilesetModel(self, view, tileset): view.setModel(TilesetModel(tileset, view)) s = view.selectionModel() s.selectionChanged.connect(self.selectionChanged) s.currentChanged.connect(self.currentChanged) view.pressed.connect(self.indexPressed)
class PreferencesDialog(QDialog): def __init__(self, mainwindow): super(PreferencesDialog, self).__init__(mainwindow) self.setWindowModality(Qt.WindowModal) if mainwindow: self.addAction(mainwindow.actionCollection.help_whatsthis) layout = QVBoxLayout() layout.setSpacing(10) self.setLayout(layout) # listview to the left, stacked widget to the right top = QHBoxLayout() layout.addLayout(top) self.pagelist = QListWidget(self) self.stack = QStackedWidget(self) top.addWidget(self.pagelist, 0) top.addWidget(self.stack, 2) layout.addWidget(widgets.Separator(self)) b = self.buttons = QDialogButtonBox(self) b.setStandardButtons(QDialogButtonBox.Ok | QDialogButtonBox.Cancel | QDialogButtonBox.Apply | QDialogButtonBox.Reset | QDialogButtonBox.Help) layout.addWidget(b) b.accepted.connect(self.accept) b.rejected.connect(self.reject) b.button(QDialogButtonBox.Apply).clicked.connect(self.saveSettings) b.button(QDialogButtonBox.Reset).clicked.connect(self.loadSettings) b.button(QDialogButtonBox.Help).clicked.connect(self.showHelp) b.button(QDialogButtonBox.Help).setShortcut(QKeySequence.HelpContents) b.button(QDialogButtonBox.Apply).setEnabled(False) # fill the pagelist self.pagelist.setIconSize(QSize(32, 32)) self.pagelist.setSpacing(2) for item in pageorder(): self.pagelist.addItem(item()) self.pagelist.currentItemChanged.connect(self.slotCurrentItemChanged) app.translateUI(self, 100) # read our size and selected page qutil.saveDialogSize(self, "preferences/dialog/size", QSize(500, 300)) self.pagelist.setCurrentRow(_prefsindex) def translateUI(self): self.pagelist.setFixedWidth(self.pagelist.sizeHintForColumn(0) + 12) self.setWindowTitle(app.caption(_("Preferences"))) def done(self, result): if result and self.buttons.button(QDialogButtonBox.Apply).isEnabled(): self.saveSettings() # save our size and selected page global _prefsindex _prefsindex = self.pagelist.currentRow() super(PreferencesDialog, self).done(result) def pages(self): """Yields the settings pages that are already instantiated.""" for n in range(self.stack.count()): yield self.stack.widget(n) def showHelp(self): userguide.show(self.pagelist.currentItem().help) def loadSettings(self): """Loads the settings on reset.""" for page in self.pages(): page.loadSettings() page.hasChanges = False self.buttons.button(QDialogButtonBox.Apply).setEnabled(False) def saveSettings(self): """Saves the settings and applies them.""" for page in self.pages(): if page.hasChanges: page.saveSettings() page.hasChanges = False self.buttons.button(QDialogButtonBox.Apply).setEnabled(False) # emit the signal app.settingsChanged() def slotCurrentItemChanged(self, item): item.activate() def changed(self): """Call this to enable the Apply button.""" self.buttons.button(QDialogButtonBox.Apply).setEnabled(True)
class Settings(QDialog): """Window showing the Settings/settings. Parameters ---------- parent : instance of QMainWindow the main window """ def __init__(self, parent): super().__init__(None, Qt.WindowSystemMenuHint | Qt.WindowTitleHint) self.parent = parent self.config = ConfigUtils(self.parent.refresh) self.setWindowTitle('Settings') self.create_settings() def create_settings(self): """Create the widget, organized in two parts. Notes ----- When you add widgets in config, remember to update show_settings too """ bbox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Apply | QDialogButtonBox.Cancel) self.idx_ok = bbox.button(QDialogButtonBox.Ok) self.idx_apply = bbox.button(QDialogButtonBox.Apply) self.idx_cancel = bbox.button(QDialogButtonBox.Cancel) bbox.clicked.connect(self.button_clicked) page_list = QListWidget() page_list.setSpacing(1) page_list.currentRowChanged.connect(self.change_widget) pages = ['General', 'Overview', 'Signals', 'Channels', 'Spectrum', 'Notes', 'Video'] for one_page in pages: page_list.addItem(one_page) self.stacked = QStackedWidget() self.stacked.addWidget(self.config) self.stacked.addWidget(self.parent.overview.config) self.stacked.addWidget(self.parent.traces.config) self.stacked.addWidget(self.parent.channels.config) self.stacked.addWidget(self.parent.spectrum.config) self.stacked.addWidget(self.parent.notes.config) self.stacked.addWidget(self.parent.video.config) hsplitter = QSplitter() hsplitter.addWidget(page_list) hsplitter.addWidget(self.stacked) btnlayout = QHBoxLayout() btnlayout.addStretch(1) btnlayout.addWidget(bbox) vlayout = QVBoxLayout() vlayout.addWidget(hsplitter) vlayout.addLayout(btnlayout) self.setLayout(vlayout) def change_widget(self, new_row): """Change the widget on the right side. Parameters ---------- new_row : int index of the widgets """ self.stacked.setCurrentIndex(new_row) def button_clicked(self, button): """Action when button was clicked. Parameters ---------- button : instance of QPushButton which button was pressed """ if button in (self.idx_ok, self.idx_apply): # loop over widgets, to see if they were modified for i_config in range(self.stacked.count()): one_config = self.stacked.widget(i_config) if one_config.modified: lg.debug('Settings for ' + one_config.widget + ' were modified') one_config.get_values() if self.parent.info.dataset is not None: one_config.update_widget() one_config.modified = False if button == self.idx_ok: self.accept() if button == self.idx_cancel: self.reject()
class PyStream(QMainWindow): receive_signal = pyqtSignal(Metric) def __init__(self, rootPath): super().__init__() self.rootPath = rootPath self.__server = WebSocketServer(self) self.__server.start() self.__relay = PyRelay() self.__temp = PyTemp() self.__temp self.__temp.start() self.__stats_tab_index = 0 self.__buttons_tab_index = 1 self.enable_gui_switch = True self.timer = QTimer() self.timer.timeout.connect(self.__timer_tick) try: self.backlight = Backlight() except: self.fakeBacklightSysfs = FakeBacklightSysfs() self.fakeBacklightSysfs.__enter__() self.backlight = Backlight( backlight_sysfs_path=self.fakeBacklightSysfs.path) self.initUI() self.enable_screensaver() def initUI(self): logging.info("[GUI] Init main frame") self.setWindowFlags(QtCore.Qt.FramelessWindowHint) self.setGeometry(0, 0, 800, 480) self.setWindowTitle("MediaDisplay-Server") self.setWindowIcon(QIcon("pystream/resource/pyalarm.png")) self.is_updating = False self.stack = QStackedWidget(self) self.stack.setGeometry(0, 0, 800, 480) self.panel_1 = QWidget() #self.panel_2 = QWidget() self.panel_3 = QWidget() self.stack.addWidget(self.panel_1) #self.stack.addWidget(self.panel_2) self.stack.addWidget(self.panel_3) ##################### ##### Panel 1 background_1 = QLabel(self.panel_1) background_1.setGeometry(0, 0, 800, 480) background_1.setStyleSheet( "background-image: url(pystream/resource/page_1.jpg);") self.gauge_cpu_1 = self.__create_gauge(self.panel_1, 95, 67) self.gauge_cpu_2 = self.__create_gauge(self.panel_1, 335, 67) self.gauge_cpu_3 = self.__create_gauge(self.panel_1, 580, 67) self.gauge_cpu_4 = self.__create_gauge(self.panel_1, 95, 230) self.gauge_cpu_5 = self.__create_gauge(self.panel_1, 335, 230) self.gauge_cpu_6 = self.__create_gauge(self.panel_1, 580, 230) self.label_cpu_1 = self.__create_label(self.panel_1, 135, 164, text="-- °C") self.label_cpu_2 = self.__create_label(self.panel_1, 380, 164, text="-- °C") self.label_cpu_3 = self.__create_label(self.panel_1, 620, 164, text="-- °C") self.label_cpu_4 = self.__create_label(self.panel_1, 135, 333, text="-- °C") self.label_cpu_5 = self.__create_label(self.panel_1, 380, 333, text="-- °C") self.label_cpu_6 = self.__create_label(self.panel_1, 620, 333, text="-- °C") self.__create_label(self.panel_1, 35, 384, text="GPU", font_size=20, color="#FFFFFF") self.label_gpu_temp = self.__create_label(self.panel_1, 37, 419, text="--°C", font_size=15, color="#FFFFFF") self.progress_gpu_load = self.__create_progressbar( self.panel_1, 95, 390, 174, 20) self.progress_gpu_mem_load = self.__create_progressbar( self.panel_1, 95, 420, 174, 20) self.__create_label(self.panel_1, 330, 395, text="Down", font_size=15, color="#FFFFFF") self.__create_label(self.panel_1, 330, 419, text="Up", font_size=15, color="#FFFFFF") self.label_net_down = self.__create_label(self.panel_1, 430, 395, width=100, height=25, text="0", font_size=15, color="#FFFFFF") self.label_net_down.setAlignment(Qt.AlignRight) self.label_net_up = self.__create_label(self.panel_1, 430, 419, width=100, height=25, text="0", font_size=15, color="#FFFFFF") self.label_net_up.setAlignment(Qt.AlignRight) self.__create_label(self.panel_1, 546, 379, text="Memory", font_size=18, color="#FFFFFF") self.progress_mem_load = self.__create_progressbar( self.panel_1, 551, 407, 203, 34) self.__create_button(self.panel_1, 774, 227, 26, 26, "arrow_right.png", lambda: self.__change_page("Forward")) ##################### ##### Panel 3 background_3 = QLabel(self.panel_3) background_3.setGeometry(0, 0, 800, 480) background_3.setStyleSheet( "background-image: url(pystream/resource/page_2.jpg);") self.__create_button(self.panel_3, 125, 180, 100, 120, "desk_lamp.png", lambda: self.__relay.toggle_relay(PyRelay.BIG_2), checkable=True) self.__create_button( self.panel_3, 350, 180, 100, 120, "keyboard.png", press=lambda: self.__relay.activate_relay(PyRelay.SMALL_1), release=lambda: self.__relay.deactivate_relay(PyRelay.SMALL_1)) self.__create_button(self.panel_3, 575, 180, 100, 120, "laptop.png", lambda: self.__relay.toggle_relay(PyRelay.BIG_1), checkable=True) self.__create_button(self.panel_3, 0, 227, 26, 26, "arrow_left.png", lambda: self.__change_page("Backward")) self.label_room_temp = self.__create_label(self, 110, 0, text="--°C", color="#FFFFFF") self.label_time = self.__create_label(self, 590, 0, text="00:00", font_size=15, color="#FFFFFF") self.restore_gui() self.setCursor(QtCore.Qt.BlankCursor) logging.info("[GUI] Init done") self.timer.start(1000) self.receive_signal.connect(self.receive_gui) self.show() def __create_gauge(self, parent, x, y): gauge = AnalogGaugeWidget(parent) gauge.set_enable_fine_scaled_marker(False) gauge.set_enable_big_scaled_grid(False) gauge.set_enable_ScaleText(False) gauge.set_enable_CenterPoint(False) gauge.set_enable_Needle_Polygon(False) gauge.set_enable_barGraph(False) gauge.set_start_scale_angle(165) gauge.set_total_scale_angle_size(210) gauge.set_gauge_color_inner_radius_factor(600) gauge.set_MaxValue(100) gauge.setGeometry(x, y, 130, 130) gauge.update_value(50) gauge.set_DisplayValueColor(0, 255, 255) return gauge def __create_label(self, parent, x, y, width=None, height=None, text="", font_size=15, color="#00FFFF"): label = QLabel(parent) label.setText(text) font = QFont("Decorative", font_size) font.setBold(True) label.setFont(font) label.setStyleSheet("color: %s;" % color) if width is None or height is None: label.move(x, y) else: label.setGeometry(x, y, width, height) return label def __create_progressbar(self, parent, x, y, width, height): progress = GradiantProgressBar(parent) progress.setFormat("") progress.setValue(50) progress.setMaximum(100) progress.setGeometry(x, y, width, height) return progress def __create_button(self, parent, x, y, width, height, image, click=None, press=None, release=None, checkable=False): button = QPushButton(parent) button.setCheckable(checkable) if checkable: pressed_image = image.replace(".", "_pressed.") stre = "QPushButton {border-image: url(pystream/resource/" + image + ");} " \ + "QPushButton:checked {border-image: url(pystream/resource/" + pressed_image + ");}" button.setStyleSheet(stre) else: button.setStyleSheet("border-image: url(pystream/resource/" + image + ");") if click is not None: button.clicked.connect(click) if press is not None: button.pressed.connect(press) if release is not None: button.released.connect(release) button.setGeometry(x, y, width, height) button.setFlat(True) return button def __timer_tick(self): time = QDateTime.currentDateTime() timeDisplay = time.toString('hh:mm') temp = self.__temp.temperature self.label_time.setText(timeDisplay) self.label_room_temp.setText("%1.0f°C" % temp) def __change_page(self, direction): self.enable_gui_switch = False if direction == "Forward": if self.stack.currentIndex() < self.stack.count() - 1: self.stack.setCurrentIndex(self.stack.currentIndex() + 1) elif direction == "Backward": if self.stack.currentIndex() > 0: self.stack.setCurrentIndex(self.stack.currentIndex() - 1) def __send_key(self, key): msgObj = EventMessage(Action.Click, key) msg = json.dumps(msgObj.__dict__) self.__server.broadcast(msg) def udpate_gui(self, data: Metric): self.gauge_cpu_1.update_value(data.cpus[0].load) self.label_cpu_1.setText("%1.0f°C" % data.cpus[0].temperature) self.gauge_cpu_2.update_value(data.cpus[1].load) self.label_cpu_2.setText("%1.0f°C" % data.cpus[1].temperature) self.gauge_cpu_3.update_value(data.cpus[2].load) self.label_cpu_3.setText("%1.0f°C" % data.cpus[2].temperature) self.gauge_cpu_4.update_value(data.cpus[3].load) self.label_cpu_4.setText("%1.0f°C" % data.cpus[3].temperature) self.gauge_cpu_5.update_value(data.cpus[4].load) self.label_cpu_5.setText("%1.0f°C" % data.cpus[4].temperature) self.gauge_cpu_6.update_value(data.cpus[5].load) self.label_cpu_6.setText("%1.0f°C" % data.cpus[5].temperature) self.progress_mem_load.setValue(data.memory_load) self.label_gpu_temp.setText("%1.0f°C" % data.gpu.temperature) self.progress_gpu_load.setValue(data.gpu.load) self.progress_gpu_mem_load.setValue(data.gpu.memory_load) self.label_net_down.setText(data.network.down) self.label_net_up.setText(data.network.up) def receive_gui(self, data: Metric): if data.reset is not None and data.reset: logging.info("[GUI] Restoring initial image") self.restore_gui() self.enable_screensaver() else: if self.is_updating == False: self.is_updating = True try: self.udpate_gui(data) self.enable_gui() except Exception as e: print(e) finally: self.is_updating = False else: print("Gui is locked") self.disable_screensaver() def receive(self, data: Metric): if data is None: data = Metric(reset=True) self.receive_signal.emit(data) if data.send_display_brightness == True: msgObj = EventMessage(Action.Brightness, self.backlight.brightness) msg = json.dumps(msgObj.__dict__) self.__server.broadcast(msg) if data.display_brightness is not None and data.display_brightness >= 0 and data.display_brightness <= 100: self.backlight.brightness = data.display_brightness def enable_gui(self): if self.enable_gui_switch == True and self.stack.currentIndex( ) == self.__buttons_tab_index: self.stack.setCurrentIndex(self.__stats_tab_index) def restore_gui(self): self.enable_gui_switch = True self.stack.setCurrentIndex(2) self.label_cpu_1.setText("-- °C") self.label_cpu_2.setText("-- °C") self.label_cpu_3.setText("-- °C") self.label_cpu_4.setText("-- °C") self.label_cpu_5.setText("-- °C") self.label_cpu_6.setText("-- °C") self.gauge_cpu_1.update_value(50) self.gauge_cpu_2.update_value(50) self.gauge_cpu_3.update_value(50) self.gauge_cpu_4.update_value(50) self.gauge_cpu_5.update_value(50) self.gauge_cpu_6.update_value(50) self.progress_mem_load.setValue(50) self.label_gpu_temp.setText("%1.0f°C" % 0) self.progress_gpu_load.setValue(50) self.progress_gpu_mem_load.setValue(50) self.label_net_down.setText("0") self.label_net_up.setText("0") self.stack.setCurrentIndex(self.__buttons_tab_index) def update_app(self): GitUpdater.update(self.rootPath) def disable_screensaver(self): # disp = display.Display() # disp.set_screen_saver(0, 0, X.DontPreferBlanking, X.AllowExposures) # disp.sync() pyautogui.moveRel(0, 10) def enable_screensaver(self): disp = display.Display() screensaver = disp.get_screen_saver() if screensaver.timeout != 60: disp.set_screen_saver(60, 60, X.DefaultBlanking, X.AllowExposures) disp.sync()