def __init__(self, parent): """ Constructor for JobCreator dialog :param parent: parent controller instance :return: """ self._parent = parent self._parentUi = parent._parent.ui JobCreatorDialogBase.__init__(self, self._parentUi, QtCore.Qt.WindowTitleHint | QtCore.Qt.WindowMinMaxButtonsHint | QtCore.Qt.WindowCloseButtonHint) self.setupUi(self) self.setWindowIcon(self._parentUi.windowIcon()) print("\tgui/JobCreatorDialog parent: ", self._parent, " -> self: ", self) if oPB.PRINTHIER else None print("\tgui/JobCreatorDialog parentUi: ", self._parentUi, " -> self: ", self) if oPB.PRINTHIER else None self.splash = Splash(self, translate("MainWindow", "Please wait...")) self.splash.close() # only for linux self.model_clients = None self.model_products = None self.assign_model(self._parent.model_clients, self._parent.model_products) self.dateSelector.setSelectedDate(datetime.now().date()) self.timeSelector.setTime(datetime.now().time()) self.connect_signals()
def __init__(self, parent): """ Constructor for deploy opsi client agent dialog :param parent: parent controller instance :return: """ self._parent = parent self._parentUi = parent._parent.ui DeployAgentDialogBase.__init__( self, self._parentUi, QtCore.Qt.WindowTitleHint | QtCore.Qt.WindowMinMaxButtonsHint | QtCore.Qt.WindowCloseButtonHint) self.setupUi(self) self.setWindowIcon(self._parentUi.windowIcon()) print("\tgui/DeployAgentDialog parent: ", self._parent, " -> self: ", self) if oPB.PRINTHIER else None print("\tgui/DeployAgentDialog parentUi: ", self._parentUi, " -> self: ", self) if oPB.PRINTHIER else None self.splash = Splash(self, translate("MainWindow", "Please wait...")) self.splash.close() # only for linux self.helpviewer = Help(oPB.HLP_FILE, oPB.HLP_PREFIX, self) self.create_optionsgroups() self.optionGroupDeploy.setChecked(self.chkDeployToMulti.isChecked()) self.connect_signals()
def __init__(self, parent): """ Constructor for UninstallDialog dialog :param parent: parent controller instance :return: """ self._parent = parent self._parentUi = parent._parent.ui LockedProductsDialogBase.__init__(self, self._parentUi, QtCore.Qt.WindowTitleHint | QtCore.Qt.WindowMinMaxButtonsHint | QtCore.Qt.WindowCloseButtonHint) self.setupUi(self) self.setWindowIcon(self._parentUi.windowIcon()) print("\tgui/LockedProductsDialog parent: ", self._parent, " -> self: ", self) if oPB.PRINTHIER else None print("\tgui/LockedProductsDialog parentUi: ", self._parentUi, " -> self: ", self) if oPB.PRINTHIER else None self.splash = Splash(self, translate("MainWindow", "Please wait...")) self.splash.close() # only for linux self.helpviewer = Help(oPB.HLP_FILE, oPB.HLP_PREFIX, self) self.model = None self.assign_model(self._parent.model_products) self.connect_signals()
def __init__(self, parent): """ Constructor for settings dialog :param parent: parent controller instance :return: """ self._parent = parent self._parentUi = parent._parent.ui ReportSelectorDialogBase.__init__( self, self._parentUi, QtCore.Qt.WindowTitleHint | QtCore.Qt.WindowMinMaxButtonsHint | QtCore.Qt.WindowCloseButtonHint) self.setupUi(self) self.setWindowIcon(self._parentUi.windowIcon()) print("\tgui/ReportSelectorDialog parent: ", self._parent, " -> self: ", self) if oPB.PRINTHIER else None print("\tgui/ReportSelectorDialog parentUi: ", self._parentUi, " -> self: ", self) if oPB.PRINTHIER else None self.splash = Splash(self, translate("MainWindow", "Please wait...")) self.viewer = HtmlDialog(self) self.model_left = None self.model_right = None self.assign_model(self._parent.model_report, self._parent.model_report) self.connect_signals()
def __init__(self, parent): """ Constructor for settings dialog :param parent: parent window for settings dialog :return: """ self._parent = parent self._parentUi = parent._parent.ui SettingsDialogBase.__init__(self, self._parentUi) self.setupUi(self) self.setWindowIcon(self._parentUi.windowIcon()) print("\tgui/SettingsDialog parent: ", self._parent, " -> self: ", self) if oPB.PRINTHIER else None print("\tgui/SettingsDialog parentUi: ", self._parentUi, " -> self: ", self) if oPB.PRINTHIER else None self.splash = Splash(self, translate("MainWindow", "Please wait...")) self.splash.close() # only for linux self.helpviewer = Help(oPB.HLP_FILE, oPB.HLP_PREFIX, self) # take care of sys.platform if sys.platform.startswith("linux"): self.chkUseNetworkDrive.setEnabled(False) if sys.platform.startswith("win32"): self.inpLocalShareBase.setEnabled(False) self.datamapper = None self.model = self._parent.model # setup translation combobox, must appear before data mapper creation Translator.setup_language_combobox(self, self.cmbLanguage) # additional setup self.create_optionbuttongroups() self.create_datamapper() self.connect_signals() # reset tabs self.tabWidget.setCurrentIndex(0) # hide not needed widgets self.lblBlockRecognition.setVisible(False) self.inpBlockMarker.setVisible(False) self.btnResetRecognition.setVisible(False)
def __init__(self, parent): """ Constructor of MainWindow :param parent: parent :return: """ self._parent = parent print("\tgui/MainWindow parent: ", self._parent, " -> self: ", self) if oPB.PRINTHIER else None MainWindowBase.__init__(self) self.setupUi(self) self.recentFileActions = [] if oPB.NETMODE == "offline": self.setWindowTitle("opsiPackageBuilder v" + oPB.PROGRAM_VERSION + " ( OFFLINE MODE )") else: self.setWindowTitle("opsiPackageBuilder v" + oPB.PROGRAM_VERSION) self.datamapper = None # QDataWidgetMapper object for field mapping self.datamapper_dependencies = None self.datamapper_properties = None self.helpviewer = Help(oPB.HLP_FILE, oPB.HLP_PREFIX, self) self.splash = Splash(self, translate("MainWindow", "Please wait...")) self.splash.close() # only for linux self.create_datamapper() self.connect_signals() self.connect_validators() self.reset_datamapper_and_display(0) self.fill_cmbDepProdID()
def __init__(self, parent): """ Constructor for DepotManager dialog :param parent: parent controller instance :return: """ self._parent = parent self._parentUi = parent._parent.ui DepotManagerDialogBase.__init__(self, self._parentUi, QtCore.Qt.WindowTitleHint | QtCore.Qt.WindowMinMaxButtonsHint | QtCore.Qt.WindowCloseButtonHint) self.setupUi(self) self.setWindowIcon(self._parentUi.windowIcon()) print("\tgui/DepotManagerDialog parent: ", self._parent, " -> self: ", self) if oPB.PRINTHIER else None print("\tgui/DepotManagerDialog parentUi: ", self._parentUi, " -> self: ", self) if oPB.PRINTHIER else None self.model_left = None self.model_right = None self.assign_model(self._parent.model_left, self._parent.model_right) self.splash = Splash(self, translate("MainWindow", "Please wait...")) self.splash.close() # only for linux self.helpviewer = Help(oPB.HLP_FILE, oPB.HLP_PREFIX, self) self.connect_signals() self._parent._ui_box_left = self.cmbDepotLeft self._parent._ui_box_right = self.cmbDepotRight self._parent._ui_repobtn_left = self.btnFetchRepoLeft self._parent._ui_repobtn_right = self.btnFetchRepoRight self.update_ui()
def __init__(self, parent): """ Constructor for deploy opsi client agent dialog :param parent: parent controller instance :return: """ self._parent = parent self._parentUi = parent._parent.ui DeployAgentDialogBase.__init__(self, self._parentUi, QtCore.Qt.WindowTitleHint | QtCore.Qt.WindowMinMaxButtonsHint | QtCore.Qt.WindowCloseButtonHint) self.setupUi(self) self.setWindowIcon(self._parentUi.windowIcon()) print("\tgui/DeployAgentDialog parent: ", self._parent, " -> self: ", self) if oPB.PRINTHIER else None print("\tgui/DeployAgentDialog parentUi: ", self._parentUi, " -> self: ", self) if oPB.PRINTHIER else None self.splash = Splash(self, translate("MainWindow", "Please wait...")) self.splash.close() # only for linux self.helpviewer = Help(oPB.HLP_FILE, oPB.HLP_PREFIX, self) self.create_optionsgroups() self.optionGroupDeploy.setChecked(self.chkDeployToMulti.isChecked()) self.connect_signals()
def __init__(self, parent): """ Constructor for DepotManager dialog :param parent: parent controller instance :return: """ self._parent = parent self._parentUi = parent._parent.ui DepotManagerDialogBase.__init__( self, self._parentUi, QtCore.Qt.WindowTitleHint | QtCore.Qt.WindowMinMaxButtonsHint | QtCore.Qt.WindowCloseButtonHint) self.setupUi(self) self.setWindowIcon(self._parentUi.windowIcon()) print("\tgui/DepotManagerDialog parent: ", self._parent, " -> self: ", self) if oPB.PRINTHIER else None print("\tgui/DepotManagerDialog parentUi: ", self._parentUi, " -> self: ", self) if oPB.PRINTHIER else None self.model_left = None self.model_right = None self.assign_model(self._parent.model_left, self._parent.model_right) self.splash = Splash(self, translate("MainWindow", "Please wait...")) self.splash.close() # only for linux self.helpviewer = Help(oPB.HLP_FILE, oPB.HLP_PREFIX, self) self.connect_signals() self._parent._ui_box_left = self.cmbDepotLeft self._parent._ui_box_right = self.cmbDepotRight self._parent._ui_repobtn_left = self.btnFetchRepoLeft self._parent._ui_repobtn_right = self.btnFetchRepoRight self.update_ui()
class DepotManagerDialog(DepotManagerDialogBase, DepotManagerDialogUI, LogMixin, EventMixin): dialogOpened = pyqtSignal() dialogClosed = pyqtSignal() def __init__(self, parent): """ Constructor for DepotManager dialog :param parent: parent controller instance :return: """ self._parent = parent self._parentUi = parent._parent.ui DepotManagerDialogBase.__init__( self, self._parentUi, QtCore.Qt.WindowTitleHint | QtCore.Qt.WindowMinMaxButtonsHint | QtCore.Qt.WindowCloseButtonHint) self.setupUi(self) self.setWindowIcon(self._parentUi.windowIcon()) print("\tgui/DepotManagerDialog parent: ", self._parent, " -> self: ", self) if oPB.PRINTHIER else None print("\tgui/DepotManagerDialog parentUi: ", self._parentUi, " -> self: ", self) if oPB.PRINTHIER else None self.model_left = None self.model_right = None self.assign_model(self._parent.model_left, self._parent.model_right) self.splash = Splash(self, translate("MainWindow", "Please wait...")) self.splash.close() # only for linux self.helpviewer = Help(oPB.HLP_FILE, oPB.HLP_PREFIX, self) self.connect_signals() self._parent._ui_box_left = self.cmbDepotLeft self._parent._ui_box_right = self.cmbDepotRight self._parent._ui_repobtn_left = self.btnFetchRepoLeft self._parent._ui_repobtn_right = self.btnFetchRepoRight self.update_ui() def connect_signals(self): self.finished.connect(self.dialogClosed.emit) self._parent.dataAboutToBeAquired.connect(self.splash.setProgress) self._parent.dataAquired.connect(self.update_ui) self._parent.dataAquired.connect(self.splash.close) self._parent.modelDataUpdated.connect(self.update_fields) self._parent.modelDataUpdated.connect(self.update_ui) self.btnRefresh.clicked.connect(lambda: self._parent.update_data(True)) self.btnCompare.clicked.connect(self.compare_sides) self.btnShowLog.clicked.connect(self._parentUi.showLogRequested) self.btnReport.clicked.connect(self._parent.ui_report.show_) self.btnInstall.clicked.connect(self._parent.install) self.btnUninstall.clicked.connect(self.remove_delegate) self.btnUpload.clicked.connect(self._parentUi.upload) self.btnUnregister.clicked.connect(self._parent.unregister_depot) self.btnSetRights.clicked.connect(self._parent.set_rights) self.btnRunProdUpdater.clicked.connect( self._parent.run_product_updater) self.btnGenMD5.clicked.connect(self.generate_md5) self.btnOnlineCheck.clicked.connect(self._parent.onlinecheck) self.btnReboot.clicked.connect(self._parent.reboot_depot) self.btnPoweroff.clicked.connect(self._parent.poweroff_depot) self.btnHelp.clicked.connect( lambda: self.helpviewer.showHelp(oPB.HLP_DST_DEPOTM, False)) #self.cmbDepotLeft.currentTextChanged.connect(self._parent.side_content) #side_content #self.cmbDepotRight.currentTextChanged.connect(self._parent.side_content) self.cmbDepotLeft.currentTextChanged.connect( self._parent.switch_content) #side_content self.cmbDepotRight.currentTextChanged.connect( self._parent.switch_content) self.cmbDepotLeft.currentTextChanged.connect( self.set_active_side) #side_content self.cmbDepotRight.currentTextChanged.connect(self.set_active_side) self.tblDepotLeft.clicked.connect(self.set_active_side) self.tblDepotRight.clicked.connect(self.set_active_side) self.btnFetchRepoLeft.clicked.connect(self._parent.switch_content) self.btnFetchRepoRight.clicked.connect(self._parent.switch_content) def closeEvent(self, event): """ Overrides base method, disconnects custom signals :param event: close event """ try: self._parent.dataAboutToBeAquired.disconnect(self.splash.show_) self._parent.dataAquired.disconnect(self.splash.close) except: pass event.accept() self.finished.emit( 0 ) # we have to emit this manually, because of subclassing closeEvent def keyPressEvent(self, evt: QKeyEvent): """ Ignore escape key event, because it would close startup window. Any other key will be passed to the super class key event handler for further processing. :param evt: key event :return: """ if evt.key() == QtCore.Qt.Key_Escape: self.close() else: super().keyPressEvent(evt) pass def assign_model(self, model_left, model_right): self.logger.debug("Assign data model") self.model_left = model_left self.model_right = model_right self.tblDepotLeft.setModel(self.model_left) self.tblDepotRight.setModel(self.model_right) self.resizeTables() def resizeTables(self): self.resizeLeft() self.resizeRight() @pyqtSlot() def resizeLeft(self): self.tblDepotLeft.resizeRowsToContents() self.tblDepotLeft.resizeColumnsToContents() def resizeRight(self): self.tblDepotRight.resizeRowsToContents() self.tblDepotRight.resizeColumnsToContents() @pyqtSlot() def update_ui(self): """Update ui state""" self.logger.debug("Update ui") self.resizeTables() # enable / disable buttons if self.cmbDepotLeft.currentIndex() == -1: self.btnFetchRepoLeft.setEnabled(False) else: self.btnFetchRepoLeft.setEnabled(True) if self.cmbDepotRight.currentIndex() == -1: self.btnFetchRepoRight.setEnabled(False) else: self.btnFetchRepoRight.setEnabled(True) if self.cmbDepotLeft.currentIndex( ) == -1 or self.cmbDepotRight.currentIndex() == -1: self.btnCompare.setEnabled(False) else: self.btnCompare.setEnabled(True) if self._parent._active_side is None: self.btnInstall.setEnabled(False) self.btnUninstall.setEnabled(False) self.btnUpload.setEnabled(False) self.btnUnregister.setEnabled(False) self.btnSetRights.setEnabled(False) self.btnRunProdUpdater.setEnabled(False) self.btnGenMD5.setEnabled(False) self.btnOnlineCheck.setEnabled(False) self.btnPoweroff.setEnabled(False) self.btnReboot.setEnabled(False) else: if self._parent._compare is False: self.btnInstall.setEnabled(True) self.btnUninstall.setEnabled(True) self.btnUpload.setEnabled(True) else: self.btnInstall.setEnabled(False) self.btnUninstall.setEnabled(False) self.btnUpload.setEnabled(False) self.btnUnregister.setEnabled(True) self.btnOnlineCheck.setEnabled(True) self.btnPoweroff.setEnabled(True) self.btnReboot.setEnabled(True) if self._parent._active_side == "left": if self._parent._type_left == "depot": self.btnSetRights.setEnabled(False) self.btnRunProdUpdater.setEnabled(False) self.btnGenMD5.setEnabled(False) self.btnUninstall.setText( translate("DepotManagerDialog", "Uninstall")) else: if self._parent._compare is not True: self.btnSetRights.setEnabled(True) self.btnRunProdUpdater.setEnabled(True) self.btnGenMD5.setEnabled(True) self.btnUninstall.setText( translate("DepotManagerDialog", "Delete")) else: if self._parent._type_right == "depot": self.btnSetRights.setEnabled(False) self.btnRunProdUpdater.setEnabled(False) self.btnGenMD5.setEnabled(False) self.btnUninstall.setText( translate("DepotManagerDialog", "Uninstall")) else: if self._parent._compare is not True: self.btnSetRights.setEnabled(True) self.btnRunProdUpdater.setEnabled(True) self.btnGenMD5.setEnabled(True) self.btnUninstall.setText( translate("DepotManagerDialog", "Delete")) if self._parent._compare is False: self.decorate_button(self.btnCompare, "") else: self.decorate_button(self.btnCompare, "green") # set decoration and text if self._parent._type_left == "repo": self.decorate_button(self.btnFetchRepoLeft, "blue") self.btnFetchRepoLeft.setText( translate("DepotManagerDialog", "Fetch DEPOT content")) else: self.decorate_button(self.btnFetchRepoLeft, "") self.btnFetchRepoLeft.setText( translate("DepotManagerDialog", "Fetch REPO content")) if self._parent._type_right == "repo": self.decorate_button(self.btnFetchRepoRight, "blue") self.btnFetchRepoRight.setText( translate("DepotManagerDialog", "Fetch DEPOT content")) else: self.decorate_button(self.btnFetchRepoRight, "") self.btnFetchRepoRight.setText( translate("DepotManagerDialog", "Fetch REPO content")) @pyqtSlot() def update_fields(self): """Reload combobox content and reset their state""" self.logger.debug("Update field content") l = [] for key, val in ConfigHandler.cfg.depotcache.items(): l.append(key + " (" + val + ")") l.sort() # temporary disconnect events for smoother display self.cmbDepotLeft.currentTextChanged.disconnect( self._parent.switch_content) self.cmbDepotRight.currentTextChanged.disconnect( self._parent.switch_content) self.cmbDepotLeft.currentTextChanged.disconnect(self.set_active_side) self.cmbDepotRight.currentTextChanged.disconnect(self.set_active_side) self.cmbDepotLeft.clear() self.cmbDepotRight.clear() self.cmbDepotLeft.addItems(l) self.cmbDepotRight.addItems(l) self.cmbDepotLeft.currentTextChanged.connect( self._parent.switch_content) self.cmbDepotRight.currentTextChanged.connect( self._parent.switch_content) self.cmbDepotLeft.setCurrentIndex(-1) self.cmbDepotRight.setCurrentIndex(-1) self.cmbDepotLeft.currentTextChanged.connect(self.set_active_side) self.cmbDepotRight.currentTextChanged.connect(self.set_active_side) def decorate_button(self, button, state): """ Set custom button property ``dispState`` ``dispState`` is a conditional CSS parameter, like:: QPushButton[dispState="red"] { color: rgb(255, 0, 0); } Dependend on its value, the button will be colored differently. :param button: QPushButton :param state: color ["red", "green", "blue"] """ button.setProperty("dispState", state) button.style().unpolish(button) button.style().polish(button) button.update() def show_(self): self.logger.debug("Open depot manager") self.dialogOpened.emit() self.show() w = self.splitter.geometry().width() self.splitter.setSizes([w * (1 / 2), w * (1 / 2)]) self._parent.update_data() self._parent.dataAquired.emit() self.cmbDepotLeft.setCurrentIndex(-1) self.cmbDepotRight.setCurrentIndex(-1) self.resizeTables() self.activateWindow() @pyqtSlot() def compare_sides(self): """ Initiate side-by-side comparison of table views See: :meth:`oPB.controller.components.depotmanager.DepotManagerComponent.compare_leftright` """ if self.cmbDepotLeft.currentIndex( ) == -1 or self.cmbDepotRight.currentIndex() == -1: return self._parent._compare = True if self._parent._compare is False else False self._parent.compare_leftright() @pyqtSlot() def set_active_side(self): """Set active table marker and initiate ui update accordingly""" if self.sender() == self.tblDepotLeft or self.sender( ) == self.cmbDepotLeft: self.logger.debug("Set active tableview: left") self._parent._active_side = "left" if self.sender() == self.tblDepotRight or self.sender( ) == self.cmbDepotRight: self.logger.debug("Set active tableview: right") self._parent._active_side = "right" self.update_ui() @pyqtSlot() def remove_delegate(self): """ Decide between depot or repository removal of selected product(s) See: :meth:`oPB.controller.components.depotmanager.DepotManagerComponent.remove_from_depot` See: :meth:`oPB.controller.components.depotmanager.DepotManagerComponent.delete_from_repo` """ if self._parent._active_side == "left": depot = self.cmbDepotLeft.currentText().split()[0] selection = self.tblDepotLeft.selectionModel().selectedRows() prodIdx = [] if self._parent._type_left == "depot": for row in selection: prodIdx.append(self.model_left.item(row.row(), 0).text()) self._parent.remove_from_depot(depot, prodIdx) else: for row in selection: prodIdx.append( self.model_left.item(row.row(), 0).text() + "_" + self.model_left.item(row.row(), 1).text() + "-" + self.model_left.item(row.row(), 2).text()) self._parent.delete_from_repo(depot, prodIdx) if self._parent._active_side == "right": depot = self.cmbDepotLeft.currentText().split()[0] selection = self.tblDepotRight.selectionModel().selectedRows() prodIdx = [] if self._parent._type_right == "depot": for row in selection: prodIdx.append(self.model_right.item(row.row(), 0).text()) self._parent.remove_from_depot(depot, prodIdx) else: for row in selection: prodIdx.append( self.model_right.item(row.row(), 0).text() + "_" + self.model_right.item(row.row(), 1).text() + "-" + self.model_right.item(row.row(), 2).text()) self._parent.delete_from_repo(depot, prodIdx) def generate_md5(self): """ Get dialog widget state and initiate backend MD5 generation See: :meth:`oPB.controller.components.depotmanager.DepotManagerComponent.generate_md5` """ if self._parent._active_side == "left": depot = self.cmbDepotLeft.currentText().split()[0] selection = self.tblDepotLeft.selectionModel().selectedRows() prodIdx = [] if self._parent._type_left == "repo": for row in selection: prodIdx.append( self.model_left.item(row.row(), 0).text() + "_" + self.model_left.item(row.row(), 1).text() + "-" + self.model_left.item(row.row(), 2).text()) self._parent.generate_md5(depot, prodIdx) if self._parent._active_side == "right": depot = self.cmbDepotRight.currentText().split()[0] selection = self.tblDepotRight.selectionModel().selectedRows() prodIdx = [] if self._parent._type_right == "repo": for row in selection: prodIdx.append( self.model_right.item(row.row(), 0).text() + "_" + self.model_right.item(row.row(), 1).text() + "-" + self.model_right.item(row.row(), 2).text()) self._parent.generate_md5(depot, prodIdx) def retranslateMsg(self): self.logger.debug("Retranslating further messages...") self.splash.msg = translate("MainWindow", "Please wait...")
class DeployAgentDialog(DeployAgentDialogBase, DeployAgentDialogUI, LogMixin, EventMixin): dialogOpened = pyqtSignal() dialogClosed = pyqtSignal() def __init__(self, parent): """ Constructor for deploy opsi client agent dialog :param parent: parent controller instance :return: """ self._parent = parent self._parentUi = parent._parent.ui DeployAgentDialogBase.__init__(self, self._parentUi, QtCore.Qt.WindowTitleHint | QtCore.Qt.WindowMinMaxButtonsHint | QtCore.Qt.WindowCloseButtonHint) self.setupUi(self) self.setWindowIcon(self._parentUi.windowIcon()) print("\tgui/DeployAgentDialog parent: ", self._parent, " -> self: ", self) if oPB.PRINTHIER else None print("\tgui/DeployAgentDialog parentUi: ", self._parentUi, " -> self: ", self) if oPB.PRINTHIER else None self.splash = Splash(self, translate("MainWindow", "Please wait...")) self.splash.close() # only for linux self.helpviewer = Help(oPB.HLP_FILE, oPB.HLP_PREFIX, self) self.create_optionsgroups() self.optionGroupDeploy.setChecked(self.chkDeployToMulti.isChecked()) self.connect_signals() def connect_signals(self): self.finished.connect(self.dialogClosed.emit) self.btnShowLog.clicked.connect(self._parentUi.showLogRequested) self.btnDeploy.clicked.connect(self.deploy) self.chkDeployToMulti.clicked.connect(lambda: self.optionGroupDeploy.setChecked(self.chkDeployToMulti.isChecked())) self.btnHelp.clicked.connect(lambda: self.helpviewer.showHelp(oPB.HLP_DST_DEPLOY, False)) def keyPressEvent(self, evt: QKeyEvent): """ Ignore escape key event, because it would close startup window. Any other key will be passed to the super class key event handler for further processing. :param evt: key event :return: """ if evt.key() == QtCore.Qt.Key_Escape: self.close() else: super().keyPressEvent(evt) pass def show_(self): self.logger.debug("Open deploy agent dialog") self.dialogOpened.emit() self.cmbPreExec.clear() self.cmbPreExec.addItems([""]) self.cmbPreExec.addItems(ConfigHandler.cfg.predeploycmds) self.show() self.activateWindow() def closeEvent(self, event): self.logger.debug("Closing dialog") # Save the 10 last used pre deployment commands c = deque('', 10) for i in range(self.cmbPreExec.count()): if self.cmbPreExec.itemText(i) != "": if not self.cmbPreExec.itemText(i) in c: c.append(self.cmbPreExec.itemText(i)) if self.cmbPreExec.currentText() != "": if not self.cmbPreExec.currentText() in c: c.append(self.cmbPreExec.currentText()) ConfigHandler.cfg.predeploycmds = list(c) event.accept() self.finished.emit(0) # we have to emit this manually, because of subclassing closeEvent def create_optionsgroups(self): """ Create group of dependend dialog widgets See :class:`oPB.gui.utilities.SpecialOptionButtonGroup` """ self.logger.debug("Create option button group") # build special button groups for False/True choice self.optionGroupDeploy = SpecialOptionButtonGroup(self.chkDeployToMulti, None, [self.inpDestMulti], [self.inpDestSingle]) def deploy(self): """ Get values from dialog widgets and pass them to backend method start_deploy. See: :meth:`oPB.controller.components.deployagent.DeployAgentComponent.start_deploy` :return: """ self.logger.debug("Deploy client agent") check_ip = re.compile(oPB.OPB_VALID_IP_ADDRESS_REGEX) check_dns = re.compile(oPB.OPB_VALID_HOSTNAME_REGEX) destination = [] if self.chkDeployToMulti.isChecked(): text = self.inpDestMulti.toPlainText().splitlines() for line in text: if line.strip() != "": if check_ip.search(line.strip()) or check_dns.search(line.strip()): destination.append(line.strip()) else: self.logger.warning("Obviously, no network name or ip: " + line.strip()) else: if self.inpDestSingle.text().strip() != "": destination.append(self.inpDestSingle.text().strip()) if not destination: self.logger.info('No destination.') return self.splash.show_() self.logger.info('Possible destinations: ' + str(destination)) if self.rdDoNothing.isChecked(): post = "" elif self.rdStartOpsiclientd.isChecked(): post = "startclient" elif self.rdReboot.isChecked(): post = "reboot" elif self.rdShutdown.isChecked(): post = "shutdown" # now build option dict # Win10 - removed escaping of baslashes, because smbclient 4.3.11 (opsiVM) handles them correctly: # "user": self.inpUser.text().strip().replace("\\", "\\\\"), options = { "pre_action": self.cmbPreExec.currentText().strip(), "user": self.inpUser.text().strip(), "pass": self.inpPass.text().strip(), "usefqdn": self.chkUseFQDN.isChecked(), "ignoreping": self.chkIgnorePing.isChecked(), "skipexisting": self.chkSkipExisting.isChecked(), "post_action": post, "proceed": self.chkProceed.isChecked() } self._parent.start_deploy(destination, options) self.splash.close() def retranslateMsg(self): self.logger.debug("Retranslating further messages...") self.splash.msg = translate("MainWindow", "Please wait...")
class MainWindow(MainWindowBase, MainWindowUI, LogMixin, EventMixin): showLogRequested = pyqtSignal() windowMoved = pyqtSignal() MaxRecentFiles = 5 def __init__(self, parent): """ Constructor of MainWindow :param parent: parent :return: """ self._parent = parent print("\tgui/MainWindow parent: ", self._parent, " -> self: ", self) if oPB.PRINTHIER else None MainWindowBase.__init__(self) self.setupUi(self) self.recentFileActions = [] if oPB.NETMODE == "offline": self.setWindowTitle("opsiPackageBuilder v" + oPB.PROGRAM_VERSION + " ( OFFLINE MODE )") else: self.setWindowTitle("opsiPackageBuilder v" + oPB.PROGRAM_VERSION) self.datamapper = None # QDataWidgetMapper object for field mapping self.datamapper_dependencies = None self.datamapper_properties = None self.helpviewer = Help(oPB.HLP_FILE, oPB.HLP_PREFIX, self) self.splash = Splash(self, translate("MainWindow", "Please wait...")) self.splash.close() # only for linux self.create_datamapper() self.connect_signals() self.connect_validators() self.reset_datamapper_and_display(0) self.fill_cmbDepProdID() def init_recent(self): """Init recent files menu items""" for i in range(MainWindow.MaxRecentFiles): self.recentFileActions.append( QAction(self, visible=False, triggered=self.open_recent_project)) for i in range(MainWindow.MaxRecentFiles): self.menuRecent.addAction(self.recentFileActions[i]) self._parent.startup.menuRecent.addAction(self.recentFileActions[i]) self.update_recent_file_actions() def update_recent_file_actions(self): """Update recent file menu actions""" files = ConfigHandler.cfg.recent numRecentFiles = min(len(files), MainWindow.MaxRecentFiles) for i in range(numRecentFiles): text = "&%d %s" % (i + 1, self.stripped_name(files[i])) self.recentFileActions[i].setText(text) self.recentFileActions[i].setData(files[i]) self.recentFileActions[i].setVisible(True) for j in range(numRecentFiles, MainWindow.MaxRecentFiles): self.recentFileActions[j].setVisible(False) def stripped_name(self, fullFileName): """ Remove any path component from ``fullFileName`` :param fullFileName: complete path of file or folder :return: last path part """ return QtCore.QFileInfo(fullFileName).fileName() def set_current_project(self, project): """ Insert current project into recent files list :param project: project name """ files = ConfigHandler.cfg.recent try: files.remove(project) except ValueError: pass files.insert(0, project) del files[MainWindow.MaxRecentFiles:] ConfigHandler.cfg.recent = files for widget in QApplication.topLevelWidgets(): if isinstance(widget, MainWindow): widget.update_recent_file_actions() def create_datamapper(self): self.logger.debug("Create data widget mapper for fields") self.datamapper = QDataWidgetMapper(self) self.datamapper.setModel(self._parent.model_fields) self.datamapper.addMapping(self.lblPacketFolder, 0, b"text") # "text" property name must be added for QLabel to work with QDataWidgetmapper self.datamapper.addMapping(self.inpProductId, 1) self.datamapper.addMapping(self.inpProductName, 2) self.datamapper.addMapping(self.editDesc, 3) self.datamapper.addMapping(self.editAdvice, 4) self.datamapper.addMapping(self.cmbProductType, 5) self.datamapper.addMapping(self.inpProductVer, 6) self.datamapper.addMapping(self.inpPackageVer, 7) self.datamapper.addMapping(self.sldPrio, 8) self.datamapper.addMapping(self.cmbLicense, 9) self.datamapper.addMapping(self.inpScrSetup, 10) self.datamapper.addMapping(self.inpScrUninstall, 11) self.datamapper.addMapping(self.inpScrUpdate, 12) self.datamapper.addMapping(self.inpScrAlways, 13) self.datamapper.addMapping(self.inpScrOnce, 14) self.datamapper.addMapping(self.inpScrCustom, 15) self.datamapper.addMapping(self.inpScrUserLogin, 16) self.datamapper.toFirst() self.logger.debug("Create data widget mapper for dependencies") self.datamapper_dependencies = QDataWidgetMapper(self) self.datamapper_dependencies.setSubmitPolicy(QDataWidgetMapper.ManualSubmit) self.datamapper_dependencies.setModel(self._parent.model_dependencies) self.datamapper_dependencies.addMapping(self.cmbDepAction, 0) self.datamapper_dependencies.addMapping(self.cmbDepProdID, 1) self.datamapper_dependencies.addMapping(self.cmbDepReqAction, 2) self.datamapper_dependencies.addMapping(self.cmbDepInstState, 3) self.datamapper_dependencies.addMapping(self.cmbDepRequirement, 4) self.datamapper_dependencies.toFirst() self.logger.debug("Create data widget mapper for properties") self.datamapper_properties = QDataWidgetMapper(self) self.datamapper_properties.setSubmitPolicy(QDataWidgetMapper.ManualSubmit) self.datamapper_properties.setModel(self._parent.model_properties) self.datamapper_properties.addMapping(self.inpPropName, 0) self.datamapper_properties.addMapping(self.cmbPropType, 1) self.datamapper_properties.addMapping(self.cmbPropMulti, 2) self.datamapper_properties.addMapping(self.cmbPropEdit, 3) self.datamapper_properties.addMapping(self.inpPropDesc, 4) self.datamapper_properties.addMapping(self.inpPropVal, 5) self.datamapper_properties.addMapping(self.inpPropDef, 6) self.datamapper_properties.addMapping(self.cmbPropDef, 6) self.datamapper_properties.toFirst() def connect_signals(self): self.logger.debug("Connect signals") self.actionNew.triggered.connect(self.new_project) self.actionOpen.triggered.connect(self.open_project) self.actionClose.triggered.connect(self._parent.project_close) self.actionQuit.triggered.connect(self.close) # self.actionSave.triggered.connect(self._parent.project_save) self.actionSave.triggered.connect(self.submit_main_and_save) self.actionShowLog.triggered.connect(self.showLogRequested.emit) self.actionSaveAs.triggered.connect(self.save_as) self.actionStartWinst.triggered.connect(self.start_winst) self.actionScriptEditor.triggered.connect(self.open_scripteditor) self.actionHelp.triggered.connect(lambda: self.helpviewer.showHelp(oPB.HLP_DST_INDEX, False)) if self._parent.args.noupdate == True: self.actionSearchForUpdates.setEnabled(False) else: self.actionSearchForUpdates.triggered.connect(self._parent.update_check) self.actionShowChangeLog.triggered.connect(lambda: self.helpviewer.showHelp(oPB.HLP_DST_CHANGELOG, False)) self.actionAbout.triggered.connect(self.not_working) self.actionRefreshLogo.triggered.connect(self._parent.get_package_logos) self.actionMSIProductCode.triggered.connect(self.get_msiproductcode) self.actionAbout_Qt.triggered.connect(self.aboutqt) if oPB.NETMODE != "offline": # connect online menu action signals self.actionSetRights.triggered.connect(self._parent.do_setrights) self.actionInstall.triggered.connect(self.quickinstall) self.actionUpload.triggered.connect(self.upload) self.actionScheduler.triggered.connect(self._parent.scheduler_dialog) self.actionUninstall.triggered.connect(self._parent.quickuninstall_dialog) self.actionLockedProducts.triggered.connect(self._parent.lockedproducts_dialog) self.actionDeploy.triggered.connect(self._parent.deployagent_dialog) self.actionBundleCreation.triggered.connect(self._parent.bundle_dialog) self.actionDepotManager.triggered.connect(self._parent.depotmanager_dialog) self.actionImport.triggered.connect(self.package_import) else: # connect online menu action signals self.actionSetRights.triggered.connect(self.offline) self.actionInstall.triggered.connect(self.offline) self.actionUpload.triggered.connect(self.offline) self.actionScheduler.triggered.connect(self.offline) self.actionUninstall.triggered.connect(self.offline) self.actionLockedProducts.triggered.connect(self.offline) self.actionDeploy.triggered.connect(self.offline) self.actionBundleCreation.triggered.connect(self.offline) self.actionImport.triggered.connect(self.offline) # buttons # self.btnSave.clicked.connect(self._parent.project_save) self.btnSave.clicked.connect(self.submit_main_and_save) self.btnChangelogEdit.clicked.connect(self._parent.show_changelogeditor) self.btnShowScrStruct.clicked.connect(self._parent.show_scripttree) self.btnHelpPacket.clicked.connect(lambda: self.helpviewer.showHelp(oPB.HLP_DST_TABPACKET, False)) self.btnHelpDependencies.clicked.connect(lambda: self.helpviewer.showHelp(oPB.HLP_DST_TABDEPEND, False)) self.btnHelpProperties.clicked.connect(lambda: self.helpviewer.showHelp(oPB.HLP_DST_TABPROP, False)) self.btnScrSetup.clicked.connect(lambda: self.select_script_dialog("setup")) self.btnScrUninstall.clicked.connect(lambda: self.select_script_dialog("uninstall")) self.btnScrUpdate.clicked.connect(lambda: self.select_script_dialog("update")) self.btnScrAlways.clicked.connect(lambda: self.select_script_dialog("always")) self.btnScrOnce.clicked.connect(lambda: self.select_script_dialog("once")) self.btnScrCustom.clicked.connect(lambda: self.select_script_dialog("custom")) self.btnScrUserLogin.clicked.connect(lambda: self.select_script_dialog("userlogin")) self.btnScrSetupDel.clicked.connect(lambda: self.select_script_dialog("setup", False)) self.btnScrUninstallDel.clicked.connect(lambda: self.select_script_dialog("uninstall", False)) self.btnScrUpdateDel.clicked.connect(lambda: self.select_script_dialog("update", False)) self.btnScrAlwaysDel.clicked.connect(lambda: self.select_script_dialog("always", False)) self.btnScrOnceDel.clicked.connect(lambda: self.select_script_dialog("once", False)) self.btnScrCustomDel.clicked.connect(lambda: self.select_script_dialog("custom", False)) self.btnScrUserLoginDel.clicked.connect(lambda: self.select_script_dialog("userlogin", False)) self.btnScrSetupEdit.clicked.connect(self.open_scripteditor) self.btnScrUninstallEdit.clicked.connect(self.open_scripteditor) self.btnScrUpdateEdit.clicked.connect(self.open_scripteditor) self.btnScrAlwaysEdit.clicked.connect(self.open_scripteditor) self.btnScrOnceEdit.clicked.connect(self.open_scripteditor) self.btnScrCustomEdit.clicked.connect(self.open_scripteditor) self.btnScrUserLoginEdit.clicked.connect(self.open_scripteditor) if oPB.NETMODE != "offline": self.btnBuild.clicked.connect(self._parent.project_build) self.btnInstall.clicked.connect(lambda: self._parent.do_install(depot = self._parent.query_depot(parent = self))) self.btnInstSetup.clicked.connect(lambda: self._parent.do_installsetup(depot = self._parent.query_depot(parent = self))) self.btnUninstall.clicked.connect(lambda: self._parent.do_uninstall(depot = self._parent.query_depot(parent = self))) else: self.btnBuild.clicked.connect(self.offline) self.btnInstall.clicked.connect(self.offline) self.btnInstSetup.clicked.connect(self.offline) self.btnUninstall.clicked.connect(self.offline) self.btnDevFolder.clicked.connect(self.open_project_folder) self.btnDepAdd.clicked.connect(self.add_dependency) self.btnDepEdit.clicked.connect(self.edit_dependency) self.btnDepModify.clicked.connect(self.submit_dependencies) self.btnDepDelete.clicked.connect(lambda a: self._parent.remove_dependency(self.tblDependencies.selectionModel().currentIndex().row())) self.btnPropAdd.clicked.connect(self.add_property) self.btnPropEdit.clicked.connect(self.edit_property) self.btnPropModify.clicked.connect(self.submit_properties) self.btnPropDelete.clicked.connect(lambda a: self._parent.remove_property(self.tblProperties.selectionModel().currentIndex().row())) self.btnPropRead.clicked.connect(self._parent.get_properties_from_scripts) self.tblProperties.setModel(self._parent.model_properties) self.tblDependencies.setModel(self._parent.model_dependencies) self.tblDependencies.selectionModel().selectionChanged.connect(self.update_dependency_fields) self.tblProperties.selectionModel().selectionChanged.connect(self.update_property_fields) self._parent.modelDataUpdated.connect(self.reset_datamapper_and_display) self._parent.msgSend.connect(self.set_statbar_text, type=QtCore.Qt.DirectConnection) self._parent.processingEnded.connect(self.set_button_state) self._parent.progressChanged.connect(self.splash.incProgress, type=QtCore.Qt.DirectConnection) self._parent.processingEnded.connect(self.splash.close) self._parent.processingStarted.connect(self.splash.show_) self._parent.projectImageLoaded.connect(self.set_project_logo) self._parent.projectLoaded.connect(self.set_current_project) self._parent.projectLoaded.connect(self.set_button_state) # connect event filter to tables self.tblFilter = TableKeyEventFilter() self.tblDependencies.installEventFilter(self.tblFilter) self.tblProperties.installEventFilter(self.tblFilter) TableKeyEventFilter.actiondict[(self.tblDependencies, QtCore.Qt.Key_F2)] = self.edit_dependency TableKeyEventFilter.actiondict[(self.tblProperties, QtCore.Qt.Key_F2)] = self.edit_property def connect_validators(self): self.logger.debug("Connect validators to fields") # set validators if ConfigHandler.cfg.age == "True": self.set_regex_validator(self.inpProductId, oPB.OPB_PRODUCT_ID_REGEX_NEW) self.set_regex_validator(self.cmbDepProdID, oPB.OPB_PRODUCT_ID_REGEX_NEW) self.set_regex_validator(self.inpPropName, oPB.OPB_PROPERTY_REGEX_NEW) else: self.set_regex_validator(self.inpProductId, oPB.OPB_PRODUCT_ID_REGEX_OLD) self.set_regex_validator(self.cmbDepProdID, oPB.OPB_PRODUCT_ID_REGEX_OLD) self.set_regex_validator(self.inpPropName, oPB.OPB_PROPERTY_REGEX_OLD) # product id self.inpProductId.textChanged.connect(self.check_state) self.inpProductId.textChanged.emit(self.inpProductId.text()) self.inpProductId.textChanged.connect(self.set_button_state) self.cmbDepProdID.editTextChanged.connect(self.check_state) self.cmbDepProdID.editTextChanged.emit(self.cmbDepProdID.currentText()) # property names self.inpPropName.textChanged.connect(self.check_state) self.inpPropName.textChanged.emit(self.inpPropName.text()) # product version self.set_regex_validator(self.inpProductVer, oPB.OPB_PRODUCT_VER_REGEX) self.inpProductVer.textChanged.connect(self.check_state) self.inpProductVer.textChanged.emit(self.inpProductVer.text()) self.inpProductVer.textChanged.connect(self.set_button_state) # package version self.set_regex_validator(self.inpPackageVer, oPB.OPB_PACKAGE_VER_REGEX) self.inpPackageVer.textChanged.connect(self.check_state) self.inpPackageVer.textChanged.emit(self.inpPackageVer.text()) self.inpPackageVer.textChanged.connect(self.set_button_state) # script validator self.set_scriptfile_validator(self.inpScrSetup) self.inpScrSetup.textChanged.connect(self.check_state) self.inpScrSetup.textChanged.emit(self.inpScrSetup.text()) self.set_scriptfile_validator(self.inpScrUninstall) self.inpScrUninstall.textChanged.connect(self.check_state) self.inpScrUninstall.textChanged.emit(self.inpScrUninstall.text()) self.set_scriptfile_validator(self.inpScrUpdate) self.inpScrUpdate.textChanged.connect(self.check_state) self.inpScrUpdate.textChanged.emit(self.inpScrUpdate.text()) self.set_scriptfile_validator(self.inpScrAlways) self.inpScrAlways.textChanged.connect(self.check_state) self.inpScrAlways.textChanged.emit(self.inpScrAlways.text()) self.set_scriptfile_validator(self.inpScrOnce) self.inpScrOnce.textChanged.connect(self.check_state) self.inpScrOnce.textChanged.emit(self.inpScrOnce.text()) self.set_scriptfile_validator(self.inpScrCustom) self.inpScrCustom.textChanged.connect(self.check_state) self.inpScrCustom.textChanged.emit(self.inpScrCustom.text()) self.set_scriptfile_validator(self.inpScrUserLogin) self.inpScrUserLogin.textChanged.connect(self.check_state) self.inpScrUserLogin.textChanged.emit(self.inpScrUserLogin.text()) def fill_cmbDepProdID(self): """Fill combobox with values from opsi_depot share""" self.cmbDepProdID.clear() if oPB.NETMODE != "offline": try: self.logger.debug("Retrieve active package list from depot") subpath = "\\\\" + ConfigHandler.cfg.opsi_server + "\\" + oPB.DEPOTSHARE_BASE subdirs = Helper.get_subdirlist(subpath) subdirs.sort() for elem in subdirs: self.cmbDepProdID.addItem(elem) except: pass @pyqtSlot() def aboutqt(self): """Show Qt's About dialog""" self._parent.msgbox("", oPB.MsgEnum.MS_ABOUTQT, self) @pyqtSlot() def not_working(self): """Show a short "Not working" message""" self._parent.msgbox(translate("MainWindow", "Sorry, this function doesn't work at the moment!"), oPB.MsgEnum.MS_ALWAYS, self) @pyqtSlot() def offline(self): """Show offline message""" self._parent.msgbox(translate("MainWindow", "You are working in offline mode. Functionality not available!"), oPB.MsgEnum.MS_ALWAYS, self) @pyqtSlot() def get_msiproductcode(self): """Show MSI product code of individual MSI file""" self.logger.debug("Show MSI product code " + platform.system()) if platform.system() in ["Windows"]: ext = "MSI Package (*.msi)" msi = QFileDialog.getOpenFileName(self, translate("MainWindow", "Choose package file"), "", ext) if not msi == ("", ""): self.logger.debug("Selected package: " + msi[0]) prodcode = Helper.get_msi_property(msi[0]) self._parent.msgbox(translate("MainWindow", "Selected MSI: " + Helper.get_file_from_path(msi[0]) + "\n\n" + "Product Code: " + " " + prodcode), oPB.MsgEnum.MS_ALWAYS, self) else: self.logger.debug("Dialog aborted.") else: self._parent.msgbox(translate("MainWindow", "Function not available at the moment for system:" + " " + platform.system()), oPB.MsgEnum.MS_ALWAYS, self) @pyqtSlot() def start_winst(self): """Start opsi winst32""" self.logger.debug("Start Winst under " + platform.system()) if platform.system() in ["Windows"]: if os.path.exists(oPB.OPB_WINST_NT): subprocess.call([oPB.OPB_WINST_NT, self.lblPacketFolder.text().replace("\\","/")]) else: self._parent.msgbox(translate("MainWindow", "Local opsi-winst installation not found or client-agent not installed!"), oPB.MsgEnum.MS_ERR, self) else: self._parent.msgbox(translate("MainWindow", "Function not available at the moment for system:" + " " + platform.system()), oPB.MsgEnum.MS_ALWAYS, self) @pyqtSlot() def open_scripteditor(self): """ Open configured script editor. Method reaction depends on calling widget (self.sender()) """ self.logger.debug("Start scripteditor") if ConfigHandler.cfg.editor_intern == "True": self._parent.msgbox(translate("MainWindow", "Internal editor not available at the moment. Use external editor instead!"), oPB.MsgEnum.MS_ALWAYS, self) self.actionSettings.trigger() return if os.path.exists(ConfigHandler.cfg.scripteditor): path = Helper.concat_path_native(self.lblPacketFolder.text(), "CLIENT_DATA") if self.sender() == self.btnScrSetupEdit: if self.inpScrSetup.text().strip() == "": script = "setup.opsiscript" else: script = self.inpScrSetup.text() elif self.sender() == self.btnScrUninstallEdit: if self.inpScrUninstall.text().strip() == "": script = "uninstall.opsiscript" else: script = self.inpScrUninstall.text() elif self.sender() == self.btnScrUpdateEdit: if self.inpScrUpdate.text().strip() == "": script = "update.opsiscript" else: script = self.inpScrUpdate.text() elif self.sender() == self.btnScrAlwaysEdit: if self.inpScrAlways.text().strip() == "": script = "always.opsiscript" else: script = self.inpScrAlways.text() elif self.sender() == self.btnScrOnceEdit: if self.inpScrOnce.text().strip() == "": script = "once.opsiscript" else: script = self.inpScrOnce.text() elif self.sender() == self.btnScrCustomEdit: if self.inpScrCustom.text().strip() == "": script = "custom.opsiscript" else: script = self.inpScrCustom.text() elif self.sender() == self.btnScrUserLoginEdit: if self.inpScrUserLogin.text().strip() == "": script = "userlogin.opsiscript" else: script = self.inpScrUserLogin.text() elif self.sender() == self.actionScriptEditor: script = "new.opsiscript" # script editor from menu if path != "" and script != "": path = Helper.concat_path_native(path, script) self.logger.debug("Opening script: " + path) # construct calling array # first add basic scripteditor executable cmd = [ConfigHandler.cfg.scripteditor] # if there are options, split and append them if (ConfigHandler.cfg.editor_options).strip() != "": for part in (ConfigHandler.cfg.editor_options).split(): cmd.append(part) # if attach direct is true, combine last option with script file path if ConfigHandler.cfg.editor_attachdirect == "True": cmd[-1] = cmd[-1] + path # or else, append as separate value to list else: cmd.append(path) else: cmd.append(path) self.logger.debug("Executing subprocess: " + str(cmd)) proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True) outs, errs = proc.communicate() self.logger.info(outs) self.logger.error(errs) if proc.returncode != 0 and proc.returncode != 555: self._parent.msgbox(translate("MainWindow", "Editor did not exit as expected.\n\nThe following message(s) returned:") + "\n\nStandard Out:\n" + outs + "\nStandard Err:\n" + errs + "\n\nReturn code: " + str(proc.returncode), oPB.MsgEnum.MS_WARN, self) else: self._parent.msgbox(translate("MainWindow", "Editor not found:" + " " + ConfigHandler.cfg.scripteditor), oPB.MsgEnum.MS_ERR, self) @pyqtSlot() def quickinstall(self): """ Initiate backend quick install See: :meth:`oPB.controller.base.BaseController.do_quickinstall` """ self.logger.debug("Quick install package") ext = "opsi Package (*.opsi)" # generate file extension selection string for dialog script = QFileDialog.getOpenFileName(self, translate("MainWindow", "Choose package file"), "", ext) if not script == ("", ""): self.logger.debug("Selected package: " + script[0]) self._parent.startup.hide_me() self._parent.do_quickinstall(pack = script[0], depot = self._parent.query_depot(parent = self)) self._parent.startup.show_me() else: self.logger.debug("Dialog aborted.") @pyqtSlot() def upload(self): """ Initiate backend package upload See: :meth:`oPB.controller.base.BaseController.do_upload` """ self.logger.debug("Upload package") if self._parent.startup.isVisible(): pt = self._parent.startup else: pt = self ext = "opsi Package (*.opsi)" # generate file extension selection string for dialog script = QFileDialog.getOpenFileName(pt, translate("MainWindow", "Choose package file"), "", ext) if not script == ("", ""): self.logger.debug("Selected package: " + script[0]) if self._parent.startup.isVisible(): self._parent.startup.hide_me() self._parent.do_upload(script[0], depot = self._parent.query_depot(parent = self)) self._parent.startup.show_me() else: self._parent.do_upload(script[0], depot = self._parent.query_depot(parent = self)) else: self.logger.debug("Dialog aborted.") @pyqtSlot() def package_import(self): """ Initiate package import See: :meth:`oPB.controller.base.BaseController.do_import` """ self.logger.debug("Import package") if self._parent.startup.isVisible(): pt = self._parent.startup else: pt = self ext = "opsi Package (*.opsi)" # generate file extension selection string for dialog script = QFileDialog.getOpenFileName(pt, translate("MainWindow", "Choose package file"), "", ext) if not script == ("", ""): self.logger.debug("Selected package: " + script[0]) self._parent.package_import(script[0]) else: self.logger.debug("Dialog aborted.") @pyqtSlot() def save_as(self): """ Initiate SaveAs """ self.logger.debug("Save package as new") directory = QFileDialog.getExistingDirectory(self, translate("MainWindow", "Save current project as..."), ConfigHandler.cfg.dev_dir, QFileDialog.ShowDirsOnly) # sometimes window disappears into background, force to front self.activateWindow() if not directory == "": self.logger.info("Chosen directory for new project: " + directory) self._parent.project_copy(directory) else: self.logger.debug("Dialog aborted.") @pyqtSlot() def set_button_state(self): """Set state of online install/instsetup buttons""" self.logger.debug("Set button state") if self._parent._active_project and self.is_file_available(self.sender()): self.btnInstall.setEnabled(True) self.btnInstSetup.setEnabled(True) else: self.btnInstall.setEnabled(False) self.btnInstSetup.setEnabled(False) def is_file_available(self, sender = None): """ Check if opsi package file is available :return: True/False """ #ctr = 0 pack = self.inpProductId.text() + "_" + self.inpProductVer.text() + "-" + self.inpPackageVer.text() + ".opsi" #fullname = self.lblPacketFolder.text().replace("\\","/") + "/" + self.inpProductId.text() + \ # "_" + self.inpProductVer.text() + "-" + self.inpPackageVer.text() + ".opsi" #p = pathlib.Path(fullname) # sometimes file creation, especially on network shares, is very fast # so wait a short moment # only, if sender is main GUI, because THEN it comes via signal progressEnded from self._do() #if sender == self._parent: #while (not p.is_file()) and (ctr <= 4): # ctr += 1 # sleep(0.1) if os.path.exists(self.lblPacketFolder.text()): if pack in os.listdir(self.lblPacketFolder.text()): return True else: return False else: return False #return p.is_file() @pyqtSlot(int) def reset_datamapper_and_display(self, tabIdx = -1): """Reset tables and fields""" self.logger.debug("Reset datamapper and display") tab = self.tabWidget.currentIndex() if tabIdx == -1 else tabIdx # select first row in mapped model self.datamapper.toFirst() self.tblProperties.selectRow(0) self.tblDependencies.selectRow(0) self.tblDependencies.resizeRowsToContents() self.tblProperties.resizeRowsToContents() selection = self.tblProperties.selectionModel().selection() self.update_property_fields(selection) selection = self.tblDependencies.selectionModel().selection() self.update_dependency_fields(selection) self.tabWidget.setCurrentIndex(tab) self.set_dev_folder() # self.lblImage.setPixmap(QtGui.QPixmap()) # self.lblImage.setText(translate("MainWindow", "NO IMAGE (F6)")) @pyqtSlot(QtCore.QItemSelection) def update_dependency_fields(self, idx:QtCore.QItemSelection): """ Update single fields on dependency tab in relation to selected row in table view""" self.logger.debug("Update dependency fields") self.cmbDepAction.setEnabled(False) self.cmbDepProdID.setEnabled(False) self.cmbDepReqAction.setEnabled(False) self.cmbDepInstState.setEnabled(False) self.cmbDepRequirement.setEnabled(False) self.btnDepModify.setEnabled(False) self.btnDepAdd.setEnabled(True) # disconnect if there has been an editing event before try: self.cmbDepReqAction.currentIndexChanged.disconnect() self.cmbDepInstState.currentIndexChanged.disconnect() except: pass if self.datamapper_dependencies.model().item(0, 0) is not None: self.btnDepDelete.setEnabled(True) self.btnDepEdit.setEnabled(True) # indexes() returns list of selected items # as we only have 1 at a time, return first item and get corresponding row number if not idx.indexes() == []: row = idx.indexes()[0].row() self.datamapper_dependencies.setCurrentIndex(row) else: self.datamapper_dependencies.toFirst() else: self.btnDepDelete.setEnabled(False) self.btnDepEdit.setEnabled(False) @pyqtSlot(QtCore.QItemSelection) def update_property_fields(self, idx:QtCore.QItemSelection): """ Update single fields on property tab in relation to selected row in table view""" self.logger.debug("Update property fields") self.inpPropName.setEnabled(False) self.cmbPropType.setEnabled(False) self.cmbPropMulti.setEnabled(False) self.cmbPropEdit.setEnabled(False) self.inpPropDesc.setEnabled(False) self.inpPropVal.setEnabled(False) self.inpPropDef.setEnabled(False) self.cmbPropDef.setEnabled(False) self.btnPropModify.setEnabled(False) self.btnPropAdd.setEnabled(True) # disconnect if there has been an editing event before try: self.cmbPropType.currentIndexChanged.disconnect() except: pass if self.datamapper_properties.model().item(0, 0) is not None: self.btnPropDelete.setEnabled(True) self.btnPropEdit.setEnabled(True) # indexes() returns list of selected items # as we only have 1 at a time, return first item and get corresponding row number if not idx.indexes() == []: row = idx.indexes()[0].row() self.datamapper_properties.setCurrentIndex(row) else: self.datamapper_properties.toFirst() else: self.btnPropDelete.setEnabled(False) self.btnPropEdit.setEnabled(False) self.inpPropName.setText("") self.inpPropDesc.setText("") self.inpPropVal.setText("") self.inpPropDef.setText("") @pyqtSlot() def add_dependency(self): """Add new empty dependency and activate editing""" self.logger.debug("Add dependency") self._parent.add_dependency() self.edit_dependency() self.datamapper_dependencies.toFirst() @pyqtSlot() def add_property(self): """Add new empty property and activate editing""" self.logger.debug("Add property") self._parent.add_property() self.edit_property() self.datamapper_properties.toFirst() def edit_dependency(self): """Change field and button state for dependency editing""" self.logger.debug("Edit dependency") self.cmbDepAction.setEnabled(True) self.cmbDepProdID.setEnabled(True) self.cmbDepReqAction.setEnabled(True) self.cmbDepInstState.setEnabled(True) self.cmbDepRequirement.setEnabled(True) self.btnDepModify.setEnabled(True) self.btnDepAdd.setEnabled(False) self.btnDepDelete.setEnabled(False) self.btnDepEdit.setEnabled(False) # special combobox checker, connect only on edit self.cmbDepReqAction.currentIndexChanged.connect(self.check_combobox_selection) self.cmbDepInstState.currentIndexChanged.connect(self.check_combobox_selection) def edit_property(self): """Change field and button state for property editing""" self.logger.debug("Edit property") self.inpPropName.setEnabled(True) self.cmbPropType.setEnabled(True) self.inpPropDesc.setEnabled(True) self.btnPropModify.setEnabled(True) self.btnPropAdd.setEnabled(False) self.btnPropDelete.setEnabled(False) self.btnPropEdit.setEnabled(False) if self._parent.model_properties.item(self.datamapper_properties.currentIndex(), 1).text() == 'bool': self.datamapper_properties.removeMapping(self.inpPropDef) self.datamapper_properties.addMapping(self.cmbPropDef, 6) self.inpPropVal.setEnabled(False) self.inpPropDef.setEnabled(False) self.cmbPropMulti.setEnabled(False) self.cmbPropEdit.setEnabled(False) self.cmbPropDef.setEnabled(True) else: self.datamapper_properties.addMapping(self.inpPropDef, 6) self.datamapper_properties.removeMapping(self.cmbPropDef) self.inpPropVal.setEnabled(True) self.inpPropDef.setEnabled(True) self.cmbPropMulti.setEnabled(True) self.cmbPropEdit.setEnabled(True) self.cmbPropDef.setEnabled(False) # special combobox checker, connect only on edit self.cmbPropType.currentIndexChanged.connect(self.check_combobox_selection) @pyqtSlot() def submit_properties(self): """ Submit changes in property edit widgets to model and update fields again """ self.logger.debug("Submit properties") self.datamapper_properties.submit() selection = self.tblProperties.selectionModel().selection() self.update_property_fields(selection) @pyqtSlot() def submit_main_and_save(self): """ Submits all dialog fields and calls backend save """ self.logger.debug("Submit main and save") self.datamapper.submit() self._parent.project_save() @pyqtSlot() def submit_dependencies(self): """ Submit changes in dependency edit widgets to model and update fields again """ self.logger.debug("Submit dependencies") self.datamapper_dependencies.submit() selection = self.tblDependencies.selectionModel().selection() self.update_dependency_fields(selection) @pyqtSlot() def set_dev_folder(self): """Set special label text""" self.lblDevFolder.setText(ConfigHandler.cfg.dev_dir) # execute process loop to assure updating of label text qApp.processEvents() @pyqtSlot(list) def set_project_logo(self, logo): """ Show project logo if found. Only logos with <projectid>.<png|gif|jpg> will be shown. :param logo: full logo path :return: """ self.logger.debug("Set logo") try: pixmap = QtGui.QPixmap(logo[0]) pixmap = pixmap.scaledToHeight(160) pixmap = pixmap.scaledToWidth(160) self.lblImage.setPixmap(pixmap) except: self.lblImage.setPixmap(QtGui.QPixmap()) self.lblImage.setText(translate("MainWindow", "NO IMAGE (F6)")) @pyqtSlot() def open_project(self): """ Opens a folder selection dialog and emits selected folder name via signal projectLoadRequested """ self.logger.debug("Open project dialog") directory = QFileDialog.getExistingDirectory(self, translate("MainWindow", "Open project"), ConfigHandler.cfg.dev_dir, QFileDialog.ShowDirsOnly) # sometimes window disappears into background, force to front self.activateWindow() if not directory == "": if not self._parent.project_close(False): return self.logger.info("Chosen existing project directory: " + directory) self._parent.project_load(directory) else: self.logger.debug("Dialog aborted.") @pyqtSlot() def open_recent_project(self): """Open project via recent files menu entry""" action = self.sender() if action: if not self._parent.project_close(False): return self.logger.debug("Chosen recent project: " + action.data()) self._parent.project_load(action.data()) @pyqtSlot() def new_project(self): """ Opens a folder selection dialog and emits selected folder name via Signal projectNewRequested """ self.logger.debug("New project dialog") directory = QFileDialog.getExistingDirectory(self, translate("MainWindow", "Create new project"), ConfigHandler.cfg.dev_dir, QFileDialog.ShowDirsOnly) # sometimes window disappears into background, force to front self.activateWindow() if not directory == "": self.logger.info("Chosen directory for new project: " + directory) self._parent.project_create(directory) else: self.logger.debug("Dialog aborted.") def closeEvent(self, event): """Delegate closeEvent handling to parent controller""" [self.helpviewer.close(), None][self.helpviewer.isVisible()] self._parent.quit_application(event) def moveEvent(self, *args, **kwargs): """ Send signal if main window is moved Used to position startup windows """ self.windowMoved.emit() def resizeEvent(self, *args, **kwargs): """ Send signal if main window is resized Used to position startup windows """ self.windowMoved.emit() def open_project_folder(self): """Open os based explorer dialog""" self.logger.debug("Open project folder" + platform.system()) if platform.system() in ["Windows", "Linux"]: webbrowser.open(self.lblPacketFolder.text()) elif platform.system() == "Darwin": webbrowser.open("file://" + self.lblPacketFolder.text()) @pyqtSlot() def select_script_dialog(self, script_type, setvalue = True): """ Opens a dialog to select a script file or clear field content :param script_type: field type identifier (setup, uninstall, update, always, once, custom, userlogin) :param setvalue: set new value = True, empty field only = False """ self.logger.debug("Select script dialog") ext = "Scripts (" + " ".join(["*." + x for x in oPB.SCRIPT_EXT]) + ")" # generate file extension selection string for dialog if setvalue: if self.lblPacketFolder.text() == "": script = QFileDialog.getOpenFileName(self, translate("MainWindow", "Choose script"), ConfigHandler.cfg.dev_dir, ext) else: script = QFileDialog.getOpenFileName(self, translate("MainWindow", "Choose script"), Helper.concat_path_native(self.lblPacketFolder.text(), "CLIENT_DATA"), ext) if not script == ("", ""): self._parent.set_selected_script(script[0], script_type) else: self._parent.set_selected_script("", script_type) """ @pyqtSlot() def get_inputfield_and_value(self): field = self.sender() if type(field) is QPlainTextEdit: print(field.objectName() + ": " + field.toPlainText()) if type(field) is QLineEdit: print(field.objectName() + ": " + field.text()) """ @pyqtSlot() def check_state(self, *args, **kwargs): """ Sets background color of QLineEdit depending on validator state """ sender = self.sender() validator = sender.validator() # get validator state if type(sender) == QLineEdit: state = validator.validate(sender.text(), 0)[0] elif type(sender) == QComboBox: state = validator.validate(sender.currentText(), 0)[0] # associate state with color if state == QtGui.QValidator.Acceptable: # ACC colStat = "ACC" elif state == QtGui.QValidator.Intermediate: # INT colStat = "INT" else: colStat = "ERR" if type(validator) == ScriptFileValidator: self._parent.msgbox(sender.text() + "@@" + translate("MainWindow", "The script has to be inside the CLIENT_DATA folder of the package!"), oPB.MsgEnum.MS_ERR) # set background color according to state into dynamic field property sender.setProperty("checkState", colStat) sender.style().unpolish(sender) sender.style().polish(sender) sender.update() @pyqtSlot(str) def set_statbar_text(self, msg): """ Sets status bar text :param msg: message text """ stamp = datetime.datetime.now().strftime("%H:%M:%S") #print("STAT BAR: [" + stamp + "] " + msg.replace("<br>", " ").strip()) self.oPB_statBar.showMessage("[" + stamp + "] " + msg.replace("<br>", " ").strip(), 0) @pyqtSlot(int) def check_combobox_selection(self, value): """ Check combobox status and update ui (set widgets enabled/disabled accordingly) :param value: control value dependend on self.sender(), see :meth:`check_combobox_selection` """ if self.sender() == self.cmbDepReqAction: if value != 0: self.cmbDepInstState.setCurrentIndex(0) elif self.sender() == self.cmbDepInstState: if value != 0: self.cmbDepReqAction.setCurrentIndex(0) elif self.sender() == self.cmbPropType: if value == 1: self.inpPropVal.setText("") self.inpPropDef.setText("") self.datamapper_properties.addMapping(self.cmbPropDef, 6) self.datamapper_properties.removeMapping(self.inpPropDef) self.cmbPropMulti.setCurrentIndex(0) self.cmbPropEdit.setCurrentIndex(0) self.inpPropVal.setEnabled(False) self.inpPropDef.setEnabled(False) self.cmbPropMulti.setEnabled(False) self.cmbPropEdit.setEnabled(False) self.cmbPropDef.setEnabled(True) self.cmbPropDef.setCurrentIndex(0) else: self.datamapper_properties.addMapping(self.inpPropDef, 6) self.datamapper_properties.removeMapping(self.cmbPropDef) self.datamapper_properties.addMapping(self.inpPropDef, 6) self.datamapper_properties.removeMapping(self.cmbPropDef) self.inpPropVal.setEnabled(True) self.inpPropDef.setEnabled(True) self.cmbPropMulti.setEnabled(True) self.cmbPropEdit.setEnabled(True) self.cmbPropDef.setEnabled(False) def set_regex_validator(self, field, regex): """ Set validator for input field :param field: field to validate :param regex: regular expression """ valexp = QtCore.QRegExp(regex) validator = QtGui.QRegExpValidator(valexp) field.setValidator(validator) def set_scriptfile_validator(self, field): """ Assign file validator to field :param field: field to validate """ validator = ScriptFileValidator(self, field) field.setValidator(validator) def retranslateMsg(self): self.logger.debug("Retranslating further messages...") self.splash.msg = translate("MainWindow", "Please wait...")
class LockedProductsDialog(LockedProductsDialogBase, LockedProductsDialogUI, LogMixin, EventMixin): dialogOpened = pyqtSignal() dialogClosed = pyqtSignal() def __init__(self, parent): """ Constructor for UninstallDialog dialog :param parent: parent controller instance :return: """ self._parent = parent self._parentUi = parent._parent.ui LockedProductsDialogBase.__init__(self, self._parentUi, QtCore.Qt.WindowTitleHint | QtCore.Qt.WindowMinMaxButtonsHint | QtCore.Qt.WindowCloseButtonHint) self.setupUi(self) self.setWindowIcon(self._parentUi.windowIcon()) print("\tgui/LockedProductsDialog parent: ", self._parent, " -> self: ", self) if oPB.PRINTHIER else None print("\tgui/LockedProductsDialog parentUi: ", self._parentUi, " -> self: ", self) if oPB.PRINTHIER else None self.splash = Splash(self, translate("MainWindow", "Please wait...")) self.splash.close() # only for linux self.helpviewer = Help(oPB.HLP_FILE, oPB.HLP_PREFIX, self) self.model = None self.assign_model(self._parent.model_products) self.connect_signals() def connect_signals(self): self.dialogOpened.connect(self.update_ui) self.btnRefresh.clicked.connect(lambda: self.update_ui(True)) self.btnUnlock.clicked.connect(self.unlock) self.btnClose.clicked.connect(self.dialogClosed.emit) self.btnHelp.clicked.connect(lambda: self.helpviewer.showHelp(oPB.HLP_DST_UNLOCK, False)) def keyPressEvent(self, evt: QKeyEvent): """ Ignore escape key event, because it would close startup window. Any other key will be passed to the super class key event handler for further processing. :param evt: key event :return: """ if evt.key() == QtCore.Qt.Key_Escape: self.dialogOpened.emit() self.close() else: super().keyPressEvent(evt) pass def assign_model(self, model): self.model = model self.tblProducts.setModel(self.model) self.resizeTable() def resizeTable(self): self.tblProducts.resizeRowsToContents() self.tblProducts.resizeColumnsToContents() def show_(self): self.logger.debug("Open unlocked products dialog") self.show() self.activateWindow() self.dialogOpened.emit() def update_ui(self, force = False): """ Update model data and reset tableviews See: :meth:`oPB.controller.components.lockedproducts.LockedProductsComponent.update_model_data` :param force: Force ui AND backend data refresh """ self.logger.debug("Update UI") self.splash.show_() self._parent.update_model_data(force) self.setWindowTitle(translate("LockedProductsDialog", "Locked products") + translate("LockedProductsDialog", " - Selected depot: ") + self._parent.selected_depot) self.resizeTable() self.splash.close() def unlock(self): """ Initiate product unlocking via backend See: :meth:`oPB.controller.components.lockedproducts.LockedProductsComponent.unlock_selection` """ prods = [] for row in self.tblProducts.selectionModel().selectedRows(): prods.append(self.model.item(row.row(), 0).text()) self.splash.show_() self._parent.unlock_selection(prods) self.resizeTable() self.splash.close() def retranslateMsg(self): self.logger.debug("Retranslating further messages...") self.splash.msg = translate("MainWindow", "Please wait...")
class JobListDialog(JobListDialogBase, JobListDialogUI, LogMixin, EventMixin): dialogOpened = pyqtSignal() dialogClosed = pyqtSignal() def __init__(self, parent): """ Constructor for JobList dialog :param parent: parent controller instance :return: """ self._parent = parent self._parentUi = parent._parent.ui JobListDialogBase.__init__(self, self._parentUi, QtCore.Qt.WindowTitleHint | QtCore.Qt.WindowMinMaxButtonsHint | QtCore.Qt.WindowCloseButtonHint) self.setupUi(self) self.setWindowIcon(self._parentUi.windowIcon()) print("\tgui/JobListDialog parent: ", self._parent, " -> self: ", self) if oPB.PRINTHIER else None print("\tgui/JobListDialog parent: ", self._parentUi, " -> self: ", self) if oPB.PRINTHIER else None self.splash = Splash(self, translate("MainWindow", "Please wait...")) self.splash.close() # only for linux self.helpviewer = Help(oPB.HLP_FILE, oPB.HLP_PREFIX, self) self.model = None self.assign_model(self._parent.model_jobs) self.connect_signals() def connect_signals(self): self.dialogOpened.connect(self.update_ui) self.finished.connect(self.dialogClosed.emit) self.btnRefresh.clicked.connect(lambda: self.update_ui(force = True)) self.btnCreate.clicked.connect(self._parent.ui_jobcreator.show_) self.btnRemove.clicked.connect(self.delete_jobs) self.btnClearAll.clicked.connect(self._parent.delete_all_jobs) self.btnHelp.clicked.connect(lambda: self.helpviewer.showHelp(oPB.HLP_DST_JOBLIST, False)) def keyPressEvent(self, evt: QKeyEvent): """ Ignore escape key event, because it would close startup window. Any other key will be passed to the super class key event handler for further processing. :param evt: key event :return: """ if evt.key() == QtCore.Qt.Key_Escape: self.close() else: super().keyPressEvent(evt) pass def assign_model(self, model): self.model = model self.tblJobs.setModel(self.model) self.resizeTable() def resizeTable(self): self.tblJobs.resizeRowsToContents() self.tblJobs.resizeColumnsToContents() self.tblJobs.sortByColumn(5, QtCore.Qt.AscendingOrder) def show_(self): self.logger.debug("Show job list dialog") if ConfigHandler.cfg.no_at_warning_msg == "False": self.usage_hint() self.show() self.activateWindow() self.dialogOpened.emit() def usage_hint(self): """Show longer AT usage hint message""" msg = translate("infoMessages", "infoAT") self._parent.msgbox(msg, oPB.MsgEnum.MS_ALWAYS) def update_ui(self, force = False): """ Update model data and reset tableview See: :meth:`oPB.controller.components.scheduler.SchedulerComponent.update_model_data_jobs` :param force: Force ui AND backend data refresh """ self.splash.show_() self.splash.setProgress(50) self._parent.update_model_data_jobs(force = force) self.setWindowTitle(translate("JobListDialog", "Job list") + translate("JobListDialog", " - Current server: ") + self._parent.at_server) self.resizeTable() self.splash.close() def delete_jobs(self): """Initiate job deletion via backend See: :meth:`oPB.controller.components.scheduler.SchedulerComponent.delete_jobs` """ self.splash.show_() selection = self.tblJobs.selectionModel().selectedRows() remIdx = [] for row in selection: remIdx.append(self.model.item(row.row(),5).text()) self._parent.delete_jobs(remIdx) self.splash.close() def retranslateMsg(self): self.logger.debug("Retranslating further messages...") self.splash.msg = translate("MainWindow", "Please wait...")
class SettingsDialog(SettingsDialogBase, SettingsDialogUI, LogMixin, EventMixin): settingsAboutToBeClosed = pyqtSignal() dataChanged = pyqtSignal() def __init__(self, parent): """ Constructor for settings dialog :param parent: parent window for settings dialog :return: """ self._parent = parent self._parentUi = parent._parent.ui SettingsDialogBase.__init__(self, self._parentUi) self.setupUi(self) self.setWindowIcon(self._parentUi.windowIcon()) print("\tgui/SettingsDialog parent: ", self._parent, " -> self: ", self) if oPB.PRINTHIER else None print("\tgui/SettingsDialog parentUi: ", self._parentUi, " -> self: ", self) if oPB.PRINTHIER else None self.splash = Splash(self, translate("MainWindow", "Please wait...")) self.splash.close() # only for linux self.helpviewer = Help(oPB.HLP_FILE, oPB.HLP_PREFIX, self) # take care of sys.platform if sys.platform.startswith("linux"): self.chkUseNetworkDrive.setEnabled(False) if sys.platform.startswith("win32"): self.inpLocalShareBase.setEnabled(False) self.datamapper = None self.model = self._parent.model # setup translation combobox, must appear before data mapper creation Translator.setup_language_combobox(self, self.cmbLanguage) # additional setup self.create_optionbuttongroups() self.create_datamapper() self.connect_signals() # reset tabs self.tabWidget.setCurrentIndex(0) # hide not needed widgets self.lblBlockRecognition.setVisible(False) self.inpBlockMarker.setVisible(False) self.btnResetRecognition.setVisible(False) def connect_signals(self): self.logger.debug("Connect signals") self.btnCancel.clicked.connect(self.request_close_dialog) self.btnSetDevFolder.clicked.connect(self.select_dev_dir) self.btnSetKeyFile.clicked.connect(self.select_keyfile) self.btnExternalEditor.clicked.connect(self.select_externaleditor) self.btnLogFile.clicked.connect(self.select_logfile) self.btnHelp.clicked.connect( lambda: self.helpviewer.showHelp(oPB.HLP_DST_SETTINGS, False)) self.btnSave.clicked.connect(self._parent.save_config) self.btnRefreshDepotCache.clicked.connect( self._parent.refresh_depot_cache) self.dataChanged.connect(self.datamapper.submit) self.settingsAboutToBeClosed.connect(self._parent.close_dialog) self.rdWBOld.clicked.connect(self.set_model_data) self.rdWBNew.clicked.connect(self.set_model_data) self.rdOpsi40.clicked.connect(self.set_model_data) self.rdOpsi41.clicked.connect(self.set_model_data) self.rdOpsiSrvNew.clicked.connect(self.set_model_data) self.rdOpsiSrvOld.clicked.connect(self.set_model_data) self.rdSUDOWithPass.clicked.connect(self.set_model_data) self.rdSUDOWithoutPass.clicked.connect(self.set_model_data) self.rdEditorInternal.clicked.connect(self.set_model_data) self.rdEditorExternal.clicked.connect(self.set_model_data) self.chkUseDepotFunctions.clicked.connect(self.set_model_data) self.chkUseProxy.clicked.connect(self.set_model_data) self.chkUseKeyFile.clicked.connect(self.set_model_data) self.chkWriteLog.clicked.connect(self.set_model_data) def create_datamapper(self): self.logger.debug("Create data widget mapper") self.datamapper = QDataWidgetMapper(self) self.datamapper.setModel(self._parent.model) self.datamapper.addMapping(self.inpConfigServer, 0) self.datamapper.addMapping(self.inpOpsiUser, 1) self.datamapper.addMapping(self.inpOpsiPass, 2) self.datamapper.addMapping(self.inpRootPass, 3) self.datamapper.addMapping(self.chkUseNetworkDrive, 4, b"checked") self.datamapper.addMapping(self.optionGroupSrvVersion, 5, b"checked") self.datamapper.addMapping(self.optionGroupSUDO, 6, b"checked") self.datamapper.addMapping(self.inpSSHPort, 7) self.datamapper.addMapping(self.optionGroupSSHKeyFile, 8, b"checked") self.datamapper.addMapping(self.inpKeyFile, 9) self.datamapper.addMapping(self.inpMaintainer, 10) self.datamapper.addMapping(self.inpMailAddress, 11) self.datamapper.addMapping(self.inpDevFolder, 12) self.datamapper.addMapping(self.inpBuildCommand, 13) self.datamapper.addMapping(self.inpInstallCommand, 14) self.datamapper.addMapping(self.inpUninstallCommand, 15) self.datamapper.addMapping(self.chkShowOutput, 16, b"checked") self.datamapper.addMapping(self.chkAlwaysReload, 17, b"checked") self.datamapper.addMapping(self.inpWOLLeadTime, 18) self.datamapper.addMapping(self.inpUploadCommand, 19) self.datamapper.addMapping(self.inpInstSetupCommand, 20) #self.datamapper.addMapping(self.settings.chkUseDepotFunctions, 21, "checked") self.datamapper.addMapping(self.optionGroupDepotFuncs, 21, b"checked") self.datamapper.addMapping(self.chkExtendedEditor, 22, b"checked") self.datamapper.addMapping(self.inpExternalEditor, 23) self.datamapper.addMapping(self.inpBlockMarker, 24) self.datamapper.addMapping(self.optionGroupEditorTyp, 25, b"checked") self.datamapper.addMapping(self.chkSyntaxHighlight, 26, b"checked") self.datamapper.addMapping(self.chkCodeFolding, 27, b"checked") self.datamapper.addMapping(self.chkForceEntryBuild, 28, b"checked") self.datamapper.addMapping(self.chkForceEntrySave, 29, b"checked") self.datamapper.addMapping(self.chkMsgError, 30, b"checked") self.datamapper.addMapping(self.chkMsgWarning, 31, b"checked") self.datamapper.addMapping(self.chkMsgInfo, 32, b"checked") self.datamapper.addMapping(self.chkMsgAT, 33, b"checked") self.datamapper.addMapping(self.cmbLanguage, 34, b"currentText") self.datamapper.addMapping(self.optionGroupProxy, 35, b"checked") self.datamapper.addMapping(self.chkUpdates, 36, b"checked") self.datamapper.addMapping(self.inpProxyServer, 37) self.datamapper.addMapping(self.inpProxyPort, 38) self.datamapper.addMapping(self.inpProxyUser, 39) self.datamapper.addMapping(self.inpProxyPass, 40) self.datamapper.addMapping(self.optionGroupLogFile, 41, b"checked") self.datamapper.addMapping(self.inpLogFile, 42) self.datamapper.addMapping(self.cmbLogLevel, 43) self.datamapper.addMapping(self.inpEditorOptions, 44) self.datamapper.addMapping(self.chkAttachDirect, 45, b"checked") self.datamapper.addMapping(self.inpLocalShareBase, 46) self.datamapper.addMapping(self.optionGroupBaseOS, 47, b"checked") self.datamapper.addMapping(self.optionGroupOpsi41, 48, b"checked") self.datamapper.toFirst() def create_optionbuttongroups(self): self.logger.debug("Create option button group") # build special button groups for False/True choice self.optionGroupSrvVersion = SpecialOptionButtonGroup( self.rdOpsiSrvNew, self.rdOpsiSrvOld, [self.rdSUDOWithPass, self.rdSUDOWithoutPass], [self.inpRootPass]) self.optionGroupOpsi41 = SpecialOptionButtonGroup( self.rdOpsi41, self.rdOpsi40) self.optionGroupBaseOS = SpecialOptionButtonGroup( self.rdWBNew, self.rdWBOld) self.optionGroupSUDO = SpecialOptionButtonGroup( self.rdSUDOWithPass, self.rdSUDOWithoutPass) self.optionGroupEditorTyp = SpecialOptionButtonGroup( self.rdEditorInternal, self.rdEditorExternal, [self.chkSyntaxHighlight, self.chkCodeFolding], [ self.btnExternalEditor, self.inpExternalEditor, self.inpEditorOptions, self.chkAttachDirect ]) self.optionGroupDepotFuncs = SpecialOptionButtonGroup( self.chkUseDepotFunctions, None, [self.btnRefreshDepotCache], [ self.inpInstallCommand, self.inpInstSetupCommand, self.inpUninstallCommand, self.inpUploadCommand ]) self.optionGroupProxy = SpecialOptionButtonGroup( self.chkUseProxy, None, [ self.inpProxyServer, self.inpProxyPort, self.inpProxyUser, self.inpProxyPass ], []) self.optionGroupSSHKeyFile = SpecialOptionButtonGroup( self.chkUseKeyFile, None, [self.btnSetKeyFile, self.inpKeyFile], []) self.optionGroupLogFile = SpecialOptionButtonGroup( self.chkWriteLog, None, [self.btnLogFile, self.inpLogFile, self.cmbLogLevel], []) @pyqtSlot() def set_model_data(self): """ Whenever a special radio button or checkbox is clicked, the corresponding model data element will be set accordingly. This has to be done like so, because radio buttons and checkboxes are not directly linked to the model, but via a SpecialOptionButtonGroup object. """ self.logger.debug("Set model data values from button: " + self.sender().objectName()) # radio buttons if self.sender().objectName() == "rdOpsiSrvNew": if self.rdOpsiSrvNew.isChecked(): self.model.item(0, 5).setText("True") if self.sender().objectName() == "rdOpsiSrvOld": if self.rdOpsiSrvOld.isChecked(): self.model.item(0, 5).setText("False") if self.sender().objectName() == "rdSUDOWithPass": if self.rdSUDOWithPass.isChecked(): self.model.item(0, 6).setText("True") if self.sender().objectName() == "rdSUDOWithoutPass": if self.rdSUDOWithoutPass.isChecked(): self.model.item(0, 6).setText("False") if self.sender().objectName() == "rdEditorInternal": if self.rdEditorInternal.isChecked(): self.model.item(0, 25).setText("True") if self.sender().objectName() == "rdEditorExternal": if self.rdEditorExternal.isChecked(): self.model.item(0, 25).setText("False") # check boxes if self.sender().objectName() == "chkUseKeyFile": if self.chkUseKeyFile.isChecked(): self.model.item(0, 8).setText("True") else: self.model.item(0, 8).setText("False") if self.sender().objectName() == "chkUseDepotFunctions": if self.chkUseDepotFunctions.isChecked(): self.model.item(0, 21).setText("True") else: self.model.item(0, 21).setText("False") if self.sender().objectName() == "chkUseProxy": if self.chkUseProxy.isChecked(): self.model.item(0, 35).setText("True") else: self.model.item(0, 35).setText("False") if self.sender().objectName() == "chkWriteLog": if self.chkWriteLog.isChecked(): self.model.item(0, 41).setText("True") else: self.model.item(0, 41).setText("False") if self.sender().objectName() == "rdWBNew": if self.rdWBNew.isChecked(): self.model.item(0, 47).setText("True") if self.sender().objectName() == "rdWBOld": if self.rdWBOld.isChecked(): self.model.item(0, 47).setText("False") if self.sender().objectName() == "rdOpsi41": if self.rdOpsi41.isChecked(): self.model.item(0, 48).setText("True") self.change_build_command() if self.sender().objectName() == "rdOpsi40": if self.rdOpsi40.isChecked(): self.model.item(0, 48).setText("False") self.change_build_command() def change_build_command(self): if self.optionGroupOpsi41.getChecked() == True: #com: str = self.inpBuildCommand.text() #com = com.replace(oPB.OPB_BUILD40, oPB.OPB_BUILD41) #self.inpBuildCommand.setText(com) self.inpBuildCommand.setText(oPB.OPB_BUILD41) else: #com: str = self.inpBuildCommand.text() #com = com.replace(oPB.OPB_BUILD41, oPB.OPB_BUILD40) #com = com.replace("--no-md5", "") #com = com.replace("--no-zsync", "") #self.inpBuildCommand.setText(com) self.inpBuildCommand.setText(oPB.OPB_BUILD40) self.dataChanged.emit() def keyPressEvent(self, evt: QKeyEvent): """ Ignore escape key event, because it would close startup window. Any other key will be passed to the super class key event handler for further processing. :param evt: key event :return: """ if evt.key() == QtCore.Qt.Key_Escape: self.request_close_dialog() else: super().keyPressEvent(evt) @pyqtSlot() def request_close_dialog(self): """Request closing of settings dialog""" self.logger.debug("Emit signal settingsAboutToBeClosed") self.settingsAboutToBeClosed.emit() @pyqtSlot() def select_dev_dir(self): """Development directory selector dialog""" self.logger.debug("Select development directory") directory = QFileDialog.getExistingDirectory( self, translate("SettingsDialog", "Select development folder"), ConfigHandler.cfg.dev_dir, QFileDialog.ShowDirsOnly) if not directory == "": self.logger.info("Chosen directory: " + directory) value = Helper.concat_path_native(directory, "") if value[-1:] == "/" or value[-1:] == '\\': value = value[:-1] self.inpDevFolder.setText(value) self.inpLocalShareBase.setText(value) self.dataChanged.emit() else: self.logger.debug("Dialog aborted.") @pyqtSlot() def select_keyfile(self): """SSH keyfile selector dialog""" self.logger.debug("Select SSH keyfile dialog") ext = "Private key file (" + (" ").join([ "*." + x for x in oPB.KEYFILE_EXT ]) + ")" # generate file extension selection string for dialog script = QFileDialog.getOpenFileName( self, translate("SettingsDialog", "Choose keyfile"), ConfigHandler.cfg.dev_dir, ext) if not script == ("", ""): self.logger.debug("Selected SSH keyfile: " + script[0]) self.inpKeyFile.setText(Helper.concat_path_native(script[0], "")) self.dataChanged.emit() else: self.logger.debug("Dialog aborted.") @pyqtSlot() def select_externaleditor(self): """External editor selector dialog""" self.logger.debug("Select scripteditor dialog") if platform.system() != "Windows": ext = "Program (" + (" ").join([ "*." + x for x in oPB.PRG_EXT ]) + ")" # generate file extension selection string for dialog else: ext = "Any (*)" script = QFileDialog.getOpenFileName( self, translate("SettingsDialog", "Choose Scripteditor"), ConfigHandler.cfg.dev_dir, ext) if not script == ("", ""): self.logger.debug("Selected Scripeditor: " + script[0]) self.inpExternalEditor.setText( Helper.concat_path_native(script[0], "")) self.dataChanged.emit() else: self.logger.debug("Dialog aborted.") @pyqtSlot() def select_logfile(self): """Logfile selector dialog""" self.logger.debug("Select log file dialog") """ ext = "Log (*.log)" # generate file extension selection string for dialog script = QFileDialog.getOpenFileName(self, translate("SettingsDialog", "Choose folder for logfile"), ConfigHandler.cfg.log_file, ext) if not script == ("", ""): self.logger.debug("Selected Logile: " + script[0]) """ directory = QFileDialog.getExistingDirectory( self, translate("SettingsDialog", "Select logfile folder"), ConfigHandler.cfg.dev_dir, QFileDialog.ShowDirsOnly) if not directory == "": self.logger.info("Chosen directory: " + directory) self.inpLogFile.setText( Helper.concat_path_native(directory, "opb-session.log")) self.dataChanged.emit() else: self.logger.debug("Dialog aborted.")
class JobCreatorDialog(JobCreatorDialogBase, JobCreatorDialogUI, LogMixin, EventMixin): dialogOpened = pyqtSignal() dialogClosed = pyqtSignal() def __init__(self, parent): """ Constructor for JobCreator dialog :param parent: parent controller instance :return: """ self._parent = parent self._parentUi = parent._parent.ui JobCreatorDialogBase.__init__(self, self._parentUi, QtCore.Qt.WindowTitleHint | QtCore.Qt.WindowMinMaxButtonsHint | QtCore.Qt.WindowCloseButtonHint) self.setupUi(self) self.setWindowIcon(self._parentUi.windowIcon()) print("\tgui/JobCreatorDialog parent: ", self._parent, " -> self: ", self) if oPB.PRINTHIER else None print("\tgui/JobCreatorDialog parentUi: ", self._parentUi, " -> self: ", self) if oPB.PRINTHIER else None self.splash = Splash(self, translate("MainWindow", "Please wait...")) self.splash.close() # only for linux self.model_clients = None self.model_products = None self.assign_model(self._parent.model_clients, self._parent.model_products) self.dateSelector.setSelectedDate(datetime.now().date()) self.timeSelector.setTime(datetime.now().time()) self.connect_signals() def connect_signals(self): self.dialogOpened.connect(self.update_ui) self.btnCreate.clicked.connect(self.create_jobs) self.finished.connect(lambda: self._parent.ui_joblist.update_ui(True)) self.btnHelp.clicked.connect(lambda: oPB.gui.helpviewer.Help(oPB.HLP_FILE, oPB.HLP_PREFIX, oPB.HLP_DST_JOBCREATOR)) def keyPressEvent(self, evt: QKeyEvent): """ Ignore escape key event, because it would close startup window. Any other key will be passed to the super class key event handler for further processing. :param evt: key event :return: """ if evt.key() == QtCore.Qt.Key_Escape: self.close() else: super().keyPressEvent(evt) pass def assign_model(self, model_clients, model_products): self.model_clients = model_clients self.model_products = model_products self.tblClients.setModel(self.model_clients) self.tblProducts.setModel(self.model_products) def show_(self): self.logger.debug("Show job creator dialog") self.show() w = self.splitter.geometry().width() self.splitter.setSizes([w*(2/5), w*(3/5)]) self.activateWindow() self.dialogOpened.emit() def spanParents(self, model, parent=QModelIndex()): """Iterate through the whole Qtreeview model to span to headlines see: https://stackoverflow.com/questions/33124903/how-to-iterate-trough-a-qstandarditemmodel-completely :param model: data model of the QTreeview :param parent: current QModelIndex to inspect for children """ # the first ``parent`` is invalid, so ``childcount`` returns 1 (for root) # in any succeeding recursion it returns the actual row count of children childcount = model.rowCount(parent) for r in range(0, childcount): # get the index of the first column item in the topmost row based on parent index = model.index(r, 0, parent) name = model.data(index) # these two are always there, so span them anyways if name == "clientdirectory" or name == "software-on-demand": self.tblClients.setFirstColumnSpanned(r, parent, True) # if the model has children at the index position, it must be a headline, so span it # this possibly leads to spanning clientdirectory and/or sod twice, but who cares... if model.hasChildren(index): self.tblClients.setFirstColumnSpanned(r, parent, True) self.spanParents(model, index) def update_ui(self): """ Update model data and reset tableview See: :meth:`oPB.controller.components.scheduler.SchedulerComponent.update_model_data_clients` See: :meth:`oPB.controller.components.scheduler.SchedulerComponent.update_model_data_products` """ self.splash.show_() self.splash.setProgress(10) self._parent.update_model_data_clients() self.splash.setProgress(80) self._parent.update_model_data_products() self.tblClients.expand(self.model_clients.item(0).index()) self.tblProducts.resizeRowsToContents() self.tblProducts.resizeColumnsToContents() self.tblClients.setSortingEnabled(True) self.spanParents(self.tblClients.model()) self.splash.close() def create_jobs(self): """ Initiate AT job creation via backend See: :meth:`oPB.controller.components.scheduler.SchedulerComponent.create_jobs` """ self.logger.debug("Create AT jobs") self.splash.show_() # get selected clients selection = self.tblClients.selectedIndexes() clIdx = [] for row in selection: clIdx.append(row.model().itemFromIndex(row).text().split()[0]) # get selected products selection = self.tblProducts.selectionModel().selectedRows() prodIdx = [] for row in selection: prodIdx.append(self.model_products.item(row.row(), 0).text()) # get date/time dateVal = self.dateSelector.selectedDate().toString("yyyyMMdd") timeVal = self.timeSelector.time().toString("hhmm") # get action if self.rdInstall.isChecked(): action = "setup" if self.rdUninstall.isChecked(): action = "uninstall" if self.rdUpdate.isChecked(): action = "update" if self.rdCustom.isChecked(): action = "custom" # get options od = False if self.chkOnDemand.isChecked(): od = True wol = False if self.chkWOL.isChecked(): wol = True self._parent.create_jobs(clients = clIdx, products = prodIdx, ataction = action, dateVal = dateVal, timeVal = timeVal, on_demand = od, wol = wol) self.splash.close() self.close() def retranslateMsg(self): self.logger.debug("Retranslating further messages...") self.splash.msg = translate("MainWindow", "Please wait...")
class ReportSelectorDialog(ReportSelectorDialogBase, ReportSelectorDialogUI, LogMixin, EventMixin): dialogOpened = pyqtSignal() dialogClosed = pyqtSignal() def __init__(self, parent): """ Constructor for settings dialog :param parent: parent controller instance :return: """ self._parent = parent self._parentUi = parent._parent.ui ReportSelectorDialogBase.__init__( self, self._parentUi, QtCore.Qt.WindowTitleHint | QtCore.Qt.WindowMinMaxButtonsHint | QtCore.Qt.WindowCloseButtonHint) self.setupUi(self) self.setWindowIcon(self._parentUi.windowIcon()) print("\tgui/ReportSelectorDialog parent: ", self._parent, " -> self: ", self) if oPB.PRINTHIER else None print("\tgui/ReportSelectorDialog parentUi: ", self._parentUi, " -> self: ", self) if oPB.PRINTHIER else None self.splash = Splash(self, translate("MainWindow", "Please wait...")) self.viewer = HtmlDialog(self) self.model_left = None self.model_right = None self.assign_model(self._parent.model_report, self._parent.model_report) self.connect_signals() def connect_signals(self): self.btnSelAll.clicked.connect(lambda: self.select(True)) self.btnSelNone.clicked.connect(lambda: self.select(False)) self.btnGenerate.clicked.connect(self.generate_report) self._parent.modelDataUpdated.connect(self.splash.close) def show_(self): """Open report selector dialog and update values""" self.logger.debug("Open report selector") self.dialogOpened.emit() self.btnSelNone.setVisible(False) self.show() self._parent.update_reportmodel_data() self.resizeTables() self.select(None) self.activateWindow() def keyPressEvent(self, evt: QKeyEvent): """ Ignore escape key event, because it would close startup window. Any other key will be passed to the super class key event handler for further processing. :param evt: key event :return: """ if evt.key() == QtCore.Qt.Key_Escape: self.close() else: super().keyPressEvent(evt) pass def assign_model(self, model_left, model_right): self.logger.debug("Assign data model") self.model_left = model_left self.model_right = model_right self.tblSrvLeft.setModel(self.model_left) self.tblSrvRight.setModel(self.model_right) self.resizeTables() def resizeTables(self): self.resizeLeft() self.resizeRight() def resizeLeft(self): self.tblSrvLeft.resizeRowsToContents() self.tblSrvLeft.resizeColumnsToContents() def resizeRight(self): self.tblSrvRight.resizeRowsToContents() self.tblSrvRight.resizeColumnsToContents() def select(self, all_: bool): """ Select / Unselect every row in tableview :param all_: True = select all / False = select nothing :return: """ if all_: self.btnSelAll.setVisible(False) self.btnSelNone.setVisible(True) for row in range( self.tblSrvRight.selectionModel().model().rowCount()): for col in [0, 1]: idx = self.tblSrvRight.selectionModel().model( ).indexFromItem( self.tblSrvRight.selectionModel().model().item( row, col)) self.tblSrvRight.selectionModel().select( idx, QtCore.QItemSelectionModel.Select) else: self.btnSelAll.setVisible(True) self.btnSelNone.setVisible(False) self.tblSrvRight.selectionModel().clearSelection() def generate_report(self): """ Generate HTML comparison report :return: """ self.logger.debug("Generate HTML report") left = "" time = "" html = "" if self.rdDepot.isChecked(): modus = "depot" else: modus = "repo" try: self.logger.debug("Getting reference server from left tableview") server_left = self.tblSrvLeft.selectionModel().model().item( self.tblSrvLeft.selectionModel().selectedRows()[0].row(), 0).text() except: self.logger.debug("No reference server selected.") return self.logger.debug( "Getting list of server to compare to from right tableview") slave = self.tblSrvRight.selectionModel().selectedRows() if not slave: self.logger.debug("Nothing to compare to selected.") return steps = 1 + len(slave) step = 100 / steps self.splash.incProgress(step) if modus == "depot": self.logger.debug("Depot comparison modus") html = HtmlTools.HTMLHeader( translate("depotManagerController", "Compare depots:") + " " + server_left + " / " + str(datetime.now()), "#ffffff", "#F0F9FF", "#007EE5", "#000000", "#ffffff") data_left = self.get_prodlist_to_server( server_left, self._parent.productsondepotslist) else: self.logger.debug("Repository comparison modus") html = HtmlTools.HTMLHeader( translate("depotManagerController", "Compare repositories:") + " " + server_left + " / " + str(datetime.now()), "#ffffff", "#F0F9FF", "#007EE5", "#000000", "#ffffff") data_left = self.get_repolist_to_server( self._parent.do_getrepocontent(dest=server_left)) self.logger.debug("Processing server list...") for row in slave: self.splash.incProgress(step) server_right = self.tblSrvRight.selectionModel().model().item( row.row(), 0).text() self.logger.debug("Processing server: " + server_right) if modus == "depot": data_right = self.get_prodlist_to_server( server_right, self._parent.productsondepotslist) else: data_right = self.get_repolist_to_server( self._parent.do_getrepocontent(dest=server_right)) tmp = self.compare(data_left, data_right, tablefill="") if tmp: colspan = len(tmp[0]) / 2 else: tmp = [] colspan = 1 tmp.insert(0, [ translate("depotManagerController", "Reference:") + " " + server_left, "Depot: " + server_right ]) if tmp: html += HtmlTools.Array2HTMLTable(element_list=tmp, colspan=colspan, title='', bodybgcolor="#ffffff", hightlightbgcolor="#F0F9FF", headerbgcolor="#007EE5", bodytxtcolor="#000000", headertxtcolor="#ffffff", headers_on=True, only_table=True) html += HtmlTools.HTMLFooter() self.splash.close() self.viewer.showHtml(html) def get_repolist_to_server(self, repolist): """ Turn raw repository product list into comparable list format :param repolist: raw list of products in repository :return: list of products """ self.logger.debug("Return product list to server via list") ret = [] for elem in repolist: d = elem.split(";") ret.append([ d[0], d[2] + "-" + d[3], d[1] ]) # [['mysql.workbench, '6.2.4-go1', 456453de782...],...] return ret def get_prodlist_to_server(self, depot, dict_): """ Evalute products to named depot server from server-product dictionary. :param depot: depotserver name :param dict_: server-product dictionary :return: list of products """ self.logger.debug("Return product list to server via dict") tmplist = [] if dict_: for elem in dict_: d = elem.split(";") if d[4] == depot: tmplist.append([d[0], d[2] + "-" + d[3] ]) # [['mysql.workbench, '6.2.4-go1'],...] return tmplist @pyqtSlot() def compare(self, data_left: list, data_right: list, tablefill: str = "--"): """ Compare two lists and combine them side by side. :param data_left: left list :param data_right: right list :param tablefill: fill empty values with ``tablefill`` :return: combined list """ self.logger.debug("Comparing sides") uniqueLeft = [item for item in data_left if item not in data_right] uniqueRight = [item for item in data_right if item not in data_left] try: maxLeft = max(len(s) for s in uniqueLeft) except: maxLeft = 0 try: maxRight = max(len(s) for s in uniqueRight) except: maxRight = 0 fillvalue = [] if maxLeft >= maxRight: fillvalue.extend([tablefill] * maxLeft) else: fillvalue.extend([tablefill] * maxRight) zipped = zip_longest(uniqueLeft, uniqueRight, fillvalue=fillvalue) ret = [] for row in zipped: row_zip = [] for part in row: row_zip += part if part else [''] ret.append(row_zip) if len(ret) == 0: ret.append([ translate("depotManagerController", "(no differences found)"), "" ]) return ret def retranslateMsg(self): self.logger.debug("Retranslating further messages...") self.splash.msg = translate("MainWindow", "Please wait...")
class DeployAgentDialog(DeployAgentDialogBase, DeployAgentDialogUI, LogMixin, EventMixin): dialogOpened = pyqtSignal() dialogClosed = pyqtSignal() def __init__(self, parent): """ Constructor for deploy opsi client agent dialog :param parent: parent controller instance :return: """ self._parent = parent self._parentUi = parent._parent.ui DeployAgentDialogBase.__init__( self, self._parentUi, QtCore.Qt.WindowTitleHint | QtCore.Qt.WindowMinMaxButtonsHint | QtCore.Qt.WindowCloseButtonHint) self.setupUi(self) self.setWindowIcon(self._parentUi.windowIcon()) print("\tgui/DeployAgentDialog parent: ", self._parent, " -> self: ", self) if oPB.PRINTHIER else None print("\tgui/DeployAgentDialog parentUi: ", self._parentUi, " -> self: ", self) if oPB.PRINTHIER else None self.splash = Splash(self, translate("MainWindow", "Please wait...")) self.splash.close() # only for linux self.helpviewer = Help(oPB.HLP_FILE, oPB.HLP_PREFIX, self) self.create_optionsgroups() self.optionGroupDeploy.setChecked(self.chkDeployToMulti.isChecked()) self.connect_signals() def connect_signals(self): self.finished.connect(self.dialogClosed.emit) self.btnShowLog.clicked.connect(self._parentUi.showLogRequested) self.btnDeploy.clicked.connect(self.deploy) self.chkDeployToMulti.clicked.connect( lambda: self.optionGroupDeploy.setChecked(self.chkDeployToMulti. isChecked())) self.btnHelp.clicked.connect( lambda: self.helpviewer.showHelp(oPB.HLP_DST_DEPLOY, False)) def keyPressEvent(self, evt: QKeyEvent): """ Ignore escape key event, because it would close startup window. Any other key will be passed to the super class key event handler for further processing. :param evt: key event :return: """ if evt.key() == QtCore.Qt.Key_Escape: self.close() else: super().keyPressEvent(evt) pass def show_(self): self.logger.debug("Open deploy agent dialog") self.dialogOpened.emit() self.cmbPreExec.clear() self.cmbPreExec.addItems([""]) self.cmbPreExec.addItems(ConfigHandler.cfg.predeploycmds) self.show() self.activateWindow() def closeEvent(self, event): self.logger.debug("Closing dialog") # Save the 10 last used pre deployment commands c = deque('', 10) for i in range(self.cmbPreExec.count()): if self.cmbPreExec.itemText(i) != "": if not self.cmbPreExec.itemText(i) in c: c.append(self.cmbPreExec.itemText(i)) if self.cmbPreExec.currentText() != "": if not self.cmbPreExec.currentText() in c: c.append(self.cmbPreExec.currentText()) ConfigHandler.cfg.predeploycmds = list(c) event.accept() self.finished.emit( 0 ) # we have to emit this manually, because of subclassing closeEvent def create_optionsgroups(self): """ Create group of dependend dialog widgets See :class:`oPB.gui.utilities.SpecialOptionButtonGroup` """ self.logger.debug("Create option button group") # build special button groups for False/True choice self.optionGroupDeploy = SpecialOptionButtonGroup( self.chkDeployToMulti, None, [self.inpDestMulti], [self.inpDestSingle]) def deploy(self): """ Get values from dialog widgets and pass them to backend method start_deploy. See: :meth:`oPB.controller.components.deployagent.DeployAgentComponent.start_deploy` :return: """ self.logger.debug("Deploy client agent") check_ip = re.compile(oPB.OPB_VALID_IP_ADDRESS_REGEX) check_dns = re.compile(oPB.OPB_VALID_HOSTNAME_REGEX) destination = [] if self.chkDeployToMulti.isChecked(): text = self.inpDestMulti.toPlainText().splitlines() for line in text: if line.strip() != "": if check_ip.search(line.strip()) or check_dns.search( line.strip()): destination.append(line.strip()) else: self.logger.warning( "Obviously, no network name or ip: " + line.strip()) else: if self.inpDestSingle.text().strip() != "": destination.append(self.inpDestSingle.text().strip()) if not destination: self.logger.info('No destination.') return self.splash.show_() self.logger.info('Possible destinations: ' + str(destination)) if self.rdDoNothing.isChecked(): post = "" elif self.rdStartOpsiclientd.isChecked(): post = "startclient" elif self.rdReboot.isChecked(): post = "reboot" elif self.rdShutdown.isChecked(): post = "shutdown" # now build option dict # Win10 - removed escaping of baslashes, because smbclient 4.3.11 (opsiVM) handles them correctly: # "user": self.inpUser.text().strip().replace("\\", "\\\\"), options = { "pre_action": self.cmbPreExec.currentText().strip(), "user": self.inpUser.text().strip(), "pass": self.inpPass.text().strip(), "usefqdn": self.chkUseFQDN.isChecked(), "ignoreping": self.chkIgnorePing.isChecked(), "skipexisting": self.chkSkipExisting.isChecked(), "post_action": post, "proceed": self.chkProceed.isChecked() } self._parent.start_deploy(destination, options) self.splash.close() def retranslateMsg(self): self.logger.debug("Retranslating further messages...") self.splash.msg = translate("MainWindow", "Please wait...")
class SettingsDialog(SettingsDialogBase, SettingsDialogUI, LogMixin, EventMixin): settingsAboutToBeClosed = pyqtSignal() dataChanged = pyqtSignal() def __init__(self, parent): """ Constructor for settings dialog :param parent: parent window for settings dialog :return: """ self._parent = parent self._parentUi = parent._parent.ui SettingsDialogBase.__init__(self, self._parentUi) self.setupUi(self) self.setWindowIcon(self._parentUi.windowIcon()) print("\tgui/SettingsDialog parent: ", self._parent, " -> self: ", self) if oPB.PRINTHIER else None print("\tgui/SettingsDialog parentUi: ", self._parentUi, " -> self: ", self) if oPB.PRINTHIER else None self.splash = Splash(self, translate("MainWindow", "Please wait...")) self.splash.close() # only for linux self.helpviewer = Help(oPB.HLP_FILE, oPB.HLP_PREFIX, self) # take care of sys.platform if sys.platform.startswith("linux"): self.chkUseNetworkDrive.setEnabled(False) if sys.platform.startswith("win32"): self.inpLocalShareBase.setEnabled(False) self.datamapper = None self.model = self._parent.model # setup translation combobox, must appear before data mapper creation Translator.setup_language_combobox(self, self.cmbLanguage) # additional setup self.create_optionbuttongroups() self.create_datamapper() self.connect_signals() # reset tabs self.tabWidget.setCurrentIndex(0) # hide not needed widgets self.lblBlockRecognition.setVisible(False) self.inpBlockMarker.setVisible(False) self.btnResetRecognition.setVisible(False) def connect_signals(self): self.logger.debug("Connect signals") self.btnCancel.clicked.connect(self.request_close_dialog) self.btnSetDevFolder.clicked.connect(self.select_dev_dir) self.btnSetKeyFile.clicked.connect(self.select_keyfile) self.btnExternalEditor.clicked.connect(self.select_externaleditor) self.btnLogFile.clicked.connect(self.select_logfile) self.btnHelp.clicked.connect(lambda: self.helpviewer.showHelp(oPB.HLP_DST_SETTINGS, False)) self.btnSave.clicked.connect(self._parent.save_config) self.btnRefreshDepotCache.clicked.connect(self._parent.refresh_depot_cache) self.dataChanged.connect(self.datamapper.submit) self.settingsAboutToBeClosed.connect(self._parent.close_dialog) self.rdWBOld.clicked.connect(self.set_model_data) self.rdWBNew.clicked.connect(self.set_model_data) self.rdOpsi40.clicked.connect(self.set_model_data) self.rdOpsi41.clicked.connect(self.set_model_data) self.rdOpsiSrvNew.clicked.connect(self.set_model_data) self.rdOpsiSrvOld.clicked.connect(self.set_model_data) self.rdSUDOWithPass.clicked.connect(self.set_model_data) self.rdSUDOWithoutPass.clicked.connect(self.set_model_data) self.rdEditorInternal.clicked.connect(self.set_model_data) self.rdEditorExternal.clicked.connect(self.set_model_data) self.chkUseDepotFunctions.clicked.connect(self.set_model_data) self.chkUseProxy.clicked.connect(self.set_model_data) self.chkUseKeyFile.clicked.connect(self.set_model_data) self.chkWriteLog.clicked.connect(self.set_model_data) def create_datamapper(self): self.logger.debug("Create data widget mapper") self.datamapper = QDataWidgetMapper(self) self.datamapper.setModel(self._parent.model) self.datamapper.addMapping(self.inpConfigServer, 0) self.datamapper.addMapping(self.inpOpsiUser, 1) self.datamapper.addMapping(self.inpOpsiPass, 2) self.datamapper.addMapping(self.inpRootPass, 3) self.datamapper.addMapping(self.chkUseNetworkDrive, 4, b"checked") self.datamapper.addMapping(self.optionGroupSrvVersion, 5, b"checked") self.datamapper.addMapping(self.optionGroupSUDO, 6, b"checked") self.datamapper.addMapping(self.inpSSHPort, 7) self.datamapper.addMapping(self.optionGroupSSHKeyFile, 8, b"checked") self.datamapper.addMapping(self.inpKeyFile, 9) self.datamapper.addMapping(self.inpMaintainer, 10) self.datamapper.addMapping(self.inpMailAddress, 11) self.datamapper.addMapping(self.inpDevFolder, 12) self.datamapper.addMapping(self.inpBuildCommand, 13) self.datamapper.addMapping(self.inpInstallCommand, 14) self.datamapper.addMapping(self.inpUninstallCommand, 15) self.datamapper.addMapping(self.chkShowOutput, 16, b"checked") self.datamapper.addMapping(self.chkAlwaysReload, 17, b"checked") self.datamapper.addMapping(self.inpWOLLeadTime, 18) self.datamapper.addMapping(self.inpUploadCommand, 19) self.datamapper.addMapping(self.inpInstSetupCommand, 20) #self.datamapper.addMapping(self.settings.chkUseDepotFunctions, 21, "checked") self.datamapper.addMapping(self.optionGroupDepotFuncs, 21, b"checked") self.datamapper.addMapping(self.chkExtendedEditor, 22, b"checked") self.datamapper.addMapping(self.inpExternalEditor, 23) self.datamapper.addMapping(self.inpBlockMarker, 24) self.datamapper.addMapping(self.optionGroupEditorTyp, 25, b"checked") self.datamapper.addMapping(self.chkSyntaxHighlight, 26, b"checked") self.datamapper.addMapping(self.chkCodeFolding, 27, b"checked") self.datamapper.addMapping(self.chkForceEntryBuild, 28, b"checked") self.datamapper.addMapping(self.chkForceEntrySave, 29, b"checked") self.datamapper.addMapping(self.chkMsgError, 30, b"checked") self.datamapper.addMapping(self.chkMsgWarning, 31, b"checked") self.datamapper.addMapping(self.chkMsgInfo, 32, b"checked") self.datamapper.addMapping(self.chkMsgAT, 33, b"checked") self.datamapper.addMapping(self.cmbLanguage, 34, b"currentText") self.datamapper.addMapping(self.optionGroupProxy, 35, b"checked") self.datamapper.addMapping(self.chkUpdates, 36, b"checked") self.datamapper.addMapping(self.inpProxyServer, 37) self.datamapper.addMapping(self.inpProxyPort, 38) self.datamapper.addMapping(self.inpProxyUser, 39) self.datamapper.addMapping(self.inpProxyPass, 40) self.datamapper.addMapping(self.optionGroupLogFile, 41, b"checked") self.datamapper.addMapping(self.inpLogFile, 42) self.datamapper.addMapping(self.cmbLogLevel, 43) self.datamapper.addMapping(self.inpEditorOptions, 44) self.datamapper.addMapping(self.chkAttachDirect, 45, b"checked") self.datamapper.addMapping(self.inpLocalShareBase, 46) self.datamapper.addMapping(self.optionGroupBaseOS, 47, b"checked") self.datamapper.addMapping(self.optionGroupOpsi41, 48, b"checked") self.datamapper.toFirst() def create_optionbuttongroups(self): self.logger.debug("Create option button group") # build special button groups for False/True choice self.optionGroupSrvVersion = SpecialOptionButtonGroup(self.rdOpsiSrvNew, self.rdOpsiSrvOld, [self.rdSUDOWithPass, self.rdSUDOWithoutPass], [self.inpRootPass]) self.optionGroupOpsi41 = SpecialOptionButtonGroup(self.rdOpsi41, self.rdOpsi40) self.optionGroupBaseOS = SpecialOptionButtonGroup(self.rdWBNew, self.rdWBOld) self.optionGroupSUDO = SpecialOptionButtonGroup(self.rdSUDOWithPass, self.rdSUDOWithoutPass) self.optionGroupEditorTyp = SpecialOptionButtonGroup(self.rdEditorInternal, self.rdEditorExternal, [self.chkSyntaxHighlight, self.chkCodeFolding], [self.btnExternalEditor, self.inpExternalEditor, self.inpEditorOptions, self.chkAttachDirect]) self.optionGroupDepotFuncs = SpecialOptionButtonGroup(self.chkUseDepotFunctions, None, [self.btnRefreshDepotCache], [self.inpInstallCommand, self.inpInstSetupCommand, self.inpUninstallCommand, self.inpUploadCommand]) self.optionGroupProxy = SpecialOptionButtonGroup(self.chkUseProxy, None, [self.inpProxyServer, self.inpProxyPort, self.inpProxyUser, self.inpProxyPass], []) self.optionGroupSSHKeyFile = SpecialOptionButtonGroup(self.chkUseKeyFile, None, [self.btnSetKeyFile, self.inpKeyFile], []) self.optionGroupLogFile = SpecialOptionButtonGroup(self.chkWriteLog, None, [self.btnLogFile, self.inpLogFile, self.cmbLogLevel], []) @pyqtSlot() def set_model_data(self): """ Whenever a special radio button or checkbox is clicked, the corresponding model data element will be set accordingly. This has to be done like so, because radio buttons and checkboxes are not directly linked to the model, but via a SpecialOptionButtonGroup object. """ self.logger.debug("Set model data values from button: " + self.sender().objectName()) # radio buttons if self.sender().objectName() == "rdOpsiSrvNew": if self.rdOpsiSrvNew.isChecked(): self.model.item(0, 5).setText("True") if self.sender().objectName() == "rdOpsiSrvOld": if self.rdOpsiSrvOld.isChecked(): self.model.item(0, 5).setText("False") if self.sender().objectName() == "rdSUDOWithPass": if self.rdSUDOWithPass.isChecked(): self.model.item(0, 6).setText("True") if self.sender().objectName() == "rdSUDOWithoutPass": if self.rdSUDOWithoutPass.isChecked(): self.model.item(0, 6).setText("False") if self.sender().objectName() == "rdEditorInternal": if self.rdEditorInternal.isChecked(): self.model.item(0, 25).setText("True") if self.sender().objectName() == "rdEditorExternal": if self.rdEditorExternal.isChecked(): self.model.item(0, 25).setText("False") # check boxes if self.sender().objectName() == "chkUseKeyFile": if self.chkUseKeyFile.isChecked(): self.model.item(0, 8).setText("True") else: self.model.item(0, 8).setText("False") if self.sender().objectName() == "chkUseDepotFunctions": if self.chkUseDepotFunctions.isChecked(): self.model.item(0, 21).setText("True") else: self.model.item(0, 21).setText("False") if self.sender().objectName() == "chkUseProxy": if self.chkUseProxy.isChecked(): self.model.item(0, 35).setText("True") else: self.model.item(0, 35).setText("False") if self.sender().objectName() == "chkWriteLog": if self.chkWriteLog.isChecked(): self.model.item(0, 41).setText("True") else: self.model.item(0, 41).setText("False") if self.sender().objectName() == "rdWBNew": if self.rdWBNew.isChecked(): self.model.item(0, 47).setText("True") if self.sender().objectName() == "rdWBOld": if self.rdWBOld.isChecked(): self.model.item(0, 47).setText("False") if self.sender().objectName() == "rdOpsi41": if self.rdOpsi41.isChecked(): self.model.item(0, 48).setText("True") self.change_build_command() if self.sender().objectName() == "rdOpsi40": if self.rdOpsi40.isChecked(): self.model.item(0, 48).setText("False") self.change_build_command() def change_build_command(self): if self.optionGroupOpsi41.getChecked() == True: #com: str = self.inpBuildCommand.text() #com = com.replace(oPB.OPB_BUILD40, oPB.OPB_BUILD41) #self.inpBuildCommand.setText(com) self.inpBuildCommand.setText(oPB.OPB_BUILD41) else: #com: str = self.inpBuildCommand.text() #com = com.replace(oPB.OPB_BUILD41, oPB.OPB_BUILD40) #com = com.replace("--no-md5", "") #com = com.replace("--no-zsync", "") #self.inpBuildCommand.setText(com) self.inpBuildCommand.setText(oPB.OPB_BUILD40) self.dataChanged.emit() def keyPressEvent(self, evt: QKeyEvent): """ Ignore escape key event, because it would close startup window. Any other key will be passed to the super class key event handler for further processing. :param evt: key event :return: """ if evt.key() == QtCore.Qt.Key_Escape: self.request_close_dialog() else: super().keyPressEvent(evt) @pyqtSlot() def request_close_dialog(self): """Request closing of settings dialog""" self.logger.debug("Emit signal settingsAboutToBeClosed") self.settingsAboutToBeClosed.emit() @pyqtSlot() def select_dev_dir(self): """Development directory selector dialog""" self.logger.debug("Select development directory") directory = QFileDialog.getExistingDirectory(self, translate("SettingsDialog", "Select development folder"), ConfigHandler.cfg.dev_dir, QFileDialog.ShowDirsOnly) if not directory == "": self.logger.info("Chosen directory: " + directory) value = Helper.concat_path_native(directory, "") if value[-1:] == "/" or value[-1:] == '\\': value = value[:-1] self.inpDevFolder.setText(value) self.inpLocalShareBase.setText(value) self.dataChanged.emit() else: self.logger.debug("Dialog aborted.") @pyqtSlot() def select_keyfile(self): """SSH keyfile selector dialog""" self.logger.debug("Select SSH keyfile dialog") ext = "Private key file (" + (" ").join(["*." + x for x in oPB.KEYFILE_EXT]) + ")" # generate file extension selection string for dialog script = QFileDialog.getOpenFileName(self, translate("SettingsDialog", "Choose keyfile"), ConfigHandler.cfg.dev_dir, ext) if not script == ("", ""): self.logger.debug("Selected SSH keyfile: " + script[0]) self.inpKeyFile.setText(Helper.concat_path_native(script[0], "")) self.dataChanged.emit() else: self.logger.debug("Dialog aborted.") @pyqtSlot() def select_externaleditor(self): """External editor selector dialog""" self.logger.debug("Select scripteditor dialog") if platform.system() != "Windows": ext = "Program (" + (" ").join(["*." + x for x in oPB.PRG_EXT]) + ")" # generate file extension selection string for dialog else: ext = "Any (*)" script = QFileDialog.getOpenFileName(self, translate("SettingsDialog", "Choose Scripteditor"), ConfigHandler.cfg.dev_dir, ext) if not script == ("", ""): self.logger.debug("Selected Scripeditor: " + script[0]) self.inpExternalEditor.setText(Helper.concat_path_native(script[0], "")) self.dataChanged.emit() else: self.logger.debug("Dialog aborted.") @pyqtSlot() def select_logfile(self): """Logfile selector dialog""" self.logger.debug("Select log file dialog") """ ext = "Log (*.log)" # generate file extension selection string for dialog script = QFileDialog.getOpenFileName(self, translate("SettingsDialog", "Choose folder for logfile"), ConfigHandler.cfg.log_file, ext) if not script == ("", ""): self.logger.debug("Selected Logile: " + script[0]) """ directory = QFileDialog.getExistingDirectory(self, translate("SettingsDialog", "Select logfile folder"), ConfigHandler.cfg.dev_dir, QFileDialog.ShowDirsOnly) if not directory == "": self.logger.info("Chosen directory: " + directory) self.inpLogFile.setText(Helper.concat_path_native(directory, "opb-session.log")) self.dataChanged.emit() else: self.logger.debug("Dialog aborted.")
class DepotManagerDialog(DepotManagerDialogBase, DepotManagerDialogUI, LogMixin, EventMixin): dialogOpened = pyqtSignal() dialogClosed = pyqtSignal() def __init__(self, parent): """ Constructor for DepotManager dialog :param parent: parent controller instance :return: """ self._parent = parent self._parentUi = parent._parent.ui DepotManagerDialogBase.__init__(self, self._parentUi, QtCore.Qt.WindowTitleHint | QtCore.Qt.WindowMinMaxButtonsHint | QtCore.Qt.WindowCloseButtonHint) self.setupUi(self) self.setWindowIcon(self._parentUi.windowIcon()) print("\tgui/DepotManagerDialog parent: ", self._parent, " -> self: ", self) if oPB.PRINTHIER else None print("\tgui/DepotManagerDialog parentUi: ", self._parentUi, " -> self: ", self) if oPB.PRINTHIER else None self.model_left = None self.model_right = None self.assign_model(self._parent.model_left, self._parent.model_right) self.splash = Splash(self, translate("MainWindow", "Please wait...")) self.splash.close() # only for linux self.helpviewer = Help(oPB.HLP_FILE, oPB.HLP_PREFIX, self) self.connect_signals() self._parent._ui_box_left = self.cmbDepotLeft self._parent._ui_box_right = self.cmbDepotRight self._parent._ui_repobtn_left = self.btnFetchRepoLeft self._parent._ui_repobtn_right = self.btnFetchRepoRight self.update_ui() def connect_signals(self): self.finished.connect(self.dialogClosed.emit) self._parent.dataAboutToBeAquired.connect(self.splash.setProgress) self._parent.dataAquired.connect(self.update_ui) self._parent.dataAquired.connect(self.splash.close) self._parent.modelDataUpdated.connect(self.update_fields) self._parent.modelDataUpdated.connect(self.update_ui) self.btnRefresh.clicked.connect(lambda: self._parent.update_data(True)) self.btnCompare.clicked.connect(self.compare_sides) self.btnShowLog.clicked.connect(self._parentUi.showLogRequested) self.btnReport.clicked.connect(self._parent.ui_report.show_) self.btnInstall.clicked.connect(self._parent.install) self.btnUninstall.clicked.connect(self.remove_delegate) self.btnUpload.clicked.connect(self._parentUi.upload) self.btnUnregister.clicked.connect(self._parent.unregister_depot) self.btnSetRights.clicked.connect(self._parent.set_rights) self.btnRunProdUpdater.clicked.connect(self._parent.run_product_updater) self.btnGenMD5.clicked.connect(self.generate_md5) self.btnOnlineCheck.clicked.connect(self._parent.onlinecheck) self.btnReboot.clicked.connect(self._parent.reboot_depot) self.btnPoweroff.clicked.connect(self._parent.poweroff_depot) self.btnHelp.clicked.connect(lambda: self.helpviewer.showHelp(oPB.HLP_DST_DEPOTM, False)) #self.cmbDepotLeft.currentTextChanged.connect(self._parent.side_content) #side_content #self.cmbDepotRight.currentTextChanged.connect(self._parent.side_content) self.cmbDepotLeft.currentTextChanged.connect(self._parent.switch_content) #side_content self.cmbDepotRight.currentTextChanged.connect(self._parent.switch_content) self.cmbDepotLeft.currentTextChanged.connect(self.set_active_side) #side_content self.cmbDepotRight.currentTextChanged.connect(self.set_active_side) self.tblDepotLeft.clicked.connect(self.set_active_side) self.tblDepotRight.clicked.connect(self.set_active_side) self.btnFetchRepoLeft.clicked.connect(self._parent.switch_content) self.btnFetchRepoRight.clicked.connect(self._parent.switch_content) def closeEvent(self, event): """ Overrides base method, disconnects custom signals :param event: close event """ try: self._parent.dataAboutToBeAquired.disconnect(self.splash.show_) self._parent.dataAquired.disconnect(self.splash.close) except: pass event.accept() self.finished.emit(0) # we have to emit this manually, because of subclassing closeEvent def keyPressEvent(self, evt: QKeyEvent): """ Ignore escape key event, because it would close startup window. Any other key will be passed to the super class key event handler for further processing. :param evt: key event :return: """ if evt.key() == QtCore.Qt.Key_Escape: self.close() else: super().keyPressEvent(evt) pass def assign_model(self, model_left, model_right): self.logger.debug("Assign data model") self.model_left = model_left self.model_right = model_right self.tblDepotLeft.setModel(self.model_left) self.tblDepotRight.setModel(self.model_right) self.resizeTables() def resizeTables(self): self.resizeLeft() self.resizeRight() @pyqtSlot() def resizeLeft(self): self.tblDepotLeft.resizeRowsToContents() self.tblDepotLeft.resizeColumnsToContents() def resizeRight(self): self.tblDepotRight.resizeRowsToContents() self.tblDepotRight.resizeColumnsToContents() @pyqtSlot() def update_ui(self): """Update ui state""" self.logger.debug("Update ui") self.resizeTables() # enable / disable buttons if self.cmbDepotLeft.currentIndex() == -1: self.btnFetchRepoLeft.setEnabled(False) else: self.btnFetchRepoLeft.setEnabled(True) if self.cmbDepotRight.currentIndex() == -1: self.btnFetchRepoRight.setEnabled(False) else: self.btnFetchRepoRight.setEnabled(True) if self.cmbDepotLeft.currentIndex() == -1 or self.cmbDepotRight.currentIndex() == -1: self.btnCompare.setEnabled(False) else: self.btnCompare.setEnabled(True) if self._parent._active_side is None: self.btnInstall.setEnabled(False) self.btnUninstall.setEnabled(False) self.btnUpload.setEnabled(False) self.btnUnregister.setEnabled(False) self.btnSetRights.setEnabled(False) self.btnRunProdUpdater.setEnabled(False) self.btnGenMD5.setEnabled(False) self.btnOnlineCheck.setEnabled(False) self.btnPoweroff.setEnabled(False) self.btnReboot.setEnabled(False) else: if self._parent._compare is False: self.btnInstall.setEnabled(True) self.btnUninstall.setEnabled(True) self.btnUpload.setEnabled(True) else: self.btnInstall.setEnabled(False) self.btnUninstall.setEnabled(False) self.btnUpload.setEnabled(False) self.btnUnregister.setEnabled(True) self.btnOnlineCheck.setEnabled(True) self.btnPoweroff.setEnabled(True) self.btnReboot.setEnabled(True) if self._parent._active_side == "left": if self._parent._type_left == "depot": self.btnSetRights.setEnabled(False) self.btnRunProdUpdater.setEnabled(False) self.btnGenMD5.setEnabled(False) self.btnUninstall.setText(translate("DepotManagerDialog", "Uninstall")) else: if self._parent._compare is not True: self.btnSetRights.setEnabled(True) self.btnRunProdUpdater.setEnabled(True) self.btnGenMD5.setEnabled(True) self.btnUninstall.setText(translate("DepotManagerDialog", "Delete")) else: if self._parent._type_right == "depot": self.btnSetRights.setEnabled(False) self.btnRunProdUpdater.setEnabled(False) self.btnGenMD5.setEnabled(False) self.btnUninstall.setText(translate("DepotManagerDialog", "Uninstall")) else: if self._parent._compare is not True: self.btnSetRights.setEnabled(True) self.btnRunProdUpdater.setEnabled(True) self.btnGenMD5.setEnabled(True) self.btnUninstall.setText(translate("DepotManagerDialog", "Delete")) if self._parent._compare is False: self.decorate_button(self.btnCompare, "") else: self.decorate_button(self.btnCompare, "green") # set decoration and text if self._parent._type_left == "repo": self.decorate_button(self.btnFetchRepoLeft, "blue") self.btnFetchRepoLeft.setText(translate("DepotManagerDialog", "Fetch DEPOT content")) else: self.decorate_button(self.btnFetchRepoLeft, "") self.btnFetchRepoLeft.setText(translate("DepotManagerDialog", "Fetch REPO content")) if self._parent._type_right == "repo": self.decorate_button(self.btnFetchRepoRight, "blue") self.btnFetchRepoRight.setText(translate("DepotManagerDialog", "Fetch DEPOT content")) else: self.decorate_button(self.btnFetchRepoRight, "") self.btnFetchRepoRight.setText(translate("DepotManagerDialog", "Fetch REPO content")) @pyqtSlot() def update_fields(self): """Reload combobox content and reset their state""" self.logger.debug("Update field content") l = [] for key, val in ConfigHandler.cfg.depotcache.items(): l.append(key + " (" + val + ")") l.sort() # temporary disconnect events for smoother display self.cmbDepotLeft.currentTextChanged.disconnect(self._parent.switch_content) self.cmbDepotRight.currentTextChanged.disconnect(self._parent.switch_content) self.cmbDepotLeft.currentTextChanged.disconnect(self.set_active_side) self.cmbDepotRight.currentTextChanged.disconnect(self.set_active_side) self.cmbDepotLeft.clear() self.cmbDepotRight.clear() self.cmbDepotLeft.addItems(l) self.cmbDepotRight.addItems(l) self.cmbDepotLeft.currentTextChanged.connect(self._parent.switch_content) self.cmbDepotRight.currentTextChanged.connect(self._parent.switch_content) self.cmbDepotLeft.setCurrentIndex(-1) self.cmbDepotRight.setCurrentIndex(-1) self.cmbDepotLeft.currentTextChanged.connect(self.set_active_side) self.cmbDepotRight.currentTextChanged.connect(self.set_active_side) def decorate_button(self, button, state): """ Set custom button property ``dispState`` ``dispState`` is a conditional CSS parameter, like:: QPushButton[dispState="red"] { color: rgb(255, 0, 0); } Dependend on its value, the button will be colored differently. :param button: QPushButton :param state: color ["red", "green", "blue"] """ button.setProperty("dispState", state) button.style().unpolish(button) button.style().polish(button) button.update() def show_(self): self.logger.debug("Open depot manager") self.dialogOpened.emit() self.show() w = self.splitter.geometry().width() self.splitter.setSizes([w*(1/2), w*(1/2)]) self._parent.update_data() self._parent.dataAquired.emit() self.cmbDepotLeft.setCurrentIndex(-1) self.cmbDepotRight.setCurrentIndex(-1) self.resizeTables() self.activateWindow() @pyqtSlot() def compare_sides(self): """ Initiate side-by-side comparison of table views See: :meth:`oPB.controller.components.depotmanager.DepotManagerComponent.compare_leftright` """ if self.cmbDepotLeft.currentIndex() == -1 or self.cmbDepotRight.currentIndex() == -1: return self._parent._compare = True if self._parent._compare is False else False self._parent.compare_leftright() @pyqtSlot() def set_active_side(self): """Set active table marker and initiate ui update accordingly""" if self.sender() == self.tblDepotLeft or self.sender() == self.cmbDepotLeft: self.logger.debug("Set active tableview: left") self._parent._active_side = "left" if self.sender() == self.tblDepotRight or self.sender() == self.cmbDepotRight: self.logger.debug("Set active tableview: right") self._parent._active_side = "right" self.update_ui() @pyqtSlot() def remove_delegate(self): """ Decide between depot or repository removal of selected product(s) See: :meth:`oPB.controller.components.depotmanager.DepotManagerComponent.remove_from_depot` See: :meth:`oPB.controller.components.depotmanager.DepotManagerComponent.delete_from_repo` """ if self._parent._active_side == "left": depot = self.cmbDepotLeft.currentText().split()[0] selection = self.tblDepotLeft.selectionModel().selectedRows() prodIdx = [] if self._parent._type_left == "depot": for row in selection: prodIdx.append(self.model_left.item(row.row(), 0).text()) self._parent.remove_from_depot(depot, prodIdx) else: for row in selection: prodIdx.append(self.model_left.item(row.row(), 0).text() + "_" + self.model_left.item(row.row(), 1).text() + "-" + self.model_left.item(row.row(),2).text()) self._parent.delete_from_repo(depot, prodIdx) if self._parent._active_side == "right": depot = self.cmbDepotLeft.currentText().split()[0] selection = self.tblDepotRight.selectionModel().selectedRows() prodIdx = [] if self._parent._type_right == "depot": for row in selection: prodIdx.append(self.model_right.item(row.row(), 0).text()) self._parent.remove_from_depot(depot, prodIdx) else: for row in selection: prodIdx.append(self.model_right.item(row.row(), 0).text() + "_" + self.model_right.item(row.row(), 1).text() + "-" + self.model_right.item(row.row(),2).text()) self._parent.delete_from_repo(depot, prodIdx) def generate_md5(self): """ Get dialog widget state and initiate backend MD5 generation See: :meth:`oPB.controller.components.depotmanager.DepotManagerComponent.generate_md5` """ if self._parent._active_side == "left": depot = self.cmbDepotLeft.currentText().split()[0] selection = self.tblDepotLeft.selectionModel().selectedRows() prodIdx = [] if self._parent._type_left == "repo": for row in selection: prodIdx.append(self.model_left.item(row.row(), 0).text() + "_" + self.model_left.item(row.row(), 1).text() + "-" + self.model_left.item(row.row(),2).text()) self._parent.generate_md5(depot, prodIdx) if self._parent._active_side == "right": depot = self.cmbDepotRight.currentText().split()[0] selection = self.tblDepotRight.selectionModel().selectedRows() prodIdx = [] if self._parent._type_right == "repo": for row in selection: prodIdx.append(self.model_right.item(row.row(), 0).text() + "_" + self.model_right.item(row.row(), 1).text() + "-" + self.model_right.item(row.row(),2).text()) self._parent.generate_md5(depot, prodIdx) def retranslateMsg(self): self.logger.debug("Retranslating further messages...") self.splash.msg = translate("MainWindow", "Please wait...")
class BundleDialog(BundleDialogBase, BundleDialogUI, LogMixin, EventMixin): dialogOpened = pyqtSignal() dialogClosed = pyqtSignal() def __init__(self, parent): """ Constructor for bundle creation dialog :param parent: parent controller instance :return: """ self._parent = parent self._parentUi = parent._parent.ui BundleDialogBase.__init__( self, self._parentUi, QtCore.Qt.WindowTitleHint | QtCore.Qt.WindowMinMaxButtonsHint | QtCore.Qt.WindowCloseButtonHint) self.setupUi(self) self.setWindowIcon(self._parentUi.windowIcon()) print("\tgui/BundleDialog parent: ", self._parent, " -> self: ", self) if oPB.PRINTHIER else None print("\tgui/BundleDialog parentUi: ", self._parentUi, " -> self: ", self) if oPB.PRINTHIER else None self.splash = Splash(self, translate("MainWindow", "Please wait...")) self.splash.close() # only for linux self.helpviewer = Help(oPB.HLP_FILE, oPB.HLP_PREFIX, self) self.model = None self.assign_model(self._parent.model_products) self.connect_signals() def connect_signals(self): self.dialogOpened.connect(self.update_ui) self.btnCreate.clicked.connect(self.create) self.btnCancel.clicked.connect(self.dialogClosed.emit) self.btnHelp.clicked.connect( lambda: self.helpviewer.showHelp(oPB.HLP_DST_BUNDLE, False)) def keyPressEvent(self, evt: QKeyEvent): """ Ignore escape key event, because it would close startup window. Any other key will be passed to the super class key event handler for further processing. :param evt: key event :return: """ if evt.key() == QtCore.Qt.Key_Escape: self.dialogClosed.emit() self.close() else: super().keyPressEvent(evt) pass def assign_model(self, model): self.model = model self.tblProducts.setModel(self.model) self.resizeTable() def resizeTable(self): self.tblProducts.resizeRowsToContents() self.tblProducts.resizeColumnsToContents() def show_(self): self.logger.debug("Open bundle creation dialog") self.show() self.activateWindow() self.dialogOpened.emit() @pyqtSlot() def update_ui(self): """ Update model data and reset tableviews See: :meth:`oPB.controller.components.bundle.BundleComponent.update_model_data` """ self.splash.show_() self._parent.update_model_data() self.resizeTable() self.splash.close() @pyqtSlot() def create(self): """ Get selected products from tableview and initiate backend bundle creation See: :meth:`oPB.controller.components.bundle.BundleComponent.create_bundle` """ self.close() prods = [] for row in self.tblProducts.selectionModel().selectedRows(): prods.append(self.model.item(row.row(), 0).text()) self._parent.create_bundle(prods) def retranslateMsg(self): self.logger.debug("Retranslating further messages...") self.splash.msg = translate("MainWindow", "Please wait...")
class BundleDialog(BundleDialogBase, BundleDialogUI, LogMixin, EventMixin): dialogOpened = pyqtSignal() dialogClosed = pyqtSignal() def __init__(self, parent): """ Constructor for bundle creation dialog :param parent: parent controller instance :return: """ self._parent = parent self._parentUi = parent._parent.ui BundleDialogBase.__init__(self, self._parentUi, QtCore.Qt.WindowTitleHint | QtCore.Qt.WindowMinMaxButtonsHint | QtCore.Qt.WindowCloseButtonHint) self.setupUi(self) self.setWindowIcon(self._parentUi.windowIcon()) print("\tgui/BundleDialog parent: ", self._parent, " -> self: ", self) if oPB.PRINTHIER else None print("\tgui/BundleDialog parentUi: ", self._parentUi, " -> self: ", self) if oPB.PRINTHIER else None self.splash = Splash(self, translate("MainWindow", "Please wait...")) self.splash.close() # only for linux self.helpviewer = Help(oPB.HLP_FILE, oPB.HLP_PREFIX, self) self.model = None self.assign_model(self._parent.model_products) self.connect_signals() def connect_signals(self): self.dialogOpened.connect(self.update_ui) self.btnCreate.clicked.connect(self.create) self.btnCancel.clicked.connect(self.dialogClosed.emit) self.btnHelp.clicked.connect(lambda: self.helpviewer.showHelp(oPB.HLP_DST_BUNDLE, False)) def keyPressEvent(self, evt: QKeyEvent): """ Ignore escape key event, because it would close startup window. Any other key will be passed to the super class key event handler for further processing. :param evt: key event :return: """ if evt.key() == QtCore.Qt.Key_Escape: self.dialogClosed.emit() self.close() else: super().keyPressEvent(evt) pass def assign_model(self, model): self.model = model self.tblProducts.setModel(self.model) self.resizeTable() def resizeTable(self): self.tblProducts.resizeRowsToContents() self.tblProducts.resizeColumnsToContents() def show_(self): self.logger.debug("Open bundle creation dialog") self.show() self.activateWindow() self.dialogOpened.emit() @pyqtSlot() def update_ui(self): """ Update model data and reset tableviews See: :meth:`oPB.controller.components.bundle.BundleComponent.update_model_data` """ self.splash.show_() self._parent.update_model_data() self.resizeTable() self.splash.close() @pyqtSlot() def create(self): """ Get selected products from tableview and initiate backend bundle creation See: :meth:`oPB.controller.components.bundle.BundleComponent.create_bundle` """ self.close() prods = [] for row in self.tblProducts.selectionModel().selectedRows(): prods.append(self.model.item(row.row(), 0).text()) self._parent.create_bundle(prods) def retranslateMsg(self): self.logger.debug("Retranslating further messages...") self.splash.msg = translate("MainWindow", "Please wait...")