class DatatypeSelector(QGroupBox): def __init__(self, title: str, datatype_to_widget, parent: "QWidget" = None): super().__init__(title, parent) # Maps a datatype with its respective widget. The widget is optional self._datatype_to_widget = datatype_to_widget self._datatype_combobox = QComboBox() self._stacked_widgets = QStackedWidget() for (i, (name, datatype_factory)) in enumerate( DataTypeContainer.providers.items()): datatype_instance = datatype_factory() self._datatype_combobox.addItem(name, datatype_instance) if datatype_factory in self._datatype_to_widget: self._stacked_widgets.insertWidget( i, self._datatype_to_widget[datatype_factory]( datatype_instance)) self._main_layout = QVBoxLayout() self._main_layout.addWidget(self._datatype_combobox) self._main_layout.addWidget(self._stacked_widgets) self.setLayout(self._main_layout) self._datatype_combobox.currentIndexChanged[int].connect( self._change_active_widget) @property def selected_datatype(self): return self._datatype_combobox.currentData() def change_current_datatype(self, new_datatype_dict: dict): index = self._datatype_combobox.findText(new_datatype_dict["class"]) if index != -1: self._datatype_combobox.setCurrentIndex(index) self._datatype_combobox.currentData().from_dict(new_datatype_dict) self._stacked_widgets.currentWidget().reload() def _change_active_widget(self, index): self._stacked_widgets.setCurrentIndex(index) # Hide the `stacked_widgets` when the current datatype doesn't needs to display # a widget if self._stacked_widgets.currentIndex() != index: self._stacked_widgets.setVisible(False) else: self._stacked_widgets.setVisible(True) def to_dict(self): return self.selected_datatype.to_dict()
class appConfigScreen(QWidget): keybind_signal = Signal("QObject") update_signal = Signal("QObject") def __init__(self, appName, parms={}): super().__init__() self.dbg = False self.level = 'user' exePath = sys.argv[0] if os.path.islink(sys.argv[0]): exePath = os.path.realpath(sys.argv[0]) baseDir = os.path.abspath(os.path.dirname(exePath)) os.chdir(baseDir) self.rsrc = "%s/rsrc" % baseDir self.parms = parms self.modules = [] self.appName = appName self.textDomain = self.appName.lower().replace(" ", "_") gettext.textdomain('{}'.format(self.textDomain)) _ = gettext.gettext self.wikiPage = appName self.background = "%s/background.png" % self.rsrc self.banner = "%s/%s" % (self.rsrc, "banner.png") self.last_index = 0 self.stacks = {0: {'name': _("Options"), 'icon': 'icon'}} self.appConfig = appConfig() self.config = {} #def init def _debug(self, msg): if self.dbg: print("%s" % msg) #def _debug def setWiki(self, url): self.wikiPage = url #def setWiki def setTextDomain(self, textDomain): self.textDomain = textDomain #def setTextDomain def setRsrcPath(self, rsrc): if os.path.isdir(rsrc): self.rsrc = rsrc else: self._debug("%s doesn't exists") self._debug("Resources dir: %s" % self.rsrc) #def setRsrcPath def setIcon(self, icon): self._debug("Icon: %s" % icon) icn = icon if not os.path.isfile(icon): sw_ko = False self._debug("%s not found" % icon) if QtGui.QIcon.fromTheme(icon): icn = QtGui.QIcon.fromTheme(icon) if icn.name() == "": self._debug("%s not present at theme" % icon) sw_ko = True else: self._debug("%s found at theme" % icon) self._debug("Name: %s found at theme" % icn.name()) elif os.path.isfile("%s/%s" % (self.rsrc, icon)): icon = "%s/%s" % (self.rsrc, icon) self._debug("%s found at rsrc folder" % icon) icn = QtGui.QIcon(icon) else: icn = QtGui.QIcon.fromTheme("application-menu") self._debug("Icon not found at %s" % self.rsrc) if sw_ko: icn = QtGui.QIcon.fromTheme("application-menu") self._debug("Icon %s not found at theme" % icon) self.setWindowIcon(icn) #def setIcon def setBanner(self, banner): if not os.path.isfile(banner): if os.path.isfile("%s/%s" % (self.rsrc, banner)): banner = "%s/%s" % (self.rsrc, banner) else: banner = "" self._debug("Banner not found at %s" % self.rsrc) self.banner = banner #def setBanner def setBackgroundImage(self, background): if not os.path.isfile(background): if os.path.isfile("%s/%s" % (self.rsrc, background)): background = "%s/%s" % (self.rsrc, background) else: background = "" self._debug("Background not found at %s" % self.rsrc) self.background = background #def setBanner def setConfig(self, confDirs, confFile): self.appConfig.set_baseDirs(confDirs) self.appConfig.set_configFile(confFile) #def setConfig(self,confDirs,confFile): def _searchWiki(self): url = "" baseUrl = "https://wiki.edu.gva.es/lliurex/tiki-index.php?page=" if self.wikiPage.startswith("http"): url = self.wikiPage else: url = "%s%s" % (baseUrl, self.wikiPage) #try: # req=Request(url) # content=urlopen(req).read() #except: # self._debug("Wiki not found at %s"%url) # url="" return (url) #def _searchWiki def _get_default_config(self): data = {} data = self.appConfig.getConfig('system') self.level = data['system'].get('config', 'user') if self.level != 'system': data = self.appConfig.getConfig(self.level) level = data[self.level].get('config', 'n4d') if level != self.level: self.level = level data = self.appConfig.getConfig(level) data[self.level]['config'] = self.level self._debug("Read level from config: %s" % self.level) return (data) #def _get_default_config(self,level): def getConfig(self, level=None, exclude=[]): data = self._get_default_config() if not level: level = self.level if level != 'system': data = {} data = self.appConfig.getConfig(level, exclude) self.config = data.copy() self._debug("Read level from config: %s" % level) return (data) #def getConfig(self,level): def Show(self): if self.config == {}: self.getConfig() self.setStyleSheet(self._define_css()) if os.path.isdir("stacks"): for mod in os.listdir("stacks"): if mod.endswith(".py"): mod_name = mod.split(".")[0] mod_import = "from stacks.%s import *" % mod_name try: exec(mod_import) self.modules.append(mod_name) self._debug("Load stack %s" % mod_name) except Exception as e: # self._debug("Unable to load %s: %s"%(mod_name,e)) print("Unable to load %s: %s" % (mod_name, e)) idx = 1 for mod_name in self.modules: try: mod = eval("%s(self)" % mod_name) except Exception as e: # self._debug("Import failed for %s: %s"%(mod_name,e)) print("Import failed for %s: %s" % (mod_name, e)) continue if type(mod.index) == type(0): if mod.index > 0: idx = mod.index try: if mod.enabled == False: continue except: pass while idx in self.stacks.keys(): idx += 1 self._debug("New idx for %s: %s" % (mod_name, idx)) if 'parm' in mod.__dict__.keys(): try: if mod.parm: self._debug("Setting parms for %s" % mod_name) self._debug("self.parms['%s']" % mod.parm) mod.apply_parms(eval("self.parms['%s']" % mod.parm)) except Exception as e: self._debug("Failed to pass parm %s to %s: %s" % (mod.parm, mod_name, e)) try: mod.setTextDomain(self.textDomain) except Exception as e: print("Can't set textdomain for %s: %s" % (mod_name, e)) try: mod.setAppConfig(self.appConfig) except Exception as e: print("Can't set appConfig for %s: %s" % (mod_name, e)) try: if mod.visible == False: visible = False else: visible = True except: visible = True self.stacks[idx] = { 'name': mod.description, 'icon': mod.icon, 'tooltip': mod.tooltip, 'module': mod, 'visible': visible } try: mod.message.connect(self._show_message) except: pass self._render_gui() return (False) def _render_gui(self): self.getConfig() box = QGridLayout() img_banner = QLabel() if os.path.isfile(self.banner): img = QtGui.QPixmap(self.banner) img_banner.setPixmap(img) img_banner.setAlignment(Qt.AlignCenter) img_banner.setObjectName("banner") box.addWidget(img_banner, 0, 0, 1, 2) self.lst_options = QListWidget() self.stk_widget = QStackedWidget() idx = 0 if len(self.stacks) > 2: l_panel = self._left_panel() box.addWidget(l_panel, 1, 0, 1, 1, Qt.Alignment(1) | Qt.AlignTop) # self.stk_widget.setCurrentIndex(0) else: idx = 1 # self.stk_widget.setCurrentIndex(1) r_panel = self._right_panel() self.stk_widget.setCurrentIndex(idx) #self.gotoStack(idx,"") box.addWidget(r_panel, 1, 1, 1, 1) self.setLayout(box) self.show() #def _render_gui def _left_panel(self): panel = QWidget() box = QVBoxLayout() btn_menu = QPushButton() icn = QtGui.QIcon.fromTheme("application-menu") btn_menu.setIcon(icn) btn_menu.setIconSize(QSize(BTN_MENU_SIZE, BTN_MENU_SIZE)) btn_menu.setMaximumWidth(BTN_MENU_SIZE) btn_menu.setMaximumHeight(BTN_MENU_SIZE) btn_menu.setToolTip(_("Options")) btn_menu.setObjectName("menuButton") # box.addWidget(btn_menu,Qt.Alignment(1)) indexes = [] for index, option in self.stacks.items(): idx = index lst_widget = QListWidgetItem() lst_widget.setText(option['name']) mod = option.get('module', None) if mod: try: idx = mod.index except: pass if idx > 0: icn = QtGui.QIcon.fromTheme(option['icon']) lst_widget.setIcon(icn) if 'tooltip' in option.keys(): lst_widget.setToolTip(option['tooltip']) while idx in indexes: idx += 1 indexes.append(index) self.stacks[idx]['widget'] = lst_widget orderedStacks = {} orderedStacks[0] = self.stacks[0] #self.lst_options.addItem(orderedStacks[0]['widget']) cont = 0 indexes.sort() for index in indexes: if index: orderedStacks[cont] = self.stacks[index].copy() if self.stacks[index].get('visible', True) == True: self.lst_options.addItem(orderedStacks[cont]['widget']) cont += 1 self.stacks = orderedStacks.copy() box.addWidget(self.lst_options) self.lst_options.currentRowChanged.connect(self._show_stack) self.lst_options.setCurrentIndex(QModelIndex()) self.last_index = None panel.setLayout(box) self.resize(self.size().width() + box.sizeHint().width(), self.size().height() + box.sizeHint().height() / 2) self.lst_options.setFixedSize( self.lst_options.sizeHintForColumn(0) + 2 * (self.lst_options.frameWidth() + 15), self.height() ) #self.lst_options.sizeHintForRow(0) * self.lst_options.count() + 2 * (self.lst_options.frameWidth()+15)) return (panel) #def _left_panel def _right_panel(self): panel = QWidget() box = QVBoxLayout() idx = 0 text = [ _("Welcome to the configuration of ") + self.appName, _("From here you can:<br>") ] orderIdx = list(self.stacks.keys()) for idx in orderIdx: data = self.stacks[idx] stack = data.get('module', None) if stack: stack.setLevel(self.level) stack.setConfig(self.config) stack._load_screen() if self.stacks[idx].get('visible', True) == True: text.append( " * <a href=\"appconf://%s\"><span style=\"font-weight:bold;text-decoration:none\">%s</span></a>" % (idx + 1, stack.menu_description)) try: self.stk_widget.insertWidget(idx, stack) except: self.stk_widget.insertWidget(idx, stack.init_stack()) stack = QWidget() stack.setObjectName("panel") s_box = QVBoxLayout() lbl_txt = QLabel() lbl_txt.setTextFormat(Qt.RichText) lbl_txt.setText("<br>".join(text)) lbl_txt.linkActivated.connect(self._linkStack) lbl_txt.setObjectName("desc") lbl_txt.setAlignment(Qt.AlignTop) lbl_txt.setTextInteractionFlags(Qt.TextBrowserInteraction) s_box.addWidget(lbl_txt, 1) #Get wiki page url = self._searchWiki() if url: desc = _("Wiki help") lbl_wiki = QLabel( "<a href=\"%s\"><span style=\"text-align: right;\">%s</span></a>" % (url, desc)) lbl_wiki.setOpenExternalLinks(True) s_box.addWidget(lbl_wiki, 0, Qt.AlignRight) stack.setLayout(s_box) self.stk_widget.insertWidget(0, stack) #self.stacks[0]['module']=stack box.addWidget(self.stk_widget) panel.setLayout(box) return (panel) #def _right_panel def _linkStack(self, *args): stack = args[0].split('/')[-1] self.gotoStack(int(stack), '') def gotoStack(self, idx, parms): self._show_stack(idx=idx, parms=parms) def _show_stack(self, item=None, idx=None, parms=None): if (self.last_index == abs(self.lst_options.currentRow()) and (idx == self.last_index or isinstance(item, int))): # or self.last_index==None)): return if isinstance( self.stacks.get(self.last_index, {}).get('module', None), appConfigStack) == True: if self.stacks[self.last_index]['module'].getChanges(): if self._save_changes(self.stacks[self.last_index] ['module']) == QMessageBox.Cancel: self.lst_options.setCurrentRow(self.last_index) return else: self.stacks[self.last_index]['module'].setChanged(False) self.stacks[self.last_index]['module'].initScreen() if self.stacks[self.last_index]['module'].refresh: self._debug("Refresh config") self.getConfig() else: self._debug(self.stacks.get(self.last_index, {}).get('module')) self.last_index = 0 if isinstance(idx, int) == False: idx = self.lst_options.currentRow() + 1 self.last_index = idx - 1 try: self.stacks[idx]['module'].setConfig(self.config) except: pass self.stk_widget.setCurrentIndex(idx) # self.statusBar.hide() if parms: self.stacks[idx]['module'].setParms(parms) #def _show_stack def closeEvent(self, event): try: if self.stacks[self.last_index]['module'].getChanges(): if self._save_changes(self.stacks[self.last_index] ['module']) == QMessageBox.Cancel: event.ignore() except: pass def _show_message(self, msg, status=None): return # self.statusBar.setText(msg) # if status: # self.statusBar.show(status) # else: # self.statusBar.show(status) #def _show_message def _save_changes(self, module): dia = QMessageBox( QMessageBox.Question, _("Apply changes"), _("There're changes not saved at current screen.\nDiscard them and continue?" ), QMessageBox.Discard | QMessageBox.Cancel, self) return (dia.exec_()) def _define_css(self): css = """ QPushButton{ padding: 6px; margin:6px; font: 14px Roboto; } QPushButton#menu:active{ background:none; } QStatusBar{ background:red; color:white; font: 14px Roboto bold; } QLabel{ padding:6px; margin:6px; } #dlgLabel{ font:12px Roboto; margin:0px; border:0px; padding:3px; } QLineEdit{ border:0px; border-bottom:1px solid grey; padding:1px; font:14px Roboto; margin-right:6px; } #panel{ background-image:url("%s"); background-size:stretch; background-repeat:no-repeat; background-position:center; } #desc{ background-color:rgba(255,255,255,0.7); color:black; } #banner{ padding:1px; margin:1px; border:0px; } """ % self.background return (css)