Beispiel #1
0
    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()
Beispiel #2
0
    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()
Beispiel #4
0
    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()
Beispiel #5
0
    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()
Beispiel #10
0
    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)
Beispiel #11
0
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...")
Beispiel #13
0
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...")
Beispiel #15
0
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...")
Beispiel #16
0
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.")
Beispiel #17
0
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...")
Beispiel #18
0
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...")
Beispiel #19
0
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...")
Beispiel #20
0
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...")
Beispiel #22
0
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...")
Beispiel #23
0
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...")