Beispiel #1
0
    class Config(SignalNode.Config):
        """Config widget displayed for LSLInput."""
        def __init__(self, parent=None):
            super().__init__(parent=parent)

            self.smoothing_factor = QDoubleSpinBox()
            self.smoothing_factor.setMinimum(0)
            self.smoothing_factor.setMaximum(1)
            self.smoothing_factor.setSingleStep(0.1)
            self.smoothing_factor.setPrefix("x")
            self.smoothing_factor.valueChanged.connect(self.updateModel)

            self.method = QComboBox()
            self.method.addItem("Rectification")
            self.method.addItem("Fourier Transform")
            self.method.addItem("Hilbert Transform")
            self.method.addItem("cFIR")
            self.method.currentTextChanged.connect(self.updateModel)

            self.smoother_type = QComboBox()
            for name in EnvelopeDetector.smoother_name_to_type:
                self.smoother_type.addItem(name)
            self.smoother_type.currentTextChanged.connect(self.updateModel)

            layout = QFormLayout()
            self.setLayout(layout)

            layout.addRow("Smoothing factor", self.smoothing_factor)
            layout.addRow("Method", self.method)
            layout.addRow("Smoother type", self.smoother_type)

        def updateModel(self):
            n = self.node()
            if n is None:
                return
            
            smoothing_factor = self.smoothing_factor.value()
            method = self.method.currentText()
            smoother_type = n.smoother_name_to_type[self.smoother_type.currentText()]

            n.setSmoothingFactor(smoothing_factor)
            n.setMethod(method)
            n.setSmootherType(smoother_type)
        
        def updateView(self):
            n = self.node()
            if n is None:
                return
            
            self.smoothing_factor.blockSignals(True)
            self.method.blockSignals(True)
            self.smoother_type.blockSignals(True)

            self.smoothing_factor.setValue(n.smoothingFactor())
            self.method.setCurrentText(n.method())
            self.smoother_type.setCurrentText(n.smoother_type_to_name[n.smootherType()])

            self.smoothing_factor.blockSignals(False)
            self.method.blockSignals(False)
            self.smoother_type.blockSignals(False)
Beispiel #2
0
class Window(QWidget):
    def __init__(self):
        super().__init__()

        self.setWindowTitle(
            "Pyside2 SpinBox")  # Configure le titre de la fenêtre
        self.setGeometry(300, 300, 500,
                         400)  # Configure la taille de la fenêtre

        self.setIcon()
        self.spinBox()

    def setIcon(self):
        appIcon = QIcon("icon.png")
        self.setWindowIcon(appIcon)

    def spinBox(self):
        vbox = QVBoxLayout()

        self.label = QLabel()

        self.spinbox = QDoubleSpinBox()
        self.spinbox.setMinimum(10)
        self.spinbox.setMaximum(100)
        self.spinbox.valueChanged.connect(self.spinValue)

        vbox.addWidget(self.label)
        vbox.addWidget(self.spinbox)

        self.setLayout(vbox)

    def spinValue(self):
        self.label.setText("Current Value is : " + str(self.spinbox.value()))
Beispiel #3
0
 def generate_numeric_input(self, maximum, minimum, increment, current):
     sb = QDoubleSpinBox(self)
     sb.setMaximum(maximum)
     sb.setMinimum(minimum)
     sb.setSingleStep(increment)
     sb.setValue(current)
     return sb
Beispiel #4
0
 def createEditor(self, parent, option, index):
     doubleSpinBox = QDoubleSpinBox(parent)
     doubleSpinBox.setDecimals(5)
     doubleSpinBox.setMinimum(-100000)
     doubleSpinBox.setMaximum(100000)
     self.connect(doubleSpinBox, SIGNAL("valueChanged(float)"), self,
                  SLOT("valueChanged()"))
     return doubleSpinBox
Beispiel #5
0
def _make_double_spinbox(default, min_value, max_value, single_step):
    spinbox = QDoubleSpinBox()

    spinbox.setSingleStep(single_step)
    spinbox.setMaximum(max_value)
    spinbox.setMinimum(min_value)
    spinbox.setValue(default)
    return spinbox
Beispiel #6
0
    class Config(SignalNode.Config):
        """Config widget displayed for LSLInput."""
        def __init__(self, parent=None):
            super().__init__(parent=parent)

            # Add a new layout
            self.average = QDoubleSpinBox()
            self.average.setMaximum(sys.float_info.max)  # TODO: proper max
            self.average.valueChanged.connect(self.updateModel)

            self.standard_deviation = QDoubleSpinBox()
            self.standard_deviation.setMaximum(
                sys.float_info.max)  # TODO: proper max
            self.standard_deviation.valueChanged.connect(self.updateModel)

            layout = QFormLayout()
            self.setLayout(layout)

            layout.addRow("Average", self.average)
            layout.addRow("Std. Deviation", self.standard_deviation)

        def updateModel(self):
            n = self.node()
            if n is None:
                return

            average = self.average.value()
            standard_deviation = self.standard_deviation.value()

            n.setAverage(average)
            n.setStandardDeviation(standard_deviation)

        def updateView(self):
            n = self.node()
            if n is None:
                return

            self.average.blockSignals(True)
            self.standard_deviation.blockSignals(True)

            self.average.setValue(n.average())
            self.standard_deviation.setValue(n.standardDeviation())

            self.average.blockSignals(False)
            self.standard_deviation.blockSignals(False)
Beispiel #7
0
class FloatField(Field):
    def __init__(self, property):
        super(FloatField, self).__init__(property)
        self._widget = QDoubleSpinBox()
        self._widget.valueChanged.connect(self.emitChanged)
        self._widget.setMinimum(property.min())
        self._widget.setMaximum(property.max())
        self._widget.setSingleStep(property.step())

    def value(self):
        return self._widget.value()

    def setValue(self, value):
        self._widget.setValue(value)

    def setReadOnly(self, value):
        self._widget.setReadOnly(value)

    valueProperty = Property(
        float, value, Field.setValueBlocked)  # C++: should be in parent class
    def _gui(self):
        layout = QVBoxLayout()
        self.setLayout(layout)

        slider_layout = QHBoxLayout()
        layout.addLayout(slider_layout)

        slider_layout.addWidget(QLabel('Document-level adequacy'))
        adequacy = QDoubleSpinBox(self)
        adequacy.setMinimum(1)
        adequacy.setMaximum(5)
        self.adequacy = adequacy
        slider_layout.addWidget(adequacy)

        self.items = [
            adequacy,
        ]

        for item in self.items:
            item.valueChanged.connect(self.evaluation_changed)
Beispiel #9
0
    def _create_int_float(self,
                          val,
                          is_float: bool = False,
                          key: str = None) -> QWidget:
        """
        Creates the widget for integer or float.

        Parameters
        ----------
        val : list
            list of type, default value, description
        is_float : bool, optional
            is the value suposed to be a float, by default False
        key : str, optional
            The variable name, by default None

        Returns
        -------
        QWidget
            The given widget correctly set up
        """
        default_val = val[1] if val[1] is not None else 0
        if is_float:
            widget = QDoubleSpinBox()
        else:
            widget = QSpinBox()
        widget.setMinimum(-1000)
        widget.setMaximum(1000)
        # Needed to return to default if it is ever needed
        widget.setMinimum(-1000)
        widget.setMaximum(1000)
        widget.setValue(self._set_default(widget, default_val))
        tmp_val = self.config_val.get(key, None)
        if isinstance(tmp_val, str):
            if 'None' not in tmp_val:
                widget.setValue(float(tmp_val))
        elif tmp_val is not None:
            widget.setValue(float(tmp_val))
        widget.setToolTip(val[2])
        return widget
Beispiel #10
0
 def createPointWidget(self, x, y, z):
     layout = QHBoxLayout()
     x_label = QLabel("x:")
     x_label.setFixedWidth(10)
     x_val = QDoubleSpinBox()
     x_val.setSingleStep(0.01)
     x_val.setMinimum(-100)
     x_val.setMaximum(100)
     x_val.setDecimals(5)
     x_val.setValue(x)
     x_val.valueChanged.connect(self.onCoordsChanged)
     y_label = QLabel("y:")
     y_label.setFixedWidth(10)
     y_val = QDoubleSpinBox()
     y_val.setMinimum(-100)
     y_val.setMaximum(100)
     y_val.setDecimals(5)
     y_val.setSingleStep(0.01)
     y_val.setValue(y)
     y_val.valueChanged.connect(self.onCoordsChanged)
     z_label = QLabel("z:")
     z_label.setFixedWidth(10)
     z_val = QDoubleSpinBox()
     z_val.setMinimum(-100)
     z_val.setMaximum(100)
     z_val.setDecimals(5)
     z_val.setSingleStep(0.01)
     z_val.setValue(z)
     z_val.valueChanged.connect(self.onCoordsChanged)
     layout.addWidget(x_label)
     layout.addWidget(x_val)
     layout.addWidget(y_label)
     layout.addWidget(y_val)
     layout.addWidget(z_label)
     layout.addWidget(z_val)
     self.widgets.append([x_val, y_val, z_val])
     return layout
Beispiel #11
0
class FirstAlgorithm(QDialog):

    executed = Signal(dict, list)

    defaultParameters = {
        'offset filter': True,
        'supply filter': True,
        'LPF highcut': 3,
        'BPF lowcut': 8,
        'BPF highcut': 30,
        'art thres': 100,
        'window': 25,
        'prev sec': 5,
        'hmt larger': 3,
        'hmt larger mean': 2,
    }

    def __init__(self,
                 dataManager,
                 chartManager,
                 parent=None,
                 inputList=None,
                 initParam=None):
        QDialog.__init__(self, parent)

        self.dataManager = dataManager
        self.fs = self.dataManager.getFs()
        self.chartManager = chartManager
        self.setWindowTitle('Algorithms')
        self.inputList = inputList
        if initParam:
            self.parameters = initParam
            print('initParam')
        else:
            self.parameters = self.defaultParameters
            print('defaultParameters')

        #Setup Layouts
        self.mainLayout = QVBoxLayout(self)
        self.setupInLayout()

        self.setupSettings()

        self.setupBtnBoxLayout()

    def setupInLayout(self):
        inGroup = QGroupBox('Input to process')
        inLayout = QFormLayout(inGroup)

        self.inTree = DataSelector(self.dataManager, inputList=self.inputList)

        inLayout.addWidget(QLabel('Select Input'))
        inLayout.addWidget(self.inTree)

        self.mainLayout.addWidget(inGroup)

    def setupSettings(self):

        spinBoxLayout = QFormLayout()

        self.removeOffset = QCheckBox('Remove DC offset')
        self.removeOffset.setChecked(self.parameters['offset filter'])
        spinBoxLayout.addRow(self.removeOffset)

        self.removeSupply = QCheckBox('Remove 50Hz')
        self.removeSupply.setChecked(self.parameters['supply filter'])
        spinBoxLayout.addRow(self.removeSupply)

        ##        self.lpfHighcut = QSpinBox()
        ##        self.lpfHighcut.setMaximum(10000)
        ##        self.lpfHighcut.setValue(self.parameters['LPF highcut'])
        ##        spinBoxLayout.addRow('LPF highcut frequency:', self.lpfHighcut)

        self.bpfLowcut = QSpinBox()
        self.bpfLowcut.setMaximum(10000)
        self.bpfLowcut.setValue(self.parameters['BPF lowcut'])
        spinBoxLayout.addRow('BPF lowcut frequency:', self.bpfLowcut)

        self.bpfHighcut = QSpinBox()
        self.bpfHighcut.setMaximum(10000)
        self.bpfHighcut.setValue(self.parameters['BPF highcut'])
        spinBoxLayout.addRow('BPF highcut frequency:', self.bpfHighcut)

        ##        self.ArtefactsThreshold = QSpinBox()
        ##        self.ArtefactsThreshold.setMaximum(10000)
        ##        self.ArtefactsThreshold.setValue(self.parameters['art thres'])
        ##        spinBoxLayout.addRow('Artefacts Threshold:', self.ArtefactsThreshold)

        self.algWindowSize = QSpinBox()
        self.algWindowSize.setMaximum(10000)
        self.algWindowSize.setValue(self.parameters['window'])
        spinBoxLayout.addRow('Window (samples):', self.algWindowSize)

        self.hmtLargerMean = QDoubleSpinBox()
        self.hmtLargerMean.setMinimum(0.1)
        self.hmtLargerMean.setMaximum(10000)
        self.hmtLargerMean.setValue(self.parameters['hmt larger mean'])
        spinBoxLayout.addRow('How many times larger than mean:',
                             self.hmtLargerMean)

        self.prevSeconds = QDoubleSpinBox()
        self.prevSeconds.setMinimum(0.1)
        self.prevSeconds.setMaximum(10000)
        self.prevSeconds.setValue(self.parameters['prev sec'])
        spinBoxLayout.addRow('Number of previous seconds:', self.prevSeconds)

        self.hmtLarger = QDoubleSpinBox()
        self.hmtLarger.setMinimum(0.1)
        self.hmtLarger.setMaximum(10000)
        self.hmtLarger.setValue(self.parameters['hmt larger'])
        spinBoxLayout.addRow('How many times larger than previous:',
                             self.hmtLarger)

        gBox = QGroupBox('Settings')
        gBox.setLayout(spinBoxLayout)
        self.mainLayout.addWidget(gBox)

    def setupBtnBoxLayout(self):
        bottomLayout = QHBoxLayout()
        self.progBar = QProgressBar()
        self.progBar.setVisible(False)
        bottomLayout.addWidget(self.progBar)
        buttonBox = QDialogButtonBox(QDialogButtonBox.Ok
                                     | QDialogButtonBox.Close)
        buttonBox.accepted.connect(self.okBtnBox)
        buttonBox.rejected.connect(self.close)
        bottomLayout.addWidget(buttonBox)
        self.mainLayout.addLayout(bottomLayout)

    def okBtnBox(self):
        inStruct = self.inTree.getSelectedStruct()
        inDataStruct = self.dataManager.getData(inStruct, inv=True)

        self.progBar.setVisible(True)
        self.progBar.setValue(0)
        nProgres = 5
        progStep = 100.0 / nProgres
        progress = 0

        wName = list(inDataStruct.keys())[0]
        gName = list(inDataStruct[wName].keys())[0]

        ## Input
        inData = np.copy(inDataStruct[wName][gName])

        ## Noises
        if self.removeOffset.isChecked():
            inData = [signal.detrend(data, type='constant') for data in inData]

        if self.removeSupply.isChecked():
            b, a = filterCalc(order=5,
                              bandarr=[48, 52],
                              fs=self.fs,
                              btype='bandstop',
                              ftype='butter')
            inData = [signal.lfilter(b, a, data) for data in inData]

        winLen = self.algWindowSize.value()

        ##        ## Artefacts
        ##        lpfHighcut = self.lpfHighcut.value()
        ##        order=5
        ##
        ##        b, a = filterCalc(order, [lpfHighcut], self.fs, 'low', 'butter')
        ##        lpfData = []
        ##        for data in inData:
        ##            newData = signal.lfilter(b, a, data)
        ##            lpfData.append(newData)
        ##
        ##
        ##
        ##        segNmbr = int(np.shape(lpfData)[1]/winLen)
        ##        artefactsThreshold = self.ArtefactsThreshold.value()
        ##
        ##        lpfWinData = np.copy(lpfData)
        ##        lpfWinData = [np.array_split(ch, segNmbr) for ch in lpfWinData]
        ##
        ##        for ch in lpfWinData:
        ##            for seg in ch:
        ##                seg.fill(max(seg)- min(seg))
        ##
        progress += progStep
        self.progBar.setValue(progress)
        ##
        ####        thresLpfData = np.copy(lpfWinData)
        ##        thresLpfData = deepcopy(lpfWinData)
        ##
        ##        for x in thresLpfData:
        ##            for y in x:
        ##                if y[0] >= artefactsThreshold:
        ##                    y.fill(1)
        ##                else:
        ##                    y.fill(0)
        ##
        ##        lpfWinData = [np.concatenate(ch) for ch in lpfWinData]
        ##        thresLpfData = [np.concatenate(ch) for ch in thresLpfData]
        ##
        progress += progStep
        self.progBar.setValue(progress)

        ## Spikes
        bpfLowcut = self.bpfLowcut.value()
        bpfHighcut = self.bpfHighcut.value()
        order = 4
        b, a = filterCalc(order, [bpfLowcut, bpfHighcut], self.fs, 'band',
                          'butter')
        bpfData = []
        for data in inData:
            newData = signal.lfilter(b, a, data)
            bpfData.append(newData)

        segNmbr = int(np.shape(bpfData)[1] / winLen)

        prevSeconds = self.prevSeconds.value()
        prevWindows = int(prevSeconds * self.fs / winLen)

        progress += progStep
        self.progBar.setValue(progress)

        bpfWinData = np.copy(bpfData)
        bpfWinData = [np.array_split(ch, segNmbr) for ch in bpfWinData]
        chi = 0
        for ch in bpfWinData:
            for seg in ch:
                seg.fill(max(seg) - min(seg))

        progress += progStep
        self.progBar.setValue(progress)

        hmtLarger = self.hmtLarger.value()
        hmtLargerMean = self.hmtLargerMean.value()
        ##        thresBpfData = np.copy(bpfWinData)
        thresBpfData = deepcopy(bpfWinData)

        for ch in range(len(bpfWinData)):
            channelMean = self.hmtLargerMean.value() * np.mean(inData[ch])
            for i in range(prevWindows, len(bpfWinData[ch])):
                if bpfWinData[ch][i][0] > channelMean:
                    prev = [
                        bpfWinData[ch][j][0]
                        for j in range(i - prevWindows, i)
                    ]
                    if bpfWinData[ch][i][0] > hmtLarger * np.mean(prev):
                        thresBpfData[ch][i].fill(1)
                    else:
                        thresBpfData[ch][i].fill(0)
                else:
                    thresBpfData[ch][i].fill(0)

        progress += progStep
        self.progBar.setValue(progress)

        for ch in thresBpfData:
            for i in range(prevWindows):
                ch[i].fill(0)

        bpfWinData = [np.concatenate(ch) for ch in bpfWinData]
        thresBpfData = [np.concatenate(ch) for ch in thresBpfData]

        spikeMap = [np.sum(x) / winLen for x in thresBpfData]
        nazwaPliku = str(winLen) + '_' + str(prevSeconds) + '_' + str(
            hmtLarger) + '.csv'
        with open(nazwaPliku, 'w', encoding='utf-8') as csvfile:
            csvwriter = csv.writer(csvfile)
            csvwriter.writerow(spikeMap)


##        for ch in spikeMap
##            csvwriter.writerow(spikeMap)

## Add data or replace existing
        swsName = 'Algorithm output'
        chNames = inStruct[wName][gName]
        if swsName not in self.dataManager.getDataGroups():
            self.dataManager.createDataGroup(swsName, 'Algorithm')
            self.dataManager.addChannels(swsName, chNames)

            self.dataManager.addSignal(swsName, 'Input Signal', inData,
                                       chNames)
            ##            self.dataManager.addSignal(swsName, 'LPF', lpfData, chNames)
            ##            self.dataManager.addSignal(swsName, 'LPF+Window+Peak-to-peak', lpfWinData, chNames)
            ##            self.dataManager.addSignal(swsName, 'LPF+Threshold - Artefacts', thresLpfData, chNames)
            self.dataManager.addSignal(swsName, 'BPF', bpfData, chNames)
            self.dataManager.addSignal(swsName, 'BPF+Window+Peak-to-peak',
                                       bpfWinData, chNames)
            self.dataManager.addSignal(swsName, 'BPF+Threshold - Spikes',
                                       thresBpfData, chNames)
        else:
            self.dataManager.silentChangeSignal(swsName, 'Input Signal',
                                                inData, chNames)
            ##            self.dataManager.silentChangeSignal(swsName, 'LPF', lpfData, chNames)
            ##            self.dataManager.silentChangeSignal(swsName, 'LPF+Window+P2P', lpfWinData, chNames)
            ##            self.dataManager.silentChangeSignal(swsName, 'LPF+Threshold - Artefacts', thresLpfData, chNames)
            self.dataManager.silentChangeSignal(swsName, 'BPF', bpfData,
                                                chNames)
            self.dataManager.silentChangeSignal(swsName,
                                                'BPF+Window+Peak-to-peak',
                                                bpfWinData, chNames)
            self.dataManager.silentChangeSignal(swsName,
                                                'BPF+Threshold - Spikes',
                                                thresBpfData, chNames)

        self.chartManager.setMask('Algorithm output', 'BPF+Threshold - Spikes')
        self.chartManager.setMap(spikeMap)
        self.parameters = {
            'offset filter': self.removeOffset.isChecked(),
            'supply filter': self.removeSupply.isChecked(),
            ##                           'LPF highcut'   : lpfHighcut,
            'BPF lowcut': bpfLowcut,
            'BPF highcut': bpfHighcut,
            ##                           'art thres'     : artefactsThreshold,
            'window': winLen,
            'prev sec': prevSeconds,
            'hmt larger': hmtLarger,
            'hmt larger mean': hmtLargerMean,
        }
        self.executed.emit(self.parameters, self.inputList)
    def _gui(self):
        layout = QVBoxLayout()
        self.setLayout(layout)

        slider_layout = QHBoxLayout()
        layout.addLayout(slider_layout)

        slider = ClickSlider(self)
        slider.setOrientation(Qt.Horizontal)
        slider.valueChanged.connect(self._slider_changed)
        slider_layout.addWidget(slider)
        self.slider = slider

        max_time = QLabel(self)
        self.max_time = max_time
        max_time.setText('--:--')
        slider_layout.addWidget(max_time)

        form = QFormLayout()
        layout.addLayout(form)

        time = QTimeEdit(self)
        time.setDisplayFormat('mm:ss.zzz')
        time.setAlignment(Qt.AlignRight)
        time.timeChanged.connect(self._edit_time_changed)
        self.time = time
        form.addRow(QLabel('Time', self),time)

        speed = QDoubleSpinBox(self)
        speed.setMinimum(0.1)
        speed.setMaximum(2.0)
        speed.setSingleStep(0.1)
        speed.setValue(1)
        speed.setAlignment(Qt.AlignRight)
        self.speed = speed
        speed.valueChanged.connect(self._speed_changed)
        form.addRow(QLabel('Speed', self), speed)

        playback = QHBoxLayout()
        layout.addLayout(playback)

        back_fast = QToolButton(self)
        playback.addWidget(back_fast)
        abf = QAction('<<', back_fast)
        abf.setShortcut(QKeySequence(Qt.Key_F1))
        abf.triggered.connect(self.back_fast)
        back_fast.setDefaultAction(abf)

        back = QToolButton(self)
        playback.addWidget(back)
        ab = QAction('<', back)
        ab.setShortcut(QKeySequence(Qt.Key_F2))
        ab.triggered.connect(self.back)
        back.setDefaultAction(ab)

        play = QToolButton(self)
        playback.addWidget(play)
        self.play_button = play
        play.setCheckable(True)
        play.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
        ap = QAction('Play', play)
        ap.setShortcut(QKeySequence(Qt.Key_F5))
        ap.triggered.connect(self.play)
        play.setDefaultAction(ap)

        forward = QToolButton(self)
        playback.addWidget(forward)
        af = QAction('>', forward)
        af.setShortcut(QKeySequence(Qt.Key_F3))
        af.triggered.connect(self.forward)
        forward.setDefaultAction(af)

        forward_fast = QToolButton(self)
        playback.addWidget(forward_fast)
        aff = QAction('>>', forward_fast)
        aff.setShortcut(QKeySequence(Qt.Key_F4))
        aff.triggered.connect(self.forward_fast)
        forward_fast.setDefaultAction(aff)

        speed_up = QAction('Speed up', forward_fast)
        speed_up.setShortcut(QKeySequence(Qt.Key_F7))
        speed_up.triggered.connect(self.speed.stepUp)

        speed_down = QAction('Speed down', forward_fast)
        speed_down.setShortcut(QKeySequence(Qt.Key_F6))
        speed_down.triggered.connect(self.speed.stepDown)
        
        self.utils = [
            slider,
            back_fast,
            back,
            play,
            forward,
            forward_fast,
            time,
            speed
        ]
        self.playback_actions = [abf, ab, ap, af, aff, speed_down, speed_up]
        self.set_enabled_utils(False)

        self.prevent = False
        self.timer = QTimer(self)
        self.timer.timeout.connect(self._update)
        self.timer.start(1)
        self.player = None
class AcquistoForm(QDialog):
    '''
    Widget per l'inserimento di un acquisto immediato.
    '''
    def __init__(self, conn):
        '''
        Parameters:
            conn : connection
                Connection to the database.
        '''
        super().__init__()
        self.setWindowTitle('Aggiungi Acquisto')
        self.setWindowFlag(QtCore.Qt.WindowContextHelpButtonHint, False)

        self.conn = conn
        self.cursor = conn.cursor()

        self.books = dict()
        self.books_qt = dict()

        self.gen_layout = QVBoxLayout()

        self.form_layout = QFormLayout()
        self.form_layout.setRowWrapPolicy(QFormLayout.WrapLongRows)

        # Widgets
        self.client_field = QLineEdit()
        self.form_layout.addRow('Numero Cliente (facoltativo): ',
                                self.client_field)

        self.dip_field = QLineEdit()
        self.form_layout.addRow('Dipendente (CF): ', self.dip_field)

        self.date_picker = QDateEdit(QDate.currentDate())
        self.date_picker.setDisplayFormat("MM/dd/yyyy")
        self.date_picker.setCalendarPopup(True)
        self.form_layout.addRow('Data (mm/gg/aaaa): ', self.date_picker)

        self.importo_field = QDoubleSpinBox()
        self.importo_field.setMaximum(999999999.99)
        self.form_layout.addRow('Importo: ', self.importo_field)

        self.ins_layout = QHBoxLayout()
        self.libro_field = QLineEdit()
        self.quantita_field = QSpinBox()
        self.quantita_field.setMinimum(1)
        self.libro_button = QPushButton('Aggiungi')
        self.libro_button.clicked.connect(self.aggiungi_libro)
        self.ins_layout.addWidget(QLabel('Libro (ISBN): '))
        self.ins_layout.addWidget(self.libro_field)
        self.ins_layout.addWidget(QLabel('Quantità: '))
        self.ins_layout.addWidget(self.quantita_field)
        self.ins_layout.addWidget(self.libro_button)

        self.labels = ['ISBN', 'Quantità', '']
        self.table = QTableWidget(0, 3)
        self.table.setHorizontalHeaderLabels(self.labels)
        self.table.horizontalHeader().setSectionResizeMode(
            0, QHeaderView.Stretch)
        self.table.horizontalHeader().setSectionResizeMode(
            1, QHeaderView.Stretch)
        self.table.horizontalHeader().setSectionResizeMode(
            2, QHeaderView.ResizeToContents)

        self.confirm_button = QPushButton('Conferma')
        self.confirm_button.clicked.connect(self.post_acquisto)

        self.gen_layout.addLayout(self.form_layout)
        self.gen_layout.addLayout(self.ins_layout)
        self.gen_layout.addWidget(self.table)
        self.gen_layout.addWidget(self.confirm_button)
        self.setLayout(self.gen_layout)

    def aggiungi_libro(self):
        self.books[self.libro_field.text()] = self.quantita_field.value()
        self.update_table()

    def remove_libro(self, book):
        self.books.pop(book, None)
        self.update_table()

    def update_table(self):
        self.table.clearContents()
        while self.table.rowCount() > 0:
            self.table.removeRow(0)
        for book in self.books.keys():
            row = self.table.rowCount()
            button = QPushButton('Rimuovi')
            button.clicked.connect(lambda: self.remove_libro(book))
            self.table.insertRow(row)
            self.table.setItem(row, 0, QTableWidgetItem(book))
            self.table.setItem(row, 1, QTableWidgetItem(str(self.books[book])))
            self.table.setCellWidget(row, 2, button)

    def verif_qty(self):
        '''
        Shows error messages based on the validity of inserted books, and
        returns False, or returns True if all the books are ok to sell right now.
        '''
        if len(self.books.items()) == 0:
            self._show_error('Non ci sono libri nell\'acquisto')
            return 1
        for book, qty in self.books.items():
            self.cursor.execute(FIND_QUANTITA, (book, ))
            result = self.cursor.fetchall()
            if not result:
                self._show_error('\'{}\' non è un libro valido.'.format(book))
                return 2
            stored = result[0][0]
            if stored < qty:
                return self.__show_are_you_sure(
                    'L\'acquisto richiede {} libri {}, ma ne sono presenti solo {}.\nNon sarà possibile sottrarre i libri acquistati.\nVuoi proseguire ugualmente?'
                    .format(qty, book, stored))
        return 0

    def verif_client_dip(self):
        '''
        Returns false and displays and error message if cliente and dipendente are
        not valid tuples in the database, returns true if they are ok
        '''
        cliente = self.client_field.text()
        if cliente:
            if not cliente.isdigit():
                self._show_error('Il codice del Cliente deve essere numerico.')
                return 1
            self.cursor.execute(FIND_CLIENTE, (cliente, ))
            result = self.cursor.fetchall()
            if not result or not result[0][0]:
                self._show_error('Cliente {} non esiste.'.format(cliente))
                return 2

        dipendente = self.dip_field.text()
        if not dipendente:
            self._show_error('Il Dipendente non può essere vuoto.')
            return 3
        self.cursor.execute(FIND_DIPENDENTE, (dipendente, ))
        result = self.cursor.fetchall()
        if not result or not result[0][0]:
            self._show_error('Dipendente {} non esiste.'.format(dipendente))
            return 4
        return 0

    def post_acquisto(self):
        if self.verif_client_dip():
            return
        should_show = self.verif_qty()
        if should_show > 0:
            return
        cliente = self.client_field.text().strip() if self.client_field.text(
        ).strip() else None
        importo = self.importo_field.value()
        self.cursor.execute(
            INSERT_ACQUISTO,
            (self.date_picker.date().toString('MM/dd/yyyy'),
             self.importo_field.value(), self.dip_field.text()))
        new_id = self.cursor.fetchall()[0][0]
        self.cursor.execute(INSERT_IMMEDIATO, (new_id, cliente))
        for book in self.books.keys():
            self.cursor.execute(INSERT_COMPRENDE,
                                (new_id, book, self.books[book]))
        self.conn.commit()
        if should_show == 0: self._show_confirm()
        self.accept()

    def __show_are_you_sure(self, msg=''):
        dialog = _AreYouSureDialog(msg)
        dialog.setWindowTitle('ATTENZIONE')
        result = dialog.exec_()
        return -1 if result == QDialog.Accepted else 3

    def _show_confirm(self):
        dialog = _ScalaAcquistiDialog(self.books, self.conn)
        dialog.setWindowTitle('Rimozione libri')
        dialog.exec_()

    def _show_error(self, msg=''):
        dialog = QMessageBox()
        dialog.setWindowTitle('ERRORE')
        dialog.setText(msg)
        dialog.exec_()
Beispiel #14
0
 def __init_advanced_features_widget__(self, parent=None):
     self.__advanced_widget = QGroupBox("Advanced Features Options", parent)
     fixed_layer_check = QCheckBox("Fixed Layer", self.__advanced_widget)
     fixed_layer_check.setChecked(self.dlp_controller.fixed_layer)
     fixed_layer_check.toggled.connect(self.dlp_controller.set_fixed_layer)
     incremental_amplitude_check = QCheckBox("Incremental Amplitude",
                                             self.__advanced_widget)
     incremental_amplitude_check.setChecked(
         self.dlp_controller.incremental_amplitude)
     incremental_amplitude_check.toggled.connect(
         self.dlp_controller.set_incremental_amplitude)
     starting_amplitude_label = QLabel("Starting Amplitude",
                                       self.__advanced_widget)
     starting_amplitude_edit = QSpinBox(self.__advanced_widget)
     starting_amplitude_edit.setMaximum(1000)
     starting_amplitude_edit.setMinimum(0)
     starting_amplitude_edit.setSingleStep(1)
     starting_amplitude_edit.setValue(
         self.dlp_controller.starting_incremental_amplitude)
     starting_amplitude_edit.valueChanged.connect(
         self.dlp_controller.set_starting_incremental_amplitude)
     amplitude_step_label = QLabel("Step Size", self.__advanced_widget)
     amplitude_step_edit = QSpinBox(self.__advanced_widget)
     amplitude_step_edit.setMaximum(1000)
     amplitude_step_edit.setMinimum(0)
     amplitude_step_edit.setSingleStep(1)
     amplitude_step_edit.setValue(
         self.dlp_controller.incremental_step_amplitude)
     amplitude_step_edit.valueChanged.connect(
         self.dlp_controller.set_incremental_step_amplitude)
     incremental_exposure_check = QCheckBox("Incremental Exposure",
                                            self.__advanced_widget)
     incremental_exposure_check.setChecked(
         self.dlp_controller.incremental_exposure)
     incremental_exposure_check.toggled.connect(
         self.dlp_controller.set_incremental_exposure)
     starting_exposure_label = QLabel("Starting Exposure",
                                      self.__advanced_widget)
     starting_exposure_edit = QDoubleSpinBox(self.__advanced_widget)
     starting_exposure_edit.setSuffix(str('ms'))
     starting_exposure_edit.setMaximum(100000)
     starting_exposure_edit.setMinimum(0)
     starting_exposure_edit.setDecimals(1)
     starting_exposure_edit.setSingleStep(0.1)
     starting_exposure_edit.valueChanged.connect(
         self.dlp_controller.set_starting_incremental_exposure)
     starting_exposure_edit.setValue(
         self.dlp_controller.starting_incremental_exposure)
     exposure_step_label = QLabel("Step Size", self.__advanced_widget)
     exposure_step_edit = QDoubleSpinBox(self.__advanced_widget)
     exposure_step_edit.setSuffix(str('ms'))
     exposure_step_edit.setMaximum(100000)
     exposure_step_edit.setMinimum(0)
     exposure_step_edit.setDecimals(1)
     exposure_step_edit.setSingleStep(0.1)
     exposure_step_edit.valueChanged.connect(
         self.dlp_controller.set_incremental_step_exposure)
     exposure_step_edit.setValue(
         self.dlp_controller.incremental_step_exposure)
     incremental_thickness_check = QCheckBox("Incremental Thickness",
                                             self.__advanced_widget)
     incremental_thickness_check.setChecked(
         self.dlp_controller.incremental_thickness)
     incremental_thickness_check.toggled.connect(
         self.dlp_controller.set_incremental_thickness)
     thickness_label = QLabel("Starting Thickness", self.__features_widget)
     self.starting_thickness_edit = MyDiscreteStepsSpinBox(
         self.dlp_controller.get_step_length_microns(),
         self.__features_widget)
     self.starting_thickness_edit.setSuffix(str('\u03BCm'))
     self.starting_thickness_edit.setMaximum(1000000)
     self.starting_thickness_edit.setMinimum(0)
     self.starting_thickness_edit.setDecimals(3)
     self.starting_thickness_edit.my_value_changed_signal.connect(
         self.dlp_controller.set_starting_incremental_thickness)
     self.starting_thickness_edit.setValue(
         self.dlp_controller.starting_incremental_thickness * 1000)
     thickness_step_label = QLabel("Step Size", self.__features_widget)
     self.thickness_step_edit = MyDiscreteStepsSpinBox(
         self.dlp_controller.get_step_length_microns(),
         self.__features_widget)
     self.thickness_step_edit.setSuffix(str('\u03BCm'))
     self.thickness_step_edit.setMaximum(1000000)
     self.thickness_step_edit.setMinimum(0)
     self.thickness_step_edit.setDecimals(3)
     self.thickness_step_edit.my_value_changed_signal.connect(
         self.dlp_controller.set_incremental_step_thickness)
     self.thickness_step_edit.setValue(
         self.dlp_controller.incremental_step_thickness * 1000)
     apply_grayscale_correction_check = QCheckBox("Grayscale Correction",
                                                  self.__advanced_widget)
     apply_grayscale_correction_check.setChecked(
         self.dlp_controller.grayscale_correction)
     apply_grayscale_correction_check.toggled.connect(
         self.dlp_controller.set_grayscale_correction)
     grayscale_parameters_widget = QWidget(self.__features_widget)
     a_parameter_label = QLabel("\u03B1", grayscale_parameters_widget)
     alpha_parameter_edit = QDoubleSpinBox(grayscale_parameters_widget)
     alpha_parameter_edit.setMaximum(1000)
     alpha_parameter_edit.setMinimum(0)
     alpha_parameter_edit.setDecimals(3)
     alpha_parameter_edit.setSingleStep(0.001)
     alpha_parameter_edit.valueChanged.connect(
         self.dlp_controller.set_grayscale_alpha)
     alpha_parameter_edit.setValue(self.dlp_controller.grayscale_alpha)
     beta_parameter_label = QLabel("\u03B2", grayscale_parameters_widget)
     beta_parameter_edit = QDoubleSpinBox(grayscale_parameters_widget)
     beta_parameter_edit.setMaximum(1000)
     beta_parameter_edit.setMinimum(0)
     beta_parameter_edit.setDecimals(3)
     beta_parameter_edit.setSingleStep(0.001)
     beta_parameter_edit.valueChanged.connect(
         self.dlp_controller.set_grayscale_beta)
     beta_parameter_edit.setValue(self.dlp_controller.grayscale_beta)
     gamma_parameter_label = QLabel("\u03B3", grayscale_parameters_widget)
     gamma_parameter_edit = QDoubleSpinBox(grayscale_parameters_widget)
     gamma_parameter_edit.setMaximum(1000)
     gamma_parameter_edit.setMinimum(0)
     gamma_parameter_edit.setDecimals(3)
     gamma_parameter_edit.setSingleStep(0.001)
     gamma_parameter_edit.valueChanged.connect(
         self.dlp_controller.set_grayscale_gamma)
     gamma_parameter_edit.setValue(self.dlp_controller.grayscale_gamma)
     grayscale_parameters_layout = QHBoxLayout(grayscale_parameters_widget)
     grayscale_parameters_layout.addWidget(a_parameter_label)
     grayscale_parameters_layout.addWidget(alpha_parameter_edit)
     grayscale_parameters_layout.addWidget(beta_parameter_label)
     grayscale_parameters_layout.addWidget(beta_parameter_edit)
     grayscale_parameters_layout.addWidget(gamma_parameter_label)
     grayscale_parameters_layout.addWidget(gamma_parameter_edit)
     grayscale_parameters_widget.setLayout(grayscale_parameters_layout)
     advanced_features_layout = QGridLayout(self.__advanced_widget)
     advanced_features_layout.addWidget(incremental_amplitude_check, 1, 0,
                                        1, 2)
     advanced_features_layout.addWidget(fixed_layer_check, 1, 3)
     advanced_features_layout.addWidget(starting_amplitude_label, 2, 0)
     advanced_features_layout.addWidget(starting_amplitude_edit, 2, 1)
     advanced_features_layout.addWidget(amplitude_step_label, 2, 2)
     advanced_features_layout.addWidget(amplitude_step_edit, 2, 3)
     advanced_features_layout.addWidget(incremental_exposure_check, 3, 0, 1,
                                        2)
     advanced_features_layout.addWidget(starting_exposure_label, 4, 0)
     advanced_features_layout.addWidget(starting_exposure_edit, 4, 1)
     advanced_features_layout.addWidget(exposure_step_label, 4, 2)
     advanced_features_layout.addWidget(exposure_step_edit, 4, 3)
     advanced_features_layout.addWidget(incremental_thickness_check, 5, 0,
                                        1, 2)
     advanced_features_layout.addWidget(thickness_label, 6, 0)
     advanced_features_layout.addWidget(self.starting_thickness_edit, 6, 1)
     advanced_features_layout.addWidget(thickness_step_label, 6, 2)
     advanced_features_layout.addWidget(self.thickness_step_edit, 6, 3)
     advanced_features_layout.addWidget(apply_grayscale_correction_check, 7,
                                        0, 1, 2)
     advanced_features_layout.addWidget(grayscale_parameters_widget, 8, 0,
                                        1, 4)
     self.__advanced_widget.setLayout(advanced_features_layout)
class InputsLayout(QFormLayout):
    # this signal is connected to print_output from output_layout class. Connection is done in center_layout
    ga_result = Signal(
        str
    )  # a signal that is emitted so it can transfer resulting string to the output_layout class

    def __init__(self):
        super(InputsLayout, self).__init__()
        self.big_font = QFont()
        self.medium_font = QFont()
        self.header = QLabel()
        self.header_general = QLabel()
        self.header_fitness_remapping = QLabel()
        self.header_stop = QLabel()
        self.header_selection = QLabel()
        self.header_pairing = QLabel()
        self.header_crossover = QLabel()
        self.header_mutation = QLabel()

        self.inp_functions_combo = QComboBox()
        self.inp_num_variables = QSpinBox()
        self.inp_extrema_min = QRadioButton("Minimum")
        self.inp_extrema_max = QRadioButton("Maximum")
        self.inp_pop_size = QSpinBox()
        self.inp_lower_bound = QDoubleSpinBox()
        self.inp_upper_bound = QDoubleSpinBox()
        # Stopping
        self.inp_max_iter = QSpinBox()
        self.inp_similarity_cb = QCheckBox()
        self.inp_similarity = QSpinBox()
        self.inp_best_result_cb = QCheckBox()
        self.inp_best_result = QDoubleSpinBox()
        self.inp_average_result_cb = QCheckBox()
        self.inp_average_result = QDoubleSpinBox()
        # Fitness remapping
        self.inp_fitness_remapping = QComboBox()
        # Selection
        self.inp_selection_method = QComboBox()
        self.inp_elitism = QDoubleSpinBox()
        # Pairing
        self.inp_pairing_method = QComboBox()
        # Crossover
        self.inp_crossover_method = QComboBox()
        self.inp_crossover_fraction = QDoubleSpinBox()
        self.intermediate_offset = QDoubleSpinBox()
        # Mutation
        self.inp_mutation_method = QComboBox()
        self.inp_mutation_intensity = QDoubleSpinBox()
        self.inp_mutation_intensity_final = QDoubleSpinBox()

        self.init_fonts()
        self.init_header()
        self.init_row_functions()
        self.init_row_general()
        self.init_row_fitness_remapping()
        self.init_row_stop()
        self.init_row_selection()
        self.init_row_pairing()
        self.init_row_crossover()
        self.init_row_mutation()

    def init_fonts(self):
        self.big_font.setPointSizeF(14)
        self.medium_font.setPointSizeF(12)

    def init_header(self):
        self.header.setFont(self.big_font)
        self.header.setAlignment(Qt.AlignCenter)
        self.header.setText("Genetic Algorithm Continuous Optimization")
        self.addRow(self.header)
        self.addRow(QHLine())

    def init_row_functions(self):
        self.inp_functions_combo.addItem("Ackley", ackley)
        self.inp_functions_combo.addItem("Griewank", griewank)
        self.inp_functions_combo.addItem("Michalewicz", michalewicz)

        self.inp_extrema_min.setChecked(True)

        radio_box = QHBoxLayout()
        radio_box.addWidget(self.inp_extrema_min)
        radio_box.addWidget(self.inp_extrema_max)
        self.addRow("Function:", self.inp_functions_combo)
        self.inp_num_variables.setMaximum(10000)
        self.inp_num_variables.setValue(10)
        self.addRow("Number of variables:", self.inp_num_variables)
        self.addRow("Find:", radio_box)
        self.addRow(QHLine())

    def init_row_general(self):
        self.header_general.setFont(self.medium_font)
        self.header_general.setText("General")

        self.inp_pop_size.setMaximum(10000)
        self.inp_pop_size.setValue(300)
        self.inp_lower_bound.setMaximum(1000000)
        self.inp_lower_bound.setMinimum(-1000000.0)
        self.inp_lower_bound.setValue(-10)
        self.inp_upper_bound.setMaximum(1000000)
        self.inp_upper_bound.setMinimum(-1000000.0)
        self.inp_upper_bound.setValue(10)

        self.addRow(self.header_general)
        self.addRow("Population size", self.inp_pop_size)
        self.addRow("Lower Bound", self.inp_lower_bound)
        self.addRow("Upper Bound", self.inp_upper_bound)
        self.addRow(QHLine())

    def init_row_fitness_remapping(self):
        self.header_fitness_remapping.setFont(self.medium_font)
        self.header_fitness_remapping.setText("Fitness Remapping")

        self.inp_fitness_remapping.addItem("Rank Scaling", "Rank Scaling")
        self.inp_fitness_remapping.addItem("Fitness Scaling",
                                           "Fitness Scaling")

        self.addRow(self.header_fitness_remapping)
        self.addRow("Fitness remapping", self.inp_fitness_remapping)
        self.addRow(QHLine())

    def init_row_stop(self):
        self.header_stop.setFont(self.medium_font)
        self.header_stop.setText("Stopping Criteria")

        self.inp_max_iter.setMaximum(100000)
        self.inp_similarity.setMaximum(100000)
        self.inp_best_result.setMinimum(-100000)
        self.inp_best_result.setMaximum(100000)
        self.inp_average_result.setMinimum(-100000)
        self.inp_average_result.setMaximum(100000)

        self.inp_max_iter.setValue(500)
        self.inp_similarity.setValue(80)
        self.inp_best_result.setValue(-10)
        self.inp_average_result.setValue(-10000)

        self.inp_similarity_cb.setText("Similar Results")
        self.inp_best_result_cb.setText("Best Result")
        self.inp_average_result_cb.setText("Average Result")
        self.inp_similarity_cb.stateChanged.connect(self.cb_similarity_signal)
        self.inp_best_result_cb.stateChanged.connect(
            self.cb_best_result_signal)
        self.inp_average_result_cb.stateChanged.connect(
            self.cb_average_result_signal)

        self.inp_similarity_cb.setChecked(False)
        self.inp_best_result_cb.setChecked(False)
        self.inp_average_result_cb.setChecked(False)

        self.inp_similarity.setEnabled(True)
        self.inp_best_result.setEnabled(False)
        self.inp_best_result.setStyleSheet("background:#555")
        self.inp_average_result.setEnabled(False)
        self.inp_average_result.setStyleSheet("background:#555")

        self.addRow(self.header_stop)
        self.addRow("Max iter", self.inp_max_iter)
        self.addRow(self.inp_similarity_cb, self.inp_similarity)
        self.addRow(self.inp_best_result_cb, self.inp_best_result)
        self.addRow(self.inp_average_result_cb, self.inp_average_result)
        self.addRow(QHLine())

    def init_row_selection(self):
        self.header_selection.setFont(self.medium_font)
        self.header_selection.setText("Selection")

        self.inp_selection_method.addItem("Fittest Half", "Fittest Half")
        self.inp_selection_method.addItem("Roulette Wheel", "Roulette Wheel")
        self.inp_selection_method.addItem("Random", "Random")
        self.inp_selection_method.addItem("Whole Population",
                                          "Whole Population")
        self.inp_elitism.setMaximum(1)
        self.inp_elitism.setValue(0.01)
        self.inp_elitism.setSingleStep(0.01)

        self.addRow(self.header_selection)
        self.addRow("Selection Method", self.inp_selection_method)
        self.addRow("Elitism Percentage", self.inp_elitism)
        self.addRow(QHLine())

    def init_row_pairing(self):
        self.header_pairing.setFont(self.medium_font)
        self.header_pairing.setText("Pairing")

        self.inp_pairing_method.addItem("Random", "Random")
        self.inp_pairing_method.addItem("Roulette Wheel", "Roulette Wheel")
        self.inp_pairing_method.addItem("Fittest", "Fittest")

        self.addRow(self.header_pairing)
        self.addRow("Pairing Method", self.inp_pairing_method)
        self.addRow(QHLine())

    def init_row_crossover(self):
        self.header_crossover.setFont(self.medium_font)
        self.header_crossover.setText("Crossover")

        self.inp_crossover_method.addItem("Intermediate", "Intermediate")
        self.inp_crossover_method.addItem("Line Intermediate",
                                          "Line Intermediate")
        self.inp_crossover_method.addItem("Heuristic", "Heuristic")
        self.inp_crossover_method.addItem("One point", "One point")
        self.inp_crossover_method.addItem("Two point", "Two point")
        self.inp_crossover_method.addItem("Random", "Random")
        self.inp_mutation_method.setCurrentIndex(2)
        self.inp_crossover_fraction.setMaximum(1)
        self.inp_crossover_fraction.setValue(0.7)
        self.inp_crossover_fraction.setSingleStep(0.05)
        self.intermediate_offset.setMaximum(20)
        self.intermediate_offset.setValue(1.55)
        self.intermediate_offset.setSingleStep(0.05)

        self.addRow(self.header_crossover)
        self.addRow("Crossover Method", self.inp_crossover_method)
        self.addRow("Crossover Fraction", self.inp_crossover_fraction)
        self.addRow("Intermediate Offset", self.intermediate_offset)
        self.addRow(QHLine())

    def init_row_mutation(self):
        self.header_mutation.setFont(self.medium_font)
        self.header_mutation.setText("Mutation")

        self.inp_mutation_method.addItem("Gauss", "Gauss")
        self.inp_mutation_method.addItem("Random", "Random")
        self.inp_mutation_intensity.setMaximum(200)
        self.inp_mutation_intensity.setValue(2)
        self.inp_mutation_intensity.setDecimals(4)

        self.inp_mutation_intensity.setSingleStep(0.01)
        self.inp_mutation_intensity_final.setMaximum(200)
        self.inp_mutation_intensity_final.setDecimals(4)
        self.inp_mutation_intensity_final.setValue(0.001)
        self.inp_mutation_intensity_final.setSingleStep(0.5)

        self.addRow(self.header_mutation)
        self.addRow("Mutation Method", self.inp_mutation_method)
        self.addRow("Mutation Intensity", self.inp_mutation_intensity)
        self.addRow("Final Mutation Intensity",
                    self.inp_mutation_intensity_final)
        self.addRow(QHLine())

    def get_options(self):
        function = self.inp_functions_combo.currentData()
        num_var = self.inp_num_variables.text()
        if self.inp_extrema_min.isChecked():
            extrem = 0
        else:
            extrem = 1
        pop_size = self.inp_pop_size.text()
        low_bound = self.inp_lower_bound.text()
        upp_bound = self.inp_upper_bound.text()
        max_iter = self.inp_max_iter.text()
        sim_results = self.inp_similarity.text()
        best_res = self.inp_best_result.text()
        average_res = self.inp_average_result.text()
        select_method = self.inp_selection_method.currentText()
        elite_percent = self.inp_elitism.text()
        pairing = self.inp_pairing_method.currentText()
        crossover_method = self.inp_crossover_method.currentText()
        crossover_fraction = self.inp_crossover_fraction.text()
        intermediate_offset = self.intermediate_offset.text()
        mutation_method = self.inp_mutation_method.currentText()
        mutation_intensity = self.inp_mutation_intensity.text()
        mutation_intensity_final = self.inp_mutation_intensity_final.text()
        fitness_remapping = self.inp_fitness_remapping.currentText()

        options = {
            "function":
            function,
            "num_var":
            num_var,
            "pop_size":
            int(pop_size),
            "max_iter":
            int(max_iter),
            "lower_bound":
            float(low_bound.replace(",", ".")),
            "upper_bound":
            float(upp_bound.replace(",", ".")),
            "find_max":
            extrem,
            "prints":
            0,
            "average_result":
            float(average_res.replace(",", ".")),
            "best_result":
            float(best_res.replace(",", ".")),
            "similarity":
            float(sim_results.replace(",", ".")),
            "selection":
            select_method,
            "pairing":
            pairing,
            "crossover":
            crossover_method,
            "crossover_fraction":
            float(crossover_fraction.replace(",", ".")),
            "intermediate_offset":
            float(intermediate_offset.replace(",", ".")),
            # 0 mean child will be between parents, 1 mean offset is same as two parent distance
            "mutation":
            mutation_method,
            "mutation_intensity":
            float(mutation_intensity.replace(",", ".")),
            "mutation_intensity_final":
            float(mutation_intensity_final.replace(",", ".")),
            "elitism":
            float(elite_percent.replace(",", ".")),
            "fitness_remapping":
            fitness_remapping
        }

        if not self.inp_similarity_cb.isChecked():
            options["similarity"] = None
        if not self.inp_best_result_cb.isChecked():
            options["best_result"] = None
        if not self.inp_average_result_cb.isChecked():
            options["average_result"] = None
        return options

    def cb_similarity_signal(self):
        if self.inp_similarity_cb.isChecked():
            self.inp_similarity.setEnabled(True)
            self.inp_similarity.setStyleSheet("")
        else:
            self.inp_similarity.setEnabled(False)
            self.inp_similarity.setStyleSheet("background:#555")

    def cb_best_result_signal(self):
        if self.inp_best_result_cb.isChecked():
            self.inp_best_result.setEnabled(True)
            self.inp_best_result.setStyleSheet("")
        else:
            self.inp_best_result.setEnabled(False)
            self.inp_best_result.setStyleSheet("background:#555")

    def cb_average_result_signal(self):
        if self.inp_average_result_cb.isChecked():
            self.inp_average_result.setEnabled(True)
            self.inp_average_result.setStyleSheet("")
        else:
            self.inp_average_result.setEnabled(False)
            self.inp_average_result.setStyleSheet("background:#555")
Beispiel #16
0
 def __init_projector_widget__(self, parent=None):
     self.__projector_widget = QGroupBox("Projector Options", parent)
     # self.__projector_widget.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum)
     mirror_x = QCheckBox("Mirror X")
     mirror_x.setChecked(self.dlp_controller.is_horizontal_mirrored())
     mirror_x.toggled.connect(self.dlp_controller.set_horizontal_mirroring)
     mirror_y = QCheckBox("Mirror Y")
     mirror_y.setChecked(self.dlp_controller.is_vertical_mirrored())
     mirror_y.toggled.connect(self.dlp_controller.set_vertical_mirroring)
     start_projector_button = QPushButton("Start Projector",
                                          self.__projector_widget)
     start_projector_button.clicked.connect(
         self.dlp_controller.start_projector)
     project_pattern_button = QPushButton("Project Pattern",
                                          self.__projector_widget)
     project_pattern_button.clicked.connect(
         self.dlp_controller.project_calibration_pattern)
     print_position_button = QPushButton("Print Position",
                                         self.__projector_widget)
     print_position_button.clicked.connect(
         self.dlp_controller.print_motor_position)
     home_projector_button = QPushButton("Home Projector",
                                         self.__projector_widget)
     home_projector_button.clicked.connect(
         self.dlp_controller.home_projector)
     move_projector_button = QPushButton("Move Projector",
                                         self.__projector_widget)
     move_projector_button.clicked.connect(
         self.dlp_controller.move_projector)
     move_projector_edit = QDoubleSpinBox(self.__projector_widget)
     move_projector_edit.setSuffix("mm")
     move_projector_edit.setMaximum(10000)
     move_projector_edit.setMinimum(-10000)
     move_projector_edit.setDecimals(3)
     move_projector_edit.setSingleStep(0.001)
     move_projector_edit.valueChanged.connect(
         self.dlp_controller.update_projector_distance)
     set_amplitude_button = QPushButton("Set Projector Amplitude",
                                        self.__projector_widget)
     set_amplitude_button.clicked.connect(
         self.dlp_controller.set_projector_amplitude)
     set_amplitude_edit = QSpinBox(self.__projector_widget)
     set_amplitude_edit.setMaximum(1000)
     set_amplitude_edit.setMinimum(0)
     set_amplitude_edit.setValue(self.dlp_controller.projector_amplitude)
     set_amplitude_edit.valueChanged.connect(
         self.dlp_controller.update_projector_amplitude)
     lock_unlock_projector_button = QPushButton("Lock/Unlock Projector",
                                                self.__projector_widget)
     lock_unlock_projector_button.clicked.connect(
         self.dlp_controller.lock_unlock_projector)
     projector_layout = QGridLayout(self.__projector_widget)
     # projector_layout.addWidget(projector_label, 0, 0)
     projector_layout.addWidget(mirror_x, 0, 1)
     projector_layout.addWidget(mirror_y, 0, 2)
     projector_layout.addWidget(start_projector_button, 1, 0, 1, 1)
     projector_layout.addWidget(project_pattern_button, 1, 1, 1, 1)
     projector_layout.addWidget(print_position_button, 1, 2, 1, 1)
     projector_layout.addWidget(home_projector_button, 2, 0, 1, 1)
     projector_layout.addWidget(move_projector_button, 2, 1, 1, 1)
     projector_layout.addWidget(move_projector_edit, 2, 2)
     projector_layout.addWidget(set_amplitude_button, 3, 0, 1, 1)
     projector_layout.addWidget(set_amplitude_edit, 3, 1)
     projector_layout.addWidget(lock_unlock_projector_button, 3, 2, 1, 1)
     self.__projector_widget.setLayout(projector_layout)
Beispiel #17
0
 def __init_features_widget__(self, parent=None):
     self.__features_widget = QGroupBox("Features Layers Parameters",
                                        parent)
     # self.__features_widget.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum)
     thickness_label = QLabel("Layer Thickness", self.__features_widget)
     self.features_thickness_edit = MyDiscreteStepsSpinBox(
         self.dlp_controller.get_step_length_microns(),
         self.__features_widget)
     self.features_thickness_edit.setSuffix(str('\u03BCm'))
     self.features_thickness_edit.setMaximum(1000000)
     self.features_thickness_edit.setMinimum(0)
     self.features_thickness_edit.setDecimals(3)
     self.features_thickness_edit.my_value_changed_signal.connect(
         self.dlp_controller.set_features_thickness)
     self.features_thickness_edit.setValue(
         self.dlp_controller.features_thickness * 1000)
     exposure_label = QLabel("Exposure Time", self.__features_widget)
     exposure_edit = QDoubleSpinBox(self.__features_widget)
     exposure_edit.setSuffix(str('ms'))
     exposure_edit.setMaximum(10000000)
     exposure_edit.setMinimum(0)
     exposure_edit.setDecimals(1)
     exposure_edit.setSingleStep(0.1)
     exposure_edit.setValue(self.dlp_controller.features_exposure)
     exposure_edit.valueChanged.connect(
         self.dlp_controller.set_features_exposure_time)
     amplitude_label = QLabel("Light Amplitude", self.__features_widget)
     amplitude_edit = QSpinBox(self.__features_widget)
     amplitude_edit.setMaximum(1600)
     amplitude_edit.setMinimum(0)
     amplitude_edit.setSingleStep(1)
     amplitude_edit.setValue(self.dlp_controller.features_amplitude)
     amplitude_edit.valueChanged.connect(
         self.dlp_controller.set_features_amplitude)
     burn_layers_label = QLabel("Burn Layers", self.__features_widget)
     burn_layers_edit = QSpinBox(self.__features_widget)
     burn_layers_edit.setMaximum(1000)
     burn_layers_edit.setMinimum(0)
     burn_layers_edit.setSingleStep(1)
     burn_layers_edit.setValue(self.dlp_controller.features_burn_layers)
     burn_layers_edit.valueChanged.connect(
         self.dlp_controller.set_features_burning_layers)
     burn_exposure_label = QLabel("Burn Exposure", self.__features_widget)
     burn_exposure_edit = QDoubleSpinBox(self.__features_widget)
     burn_exposure_edit.setSuffix(str('ms'))
     burn_exposure_edit.setMaximum(100000)
     burn_exposure_edit.setMinimum(0)
     burn_exposure_edit.setDecimals(1)
     burn_exposure_edit.setSingleStep(0.1)
     burn_exposure_edit.setValue(self.dlp_controller.features_burn_exposure)
     burn_exposure_edit.valueChanged.connect(
         self.dlp_controller.set_features_burning_exposure_time)
     burn_amplitude_label = QLabel("Burn Amplitude", self.__features_widget)
     burn_amplitude_edit = QSpinBox(self.__features_widget)
     burn_amplitude_edit.setMaximum(1600)
     burn_amplitude_edit.setMinimum(0)
     burn_amplitude_edit.setSingleStep(1)
     burn_amplitude_edit.setValue(
         self.dlp_controller.features_burn_amplitude)
     burn_amplitude_edit.valueChanged.connect(
         self.dlp_controller.set_features_burning_amplitude)
     select_layers_button = QPushButton("Select Features Images")
     select_layers_button.clicked.connect(self.load_features_images)
     features_layout = QGridLayout(self.__features_widget)
     features_layout.addWidget(thickness_label, 1, 0)
     features_layout.addWidget(self.features_thickness_edit, 1, 1)
     features_layout.addWidget(exposure_label, 2, 0)
     features_layout.addWidget(exposure_edit, 2, 1)
     features_layout.addWidget(amplitude_label, 3, 0)
     features_layout.addWidget(amplitude_edit, 3, 1)
     features_layout.addWidget(burn_layers_label, 4, 0)
     features_layout.addWidget(burn_layers_edit, 4, 1)
     features_layout.addWidget(burn_exposure_label, 5, 0)
     features_layout.addWidget(burn_exposure_edit, 5, 1)
     features_layout.addWidget(burn_amplitude_label, 6, 0)
     features_layout.addWidget(burn_amplitude_edit, 6, 1)
     features_layout.addWidget(select_layers_button, 7, 0, 1, 2)
     self.__features_widget.setLayout(features_layout)
Beispiel #18
0
    def __init_slicer_options_widget__(self):
        self.__slicer_options_widget = QGroupBox("Slicer Options", self)

        thickness_label = QLabel("Layer Thickness",
                                 self.__slicer_options_widget)
        thickness_edit = QDoubleSpinBox(self.__slicer_options_widget)
        thickness_edit.setSuffix(str('\u03BCm'))
        thickness_edit.setMaximum(1000000)
        thickness_edit.setMinimum(0)
        thickness_edit.setDecimals(3)
        thickness_edit.setSingleStep(0.001)
        thickness_edit.setValue(self.dlp_controller.support_thickness * 1000)
        # self.__opengl_widget.set_slice_thickness(self.dlp_controller.support_thickness)
        thickness_edit.valueChanged.connect(
            self.__slicer_widget.set_slice_thickness)

        pixel_size_label = QLabel("Projector Pixel Size",
                                  self.__slicer_options_widget)
        pixel_size_edit = QDoubleSpinBox(self.__slicer_options_widget)
        pixel_size_edit.setSuffix(str('\u03BCm'))
        pixel_size_edit.setMaximum(1000000)
        pixel_size_edit.setMinimum(0)
        pixel_size_edit.setDecimals(2)
        pixel_size_edit.setSingleStep(0.01)
        pixel_size_edit.setValue(self.dlp_controller.projector_pixel_size *
                                 1000)
        pixel_size_edit.valueChanged.connect(
            self.__slicer_widget.set_pixel_size)

        projector_resolution_label = QLabel("Projector Resolution",
                                            self.__slicer_options_widget)
        projector_resolution_edit_x = QSpinBox(self.__slicer_options_widget)
        projector_resolution_edit_x.setSuffix(str('W'))
        projector_resolution_edit_x.setMaximum(1000000)
        projector_resolution_edit_x.setMinimum(0)
        projector_resolution_edit_x.setValue(
            self.dlp_controller.projector_width)
        projector_resolution_edit_x.valueChanged.connect(
            self.__slicer_widget.set_projector_width)
        projector_resolution_edit_y = QSpinBox(self.__slicer_options_widget)
        projector_resolution_edit_y.setSuffix(str('H'))
        projector_resolution_edit_y.setMaximum(1000000)
        projector_resolution_edit_y.setMinimum(0)
        projector_resolution_edit_y.setValue(
            self.dlp_controller.projector_height)
        projector_resolution_edit_y.valueChanged.connect(
            self.__slicer_widget.set_projector_height)

        samples_per_pixel_label = QLabel("Samples per Pixel",
                                         self.__slicer_options_widget)
        samples_per_pixel_edit = QSpinBox(self.__slicer_options_widget)
        samples_per_pixel_edit.setMaximum(1000000)
        samples_per_pixel_edit.setMinimum(1)
        samples_per_pixel_edit.setValue(self.dlp_controller.samples_per_pixel)
        samples_per_pixel_edit.valueChanged.connect(
            self.__slicer_widget.set_samples_per_pixel)

        slice_geometry_button = QPushButton("Slice Geometry")
        slice_geometry_button.clicked.connect(self.start_slicing_process)

        slice_interrupt_button = QPushButton("Stop Slicing")
        slice_interrupt_button.clicked.connect(
            self.__slicer_widget.interrupt_slicing)

        self.slices_label = QLabel(f'Slicing progress: {0:.0f}/{0:.0f}',
                                   self.__info_widget)

        thickness_label_row = 0
        pixel_size_row = 1
        projector_resolution_row = 2
        samples_per_pixel_row = 3
        slice_button_row = 4
        slices_label_row = 5
        # slice_interrupt_row = slice_button_row

        slice_layout = QGridLayout(self.__slicer_options_widget)
        slice_layout.addWidget(thickness_label, thickness_label_row, 0)
        slice_layout.addWidget(thickness_edit, thickness_label_row, 1)
        slice_layout.addWidget(pixel_size_label, pixel_size_row, 0)
        slice_layout.addWidget(pixel_size_edit, pixel_size_row, 1)
        slice_layout.addWidget(projector_resolution_label,
                               projector_resolution_row, 0)
        slice_layout.addWidget(projector_resolution_edit_x,
                               projector_resolution_row, 1)
        slice_layout.addWidget(projector_resolution_edit_y,
                               projector_resolution_row, 2)
        slice_layout.addWidget(self.slices_label, slice_button_row, 0)
        slice_layout.addWidget(slice_geometry_button, slice_button_row, 1)
        slice_layout.addWidget(slice_interrupt_button, slice_button_row, 2)
        slice_layout.addWidget(samples_per_pixel_label, samples_per_pixel_row,
                               0)
        slice_layout.addWidget(samples_per_pixel_edit, samples_per_pixel_row,
                               1)
        self.__slicer_options_widget.setLayout(slice_layout)
Beispiel #19
0
class DLPSlicerGUI(QWidget):
    def __init__(self, dlp_controller=None, parent=None):
        QWidget.__init__(self, parent)
        if dlp_controller:
            self.dlp_controller = dlp_controller
        self.main_layout = QVBoxLayout()
        self.__init_slicer_widget__()
        self.__init_options_widget__()
        self.__options_widget.setSizePolicy(QSizePolicy.MinimumExpanding,
                                            QSizePolicy.Maximum)
        self.main_layout.addWidget(self.__slicer_widget)
        self.main_layout.addWidget(self.__options_widget)
        self.setLayout(self.main_layout)

    def __init_slicer_widget__(self):
        self.__slicer_widget = DLPSlicer(parent=self,
                                         dlp_controller=self.dlp_controller)
        self.__slicer_widget.update_physical_size.connect(
            self.update_size_label)
        self.__slicer_widget.update_fps.connect(self.update_fps_label)
        self.__slicer_widget.update_slice_counts.connect(
            self.update_slices_label)

    def __init_options_widget__(self):
        self.__options_widget = QWidget(self)
        self.__init_info_widget__()
        self.__init_geometry_widget__()
        self.__init_slicer_options_widget__()
        self.__options_layout = QGridLayout()
        self.__options_layout.addWidget(self.__info_widget, 0, 0, 1, 3)
        self.__options_layout.addWidget(self.__geometry_widget, 1, 0, 1, 2)
        self.__options_layout.addWidget(self.__slicer_options_widget, 1, 2)
        self.__options_widget.setLayout(self.__options_layout)

    def __init_info_widget__(self):
        self.__info_widget = QWidget(self)
        current_geometry_label = QLabel("Selected Geometry:",
                                        self.__info_widget)
        self.current_geometry_index = -1
        self.geometry_list = MyQComboBox(self.__info_widget)
        self.geometry_list.currentIndexChanged.connect(
            self.update_geometry_transformations)
        self.fps_label = QLabel(f'fps: {0:.2f}', self.__info_widget)
        self.physical_size_label = QLabel(
            f'Width: {0:.2f} \u03BCm, Depth: {0:.2f} \u03BCm, Height: {0:.2f} \u03BCm',
            self.__info_widget)
        info_layout = QHBoxLayout()
        info_layout.addWidget(current_geometry_label)
        info_layout.addWidget(self.geometry_list)
        info_layout.addWidget(self.physical_size_label)
        info_layout.addWidget(self.fps_label)
        self.__info_widget.setLayout(info_layout)
        self.__info_widget.setSizePolicy(QSizePolicy.Preferred,
                                         QSizePolicy.Maximum)

    def __init_geometry_widget__(self):
        self.__geometry_widget = QGroupBox("Geometry", self)

        load_geometry_button = QPushButton("Load Geometry")
        load_geometry_button.clicked.connect(self.load_geometry)
        remove_geometry_button = QPushButton("Remove Geometry")
        remove_geometry_button.clicked.connect(self.remove_geometry)
        rotate_x_label = QLabel("Rotate X:", self.__geometry_widget)
        self.rotate_x_slider = QSlider(orientation=Qt.Horizontal,
                                       parent=self.__geometry_widget)
        self.rotate_x_slider.setTickPosition(QSlider.TicksBothSides)
        self.rotate_x_slider.setTickInterval(45)
        self.rotate_x_slider.setRange(-180, 180)
        self.rotate_x_slider.setValue(0)
        self.rotate_x_spin = QSpinBox(self.__geometry_widget)
        self.rotate_x_spin.setMinimum(-180)
        self.rotate_x_spin.setMaximum(180)
        self.rotate_x_spin.setValue(0)
        self.rotate_x_slider.valueChanged.connect(self.rotate_x_spin.setValue)
        self.rotate_x_slider.valueChanged.connect(
            self.__slicer_widget.set_x_rotation)
        self.rotate_x_spin.valueChanged.connect(self.rotate_x_slider.setValue)
        self.rotate_x_spin.valueChanged.connect(
            self.__slicer_widget.set_x_rotation)

        rotate_y_label = QLabel("Rotate Y:", self.__geometry_widget)
        self.rotate_y_slider = QSlider(orientation=Qt.Horizontal,
                                       parent=self.__geometry_widget)
        self.rotate_y_slider.setTickPosition(QSlider.TicksBothSides)
        self.rotate_y_slider.setTickInterval(45)
        self.rotate_y_slider.setRange(-180, 180)
        self.rotate_y_slider.setValue(0)
        self.rotate_y_spin = QSpinBox(self.__geometry_widget)
        self.rotate_y_spin.setMinimum(-180)
        self.rotate_y_spin.setMaximum(180)
        self.rotate_y_spin.setValue(0)
        self.rotate_y_slider.valueChanged.connect(self.rotate_y_spin.setValue)
        self.rotate_y_slider.valueChanged.connect(
            self.__slicer_widget.set_y_rotation)
        self.rotate_y_spin.valueChanged.connect(self.rotate_y_slider.setValue)
        self.rotate_y_spin.valueChanged.connect(
            self.__slicer_widget.set_y_rotation)

        rotate_z_label = QLabel("Rotate Z:", self.__geometry_widget)
        self.rotate_z_slider = QSlider(orientation=Qt.Horizontal,
                                       parent=self.__geometry_widget)
        self.rotate_z_slider.setTickPosition(QSlider.TicksBothSides)
        self.rotate_z_slider.setTickInterval(45)
        self.rotate_z_slider.setRange(-180, 180)
        self.rotate_z_slider.setValue(0)
        self.rotate_z_spin = QSpinBox(self.__geometry_widget)
        self.rotate_z_spin.setMinimum(-180)
        self.rotate_z_spin.setMaximum(180)
        self.rotate_z_spin.setValue(0)
        self.rotate_z_slider.valueChanged.connect(self.rotate_z_spin.setValue)
        self.rotate_z_slider.valueChanged.connect(
            self.__slicer_widget.set_z_rotation)
        self.rotate_z_spin.valueChanged.connect(self.rotate_z_slider.setValue)
        self.rotate_z_spin.valueChanged.connect(
            self.__slicer_widget.set_z_rotation)

        translate_x_label = QLabel("Translate X:", self.__geometry_widget)
        self.translate_x_slider = QSlider(orientation=Qt.Horizontal,
                                          parent=self.__geometry_widget)
        self.translate_x_slider.setTickPosition(QSlider.TicksBothSides)
        self.translate_x_slider.setTickInterval(
            self.dlp_controller.projector_pixel_size)
        self.translate_x_slider.setRange(
            -self.dlp_controller.projector_width *
            self.dlp_controller.projector_pixel_size * 0.5,
            self.dlp_controller.projector_width *
            self.dlp_controller.projector_pixel_size * 0.5)
        self.translate_x_slider.setValue(0)
        self.translate_x_spin = QDoubleSpinBox(self.__geometry_widget)
        self.translate_x_spin.setMinimum(
            -self.dlp_controller.projector_width *
            self.dlp_controller.projector_pixel_size * 0.5)
        self.translate_x_spin.setMaximum(
            self.dlp_controller.projector_width *
            self.dlp_controller.projector_pixel_size * 0.5)
        self.translate_x_spin.setSingleStep(
            self.dlp_controller.projector_pixel_size)
        self.translate_x_spin.setValue(0)
        self.translate_x_slider.valueChanged.connect(
            self.translate_x_spin.setValue)
        self.translate_x_slider.valueChanged.connect(
            self.__slicer_widget.set_x_pos)
        self.translate_x_spin.valueChanged.connect(
            self.translate_x_slider.setValue)
        self.translate_x_spin.valueChanged.connect(
            self.__slicer_widget.set_x_pos)

        translate_z_label = QLabel("Translate Z:", self.__geometry_widget)
        self.translate_z_slider = QSlider(orientation=Qt.Horizontal,
                                          parent=self.__geometry_widget)
        self.translate_z_slider.setTickPosition(QSlider.TicksBothSides)
        self.translate_z_slider.setTickInterval(
            self.dlp_controller.projector_pixel_size)
        self.translate_z_slider.setRange(
            -self.dlp_controller.projector_height *
            self.dlp_controller.projector_pixel_size * 0.5,
            self.dlp_controller.projector_height *
            self.dlp_controller.projector_pixel_size * 0.5)
        self.translate_z_slider.setValue(0)
        self.translate_z_spin = QDoubleSpinBox(self.__geometry_widget)
        self.translate_z_spin.setMinimum(
            -self.dlp_controller.projector_height *
            self.dlp_controller.projector_pixel_size * 0.5)
        self.translate_z_spin.setMaximum(
            self.dlp_controller.projector_height *
            self.dlp_controller.projector_pixel_size * 0.5)
        self.translate_z_spin.setSingleStep(
            self.dlp_controller.projector_pixel_size)
        self.translate_z_spin.setValue(0)
        self.translate_z_slider.valueChanged.connect(
            self.translate_z_spin.setValue)
        self.translate_z_slider.valueChanged.connect(
            self.__slicer_widget.set_z_pos)
        self.translate_z_spin.valueChanged.connect(
            self.translate_z_slider.setValue)
        self.translate_z_spin.valueChanged.connect(
            self.__slicer_widget.set_z_pos)

        scale_x_label = QLabel("Scale X:", self.__geometry_widget)
        self.scale_x_spin = QDoubleSpinBox(self.__geometry_widget)
        self.scale_x_spin.setMinimum(-1000)
        self.scale_x_spin.setMaximum(1000)
        self.scale_x_spin.setDecimals(2)
        self.scale_x_spin.setValue(1)
        self.scale_x_spin.setSingleStep(0.01)
        self.scale_x_spin.setObjectName("scale_x_spin")
        self.scale_x_spin.valueChanged.connect(self.set_scaling)

        scale_y_label = QLabel("Scale Y:", self.__geometry_widget)
        self.scale_y_spin = QDoubleSpinBox(self.__geometry_widget)
        self.scale_y_spin.setMinimum(-1000)
        self.scale_y_spin.setMaximum(1000)
        self.scale_y_spin.setDecimals(2)
        self.scale_y_spin.setValue(1)
        self.scale_y_spin.setSingleStep(0.01)
        self.scale_y_spin.setObjectName("scale_y_spin")
        self.scale_y_spin.valueChanged.connect(self.set_scaling)

        scale_z_label = QLabel("Scale Z:", self.__geometry_widget)
        self.scale_z_spin = QDoubleSpinBox(self.__geometry_widget)
        self.scale_z_spin.setMinimum(-1000)
        self.scale_z_spin.setMaximum(1000)
        self.scale_z_spin.setDecimals(2)
        self.scale_z_spin.setValue(1)
        self.scale_z_spin.setSingleStep(0.01)
        self.scale_z_spin.setObjectName("scale_z_spin")
        self.scale_z_spin.valueChanged.connect(self.set_scaling)

        self.uniform_scaling = QCheckBox("Uniform Scaling",
                                         self.__geometry_widget)
        self.uniform_scaling.setChecked(True)
        # self.uniform_scaling.setLayoutDirection(Qt.RightToLeft)

        list_of_measures = ('\u03BCm', 'mm', 'cm', 'dm', 'm')
        self.list_of_measures_coefficients = [0.001, 1, 10, 100, 1000]
        unit_of_measure_label = QLabel("Unit of Measure",
                                       self.__geometry_widget)
        self.unit_of_measure_combo = QComboBox(self.__geometry_widget)
        for measure in list_of_measures:
            self.unit_of_measure_combo.addItem(measure)
        self.unit_of_measure_combo.setCurrentIndex(1)
        self.unit_of_measure_combo.currentIndexChanged.connect(
            self.update_unit_of_measure)

        rotate_x_row = 0
        rotate_y_row = rotate_x_row + 1
        rotate_z_row = rotate_y_row + 1
        translate_x_row = rotate_z_row + 1
        translate_z_row = translate_x_row + 1
        scale_x_row = translate_z_row + 1
        scale_y_row = scale_x_row + 1
        scale_z_row = scale_y_row + 1
        uniform_scaling_row = scale_x_row
        unit_of_measure_row = scale_y_row
        load_geometry_row = scale_z_row + 1
        remove_geometry_row = load_geometry_row

        geometry_layout = QGridLayout(self.__geometry_widget)
        geometry_layout.addWidget(load_geometry_button, load_geometry_row, 1,
                                  1, 2)
        geometry_layout.addWidget(remove_geometry_button, remove_geometry_row,
                                  3, 1, 2)
        geometry_layout.addWidget(rotate_x_label, rotate_x_row, 0)
        geometry_layout.addWidget(self.rotate_x_slider, rotate_x_row, 1, 1, 4)
        geometry_layout.addWidget(self.rotate_x_spin, rotate_x_row, 5)
        geometry_layout.addWidget(rotate_y_label, rotate_y_row, 0)
        geometry_layout.addWidget(self.rotate_y_slider, rotate_y_row, 1, 1, 4)
        geometry_layout.addWidget(self.rotate_y_spin, rotate_y_row, 5)
        geometry_layout.addWidget(rotate_z_label, rotate_z_row, 0)
        geometry_layout.addWidget(self.rotate_z_slider, rotate_z_row, 1, 1, 4)
        geometry_layout.addWidget(self.rotate_z_spin, rotate_z_row, 5)
        geometry_layout.addWidget(translate_x_label, translate_x_row, 0)
        geometry_layout.addWidget(self.translate_x_slider, translate_x_row, 1,
                                  1, 4)
        geometry_layout.addWidget(self.translate_x_spin, translate_x_row, 5)
        geometry_layout.addWidget(translate_z_label, translate_z_row, 0)
        geometry_layout.addWidget(self.translate_z_slider, translate_z_row, 1,
                                  1, 4)
        geometry_layout.addWidget(self.translate_z_spin, translate_z_row, 5)
        geometry_layout.addWidget(scale_x_label, scale_x_row, 0)
        geometry_layout.addWidget(self.scale_x_spin, scale_x_row, 1)
        geometry_layout.addWidget(scale_y_label, scale_y_row, 0)
        geometry_layout.addWidget(self.scale_y_spin, scale_y_row, 1)
        geometry_layout.addWidget(scale_z_label, scale_z_row, 0)
        geometry_layout.addWidget(self.scale_z_spin, scale_z_row, 1)
        geometry_layout.addWidget(self.uniform_scaling, uniform_scaling_row, 3,
                                  1, 2)
        geometry_layout.addWidget(unit_of_measure_label, unit_of_measure_row,
                                  4)
        geometry_layout.addWidget(self.unit_of_measure_combo,
                                  unit_of_measure_row, 3)
        self.__geometry_widget.setLayout(geometry_layout)

    def __init_slicer_options_widget__(self):
        self.__slicer_options_widget = QGroupBox("Slicer Options", self)

        thickness_label = QLabel("Layer Thickness",
                                 self.__slicer_options_widget)
        thickness_edit = QDoubleSpinBox(self.__slicer_options_widget)
        thickness_edit.setSuffix(str('\u03BCm'))
        thickness_edit.setMaximum(1000000)
        thickness_edit.setMinimum(0)
        thickness_edit.setDecimals(3)
        thickness_edit.setSingleStep(0.001)
        thickness_edit.setValue(self.dlp_controller.support_thickness * 1000)
        # self.__opengl_widget.set_slice_thickness(self.dlp_controller.support_thickness)
        thickness_edit.valueChanged.connect(
            self.__slicer_widget.set_slice_thickness)

        pixel_size_label = QLabel("Projector Pixel Size",
                                  self.__slicer_options_widget)
        pixel_size_edit = QDoubleSpinBox(self.__slicer_options_widget)
        pixel_size_edit.setSuffix(str('\u03BCm'))
        pixel_size_edit.setMaximum(1000000)
        pixel_size_edit.setMinimum(0)
        pixel_size_edit.setDecimals(2)
        pixel_size_edit.setSingleStep(0.01)
        pixel_size_edit.setValue(self.dlp_controller.projector_pixel_size *
                                 1000)
        pixel_size_edit.valueChanged.connect(
            self.__slicer_widget.set_pixel_size)

        projector_resolution_label = QLabel("Projector Resolution",
                                            self.__slicer_options_widget)
        projector_resolution_edit_x = QSpinBox(self.__slicer_options_widget)
        projector_resolution_edit_x.setSuffix(str('W'))
        projector_resolution_edit_x.setMaximum(1000000)
        projector_resolution_edit_x.setMinimum(0)
        projector_resolution_edit_x.setValue(
            self.dlp_controller.projector_width)
        projector_resolution_edit_x.valueChanged.connect(
            self.__slicer_widget.set_projector_width)
        projector_resolution_edit_y = QSpinBox(self.__slicer_options_widget)
        projector_resolution_edit_y.setSuffix(str('H'))
        projector_resolution_edit_y.setMaximum(1000000)
        projector_resolution_edit_y.setMinimum(0)
        projector_resolution_edit_y.setValue(
            self.dlp_controller.projector_height)
        projector_resolution_edit_y.valueChanged.connect(
            self.__slicer_widget.set_projector_height)

        samples_per_pixel_label = QLabel("Samples per Pixel",
                                         self.__slicer_options_widget)
        samples_per_pixel_edit = QSpinBox(self.__slicer_options_widget)
        samples_per_pixel_edit.setMaximum(1000000)
        samples_per_pixel_edit.setMinimum(1)
        samples_per_pixel_edit.setValue(self.dlp_controller.samples_per_pixel)
        samples_per_pixel_edit.valueChanged.connect(
            self.__slicer_widget.set_samples_per_pixel)

        slice_geometry_button = QPushButton("Slice Geometry")
        slice_geometry_button.clicked.connect(self.start_slicing_process)

        slice_interrupt_button = QPushButton("Stop Slicing")
        slice_interrupt_button.clicked.connect(
            self.__slicer_widget.interrupt_slicing)

        self.slices_label = QLabel(f'Slicing progress: {0:.0f}/{0:.0f}',
                                   self.__info_widget)

        thickness_label_row = 0
        pixel_size_row = 1
        projector_resolution_row = 2
        samples_per_pixel_row = 3
        slice_button_row = 4
        slices_label_row = 5
        # slice_interrupt_row = slice_button_row

        slice_layout = QGridLayout(self.__slicer_options_widget)
        slice_layout.addWidget(thickness_label, thickness_label_row, 0)
        slice_layout.addWidget(thickness_edit, thickness_label_row, 1)
        slice_layout.addWidget(pixel_size_label, pixel_size_row, 0)
        slice_layout.addWidget(pixel_size_edit, pixel_size_row, 1)
        slice_layout.addWidget(projector_resolution_label,
                               projector_resolution_row, 0)
        slice_layout.addWidget(projector_resolution_edit_x,
                               projector_resolution_row, 1)
        slice_layout.addWidget(projector_resolution_edit_y,
                               projector_resolution_row, 2)
        slice_layout.addWidget(self.slices_label, slice_button_row, 0)
        slice_layout.addWidget(slice_geometry_button, slice_button_row, 1)
        slice_layout.addWidget(slice_interrupt_button, slice_button_row, 2)
        slice_layout.addWidget(samples_per_pixel_label, samples_per_pixel_row,
                               0)
        slice_layout.addWidget(samples_per_pixel_edit, samples_per_pixel_row,
                               1)
        self.__slicer_options_widget.setLayout(slice_layout)

    @Slot(float)
    def set_scaling(self, value):
        if self.uniform_scaling.isChecked():
            self.__slicer_widget.set_x_scale(value)
            self.__slicer_widget.set_y_scale(value)
            self.__slicer_widget.set_z_scale(value)
            self.scale_x_spin.setValue(value)
            self.scale_y_spin.setValue(value)
            self.scale_z_spin.setValue(value)
        else:
            self.__slicer_widget.set_x_scale(self.scale_x_spin.value())
            self.__slicer_widget.set_y_scale(self.scale_y_spin.value())
            self.__slicer_widget.set_z_scale(self.scale_z_spin.value())

    @Slot()
    def load_geometry(self):
        file_names = QFileDialog.getOpenFileNames(caption='Select Geometry',
                                                  dir='../',
                                                  filter="Files (*.obj *.stl)",
                                                  parent=self)
        loading_dialog = QMessageBox()
        loading_dialog.setText("Loading Geometry...")
        loading_dialog.setWindowTitle("AMLab Software")
        loading_dialog.setStandardButtons(QMessageBox.NoButton)
        loading_dialog.open()
        QGuiApplication.processEvents()
        swapyz = True
        for file_name in file_names[0]:
            if self.__slicer_widget.load_geometry(file_name, swapyz):
                self.geometry_list.addItem(
                    self.__slicer_widget.geometry_name_list[
                        self.__slicer_widget.geometries_loaded - 1])
                self.geometry_list.setCurrentIndex(self.geometry_list.count() -
                                                   1)
        QGuiApplication.processEvents()

    @Slot()
    def remove_geometry(self):
        self.__slicer_widget.remove_geometry()
        self.geometry_list.removeItem(self.geometry_list.currentIndex())

    @Slot()
    def start_slicing_process(self):
        directory_name = QFileDialog.getExistingDirectory(
            caption='Select Directory', dir='../', parent=self)
        if len(directory_name) > 0:
            self.__slicer_widget.prepare_for_slicing(directory=directory_name)

    @Slot(float, float, float)
    def update_size_label(self, width, depth, height):
        self.physical_size_label.setText(
            f'Width: {width:.3f} mm, Depth: {depth:.3f} mm, Height: {height:.3f} mm'
        )

    @Slot(float)
    def update_fps_label(self, fps):
        self.fps_label.setText(f'fps: {fps:.2f}')

    @Slot(float, float)
    def update_slices_label(self, slice, total_slices):
        self.slices_label.setText(
            f'Slicing progress: {slice:.0f}/{total_slices:.0f}')

    @Slot(int)
    def update_unit_of_measure(self, index):
        self.__slicer_widget.set_unit_of_measurement(
            self.list_of_measures_coefficients[index])

    @Slot(int)
    def update_geometry_transformations(self, index):
        if index >= 0:
            self.__slicer_widget.current_geometry_idx = index
            x_rot = self.__slicer_widget.get_x_rot()
            y_rot = self.__slicer_widget.get_y_rot()
            z_rot = self.__slicer_widget.get_z_rot()
            x_scale = self.__slicer_widget.get_x_scale()
            y_scale = self.__slicer_widget.get_y_scale()
            z_scale = self.__slicer_widget.get_z_scale()
            x_pos = self.__slicer_widget.get_x_pos()
            z_pos = self.__slicer_widget.get_z_pos()
            unit_measurement = self.__slicer_widget.get_unit_of_measurement()
            self.__geometry_widget.blockSignals(True)
            self.rotate_x_slider.setValue(x_rot)
            self.rotate_x_spin.setValue(x_rot)
            self.rotate_y_slider.setValue(y_rot)
            self.rotate_y_spin.setValue(y_rot)
            self.rotate_z_slider.setValue(z_rot)
            self.rotate_z_spin.setValue(z_rot)
            self.translate_x_slider.setValue(x_pos)
            self.translate_x_spin.setValue(x_pos)
            self.translate_z_slider.setValue(z_pos)
            self.translate_z_spin.setValue(z_pos)
            self.scale_x_spin.setValue(x_scale)
            self.scale_y_spin.setValue(y_scale)
            self.scale_z_spin.setValue(z_scale)
            self.unit_of_measure_combo.setCurrentIndex(
                self.list_of_measures_coefficients.index(unit_measurement))
            self.__geometry_widget.blockSignals(False)
class GeneralView(QWidget):
    """Config widget for general properties of an experiment.
    This "view" does not have a model. Instead, it is a part of a bigger view called ExperimentView, and gets updated
    with it.
    """

    inlet_type_export_values = {
        "LSL stream": "lsl",
        "LSL file stream": "lsl_from_file",
        "LSL generator": "lsl_generator",
        "Field trip buffer": "ftbuffer"
    }
    inlet_type_import_values = {
        v: k
        for k, v in inlet_type_export_values.items()
    }

    def __init__(self, parent=None):
        super().__init__(parent=parent)
        layout = QFormLayout()
        self.setLayout(layout)

        self.name = QLineEdit()

        # prefilter_lower_bound ---------------------------------------------------------------------------------------------
        self.prefilter_lower_bound_enable = QCheckBox()
        self.prefilter_lower_bound_enable.stateChanged.connect(self._adjust)

        self.prefilter_lower_bound = QDoubleSpinBox()
        self.prefilter_lower_bound.setEnabled(False)
        self.prefilter_lower_bound.valueChanged.connect(self._adjust)
        self.prefilter_lower_bound.setMinimum(0)
        self.prefilter_lower_bound.setMaximum(0)  # TODO: add proper value
        self.prefilter_lower_bound.setValue(0)  # TODO: add proper value

        prefilter_lower_bound_widget = QWidget()
        prefilter_lower_bound_widget.setContentsMargins(0, 0, 0, 0)
        prefilter_lower_bound_widget.setLayout(QHBoxLayout())
        prefilter_lower_bound_widget.layout().setContentsMargins(0, 0, 0, 0)
        prefilter_lower_bound_widget.layout().addWidget(
            self.prefilter_lower_bound_enable)
        prefilter_lower_bound_widget.layout().addWidget(
            self.prefilter_lower_bound)

        # prefilter_upper_bound --------------------------------------------------------------------------------------------
        self.prefilter_upper_bound_enable = QCheckBox()
        self.prefilter_upper_bound_enable.stateChanged.connect(self._adjust)

        self.prefilter_upper_bound = QDoubleSpinBox()
        self.prefilter_upper_bound.setEnabled(False)
        self.prefilter_upper_bound.valueChanged.connect(self._adjust)
        self.prefilter_upper_bound.setMinimum(
            self.prefilter_lower_bound.value())
        self.prefilter_upper_bound.setMaximum(10000)  # TODO: add proper value
        self.prefilter_upper_bound.setValue(0)  # TODO: add proper value

        prefilter_upper_bound_widget = QWidget()
        prefilter_upper_bound_widget.setContentsMargins(0, 0, 0, 0)
        prefilter_upper_bound_widget.setLayout(QHBoxLayout())
        prefilter_upper_bound_widget.layout().setContentsMargins(0, 0, 0, 0)
        prefilter_upper_bound_widget.layout().addWidget(
            self.prefilter_upper_bound_enable)
        prefilter_upper_bound_widget.layout().addWidget(
            self.prefilter_upper_bound)

        # Inlet selection ----------------------------------------------------------------------------------------------
        self.inlet_type = QComboBox()
        self.inlet_type.addItem("LSL stream")
        self.inlet_type.addItem("LSL file stream")
        self.inlet_type.addItem("LSL generator")
        self.inlet_type.addItem("Field trip buffer")

        self.lsl_stream_name = QComboBox()
        self.lsl_stream_name.addItem("NVX136_Data")
        self.lsl_stream_name.addItem("Mitsar")

        self.lsl_filename = PathEdit()
        dialog = QFileDialog(self, "Open")
        dialog.setFileMode(dialog.AnyFile)
        self.lsl_filename.setDialog(dialog)
        self.hostname_port = QLineEdit("localhost:1972")

        self.inlet_params = StackedDictWidget()
        self.inlet_params.setMaximumHeight(25)
        self.inlet_params.addWidget("LSL stream", self.lsl_stream_name)
        self.inlet_params.addWidget("LSL file stream", self.lsl_filename)
        self.inlet_params.addWidget("LSL generator", QWidget())
        self.inlet_params.addWidget("Field trip buffer", self.hostname_port)
        # TODO: LSL generator is not reflected in the exported file, even when selected.

        self.inlet_type.currentTextChanged.connect(
            self.inlet_params.setCurrentKey)

        self.inlet_config = QWidget()
        self.inlet_config.setContentsMargins(0, 0, 0, 0)
        inlet_layout = QHBoxLayout()
        inlet_layout.setContentsMargins(0, 0, 0, 0)
        inlet_layout.addWidget(self.inlet_type)
        inlet_layout.addWidget(self.inlet_params)
        self.inlet_config.setLayout(inlet_layout)

        # --------------------------------------------------------------------------------------------------------------
        self.name = QLineEdit("Experiment")
        self.dc = QCheckBox()

        self.plot_raw = QCheckBox()
        self.plot_raw.setChecked(True)
        self.plot_signals = QCheckBox()
        self.plot_signals.setChecked(True)

        self.discard_channels = QLineEdit()
        self.reference_sub = QLineEdit()
        self.show_photo_rectangle = QCheckBox()
        self.show_notch_filters = QCheckBox()

        self.reward_refractory_period = QDoubleSpinBox()
        self.reward_refractory_period.setRange(0.1, 10)
        self.reward_refractory_period.setValue(0.25)
        self.reward_refractory_period.setSuffix(" s")

        # Adding properties to the widget ------------------------------------------------------------------------------
        layout.addRow("Name", self.name)
        layout.addRow("Inlet", self.inlet_config)
        layout.addRow("Enable DC blocker", self.dc)
        layout.addRow("Prefilter band (lower bound)",
                      prefilter_lower_bound_widget)
        layout.addRow("Prefilter band (upper bound)",
                      prefilter_upper_bound_widget)
        layout.addRow("Plot raw", self.plot_raw)
        layout.addRow("Plot signals", self.plot_signals)
        layout.addRow("Discard channels", self.discard_channels)
        layout.addRow("Reference sub", self.reference_sub)
        layout.addRow("Show photo-sensor rectangle", self.show_photo_rectangle)
        layout.addRow("Show notch filters", self.show_notch_filters)
        layout.addRow("Reward refractory period",
                      self.reward_refractory_period)

    def updateModel(self, ex, /):
        ex.name = self.name.text()
        ex.inlet = self.inlet_type_export_values[self.inlet_type.currentText()]
        ex.lsl_stream_name = self.lsl_stream_name.currentText()
        ex.raw_data_path = self.lsl_filename.text()
        ex.hostname_port = self.hostname_port.text()
        ex.dc = self.dc.isChecked()

        if self.prefilter_lower_bound_enable.isChecked():
            prefilter_lower_bound = self.prefilter_lower_bound.value()
        else:
            prefilter_lower_bound = None

        if self.prefilter_upper_bound_enable.isChecked():
            prefilter_upper_bound = self.prefilter_upper_bound.value()
        else:
            prefilter_upper_bound = None

        ex.prefilter_band = (prefilter_lower_bound, prefilter_upper_bound)
        ex.plot_raw = self.plot_raw.isChecked()
        ex.plot_signals = self.plot_signals.isChecked()
        ex.discard_channels = self.discard_channels.text()
        ex.reference_sub = self.reference_sub.text()
        ex.show_photo_rectangle = self.show_photo_rectangle.isChecked()
        ex.show_notch_filters = self.show_notch_filters.isChecked()
        ex.reward_refractory_period = self.reward_refractory_period.value()

    def _adjust(self):
        if self.prefilter_lower_bound_enable.isChecked():
            self.prefilter_lower_bound.setEnabled(True)
            self.prefilter_upper_bound.setMinimum(
                self.prefilter_lower_bound.value())
        else:
            self.prefilter_lower_bound.setEnabled(False)
            self.prefilter_upper_bound.setMinimum(0)

        if self.prefilter_upper_bound_enable.isChecked():
            self.prefilter_upper_bound.setEnabled(True)
            self.prefilter_lower_bound.setMaximum(
                self.prefilter_upper_bound.value())
        else:
            self.prefilter_upper_bound.setEnabled(False)
            self.prefilter_lower_bound.setMaximum(
                10000)  # TODO: add proper value
Beispiel #21
0
class NifBatchTools(MainWindow):
    def __init__(self):
        super().__init__("HTools - NifBatchTools")
        log.info("Opening NifBatchTools window")

        self.source_folder = CONFIG.get("DEFAULT", "SourceFolder")
        self.keywords = list(
            map(lambda x: x.encode("ascii"),
                CONFIG.get("NIF", "keywords").replace(" ", "").split(",")))
        self.nif_files = set(
        )  # improve performance (better to check in a set rather than in a QListWidget
        self.ignored_nif_files = set(
        )  # improve performance (better to check in a set rather than in a QListWidget
        self.setSize(QSize(700, 600))
        self.processed_files = itertools.count()

        log.info("Source folder  : " + self.source_folder)
        log.info("Keywords       : " + str(self.keywords))

        self.init_ui()

    def init_ui(self):
        main_splitter = QSplitter(self, Qt.Horizontal)

        left_pane = QWidget(self)
        left_pane.setMaximumWidth(370)
        left_v_box = QVBoxLayout(self)
        left_pane.setLayout(left_v_box)

        right_pane = QWidget(self)
        right_v_box = QVBoxLayout(self)
        right_pane.setLayout(right_v_box)

        main_splitter.addWidget(left_pane)
        main_splitter.addWidget(right_pane)

        # ===== Details =====
        self.group_box_details = QuickyGui.create_group_box(self, "Details")

        nif_files_loaded = QuickyGui.create_label(self, ".nif files loaded")
        self.lcd_nif_files_loaded = QuickyGui.create_lcd(self)

        nif_files_ignored = QuickyGui.create_label(self, ".nif files ignored")
        self.lcd_nif_files_ignored = QuickyGui.create_lcd(self)

        self.nif_files_list_widget = NifList(self)
        self.ignored_nif_files_list_widget = NifList(self)
        self.update_nif_files()

        self.group_box_legends = QuickyGui.create_group_box(self, "Legends")
        instructions_4 = QuickyGui.create_label(
            self, "Green - File correctly processed\n")
        instructions_4.setStyleSheet(
            "QLabel { color : darkGreen; font-weight : bold }")
        instructions_5 = QuickyGui.create_label(self,
                                                "Blue - File is processing\n")
        instructions_5.setStyleSheet(
            "QLabel { color : darkBlue; font-weight : bold }")
        instructions_6 = QuickyGui.create_label(
            self, "Red - File ignored/with errors.")
        instructions_6.setStyleSheet(
            "QLabel { color : darkRed; font-weight : bold }")
        instructions_7 = QuickyGui.create_label(
            self, "Reasons : "
            "\n * Check log to see if there is an error concerning this file, or try to open it with NifSkope"
            "\n * Otherwise it couldn't find a NiTriShape block whose name is specified in provided keywords. It may be normal, if there is no body part. But if there is and you want this file to be processed by the tool, then you must add the corresponding NiTriShape block's name (use nikskope to find it) to the list of keywords, located in the .ini file, situated alongside the executable."
            " If you have Nikskope, you can open the file by double-clicking on in, in the list view, or from your explorer. Restart the tool to load the new .ini file."
        )
        instructions_7.setStyleSheet("QLabel { color : darkRed}")

        vbox = QVBoxLayout()
        vbox.setSpacing(5)
        vbox.addWidget(instructions_4)
        vbox.addWidget(instructions_5)
        vbox.addWidget(instructions_6)
        vbox.addWidget(instructions_7)

        self.group_box_legends.setLayout(vbox)

        vbox = QVBoxLayout(self)

        hbox = QHBoxLayout(self)
        hbox.addWidget(nif_files_loaded)
        hbox.addWidget(self.lcd_nif_files_loaded)

        vbox.addItem(hbox)
        vbox.addWidget(self.nif_files_list_widget)

        hbox = QHBoxLayout(self)
        hbox.addWidget(nif_files_ignored)
        hbox.addWidget(self.lcd_nif_files_ignored)
        vbox.addItem(hbox)

        vbox.addWidget(self.ignored_nif_files_list_widget)
        vbox.addWidget(self.group_box_legends)

        self.group_box_details.setLayout(vbox)
        right_v_box.addWidget(self.group_box_details)

        # ===== STEP 0 - Instructions =====
        self.group_box_instructions = QuickyGui.create_group_box(
            self, "Instructions")

        instructions_1 = QuickyGui.create_label(
            self,
            "I. By clicking on \"Scan Folder\", all .nif contained in this folder (subfolders and so on), will be added to the set of files to be processed. You can scan multiple folder, by clicking again. All files not already present will be added."
        )
        instructions_2 = QuickyGui.create_label(
            self,
            "II. Once your desired parameters are set, click on \"Apply\". Bewary, the process is quite slow (just opening the file is quite consuming somehow)"
        )

        vbox = QVBoxLayout()
        vbox.setSpacing(5)
        vbox.addWidget(instructions_1)
        vbox.addWidget(instructions_2)

        self.group_box_instructions.setLayout(vbox)
        left_v_box.addWidget(self.group_box_instructions)

        # ===== STEP I - Load Files =====
        self.group_box_load_files = QuickyGui.create_group_box(
            self, "STEP I - Load .nif files")

        button_load_files = QuickyGui.create_button(self, "Scan Folder",
                                                    self.action_load_files)
        button_clear_files = QuickyGui.create_button(self,
                                                     "Clear loaded files",
                                                     self.action_clear_files)

        hbox = QHBoxLayout()
        hbox.addWidget(button_load_files)
        hbox.addWidget(button_clear_files)

        self.group_box_load_files.setLayout(hbox)
        left_v_box.addWidget(self.group_box_load_files)

        # ===== STEP II - Set parameters =====
        self.group_box_parameters = QuickyGui.create_group_box(
            self, "STEP II - Set parameters")

        vbox = QVBoxLayout()

        # Glossiness
        label_glossiness = QuickyGui.create_label(self, "Glossiness")
        self.spin_box_glossiness = QDoubleSpinBox()
        self.spin_box_glossiness.setMinimum(0)
        self.spin_box_glossiness.setMaximum(1000)
        self.spin_box_glossiness.setValue(CONFIG.getfloat("NIF", "Glossiness"))
        log.info("Glossiness target : " +
                 str(self.spin_box_glossiness.value()))

        hbox = QHBoxLayout()
        hbox.addWidget(label_glossiness)
        hbox.addWidget(self.spin_box_glossiness)
        vbox.addItem(hbox)

        # Specular Strength
        label_specular_strength = QuickyGui.create_label(
            self, "Specular Strength")
        self.spin_box_specular_strength = QDoubleSpinBox()
        self.spin_box_specular_strength.setMinimum(0)
        self.spin_box_specular_strength.setMaximum(1000)
        self.spin_box_specular_strength.setValue(
            CONFIG.getfloat("NIF", "SpecularStrength"))
        log.info("Specular Strength target : " +
                 str(self.spin_box_specular_strength.value()))

        hbox = QHBoxLayout()
        hbox.addWidget(label_specular_strength)
        hbox.addWidget(self.spin_box_specular_strength)
        vbox.addItem(hbox)

        self.group_box_parameters.setLayout(vbox)
        left_v_box.addWidget(self.group_box_parameters)

        # ===== STEP III - Apply =====
        self.group_box_apply = QuickyGui.create_group_box(
            self, "STEP III - Apply")

        button_load_files = QuickyGui.create_button(self, "Apply",
                                                    self.action_apply)

        hbox = QHBoxLayout()
        hbox.addWidget(button_load_files)

        self.group_box_apply.setLayout(hbox)
        left_v_box.addWidget(self.group_box_apply)

        # ===== Finalizing =====
        self.progress_bar = QProgressBar(self)
        left_v_box.addWidget(self.progress_bar)

        left_v_box.setSpacing(10)
        self.mainLayout.addWidget(main_splitter)

    def toggle(self, value):
        self.group_box_parameters.setEnabled(value)
        self.group_box_load_files.setEnabled(value)
        self.group_box_apply.setEnabled(value)

    def update_nif_files(self, value=0):
        self.lcd_nif_files_loaded.display(self.nif_files_list_widget.count())
        self.lcd_nif_files_ignored.display(
            self.ignored_nif_files_list_widget.count())
        self.nif_files_list_widget.sortItems()
        self.ignored_nif_files_list_widget.sortItems()

    def finish_action(self):
        self.progress_bar.setMinimum(0)
        self.progress_bar.setMaximum(max(1,
                                         self.nif_files_list_widget.count()))
        self.progress_bar.setValue(self.nif_files_list_widget.count())
        self.toggle(True)
        log.info("Done !")

    def finish_load_action(self, result):
        self.finish_action()
        QMessageBox.information(
            self, "Results",
            "Done !\n\n" + str(self.nif_files_list_widget.count()) +
            " .nif file(s) loaded.\n" + str(result) + " .nif files ignored.")

    def start_apply_action(self, index):
        item = self.nif_files_list_widget.item(index)
        item.setForeground(Qt.blue)

    def result_apply_action(self, index, result):
        item = self.nif_files_list_widget.item(index)
        if result:
            item.setForeground(Qt.darkGreen)
        else:
            item.setForeground(Qt.darkRed)
        self.progress_bar.setValue(next(self.processed_files) + 1)

    def finish_apply_action(self):
        if self.progress_bar.value() == self.nif_files_list_widget.count():
            self.finish_action()
            QMessageBox.information(
                self, "Results", "Done !\n\n" +
                str(self.progress_bar.value()) + " .nif file(s) loaded.\n")

    def action_clear_files(self):
        log.info("Clearing loaded .nif files ...")
        self.nif_files_list_widget.clear()
        self.ignored_nif_files_list_widget.clear()
        self.nif_files.clear()
        self.ignored_nif_files.clear()
        self.update_nif_files()
        self.progress_bar.reset()

    def action_load_files(self):
        log.info("Loading .nif files ...")
        self.toggle(False)
        self.progress_bar.setMinimum(0)
        self.progress_bar.setMaximum(0)

        file_dialog = QFileDialog()
        file_dialog.setFileMode(QFileDialog.DirectoryOnly)
        file_dialog.setDirectory(self.source_folder)

        if file_dialog.exec_():
            scan_dirs = file_dialog.selectedFiles()
            if len(scan_dirs) >= 1:
                self.source_folder = scan_dirs[0]
            else:
                self.source_folder = file_dialog.directory()

        if self.source_folder:
            log.info("Scanning directory : " + self.source_folder)
            CONFIG.set("DEFAULT", "SourceFolder", self.source_folder),
            save_config()

        worker = Worker(self.load_files)
        worker.signals.progress.connect(self.update_nif_files)
        worker.signals.result.connect(self.finish_load_action)

        QThreadPool.globalInstance().start(worker)

    def load_files(self, progress_callback):
        """
        Traverse folder to find .nif files
        """
        ignored_files = 0
        for root, dirs, files in os.walk(self.source_folder):
            for file in files:
                path = root + "/" + file
                if file.endswith(
                        ".nif"
                ) and path not in self.nif_files and path not in self.ignored_nif_files:
                    stream = open(path, "rb")
                    data = NifFormat.Data()
                    success = False
                    add_to_ignored_list = False
                    try:
                        data.inspect(stream)
                        if "NiNode".encode(
                                'ascii') == data.header.block_types[0]:
                            if any(keyword in self.keywords
                                   for keyword in data.header.strings):
                                success = True
                            else:
                                add_to_ignored_list = True

                    except ValueError:
                        log.exception("[" + file +
                                      "] - Too Big to inspect - skipping")
                    except Exception:
                        log.exception("[" + file + "] - Error")
                    finally:
                        if success:
                            self.nif_files.add(path)
                            self.nif_files_list_widget.addItem(path)
                        elif add_to_ignored_list:
                            item = QListWidgetItem(
                                path, self.ignored_nif_files_list_widget)
                            item.setForeground(Qt.darkRed)
                            ignored_files += 1
                    progress_callback.emit(0)  # emit parameter is not used
        return ignored_files

    def action_apply(self):
        """
        Apply parameters to relevant .nif files
        """
        if self.nif_files_list_widget.count() == 0:
            QMessageBox.warning(self, "No .nif files loaded",
                                "Don't forget to load .nif files !")
            return

        if self.nif_files_list_widget.count() >= get_config().getint(
                "DEFAULT", "softLimit"):
            box = QMessageBox()
            box.setIcon(QMessageBox.Question)
            box.setWindowTitle('Are you sure ?')
            box.setText(
                "The tool may struggle with more than 100 .nif files at once. We advise you to process small batches.\n\nAre you sure you wish to continue ?"
            )
            box.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
            buttonY = box.button(QMessageBox.Yes)
            buttonY.setText('Yes')
            buttonN = box.button(QMessageBox.No)
            buttonN.setText('No')
            box.exec_()

            if box.clickedButton() == buttonN:
                return

        log.info("Applying parameters to " +
                 str(self.nif_files_list_widget.count()) + " files ...")
        self.toggle(False)
        self.progress_bar.setValue(0)
        self.processed_files = itertools.count()

        CONFIG.set("NIF", "Glossiness", str(self.spin_box_glossiness.value())),
        CONFIG.set("NIF", "SpecularStrength",
                   str(self.spin_box_specular_strength.value())),
        save_config()

        QMessageBox.warning(
            self, "Attention !",
            "The process is quite slow.\n\nThe gui will be mostly unresponsive to your input. Don't close the application, unless the completion pourcentage has not been updated in a long time (several minutes).\nIt took me 13 minutes to process 100 files for example."
        )

        #for indices in chunkify(range(self.nif_files_list_widget.count()), QThreadPool.globalInstance().maxThreadCount()-1):
        QThreadPool.globalInstance().setExpiryTimeout(-1)
        for index in range(self.nif_files_list_widget.count()):
            item = self.nif_files_list_widget.item(index)
            worker = NifProcessWorker(
                index=index,
                path=item.text(),
                keywords=self.keywords,
                glossiness=self.spin_box_glossiness.value(),
                specular_strength=self.spin_box_specular_strength.value())
            worker.signals.start.connect(self.start_apply_action)
            worker.signals.result.connect(self.result_apply_action)
            worker.signals.finished.connect(self.finish_apply_action)
            QThreadPool.globalInstance().start(worker)
Beispiel #22
0
    class Config(SignalNode.Config):
        """Config widget displayed for BandpassFilter."""
        def __init__(self, parent=None):
            super().__init__(parent=parent)

            # Upper bound ----------------------------------------------------------------------------------------------
            self.lower_bound_enable = QCheckBox()
            self.lower_bound_enable.setChecked(True)
            self.lower_bound_enable.stateChanged.connect(self.updateModel)

            self.lower_bound = QDoubleSpinBox()
            self.lower_bound.valueChanged.connect(self.updateModel)
            self.lower_bound.setMinimum(0)
            self.lower_bound.setMaximum(250)
            self.lower_bound.setSuffix(" Hz")

            layout = QHBoxLayout()
            layout.setContentsMargins(0, 0, 0, 0)
            lower_bound_widget = QWidget()
            lower_bound_widget.setContentsMargins(0, 0, 0, 0)
            lower_bound_widget.setLayout(layout)
            layout.addWidget(self.lower_bound_enable)
            layout.addWidget(self.lower_bound)

            # Lower bound ----------------------------------------------------------------------------------------------
            self.upper_bound_enable = QCheckBox()
            self.upper_bound_enable.setChecked(True)
            self.upper_bound_enable.stateChanged.connect(self.updateModel)

            self.upper_bound = QDoubleSpinBox()
            self.upper_bound.valueChanged.connect(self.updateModel)
            self.upper_bound.setMinimum(0)
            self.upper_bound.setMaximum(250)
            self.upper_bound.setSuffix(" Hz")

            layout = QHBoxLayout()
            layout.setContentsMargins(0, 0, 0, 0)
            upper_bound_widget = QWidget()
            upper_bound_widget.setContentsMargins(0, 0, 0, 0)
            upper_bound_widget.setLayout(layout)
            layout.addWidget(self.upper_bound_enable)
            layout.addWidget(self.upper_bound)

            # Filter type and length -----------------------------------------------------------------------------------
            self.filter_type = QComboBox()
            for name in BandpassFilter.filter_name_to_type:
                self.filter_type.addItem(name)
            self.filter_type.currentTextChanged.connect(self.updateModel)

            self.filter_length = QSpinBox()
            self.filter_length.setMinimum(2)
            self.filter_length.setMaximum(1000000)
            self.filter_length.setValue(1000)
            self.filter_length.valueChanged.connect(self.updateModel)

            self.filter_order = QSpinBox()
            self.filter_order.setRange(1, 4)
            self.filter_order.valueChanged.connect(self.updateModel)

            # ----------------------------------------------------------------------------------------------------------
            layout = QFormLayout()
            layout.addRow("Lower bound:", lower_bound_widget)
            layout.addRow("Upper bound:", upper_bound_widget)
            layout.addRow("Filter type:", self.filter_type)
            layout.addRow("Filter order:", self.filter_order)
            layout.addRow("Filter length:", self.filter_length)
            self.setLayout(layout)

        def updateModel(self):
            n = self.node()
            if n is None:
                return

            if self.lower_bound_enable.isChecked():
                lower_bound = self.lower_bound.value()
            else:
                lower_bound = None

            if self.upper_bound_enable.isChecked():
                upper_bound = self.upper_bound.value()
            else:
                upper_bound = None

            filter_type = n.filter_name_to_type[self.filter_type.currentText()]
            filter_length = self.filter_length.value()
            filter_order = self.filter_order.value()

            n.setLowerBound(lower_bound)
            n.setUpperBound(upper_bound)
            n.setFilterType(filter_type)
            n.setFilterLength(filter_length)
            n.setFilterOrder(filter_order)

        def updateView(self):
            n = self.node()
            if n is None:
                return

            # Prevent view fields from emitting signals while they are updated
            self.lower_bound.blockSignals(True)
            self.upper_bound.blockSignals(True)
            self.filter_type.blockSignals(True)
            self.filter_length.blockSignals(True)
            self.filter_order.blockSignals(True)

            if n.upperBound() is None:
                self.upper_bound_enable.setChecked(False)
            else:
                self.upper_bound_enable.setChecked(True)
                self.upper_bound.setValue(n.upperBound())

            if n.lowerBound() is None:
                self.lower_bound_enable.setChecked(False)
            else:
                self.lower_bound_enable.setChecked(True)
                self.lower_bound.setValue(n.lowerBound())

            self.filter_type.setCurrentText(
                n.filter_type_to_name[n.filterType()])
            self.filter_length.setValue(n.filterLength())
            self.filter_order.setValue(n.filterOrder())

            # Release the block and call adjust
            self.lower_bound.blockSignals(False)
            self.upper_bound.blockSignals(False)
            self.filter_type.blockSignals(False)
            self.filter_length.blockSignals(False)
            self.filter_order.blockSignals(False)
            self._adjust()

        def _adjust(self):
            """Adjust displayed values and limits in response to changes."""
            # Enable spinbox widgets based on their checkbox
            self.lower_bound.setEnabled(self.lower_bound_enable.isChecked())
            self.upper_bound.setEnabled(self.upper_bound_enable.isChecked())

            # Adjust min and max so that lower_bound is never higher than upper_bound
            if self.lower_bound_enable.isChecked():
                self.upper_bound.setMinimum(self.lower_bound.value())
            else:
                self.upper_bound.setMinimum(0)

            if self.upper_bound_enable.isChecked():
                self.lower_bound.setMaximum(self.upper_bound.value())
            else:
                self.lower_bound.setMaximum(250)

            if self.filter_type.currentText() == "Butterworth":
                self.filter_order.setEnabled(True)
            else:
                self.filter_order.setEnabled(False)
Beispiel #23
0
class SCOUTS(QMainWindow):
    """Main Window Widget for SCOUTS."""
    style = {
        'title': 'QLabel {font-size: 18pt; font-weight: 600}',
        'header': 'QLabel {font-size: 12pt; font-weight: 520}',
        'label': 'QLabel {font-size: 10pt}',
        'button': 'QPushButton {font-size: 10pt}',
        'md button': 'QPushButton {font-size: 12pt}',
        'run button': 'QPushButton {font-size: 18pt; font-weight: 600}',
        'line edit': 'QLineEdit {font-size: 10pt}',
        'checkbox': 'QCheckBox {font-size: 10pt}',
        'radio button': 'QRadioButton {font-size: 10pt}'
    }

    def __init__(self) -> None:
        """SCOUTS Constructor. Defines all aspects of the GUI."""

        # ###
        # ### Main Window setup
        # ###

        # Inherits from QMainWindow
        super().__init__()
        self.rootdir = get_project_root()
        self.threadpool = QThreadPool()
        # Sets values for QMainWindow
        self.setWindowTitle("SCOUTS")
        self.setWindowIcon(
            QIcon(
                os.path.abspath(os.path.join(self.rootdir, 'src',
                                             'scouts.ico'))))
        # Creates StackedWidget as QMainWindow's central widget
        self.stacked_pages = QStackedWidget(self)
        self.setCentralWidget(self.stacked_pages)
        # Creates Widgets for individual "pages" and adds them to the StackedWidget
        self.main_page = QWidget()
        self.samples_page = QWidget()
        self.gating_page = QWidget()
        self.pages = (self.main_page, self.samples_page, self.gating_page)
        for page in self.pages:
            self.stacked_pages.addWidget(page)
        # ## Sets widget at program startup
        self.stacked_pages.setCurrentWidget(self.main_page)

        # ###
        # ### MAIN PAGE
        # ###

        # Main page layout
        self.main_layout = QVBoxLayout(self.main_page)

        # Title section
        # Title
        self.title = QLabel(self.main_page)
        self.title.setText('SCOUTS - Single Cell Outlier Selector')
        self.title.setStyleSheet(self.style['title'])
        self.title.adjustSize()
        self.main_layout.addWidget(self.title)

        # ## Input section
        # Input header
        self.input_header = QLabel(self.main_page)
        self.input_header.setText('Input settings')
        self.input_header.setStyleSheet(self.style['header'])
        self.main_layout.addChildWidget(self.input_header)
        self.input_header.adjustSize()
        self.main_layout.addWidget(self.input_header)
        # Input frame
        self.input_frame = QFrame(self.main_page)
        self.input_frame.setFrameShape(QFrame.StyledPanel)
        self.input_frame.setLayout(QFormLayout())
        self.main_layout.addWidget(self.input_frame)
        # Input button
        self.input_button = QPushButton(self.main_page)
        self.input_button.setStyleSheet(self.style['button'])
        self.set_icon(self.input_button, 'x-office-spreadsheet')
        self.input_button.setObjectName('input')
        self.input_button.setText(' Select input file (.xlsx or .csv)')
        self.input_button.clicked.connect(self.get_path)
        # Input path box
        self.input_path = QLineEdit(self.main_page)
        self.input_path.setObjectName('input_path')
        self.input_path.setStyleSheet(self.style['line edit'])
        # Go to sample naming page
        self.samples_button = QPushButton(self.main_page)
        self.samples_button.setStyleSheet(self.style['button'])
        self.set_icon(self.samples_button, 'preferences-other')
        self.samples_button.setText(' Name samples...')
        self.samples_button.clicked.connect(self.goto_samples_page)
        # Go to gating page
        self.gates_button = QPushButton(self.main_page)
        self.gates_button.setStyleSheet(self.style['button'])
        self.set_icon(self.gates_button, 'preferences-other')
        self.gates_button.setText(' Gating && outlier options...')
        self.gates_button.clicked.connect(self.goto_gates_page)
        # Add widgets above to input frame Layout
        self.input_frame.layout().addRow(self.input_button, self.input_path)
        self.input_frame.layout().addRow(self.samples_button)
        self.input_frame.layout().addRow(self.gates_button)

        # ## Analysis section
        # Analysis header
        self.analysis_header = QLabel(self.main_page)
        self.analysis_header.setText('Analysis settings')
        self.analysis_header.setStyleSheet(self.style['header'])
        self.analysis_header.adjustSize()
        self.main_layout.addWidget(self.analysis_header)
        # Analysis frame
        self.analysis_frame = QFrame(self.main_page)
        self.analysis_frame.setFrameShape(QFrame.StyledPanel)
        self.analysis_frame.setLayout(QVBoxLayout())
        self.main_layout.addWidget(self.analysis_frame)
        # Cutoff text
        self.cutoff_text = QLabel(self.main_page)
        self.cutoff_text.setText('Type of outlier to select:')
        self.cutoff_text.setToolTip(
            'Choose whether to select outliers using the cutoff value from a reference\n'
            'sample (OutR) or by using the cutoff value calculated for each sample\n'
            'individually (OutS)')
        self.cutoff_text.setStyleSheet(self.style['label'])
        # Cutoff button group
        self.cutoff_group = QButtonGroup(self)
        # Cutoff by sample
        self.cutoff_sample = QRadioButton(self.main_page)
        self.cutoff_sample.setText('OutS')
        self.cutoff_sample.setObjectName('sample')
        self.cutoff_sample.setStyleSheet(self.style['radio button'])
        self.cutoff_sample.setChecked(True)
        self.cutoff_group.addButton(self.cutoff_sample)
        # Cutoff by reference
        self.cutoff_reference = QRadioButton(self.main_page)
        self.cutoff_reference.setText('OutR')
        self.cutoff_reference.setObjectName('ref')
        self.cutoff_reference.setStyleSheet(self.style['radio button'])
        self.cutoff_group.addButton(self.cutoff_reference)
        # Both cutoffs
        self.cutoff_both = QRadioButton(self.main_page)
        self.cutoff_both.setText('both')
        self.cutoff_both.setObjectName('sample ref')
        self.cutoff_both.setStyleSheet(self.style['radio button'])
        self.cutoff_group.addButton(self.cutoff_both)
        # Markers text
        self.markers_text = QLabel(self.main_page)
        self.markers_text.setStyleSheet(self.style['label'])
        self.markers_text.setText('Show results for:')
        self.markers_text.setToolTip(
            'Individual markers: for each marker, select outliers\n'
            'Any marker: select cells that are outliers for AT LEAST one marker'
        )
        # Markers button group
        self.markers_group = QButtonGroup(self)
        # Single marker
        self.single_marker = QRadioButton(self.main_page)
        self.single_marker.setText('individual markers')
        self.single_marker.setObjectName('single')
        self.single_marker.setStyleSheet(self.style['radio button'])
        self.single_marker.setChecked(True)
        self.markers_group.addButton(self.single_marker)
        # Any marker
        self.any_marker = QRadioButton(self.main_page)
        self.any_marker.setText('any marker')
        self.any_marker.setObjectName('any')
        self.any_marker.setStyleSheet(self.style['radio button'])
        self.markers_group.addButton(self.any_marker)
        # Both methods
        self.both_methods = QRadioButton(self.main_page)
        self.both_methods.setText('both')
        self.both_methods.setObjectName('single any')
        self.both_methods.setStyleSheet(self.style['radio button'])
        self.markers_group.addButton(self.both_methods)
        # Tukey text
        self.tukey_text = QLabel(self.main_page)
        self.tukey_text.setStyleSheet(self.style['label'])
        # Tukey button group
        self.tukey_text.setText('Tukey factor:')
        self.tukey_group = QButtonGroup(self)
        # Low Tukey value
        self.tukey_low = QRadioButton(self.main_page)
        self.tukey_low.setText('1.5')
        self.tukey_low.setStyleSheet(self.style['radio button'])
        self.tukey_low.setChecked(True)
        self.tukey_group.addButton(self.tukey_low)
        # High Tukey value
        self.tukey_high = QRadioButton(self.main_page)
        self.tukey_high.setText('3.0')
        self.tukey_high.setStyleSheet(self.style['radio button'])
        self.tukey_group.addButton(self.tukey_high)
        # Add widgets above to analysis frame layout
        self.analysis_frame.layout().addWidget(self.cutoff_text)
        self.cutoff_buttons = QHBoxLayout()
        for button in self.cutoff_group.buttons():
            self.cutoff_buttons.addWidget(button)
        self.analysis_frame.layout().addLayout(self.cutoff_buttons)
        self.analysis_frame.layout().addWidget(self.markers_text)
        self.markers_buttons = QHBoxLayout()
        for button in self.markers_group.buttons():
            self.markers_buttons.addWidget(button)
        self.analysis_frame.layout().addLayout(self.markers_buttons)
        self.analysis_frame.layout().addWidget(self.tukey_text)
        self.tukey_buttons = QHBoxLayout()
        for button in self.tukey_group.buttons():
            self.tukey_buttons.addWidget(button)
        self.tukey_buttons.addWidget(QLabel())  # aligns row with 2 buttons
        self.analysis_frame.layout().addLayout(self.tukey_buttons)

        # ## Output section
        # Output header
        self.output_header = QLabel(self.main_page)
        self.output_header.setText('Output settings')
        self.output_header.setStyleSheet(self.style['header'])
        self.output_header.adjustSize()
        self.main_layout.addWidget(self.output_header)
        # Output frame
        self.output_frame = QFrame(self.main_page)
        self.output_frame.setFrameShape(QFrame.StyledPanel)
        self.output_frame.setLayout(QFormLayout())
        self.main_layout.addWidget(self.output_frame)
        # Output button
        self.output_button = QPushButton(self.main_page)
        self.output_button.setStyleSheet(self.style['button'])
        self.set_icon(self.output_button, 'folder')
        self.output_button.setObjectName('output')
        self.output_button.setText(' Select output folder')
        self.output_button.clicked.connect(self.get_path)
        # Output path box
        self.output_path = QLineEdit(self.main_page)
        self.output_path.setStyleSheet(self.style['line edit'])
        # Generate CSV checkbox
        self.output_csv = QCheckBox(self.main_page)
        self.output_csv.setText('Export multiple text files (.csv)')
        self.output_csv.setStyleSheet(self.style['checkbox'])
        self.output_csv.setChecked(True)
        # Generate XLSX checkbox
        self.output_excel = QCheckBox(self.main_page)
        self.output_excel.setText('Export multiple Excel spreadsheets (.xlsx)')
        self.output_excel.setStyleSheet(self.style['checkbox'])
        self.output_excel.clicked.connect(self.enable_single_excel)
        # Generate single, large XLSX checkbox
        self.single_excel = QCheckBox(self.main_page)
        self.single_excel.setText(
            'Also save one multi-sheet Excel spreadsheet')
        self.single_excel.setToolTip(
            'After generating all Excel spreadsheets, SCOUTS combines them into '
            'a single\nExcel spreadsheet where each sheet corresponds to an output'
            'file from SCOUTS')
        self.single_excel.setStyleSheet(self.style['checkbox'])
        self.single_excel.setEnabled(False)
        self.single_excel.clicked.connect(self.memory_warning)
        # Add widgets above to output frame layout
        self.output_frame.layout().addRow(self.output_button, self.output_path)
        self.output_frame.layout().addRow(self.output_csv)
        self.output_frame.layout().addRow(self.output_excel)
        self.output_frame.layout().addRow(self.single_excel)

        # ## Run & help-quit section
        # Run button (stand-alone)
        self.run_button = QPushButton(self.main_page)
        self.set_icon(self.run_button, 'system-run')
        self.run_button.setText(' Run!')
        self.run_button.setStyleSheet(self.style['run button'])
        self.main_layout.addWidget(self.run_button)
        self.run_button.clicked.connect(self.run)
        # Help-quit frame (invisible)
        self.helpquit_frame = QFrame(self.main_page)
        self.helpquit_frame.setLayout(QHBoxLayout())
        self.helpquit_frame.layout().setMargin(0)
        self.main_layout.addWidget(self.helpquit_frame)
        # Help button
        self.help_button = QPushButton(self.main_page)
        self.set_icon(self.help_button, 'help-about')
        self.help_button.setText(' Help')
        self.help_button.setStyleSheet(self.style['md button'])
        self.help_button.clicked.connect(self.get_help)
        # Quit button
        self.quit_button = QPushButton(self.main_page)
        self.set_icon(self.quit_button, 'process-stop')
        self.quit_button.setText(' Quit')
        self.quit_button.setStyleSheet(self.style['md button'])
        self.quit_button.clicked.connect(self.close)
        # Add widgets above to help-quit layout
        self.helpquit_frame.layout().addWidget(self.help_button)
        self.helpquit_frame.layout().addWidget(self.quit_button)

        # ###
        # ### SAMPLES PAGE
        # ###

        # Samples page layout
        self.samples_layout = QVBoxLayout(self.samples_page)

        # ## Title section
        # Title
        self.samples_title = QLabel(self.samples_page)
        self.samples_title.setText('Name your samples')
        self.samples_title.setStyleSheet(self.style['title'])
        self.samples_title.adjustSize()
        self.samples_layout.addWidget(self.samples_title)
        # Subtitle
        self.samples_subtitle = QLabel(self.samples_page)
        string = (
            'Please name the samples to be analysed by SCOUTS.\n\nSCOUTS searches the first '
            'column of your data\nand locates the exact string as part of the sample name.'
        )
        self.samples_subtitle.setText(string)
        self.samples_subtitle.setStyleSheet(self.style['label'])
        self.samples_subtitle.adjustSize()
        self.samples_layout.addWidget(self.samples_subtitle)

        # ## Sample addition section
        # Sample addition frame
        self.samples_frame = QFrame(self.samples_page)
        self.samples_frame.setFrameShape(QFrame.StyledPanel)
        self.samples_frame.setLayout(QGridLayout())
        self.samples_layout.addWidget(self.samples_frame)
        # Sample name box
        self.sample_name = QLineEdit(self.samples_page)
        self.sample_name.setStyleSheet(self.style['line edit'])
        self.sample_name.setPlaceholderText('Sample name ...')
        # Reference check
        self.is_reference = QCheckBox(self.samples_page)
        self.is_reference.setText('Reference?')
        self.is_reference.setStyleSheet(self.style['checkbox'])
        # Add sample to table
        self.add_sample_button = QPushButton(self.samples_page)
        QShortcut(QKeySequence("Return"), self.add_sample_button,
                  self.write_to_sample_table)
        self.set_icon(self.add_sample_button, 'list-add')
        self.add_sample_button.setText(' Add sample (Enter)')
        self.add_sample_button.setStyleSheet(self.style['button'])
        self.add_sample_button.clicked.connect(self.write_to_sample_table)
        # Remove sample from table
        self.remove_sample_button = QPushButton(self.samples_page)
        QShortcut(QKeySequence("Delete"), self.remove_sample_button,
                  self.remove_from_sample_table)
        self.set_icon(self.remove_sample_button, 'list-remove')
        self.remove_sample_button.setText(' Remove sample (Del)')
        self.remove_sample_button.setStyleSheet(self.style['button'])
        self.remove_sample_button.clicked.connect(
            self.remove_from_sample_table)
        # Add widgets above to sample addition layout
        self.samples_frame.layout().addWidget(self.sample_name, 0, 0)
        self.samples_frame.layout().addWidget(self.is_reference, 1, 0)
        self.samples_frame.layout().addWidget(self.add_sample_button, 0, 1)
        self.samples_frame.layout().addWidget(self.remove_sample_button, 1, 1)

        # ## Sample table
        self.sample_table = QTableWidget(self.samples_page)
        self.sample_table.setColumnCount(2)
        self.sample_table.setHorizontalHeaderItem(0,
                                                  QTableWidgetItem('Sample'))
        self.sample_table.setHorizontalHeaderItem(
            1, QTableWidgetItem('Reference?'))
        self.sample_table.horizontalHeader().setSectionResizeMode(
            0, QHeaderView.Stretch)
        self.sample_table.horizontalHeader().setSectionResizeMode(
            1, QHeaderView.ResizeToContents)
        self.samples_layout.addWidget(self.sample_table)

        # ## Save & clear buttons
        # Save & clear frame (invisible)
        self.saveclear_frame = QFrame(self.samples_page)
        self.saveclear_frame.setLayout(QHBoxLayout())
        self.saveclear_frame.layout().setMargin(0)
        self.samples_layout.addWidget(self.saveclear_frame)
        # Clear samples button
        self.clear_samples = QPushButton(self.samples_page)
        self.set_icon(self.clear_samples, 'edit-delete')
        self.clear_samples.setText(' Clear table')
        self.clear_samples.setStyleSheet(self.style['md button'])
        self.clear_samples.clicked.connect(self.prompt_clear_data)
        # Save samples button
        self.save_samples = QPushButton(self.samples_page)
        self.set_icon(self.save_samples, 'document-save')
        self.save_samples.setText(' Save samples')
        self.save_samples.setStyleSheet(self.style['md button'])
        self.save_samples.clicked.connect(self.goto_main_page)
        # Add widgets above to save & clear layout
        self.saveclear_frame.layout().addWidget(self.clear_samples)
        self.saveclear_frame.layout().addWidget(self.save_samples)

        # ###
        # ### GATING PAGE
        # ###

        # Gating page layout
        self.gating_layout = QVBoxLayout(self.gating_page)

        # ## Title section
        # Title
        self.gates_title = QLabel(self.gating_page)
        self.gates_title.setText('Gating & outlier options')
        self.gates_title.setStyleSheet(self.style['title'])
        self.gates_title.adjustSize()
        self.gating_layout.addWidget(self.gates_title)

        # ## Gating options section
        # Gating header
        self.gate_header = QLabel(self.gating_page)
        self.gate_header.setText('Gating')
        self.gate_header.setStyleSheet(self.style['header'])
        self.gate_header.adjustSize()
        self.gating_layout.addWidget(self.gate_header)

        # Gating frame
        self.gate_frame = QFrame(self.gating_page)
        self.gate_frame.setFrameShape(QFrame.StyledPanel)
        self.gate_frame.setLayout(QFormLayout())
        self.gating_layout.addWidget(self.gate_frame)
        # Gating button group
        self.gating_group = QButtonGroup(self)
        # Do not gate samples
        self.no_gates = QRadioButton(self.gating_page)
        self.no_gates.setObjectName('no_gate')
        self.no_gates.setText("Don't gate samples")
        self.no_gates.setStyleSheet(self.style['radio button'])
        self.no_gates.setChecked(True)
        self.gating_group.addButton(self.no_gates)
        self.no_gates.clicked.connect(self.activate_gate)
        # CyToF gating
        self.cytof_gates = QRadioButton(self.gating_page)
        self.cytof_gates.setObjectName('cytof')
        self.cytof_gates.setText('Mass Cytometry gating')
        self.cytof_gates.setStyleSheet(self.style['radio button'])
        self.cytof_gates.setToolTip(
            'Exclude cells for which the average expression of all\n'
            'markers is below the selected value')
        self.gating_group.addButton(self.cytof_gates)
        self.cytof_gates.clicked.connect(self.activate_gate)
        # CyToF gating spinbox
        self.cytof_gates_value = QDoubleSpinBox(self.gating_page)
        self.cytof_gates_value.setMinimum(0)
        self.cytof_gates_value.setMaximum(1)
        self.cytof_gates_value.setValue(0.1)
        self.cytof_gates_value.setSingleStep(0.05)
        self.cytof_gates_value.setEnabled(False)
        # scRNA-Seq gating
        self.rnaseq_gates = QRadioButton(self.gating_page)
        self.rnaseq_gates.setText('scRNA-Seq gating')
        self.rnaseq_gates.setStyleSheet(self.style['radio button'])
        self.rnaseq_gates.setToolTip(
            'When calculating cutoff, ignore reads below the selected value')
        self.rnaseq_gates.setObjectName('rnaseq')
        self.gating_group.addButton(self.rnaseq_gates)
        self.rnaseq_gates.clicked.connect(self.activate_gate)
        # scRNA-Seq gating spinbox
        self.rnaseq_gates_value = QDoubleSpinBox(self.gating_page)
        self.rnaseq_gates_value.setMinimum(0)
        self.rnaseq_gates_value.setMaximum(10)
        self.rnaseq_gates_value.setValue(0)
        self.rnaseq_gates_value.setSingleStep(1)
        self.rnaseq_gates_value.setEnabled(False)
        # export gated population checkbox
        self.export_gated = QCheckBox(self.gating_page)
        self.export_gated.setText('Export gated cells as an output file')
        self.export_gated.setStyleSheet(self.style['checkbox'])
        self.export_gated.setEnabled(False)
        # Add widgets above to Gate frame layout
        self.gate_frame.layout().addRow(self.no_gates, QLabel())
        self.gate_frame.layout().addRow(self.cytof_gates,
                                        self.cytof_gates_value)
        self.gate_frame.layout().addRow(self.rnaseq_gates,
                                        self.rnaseq_gates_value)
        self.gate_frame.layout().addRow(self.export_gated, QLabel())

        # ## Outlier options section
        # Outlier header
        self.outlier_header = QLabel(self.gating_page)
        self.outlier_header.setText('Outliers')
        self.outlier_header.setStyleSheet(self.style['header'])
        self.outlier_header.adjustSize()
        self.gating_layout.addWidget(self.outlier_header)
        # Outlier frame
        self.outlier_frame = QFrame(self.gating_page)
        self.outlier_frame.setFrameShape(QFrame.StyledPanel)
        self.outlier_frame.setLayout(QVBoxLayout())
        self.gating_layout.addWidget(self.outlier_frame)
        # Top outliers information
        self.top_outliers = QLabel(self.gating_page)
        self.top_outliers.setStyleSheet(self.style['label'])
        self.top_outliers.setText(
            'By default, SCOUTS selects the top outliers from the population')
        self.top_outliers.setStyleSheet(self.style['label'])
        # Bottom outliers data
        self.bottom_outliers = QCheckBox(self.gating_page)
        self.bottom_outliers.setText('Include results for low outliers')
        self.bottom_outliers.setStyleSheet(self.style['checkbox'])
        # Non-outliers data
        self.not_outliers = QCheckBox(self.gating_page)
        self.not_outliers.setText('Include results for non-outliers')
        self.not_outliers.setStyleSheet(self.style['checkbox'])
        # Add widgets above to Gate frame layout
        self.outlier_frame.layout().addWidget(self.top_outliers)
        self.outlier_frame.layout().addWidget(self.bottom_outliers)
        self.outlier_frame.layout().addWidget(self.not_outliers)

        # ## Save/back button
        self.save_gates = QPushButton(self.gating_page)
        self.set_icon(self.save_gates, 'go-next')
        self.save_gates.setText(' Back to main menu')
        self.save_gates.setStyleSheet(self.style['md button'])
        self.gating_layout.addWidget(self.save_gates)
        self.save_gates.clicked.connect(self.goto_main_page)

        # ## Add empty label to take vertical space
        self.empty_label = QLabel(self.gating_page)
        self.empty_label.setSizePolicy(QSizePolicy.Expanding,
                                       QSizePolicy.Expanding)
        self.gating_layout.addWidget(self.empty_label)

    # ###
    # ### ICON SETTING
    # ###

    def set_icon(self, widget: QWidget, icon: str) -> None:
        """Associates an icon to a widget."""
        i = QIcon()
        i.addPixmap(
            QPixmap(
                os.path.abspath(
                    os.path.join(self.rootdir, 'src', 'default_icons',
                                 f'{icon}.svg'))))
        widget.setIcon(QIcon.fromTheme(icon, i))

    # ###
    # ### STACKED WIDGET PAGE SWITCHING
    # ###

    def goto_main_page(self) -> None:
        """Switches stacked widget pages to the main page."""
        self.stacked_pages.setCurrentWidget(self.main_page)

    def goto_samples_page(self) -> None:
        """Switches stacked widget pages to the samples table page."""
        self.stacked_pages.setCurrentWidget(self.samples_page)

    def goto_gates_page(self) -> None:
        """Switches stacked widget pages to the gating & other options page."""
        self.stacked_pages.setCurrentWidget(self.gating_page)

    # ###
    # ### MAIN PAGE GUI LOGIC
    # ###

    def get_path(self) -> None:
        """Opens a dialog box and sets the chosen file/folder path, depending on the caller widget."""
        options = QFileDialog.Options()
        options |= QFileDialog.DontUseNativeDialog
        sender_name = self.sender().objectName()
        if sender_name == 'input':
            query, _ = QFileDialog.getOpenFileName(self,
                                                   "Select file",
                                                   "",
                                                   "All Files (*)",
                                                   options=options)
        elif sender_name == 'output':
            query = QFileDialog.getExistingDirectory(self,
                                                     "Select Directory",
                                                     options=options)
        else:
            return
        if query:
            getattr(self, f'{sender_name}_path').setText(query)

    def enable_single_excel(self) -> None:
        """Enables checkbox for generating a single Excel output."""
        if self.output_excel.isChecked():
            self.single_excel.setEnabled(True)
        else:
            self.single_excel.setEnabled(False)
            self.single_excel.setChecked(False)

    # ###
    # ### SAMPLE NAME/SAMPLE TABLE GUI LOGIC
    # ###

    def write_to_sample_table(self) -> None:
        """Writes data to sample table."""
        table = self.sample_table
        ref = 'no'
        sample = self.sample_name.text()
        if sample:
            for cell in range(table.rowCount()):
                item = table.item(cell, 0)
                if item.text() == sample:
                    self.same_sample()
                    return
            if self.is_reference.isChecked():
                for cell in range(table.rowCount()):
                    item = table.item(cell, 1)
                    if item.text() == 'yes':
                        self.more_than_one_reference()
                        return
                ref = 'yes'
            sample = QTableWidgetItem(sample)
            is_reference = QTableWidgetItem(ref)
            is_reference.setFlags(Qt.ItemIsEnabled)
            row_position = table.rowCount()
            table.insertRow(row_position)
            table.setItem(row_position, 0, sample)
            table.setItem(row_position, 1, is_reference)
            self.is_reference.setChecked(False)
            self.sample_name.setText('')

    def remove_from_sample_table(self) -> None:
        """Removes data from sample table."""
        table = self.sample_table
        rows = set(index.row() for index in table.selectedIndexes())
        for index in sorted(rows, reverse=True):
            self.sample_table.removeRow(index)

    def prompt_clear_data(self) -> None:
        """Prompts option to clear all data in the sample table."""
        if self.confirm_clear_data():
            table = self.sample_table
            while table.rowCount():
                self.sample_table.removeRow(0)

    # ###
    # ### GATING GUI LOGIC
    # ###

    def activate_gate(self) -> None:
        """Activates/deactivates buttons related to gating."""
        if self.sender().objectName() == 'no_gate':
            self.cytof_gates_value.setEnabled(False)
            self.rnaseq_gates_value.setEnabled(False)
            self.export_gated.setEnabled(False)
            self.export_gated.setChecked(False)
        elif self.sender().objectName() == 'cytof':
            self.cytof_gates_value.setEnabled(True)
            self.rnaseq_gates_value.setEnabled(False)
            self.export_gated.setEnabled(True)
        elif self.sender().objectName() == 'rnaseq':
            self.cytof_gates_value.setEnabled(False)
            self.rnaseq_gates_value.setEnabled(True)
            self.export_gated.setEnabled(True)

    # ###
    # ### CONNECT SCOUTS TO ANALYTICAL MODULES
    # ###

    def run(self) -> None:
        """Runs SCOUTS as a Worker, based on user input in the GUI."""
        try:
            data = self.parse_input()
        except Exception as error:
            trace = traceback.format_exc()
            self.propagate_error((error, trace))
        else:
            data['widget'] = self
            worker = Worker(func=start_scouts, **data)
            worker.signals.started.connect(self.analysis_has_started)
            worker.signals.finished.connect(self.analysis_has_finished)
            worker.signals.success.connect(self.success_message)
            worker.signals.error.connect(self.propagate_error)
            self.threadpool.start(worker)

    def parse_input(self) -> Dict:
        """Returns user input on the GUI as a dictionary."""
        # Input and output
        input_dict = {
            'input_file': str(self.input_path.text()),
            'output_folder': str(self.output_path.text())
        }
        if not input_dict['input_file'] or not input_dict['output_folder']:
            raise NoIOPathError
        # Set cutoff by reference or by sample rule
        input_dict['cutoff_rule'] = self.cutoff_group.checkedButton(
        ).objectName()  # 'sample', 'ref', 'sample ref'
        # Outliers for each individual marker or any marker in row
        input_dict['marker_rule'] = self.markers_group.checkedButton(
        ).objectName()  # 'single', 'any', 'single any'
        # Tukey factor used for calculating cutoff
        input_dict['tukey_factor'] = float(
            self.tukey_group.checkedButton().text())  # '1.5', '3.0'
        # Output settings
        input_dict['export_csv'] = True if self.output_csv.isChecked(
        ) else False
        input_dict['export_excel'] = True if self.output_excel.isChecked(
        ) else False
        input_dict['single_excel'] = True if self.single_excel.isChecked(
        ) else False
        # Retrieve samples from sample table
        input_dict['sample_list'] = []
        for tuples in self.yield_samples_from_table():
            input_dict['sample_list'].append(tuples)
        if not input_dict['sample_list']:
            raise NoSampleError
        # Set gate cutoff (if any)
        input_dict['gating'] = self.gating_group.checkedButton().objectName(
        )  # 'no_gate', 'cytof', 'rnaseq'
        input_dict['gate_cutoff_value'] = None
        if input_dict['gating'] != 'no_gate':
            input_dict['gate_cutoff_value'] = getattr(
                self, f'{input_dict["gating"]}_gates_value').value()
        input_dict['export_gated'] = True if self.export_gated.isChecked(
        ) else False
        # Generate results for non-outliers
        input_dict['non_outliers'] = False
        if self.not_outliers.isChecked():
            input_dict['non_outliers'] = True
        # Generate results for bottom outliers
        input_dict['bottom_outliers'] = False
        if self.bottom_outliers.isChecked():
            input_dict['bottom_outliers'] = True
        # return dictionary with all gathered inputs
        return input_dict

    def yield_samples_from_table(
            self) -> Generator[Tuple[str, str], None, None]:
        """Yields sample names from the sample table."""
        table = self.sample_table
        for cell in range(table.rowCount()):
            sample_name = table.item(cell, 0).text()
            sample_type = table.item(cell, 1).text()
            yield sample_name, sample_type

    # ###
    # ### MESSAGE BOXES
    # ###

    def analysis_has_started(self) -> None:
        """Disables run button while SCOUTS analysis is underway."""
        self.run_button.setText(' Working...')
        self.run_button.setEnabled(False)

    def analysis_has_finished(self) -> None:
        """Enables run button after SCOUTS analysis has finished."""
        self.run_button.setEnabled(True)
        self.run_button.setText(' Run!')

    def success_message(self) -> None:
        """Info message box used when SCOUTS finished without errors."""
        title = "Analysis finished!"
        mes = "Your analysis has finished. No errors were reported."
        if self.stacked_pages.isEnabled() is True:
            QMessageBox.information(self, title, mes)

    def memory_warning(self) -> None:
        """Warning message box used when user wants to generate a single excel file."""
        if self.sender().isChecked():
            title = 'Memory warning!'
            mes = (
                "Depending on your dataset, this option can consume a LOT of memory and take"
                " a long time to process. Please make sure that your computer can handle it!"
            )
            QMessageBox.information(self, title, mes)

    def same_sample(self) -> None:
        """Error message box used when the user tries to input the same sample twice in the sample table."""
        title = 'Error: sample name already in table'
        mes = (
            "Sorry, you can't do this because this sample name is already in the table. "
            "Please select a different name.")
        QMessageBox.critical(self, title, mes)

    def more_than_one_reference(self) -> None:
        """Error message box used when the user tries to input two reference samples in the sample table."""
        title = "Error: more than one reference selected"
        mes = (
            "Sorry, you can't do this because there is already a reference column in the table. "
            "Please remove it before adding a reference.")
        QMessageBox.critical(self, title, mes)

    def confirm_clear_data(self) -> bool:
        """Question message box used to confirm user action of clearing sample table."""
        title = 'Confirm Action'
        mes = "Table will be cleared. Are you sure?"
        reply = QMessageBox.question(self, title, mes,
                                     QMessageBox.Yes | QMessageBox.No,
                                     QMessageBox.No)
        if reply == QMessageBox.Yes:
            return True
        return False

    # ###
    # ### EXCEPTIONS & ERRORS
    # ###

    def propagate_error(self, error: Tuple[Exception, str]) -> None:
        """Calls the appropriate error message box based on type of Exception raised."""
        if isinstance(error[0], NoIOPathError):
            self.no_io_path_error_message()
        elif isinstance(error[0], NoReferenceError):
            self.no_reference_error_message()
        elif isinstance(error[0], NoSampleError):
            self.no_sample_error_message()
        elif isinstance(error[0], PandasInputError):
            self.pandas_input_error_message()
        elif isinstance(error[0], SampleNamingError):
            self.sample_naming_error_message()
        else:
            self.generic_error_message(error)

    def no_io_path_error_message(self) -> None:
        """Message displayed when the user did not include an input file path, or an output folder path."""
        title = 'Error: no file/folder'
        message = ("Sorry, no input file and/or output folder was provided. "
                   "Please add the path to the necessary file/folder.")
        QMessageBox.critical(self, title, message)

    def no_reference_error_message(self) -> None:
        """Message displayed when the user wants to analyse cutoff based on a reference, but did not specify what
        sample corresponds to the reference."""
        title = "Error: No reference selected"
        message = (
            "Sorry, no reference sample was found on the sample list, but analysis was set to "
            "reference. Please add a reference sample, or change the rule for cutoff calculation."
        )
        QMessageBox.critical(self, title, message)

    def no_sample_error_message(self) -> None:
        """Message displayed when the user did not add any samples to the sample table."""
        title = "Error: No samples selected"
        message = (
            "Sorry, the analysis cannot be performed because no sample names were input. "
            "Please add your sample names.")
        QMessageBox.critical(self, title, message)

    def pandas_input_error_message(self) -> None:
        """Message displayed when the input file cannot be read (likely because it is not a Excel or csv file)."""
        title = 'Error: unexpected input file'
        message = (
            "Sorry, the input file could not be read. Please make sure that "
            "the data is save in a valid format (supported formats are: "
            ".csv, .xlsx).")
        QMessageBox.critical(self, title, message)

    def sample_naming_error_message(self) -> None:
        """Message displayed when none of the sample names passed by the user are found in the input DataFrame."""
        title = 'Error: sample names not in input file'
        message = (
            "Sorry, your sample names were not found in the input file. Please "
            "make sure that the names were typed correctly (case-sensitive).")
        QMessageBox.critical(self, title, message)

    def generic_error_message(self, error: Tuple[Exception, str]) -> None:
        """Error message box used to display any error message (including traceback) for any uncaught errors."""
        title = 'An error occurred!'
        name, trace = error
        QMessageBox.critical(self, title,
                             f"{str(name)}\n\nfull traceback:\n{trace}")

    def not_implemented_error_message(self) -> None:
        """Error message box used when the user accesses a functionality that hasn't been implemented yet."""
        title = "Not yet implemented"
        mes = "Sorry, this functionality has not been implemented yet."
        QMessageBox.critical(self, title, mes)

    # ###
    # ### HELP & QUIT
    # ###

    @staticmethod
    def get_help() -> None:
        """Opens SCOUTS documentation on the browser. Called when the user clicks the "help" button"""
        webbrowser.open('https://scouts.readthedocs.io/en/master/')

    def closeEvent(self, event: QEvent) -> None:
        """Defines the message box for when the user wants to quit SCOUTS."""
        title = 'Quit SCOUTS'
        mes = "Are you sure you want to quit?"
        reply = QMessageBox.question(self, title, mes,
                                     QMessageBox.Yes | QMessageBox.No,
                                     QMessageBox.No)
        if reply == QMessageBox.Yes:
            self.stacked_pages.setEnabled(False)
            message = self.quit_message()
            waiter = Waiter(waiter_func=self.threadpool.activeThreadCount)
            waiter.signals.started.connect(message.show)
            waiter.signals.finished.connect(message.destroy)
            waiter.signals.finished.connect(sys.exit)
            self.threadpool.start(waiter)
        event.ignore()

    def quit_message(self) -> QDialog:
        """Displays a window while SCOUTS is exiting"""
        message = QDialog(self)
        message.setWindowTitle('Exiting SCOUTS')
        message.resize(300, 50)
        label = QLabel('SCOUTS is exiting, please wait...', message)
        label.setStyleSheet(self.style['label'])
        label.adjustSize()
        label.setAlignment(Qt.AlignCenter)
        label.move(int((message.width() - label.width()) / 2),
                   int((message.height() - label.height()) / 2))
        return message
Beispiel #24
0
class AudioInfoDialog(QDialog):
    def __init__(self,
                 audios_name,
                 audios_delay,
                 audios_language,
                 audios_track_name,
                 audios_set_default,
                 audios_set_forced,
                 audios_default_value_delay,
                 audios_default_value_language,
                 audios_default_value_track_name,
                 audios_default_value_set_default,
                 audios_default_value_set_forced,
                 audio_set_default_disabled=False,
                 audio_set_forced_disabled=False,
                 disable_edit=False,
                 parent=None):
        super().__init__(parent)
        self.window_title = "Audio Info"
        self.state = "no"
        self.audios_count = len(audios_delay)

        self.messageIcon = QLabel()
        self.audio_tab_comboBox = InfoCellDialogTabComboBox(
            hint="Audios Groups")
        for i in range(self.audios_count):
            self.audio_tab_comboBox.addItem("Audio #" + str(i + 1))
        self.audio_tab_comboBox.setCurrentIndex(0)
        self.audio_tab_comboBox.currentIndexChanged.connect(
            self.update_current_audio_index)
        self.current_audio_index = 0

        self.disable_edit = disable_edit
        self.current_audio_name = audios_name
        self.current_audio_language = audios_language
        self.current_audio_delay = audios_delay
        self.current_audio_track_name = audios_track_name
        self.current_audio_set_default = audios_set_default
        self.current_audio_set_forced = audios_set_forced

        self.default_audio_language = audios_default_value_language
        self.default_audio_delay = audios_default_value_delay
        self.default_audio_track_name = audios_default_value_track_name
        self.default_audio_set_default = audios_default_value_set_default
        self.default_audio_set_forced = audios_default_value_set_forced

        self.audio_set_default_disabled = audio_set_default_disabled
        self.audio_set_forced_disabled = audio_set_forced_disabled

        self.audio_name_label = QLabel("Audio Name:")
        self.audio_name_value = QLabel(
            str(self.current_audio_name[self.current_audio_index]))
        width_to_be_fixed = 0
        for i in range(len(self.current_audio_name)):
            width_to_be_fixed = max(
                width_to_be_fixed,
                self.audio_name_value.fontMetrics().boundingRect(
                    self.current_audio_name[i]).width())
        self.audio_name_value.setFixedWidth(width_to_be_fixed + 10)
        self.audio_delay_label = QLabel("Audio Delay:")
        self.audio_delay_spin = QDoubleSpinBox()
        self.setup_audio_delay_spin()

        self.audio_language_label = QLabel("Audio Language:")
        self.audio_language_comboBox = QComboBox()
        self.setup_audio_language_comboBox()

        self.audio_track_name_label = QLabel("Audio Track Name:")
        self.audio_track_name_lineEdit = QLineEdit()
        self.setup_audio_track_name_lineEdit()

        self.audio_set_forced_label = QLabel("Audio Forced State:")
        self.audio_set_forced_checkBox = QCheckBox()
        self.setup_audio_set_forced_checkBox()

        self.audio_set_default_label = QLabel("Audio Default State:")
        self.audio_set_default_checkBox = QCheckBox()
        self.setup_audio_set_default_checkBox()

        self.yes_button = QPushButton("OK")
        self.no_button = QPushButton("Cancel")
        self.reset_button = QPushButton("Reset To Default")

        self.buttons_layout = QHBoxLayout()
        self.audio_delay_layout = QHBoxLayout()
        self.audio_language_layout = QHBoxLayout()
        self.audio_track_name_layout = QHBoxLayout()
        self.audio_set_default_layout = QHBoxLayout()
        self.audio_set_forced_layout = QHBoxLayout()
        self.buttons_layout.addWidget(QLabel(""), stretch=3)
        self.buttons_layout.addWidget(self.reset_button, stretch=2)
        self.buttons_layout.addWidget(self.yes_button, stretch=2)
        self.buttons_layout.addWidget(self.no_button, stretch=2)
        self.buttons_layout.addWidget(QLabel(""), stretch=3)
        self.audio_setting_layout = QGridLayout()
        self.audio_editable_setting_layout = QFormLayout()
        self.audio_editable_setting_layout.addRow(self.audio_name_label,
                                                  self.audio_name_value)
        self.audio_editable_setting_layout.addRow(
            self.audio_track_name_label, self.audio_track_name_lineEdit)
        self.audio_editable_setting_layout.addRow(self.audio_language_label,
                                                  self.audio_language_comboBox)
        self.audio_editable_setting_layout.addRow(self.audio_delay_label,
                                                  self.audio_delay_spin)
        self.audio_editable_setting_layout.addRow(
            self.audio_set_default_label, self.audio_set_default_checkBox)
        self.audio_editable_setting_layout.addRow(
            self.audio_set_forced_label, self.audio_set_forced_checkBox)
        self.audio_setting_layout.addWidget(self.audio_tab_comboBox, 0, 0)
        self.audio_setting_layout.addLayout(self.audio_editable_setting_layout,
                                            1, 0, 5, 2)
        self.audio_setting_layout.addWidget(self.messageIcon, 1, 3, 5, -1)

        self.main_layout = QGridLayout()
        self.main_layout.addLayout(self.audio_setting_layout, 0, 0, 2, 3)
        self.main_layout.addLayout(self.buttons_layout, 2, 0, 1, -1)
        self.main_layout.setContentsMargins(20, 20, 20, 20)
        self.setLayout(self.main_layout)

        self.setup_ui()
        self.signal_connect()

    def setup_ui(self):
        self.disable_question_mark_window()
        self.messageIcon.setPixmap(
            QtGui.QPixmap(GlobalFiles.AudioIconPath).scaledToHeight(100))
        self.set_dialog_values()
        self.set_default_buttons()
        if self.audio_set_default_disabled:
            self.audio_set_default_disable()
        if self.audio_set_forced_disabled:
            self.audio_set_forced_disable()
        if self.disable_edit:
            self.audio_track_name_lineEdit.setEnabled(False)
            self.audio_language_comboBox.setEnabled(False)
            self.audio_delay_spin.setEnabled(False)
            self.audio_set_default_checkBox.setEnabled(False)
            self.audio_set_forced_checkBox.setEnabled(False)
            self.reset_button.setEnabled(False)

        self.setup_tool_tip_hint_audio_set_default()
        self.setup_tool_tip_hint_audio_set_forced()

    def signal_connect(self):
        self.audio_track_name_lineEdit.textEdited.connect(
            self.update_current_audio_track_name)
        self.audio_delay_spin.editingFinished.connect(
            self.update_current_audio_delay)
        self.audio_language_comboBox.currentTextChanged.connect(
            self.update_current_audio_language)
        self.audio_set_default_checkBox.stateChanged.connect(
            self.update_current_audio_set_default)
        self.audio_set_forced_checkBox.stateChanged.connect(
            self.update_current_audio_set_forced)
        self.yes_button.clicked.connect(self.click_yes)
        self.no_button.clicked.connect(self.click_no)
        self.reset_button.clicked.connect(self.reset_audio_setting)

    def click_yes(self):
        self.state = "yes"
        self.close()

    def click_no(self):
        self.state = "no"
        self.close()

    def set_dialog_values(self):
        self.setWindowTitle(self.window_title)
        self.setWindowIcon(GlobalFiles.InfoSettingIcon)

    def disable_question_mark_window(self):
        self.setWindowFlag(Qt.WindowContextHelpButtonHint, on=False)

    def increase_message_font_size(self, value):
        message_font = self.message.font()
        message_font.setPointSize(self.message.fontInfo().pointSize() + value)
        self.message.setFont(message_font)

    def set_default_buttons(self):
        self.yes_button.setDefault(True)
        self.yes_button.setFocus()

    def showEvent(self, a0: QtGui.QShowEvent) -> None:
        super().showEvent(a0)
        self.setFixedSize(self.size())

    def setup_audio_track_name_lineEdit(self):
        self.audio_track_name_lineEdit.setClearButtonEnabled(True)
        self.audio_track_name_lineEdit.setText(
            self.current_audio_track_name[self.current_audio_index])

    def setup_audio_language_comboBox(self):
        self.audio_language_comboBox.addItems(AllAudiosLanguages)
        self.audio_language_comboBox.setCurrentIndex(
            AllAudiosLanguages.index(
                self.current_audio_language[self.current_audio_index]))
        self.audio_language_comboBox.setMaxVisibleItems(8)
        self.audio_language_comboBox.setStyleSheet(
            "QComboBox { combobox-popup: 0; }")

    def setup_audio_delay_spin(self):
        # self.audio_delay_spin.setMaximumWidth(screen_size.width() // 16)
        self.audio_delay_spin.setDecimals(3)
        self.audio_delay_spin.setMinimum(-9999.0)
        self.audio_delay_spin.setMaximum(9999.0)
        self.audio_delay_spin.setSingleStep(0.5)
        self.audio_delay_spin.setValue(
            float(self.current_audio_delay[self.current_audio_index]))

    def setup_audio_set_default_checkBox(self):
        self.audio_set_default_checkBox.setText("Set Default")
        self.audio_set_default_checkBox.setChecked(
            bool(self.current_audio_set_default[self.current_audio_index]))

    def setup_audio_set_forced_checkBox(self):
        self.audio_set_forced_checkBox.setText("Set Forced")
        self.audio_set_forced_checkBox.setChecked(
            bool(self.current_audio_set_forced[self.current_audio_index]))

    def update_current_audio_track_name(self):
        self.current_audio_track_name[self.current_audio_index] = str(
            self.audio_track_name_lineEdit.text())

    def update_current_audio_delay(self):
        self.current_audio_delay[self.current_audio_index] = round(
            self.audio_delay_spin.value(), 5)

    def update_current_audio_language(self):
        self.current_audio_language[self.current_audio_index] = str(
            self.audio_language_comboBox.currentText())

    def update_current_audio_set_default(self):
        new_state = self.audio_set_default_checkBox.checkState() == Qt.Checked
        self.current_audio_set_default[self.current_audio_index] = new_state
        if new_state:
            for i in range(len(self.current_audio_set_default)):
                if i != self.current_audio_index:
                    self.current_audio_set_default[i] = False

    def update_current_audio_set_forced(self):
        new_state = self.audio_set_forced_checkBox.checkState() == Qt.Checked
        self.current_audio_set_forced[self.current_audio_index] = new_state
        if new_state:
            for i in range(len(self.current_audio_set_forced)):
                if i != self.current_audio_index:
                    self.current_audio_set_forced[i] = False

    def reset_audio_setting(self):
        self.current_audio_language[
            self.current_audio_index] = self.default_audio_language[
                self.current_audio_index]
        self.current_audio_delay[
            self.current_audio_index] = self.default_audio_delay[
                self.current_audio_index]
        self.current_audio_track_name[
            self.current_audio_index] = self.default_audio_track_name[
                self.current_audio_index]
        self.current_audio_set_default[
            self.current_audio_index] = self.default_audio_set_default[
                self.current_audio_index]
        self.current_audio_set_forced[
            self.current_audio_index] = self.default_audio_set_forced[
                self.current_audio_index]

        self.audio_language_comboBox.setCurrentIndex(
            AllAudiosLanguages.index(
                self.current_audio_language[self.current_audio_index]))
        self.audio_delay_spin.setValue(
            float(self.current_audio_delay[self.current_audio_index]))
        self.audio_track_name_lineEdit.setText(
            self.current_audio_track_name[self.current_audio_index])
        self.audio_set_default_checkBox.setChecked(
            bool(self.current_audio_set_default[self.current_audio_index]))
        self.audio_set_forced_checkBox.setChecked(
            bool(self.current_audio_set_forced[self.current_audio_index]))

    def audio_set_default_disable(self):
        self.audio_set_default_checkBox.setDisabled(True)

    def audio_set_forced_disable(self):
        self.audio_set_forced_checkBox.setDisabled(True)

    def setup_tool_tip_hint_audio_set_default(self):
        if self.audio_set_default_checkBox.isEnabled():
            self.audio_set_default_checkBox.setToolTip(
                "<nobr>set this audio to be the default audio track "
                "when play")
            self.audio_set_default_checkBox.setToolTipDuration(12000)
        else:
            self.audio_set_default_checkBox.setToolTip(
                "<nobr>set this audio to be the default audio track when play<br><b>Disabled</b> because "
                "option "
                "<b>make this audio default</b> is enabled on mux setting tab "
            )
            self.audio_set_default_checkBox.setToolTipDuration(12000)

    def setup_tool_tip_hint_audio_set_forced(self):
        if self.audio_set_forced_checkBox.isEnabled():
            self.audio_set_forced_checkBox.setToolTip(
                "<nobr>set this audio to be the forced audio track when "
                "play")
            self.audio_set_forced_checkBox.setToolTipDuration(12000)
        else:
            self.audio_set_forced_checkBox.setToolTip(
                "<nobr>set this audio to be the forced audio track when play<br><b>Disabled</b> because "
                "option "
                "<b>make this audio default and forced</b> is enabled on mux setting tab "
            )
            self.audio_set_forced_checkBox.setToolTipDuration(12000)

    def update_current_audio_index(self, new_index):
        self.current_audio_index = new_index
        self.audio_delay_spin.setValue(
            float(self.current_audio_delay[self.current_audio_index]))
        self.audio_set_default_checkBox.setChecked(
            bool(self.current_audio_set_default[self.current_audio_index]))
        self.audio_set_forced_checkBox.setChecked(
            bool(self.current_audio_set_forced[self.current_audio_index]))
        self.audio_language_comboBox.setCurrentIndex(
            AllAudiosLanguages.index(
                self.current_audio_language[self.current_audio_index]))
        self.audio_track_name_lineEdit.setText(
            self.current_audio_track_name[self.current_audio_index])
        self.audio_name_value.setText(
            str(self.current_audio_name[self.current_audio_index]))

    def execute(self):
        self.exec_()
Beispiel #25
0
class MainWindow(QMainWindow, design.Ui_MainWindow):
    def __init__(self):
        super().__init__()
        self.setupUi(self)
        # Уточнить необходимость наличия (если краши - удалить)
        self.dialog = None

        self.spinbox = []
        self.doublebox = []
        self.box_row = None

        self.baseInfo.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self.resultTable.setEditTriggers(QAbstractItemView.NoEditTriggers)

        self.addItemBtn.clicked.connect(self.add_new_element)
        self.addXlsBtn.clicked.connect(self.input_database)
        self.changeItemBtn.clicked.connect(self.change_item)
        self.deleteBaseBtn.clicked.connect(self.delete_base)
        self.deleteItemBtn.clicked.connect(self.delete_item_from_base)
        self.printBtn.clicked.connect(self.print_specification)
        self.workBtn.clicked.connect(self.upd_result_table)

        self.BDBox.currentTextChanged.connect(self.show_chosen_bd)
        self.categoryBox.currentTextChanged.connect(self.current_text_changed)
        self.countryBox.currentTextChanged.connect(self.current_text_changed)

        self.listWidget.itemChanged.connect(self.item_selection_changed)

        self.create_bd.triggered.connect(self.clear_all)
        self.exit_action.triggered.connect(sys.exit)
        self.load_bd.triggered.connect(self.load_xls)
        self.save_bd.triggered.connect(self.save_xls)

        self.baseInfo.cellClicked.connect(self.signal_delete)

        self.ru_lang.toggled.connect(self.change_language_in_table)

    def add_new_element(self):
        # Вызывает класс добавления нового элемента и, если успешно, обновляет отображаемую таблицу
        index_base = self.BDBox.currentIndex()
        self.dialog = AddElementClass([index_base])

        if self.dialog.exec() == QDialog.Accepted:
            self.show_chosen_bd()

    def algo(self, df):
        # bug скорее всего, достаточно заменить название на RU, роли не играет же?
        df['Итог'] = 1

        sum_res = self.maxValue.text()
        sum_res = float(sum_res.replace(",", "."))

        for item in range(len(df['цена $'])):
            if ',' in str(df['цена $'].iloc[item]):
                df['цена $'].iloc[item] = str(df['цена $'].iloc[item]).replace(',', '.')
        df['цена $'] = df['цена $'].astype(float)
        df['Количество'] = df['Количество'].astype(int)
        items_ratio = 3.5  # Допустимое отношение максимального кол-ва элементов к минимальному
        price_ratio = 0.01  # Допустимое различие между заданной и полученной суммой

        finish = True
        max_item = sys.maxsize
        max_name = None

        # Проверка на достижимость суммы выбранным поддатасетом?
        df['total'] = df['Количество'] * df['цена $']
        agg = df['total'].aggregate(np.sum)
        if agg < sum_res:
            print("Ne, dont work")  # Соответственно, вопрос к обработке
            return

        agg = df['цена $'].aggregate(np.sum)
        if agg > sum_res:
            print("Always too much")
            return

        prob = LpProblem('Sell', LpMaximize)  # Objective function

        if self.ru_lang.isChecked():
            inv_items = list(df['Название RU'])  # Variable name
        else:
            inv_items = list(df['Name EN'])
        inv_items.sort()

        items = dict(zip(inv_items, df['Количество']))  # Function
        prices = dict(zip(inv_items, df['цена $']))

        inv_vars = LpVariable.dicts('Var', inv_items, lowBound=1, cat='Integer')

        prob += lpSum([prices[i] * inv_vars[i] for i in inv_items])
        prob += lpSum([prices[i] * inv_vars[i] for i in inv_items]) <= sum_res, 'Общая функция'

        for val in items:
            prob += inv_vars[val] <= items[val], val+' Demand'

        # values_check = [None,None]
        values_list = []
        old_values = None
        while finish:
            if max_name:
                prob += inv_vars[max_name] <= max_item

            prob.solve()
            values_list = []

            print('The optimal answer\n'+'-'*70)
            for v in prob.variables():
                item = v.varValue
                if item > 0:
                    values_list.append(item)

            if len(values_list) < len(inv_items):
                '''
                Причина, почему просто вернуть:
                Можно было бы обработать, но продавать одновременно одни и те же
                позиции, но с разными ценами/кол-вом - бред. Поэтому просто
                вернуть и не обращать внимания.
                '''
                print("баг с одинаковыми названиями")
                return

            max_item = max(values_list) - 1
            balance = float(max_item / min(values_list))
            max_name = inv_items[values_list.index(max(values_list))]
            print(values_list, max_item, max_name)

            t = list(prices.values())

            if len(values_list) == len(t):
                price_res = [t[i] * values_list[i] for i in range(len(t))]
                price_res = sum(price_res)

            else:
                # В настройках функции стоит, что минимум должен быть один элемент.
                # Если уже меньше, чем нужно - то значит 100% ошибка. Значит выходим.
                print("Обыграть перевызов функции с новым датасетом")
                return

            if old_values == values_list:
                '''
                Тут два варианта развития событий:
                1. Утыкаемся в невозможность на второй итерации. Во времени
                не теряем, так что можно и вывести результаты.
                2. Важнее, но маловероятнее - если долго считаем и упираемся.
                Обидно терять прогресс, лучше вывести что получилось.
                '''
                print("Stop NOW")
                if price_res < sum_res:
                    finish = False
                else:
                    return

            self.result_label.setText(str(('%.2f' % price_res)))
            curr_price_ratio = (sum_res - price_res) / sum_res
            if balance <= items_ratio and curr_price_ratio <= price_ratio:
                finish = False

            old_values = values_list

        print("Ответ:") # пока что, для отладки
        for i in range(len(values_list)):
            print(inv_items[i], ":, ", values_list[i])

        return values_list

    def change_item(self):
        #name = self.BDBox.currentText()
        #index_base = listOfXls.loc[listOfXls['Name'] == name]
        #index_base = index_base.index[0]
        index_base = self.return_index()
        #index_base = self.BDBox.currentIndex()

        index_row = self.baseInfo.currentRow()
        modified_row = base[index_base].loc[index_row]
        modified_row = modified_row.to_list()
        modified_row.insert(0, index_base)
        modified_row.insert(1, index_row)

        self.dialog = AddElementClass(modified_row)

        if self.dialog.exec() == QDialog.Accepted:
            self.show_chosen_bd()
            self.changeItemBtn.setEnabled(False)
            self.deleteItemBtn.setEnabled(False)

    def change_language_in_table(self):
        # Переключает язык в базе, если там есть элементы
        self.changeItemBtn.setEnabled(False)
        self.deleteItemBtn.setEnabled(False)
        if self.baseInfo.rowCount() > 0:
            self.show_chosen_bd()
        return

    def clear_all(self):
        # Функция для создания новой сессии
        global listOfXls
        self.categoryBox.blockSignals(True)
        self.countryBox.blockSignals(True)
        self.BDBox.blockSignals(True)

        if len(base) > 0:
            msg = QMessageBox.question(self, "Новая сессия",
                                             "Вы действительно хотите начать работу с новой базой?",
                                             QMessageBox.Yes | QMessageBox.No)

            if msg == QMessageBox.No:
                self.categoryBox.blockSignals(False)
                self.countryBox.blockSignals(False)
                self.BDBox.blockSignals(False)
                return

        base.clear()
        listOfXls = listOfXls[0:0]
        countries.clear()
        categories.clear()
        self.update_boxes()
        self.update_list(listOfXls)
        self.categoryBox.blockSignals(False)
        self.countryBox.blockSignals(False)
        self.BDBox.setEnabled(False)
        self.BDBox.blockSignals(False)

        self.workBtn.setEnabled(False)
        self.printBtn.setEnabled(False)
        self.deleteBaseBtn.setEnabled(False)
        self.addItemBtn.setEnabled(False)
        self.changeItemBtn.setEnabled(False)
        self.deleteItemBtn.setEnabled(False)
        self.langBox.setEnabled(False)
        self.baseInfo.setRowCount(0)

        self.resultTable.setRowCount(0)
        self.positions.setValue(1)
        self.maxValue.setValue(1.00)
        self.result_label.setText("")

    def current_text_changed(self):
        # Переопределение функции выбранного элемента комбобокса
        # для заполнения срезами таблицы listOfXls
        print("changed")
        current_category = self.categoryBox.currentText()
        current_country = self.countryBox.currentText()

        temp_df = listOfXls  # проверить насчёт копии

        if current_category != 'Все':
            temp_df = temp_df[temp_df.Category.astype(str).str.contains(current_category)]
        if current_country != 'Все':
            temp_df = temp_df[temp_df.Country.astype(str).str.contains(current_country)]
        temp_df = temp_df.reset_index()
        if not temp_df.empty:
            self.update_list(temp_df)
        else:
            self.listWidget.clear()

        self.update_bd_list(temp_df)
        return
        # Нумерация остаётся как взяли (т.е. 2,5... вместо 0,1...). Норм?

    def delete_base(self):
        global listOfXls
        msg = QMessageBox.question(self, 'Удаление базы',
                                   'Вы действительно хотите удалить эту базу?',
                                   QMessageBox.Yes | QMessageBox.No)

        if msg == QMessageBox.Yes:
            index_base = self.BDBox.currentIndex() - 1
            base.pop(index_base)
            listOfXls.drop(index_base, inplace=True)
            listOfXls = listOfXls.reset_index(drop=True)
            self.update_boxes()
            self.update_list()
            if len(base) == 0:
                self.workBtn.setEnabled(False)
                self.printBtn.setEnabled(False)

    def delete_item_from_base(self):
        #index_base = self.BDBox.currentIndex()

        #name = self.BDBox.currentText()
        #index_base = listOfXls.loc[listOfXls['Name'] == name]
        #index_base = index_base.index[0]
        index_base = self.return_index()

        index_item = self.baseInfo.currentRow()
        if index_item >= 0:
            base[index_base] = base[index_base].drop(index=index_item).reset_index(drop=True)
            self.baseInfo.removeRow(index_item)
            if len(base[index_base]) == 0:
                self.changeItemBtn.setEnabled(False)
                self.deleteItemBtn.setEnabled(False)

    def input_database(self):
        self.dialog = InputDBClass()
        # Блокируем сигналы, чтобы нормально заполнялись боксы после их изменений
        self.categoryBox.blockSignals(True)
        self.countryBox.blockSignals(True)

        if self.dialog.exec() == QDialog.Accepted:
            self.update_boxes()
            self.update_list()
            self.BDBox.setEnabled(True)
            self.current_text_changed()
            self.workBtn.setEnabled(True)
            # self.update_bd_list()

        self.categoryBox.blockSignals(False)
        self.countryBox.blockSignals(False)

    def item_selection_changed(self, item):
        # Изменение состояния "включения" в базе (галочки в листбоксе)
        item_index = listOfXls.loc[listOfXls['Name'] == item.text()]
        item_index = item_index.index[0]

        if not item.checkState():
            listOfXls['Activated'].iloc[item_index] = False
        else:
            listOfXls['Activated'].iloc[item_index] = True

    def load_xls(self):
        global base, listOfXls, countries, categories
        self.categoryBox.blockSignals(True)
        self.countryBox.blockSignals(True)

        bd_path = QFileDialog.getOpenFileName(self, "Выберите БД", filter="*.xlsx")
        if bd_path[0]:
            if not listOfXls.empty:
                self.clear_all()
            bd_item = pd.ExcelFile(bd_path[0], engine='openpyxl')
            listOfXls = pd.concat([listOfXls, bd_item.parse('info')]).copy()

            categories = listOfXls.Category.unique().tolist()
            countries = listOfXls.Country.unique().tolist()
            categories = list(map(str, categories))
            countries = list(map(str, countries))
            for it in listOfXls.index:
                name = listOfXls.Name[it]
                new_base = bd_item.parse(name)
                base.append(new_base)
            self.update_list()
            self.update_boxes()
            self.BDBox.setEnabled(True)
            self.workBtn.setEnabled(True)
            self.langBox.setEnabled(True)
            self.current_text_changed()

        self.categoryBox.blockSignals(False)
        self.countryBox.blockSignals(False)

    def print_specification(self):
        doc = DocxTemplate("Data\Templates\spec_template.docx")
        date = datetime.today().strftime('%d.%m.%Y')
        tbl_contents = []

        num_rows = self.resultTable.rowCount()
        for i in range(num_rows):
            label = i+1
            name = self.resultTable.item(i, 0).text()
            piece = "шт/ рс"
            #nums = self.resultTable.item(i, 1).text()
            nums = self.spinbox[i].value()
            #price = self.resultTable.item(i, 2).text()
            price = self.doublebox[i].value()
            total = self.resultTable.item(i, 3).text()
            tbl_contents.append({'label': label, 'cols': [name, piece, nums, price, total]})

        total_price = ('%.2f' % float(self.result_label.text()))
        context = {'date': date,
                   'total_price': total_price,
                   'tbl_contents': tbl_contents}
        doc.render(context)

        doc_result = QFileDialog.getSaveFileName(self, "Сохраните файл:", filter="*.docx")
        if doc_result[0]:
            doc.save(doc_result[0])
            QMessageBox.information(self, "Сохранение файла", "Файл сохранён успешно!")
            os.startfile(doc_result[0])

    def return_index(self):
        name = self.BDBox.currentText()
        index_base = listOfXls.loc[listOfXls['Name'] == name]
        index_base = index_base.index[0]
        return index_base

    def save_xls(self):
        if not base:  # base = []
            return
        xls_result = QFileDialog.getSaveFileName(self, "Сохраните файл:", filter="*.xlsx")
        if xls_result[0]:
            with pd.ExcelWriter(xls_result[0]) as writer:
                listOfXls.to_excel(writer, sheet_name='info', index=False)
                for table in range(len(base)):
                    table_name = listOfXls.iloc[table].Name
                    base[table].to_excel(writer, sheet_name=table_name, index=False)

    def set_item(self, item, status):
        # Пока просто выставляет пустой элемент, переписать под заполнение
        # из класса InputDBClass

        # Каким образом - смотреть, что выбрано в категориях и делать срез
        # с пандаса и вставлять его поэлементно
        # Подумать на тему того, чтобы изначально был пункт "Все",
        # и как это красиво можно связать со структурами
        # (в плане редактирования, чтобы не удалить случайно)
        checkbox_item = QListWidgetItem(item)
        checkbox_item.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsEnabled)
        if status:
            checkbox_item.setCheckState(Qt.Checked)
        else:
            checkbox_item.setCheckState(Qt.Unchecked)
        self.listWidget.addItem(checkbox_item)

    def show_chosen_bd(self):
        name = self.BDBox.currentText()
        index = listOfXls.loc[listOfXls['Name'] == name]
        if not index.empty:
            self.deleteBaseBtn.setEnabled(True)
            self.addItemBtn.setEnabled(True)
            self.langBox.setEnabled(True)
            self.baseInfo.setRowCount(0)
            self.baseInfo.clearContents()
            self.baseInfo.horizontalHeader().setSectionResizeMode(0, QHeaderView.Stretch)
            self.baseInfo.setColumnWidth(1, 80)
            self.baseInfo.setColumnWidth(2, 80)
            index = index.index[0]
            target = base[index]
            for i, row in target.iterrows():
                # row_name = row['Название']
                if self.ru_lang.isChecked():
                    row_name = row['Название RU']
                else:
                    row_name = row['Name EN']
                row_number = str(int(row['Количество']))

                row_price = str(row['цена $'])
                if "," in row_price:
                    row_price = str(row['цена $']).replace(',', '.')
                    row_price = float(row_price)
                    row_price = str(f"{row_price:.{2}f}")
                row_price = row_price.replace('.', ',')
                self.baseInfo.setRowCount(i+1)
                self.baseInfo.setItem(i, 0, QTableWidgetItem(row_name))
                self.baseInfo.setItem(i, 1, QTableWidgetItem(row_number))
                self.baseInfo.setItem(i, 2, QTableWidgetItem(row_price))
        else:
            self.baseInfo.setRowCount(0)
            self.changeItemBtn.setEnabled(False)
            self.deleteItemBtn.setEnabled(False)
            self.deleteBaseBtn.setEnabled(False)
            self.addItemBtn.setEnabled(False)

    def signal_delete(self):
        # Фиксирует сигнал нажатие на элемент подбазы и даёт доступ
        # на редактирование\удаление элемента (если было недоступно)
        self.changeItemBtn.setEnabled(True)
        self.deleteItemBtn.setEnabled(True)

    def update_bd_list(self, df):
        # Функция для обновления списка баз в комбобоксе BDBox (самый правый)
        self.categoryBox.blockSignals(True)

        self.BDBox.clear()
        self.BDBox.addItem("Выберите базу...")
        name = df['Name']
        if not name.empty:
            self.BDBox.setEnabled(True)
            for item in name:
                self.BDBox.addItem(item)
        else:
            self.BDBox.setEnabled(False)
        self.categoryBox.blockSignals(False)

    def update_boxes(self):
        # Функция, вызываемая при добавлении\изменении категориальных признаков
        # для обновления списков на главном окне
        self.categoryBox.clear()
        self.categoryBox.addItem("Все")

        for item in categories:
            self.categoryBox.addItem(item)

        self.countryBox.clear()
        self.countryBox.addItem("Все")
        for item in countries:
            self.countryBox.addItem(item)

    def update_list(self, *args):
        # Функция для вывода списка (по фильтрам)
        if args:
            db = args[0]
        else:
            db = listOfXls

        self.listWidget.clear()
        for item in range(len(db)):
            name = db.loc[item].Name
            status = db.loc[item].Activated
            self.set_item(name, status)

    def upd_result_table(self):
        """
        В чём суть: сначала формируем общий фрейм, откуда брать вырезку.
        Вырезка берётся как df.sample(n=count), count = число позиций
        Соответственно, берем только те, которые "включены"
        И, наверное, добавляется столбец с индексом/названием, чтобы потом
        взять и изменить количество при необходимости в исходном месте

        Сразу же: надо сделать так, чтобы нельзя было запросить позиций
        больше, чем есть в проге. То есть, сейчас лимит 20, а пускай у нас
        лежит 5 позиций. И чтобы не было такого, мол max = размер если меньше 20.

        """
        self.spinbox = []
        self.doublebox = []

        activated = listOfXls[listOfXls.Activated.astype(str).str.contains('True')]
        if activated.empty:
            QMessageBox.warning(self, "Ошибка", "Ни одна база не активна", QMessageBox.Ok)
            return
        self.result_label.setText("")
        finish_alg = False
        count_tries = 0
        while not finish_alg:
            if count_tries > 20:
                QMessageBox.warning(self, "Ошибка", "Не считается:( Возможно, что-то не так с данными", QMessageBox.Ok)
                self.printBtn.setEnabled(False)
                return

            full_list = base[0].copy()
            full_list = full_list[0:0]
            for it in activated.index:
                full_list = pd.concat([full_list, base[it]]).copy()

            count = int(self.positions.text())

            # print(full_list.sample(n = count))
            self.resultTable.setRowCount(0)

            if len(full_list) < count:
                QMessageBox.warning(self, "Ошибка", "Позиций в базах меньше, чем запрашивается", QMessageBox.Ok)
                return

            full_list = full_list.sample(n=count)
            # Возможно, индексы надо и сохранить, но тут хз
            full_list = full_list.reset_index()



            # А тут идёт блок расчётов, потому что заполнять нужно с итогом
            '''
            import tkinter.messagebox
            try:
                nums = self.algo(full_list)
                tkinter.messagebox.showinfo('Save', 'Ok')
            except:
                import traceback
                tkinter.messagebox.showinfo("ERROR", traceback.format_exc())
                raise
            '''
            nums = self.algo(full_list)
            if nums:
                for i, row in full_list.iterrows():
                    row_name = row['Название RU'] + '/' + row['Name EN']
                    row_number = str(int(nums[i]))

                    row_price = str('%.2f' % row['цена $'])

                    total = row['цена $']*int(nums[i])
                    total = ('%.2f' % total)
                    row_total = str(total)

                    self.resultTable.setRowCount(self.resultTable.rowCount() + 1)
                    self.resultTable.setItem(i, 0, QTableWidgetItem(row_name))
                    # self.resultTable.setItem(i, 1, QTableWidgetItem(row_number))
                    # self.resultTable.setItem(i, 2, QTableWidgetItem(row_price))

                    self.cur_sbox = QSpinBox()
                    self.cur_sbox.setMinimum(1)
                    self.cur_sbox.setMaximum(99999)
                    self.cur_sbox.setValue(int(row_number))
                    self.resultTable.setCellWidget(i, 1, self.cur_sbox)
                    self.spinbox.append(self.cur_sbox)

                    self.cur_dbox = QDoubleSpinBox()
                    self.cur_dbox.setMaximum(99999999.99)
                    self.cur_dbox.setValue(float(row_price))
                    self.resultTable.setCellWidget(i, 2, self.cur_dbox)
                    self.doublebox.append(self.cur_dbox)

                    self.cur_sbox.valueChanged.connect(self.spins_changed)
                    self.cur_dbox.valueChanged.connect(self.spins_changed)

                    self.resultTable.setItem(i, 3, QTableWidgetItem(row_total))

                finish_alg = True
            count_tries += 1
        self.printBtn.setEnabled(True)
        self.resultTable.horizontalHeader().setSectionResizeMode(0, QHeaderView.Stretch)
        self.resultTable.setColumnWidth(1, 80)
        self.resultTable.setColumnWidth(2, 80)
        self.resultTable.setColumnWidth(3, 80)

    def spins_changed(self, value):
        row = self.resultTable.currentRow()
        num = self.spinbox[row].value()
        price = self.doublebox[row].value()
        total = int(num) * float(price)
        total = ('%.2f' % total)
        row_total = str(total)
        self.resultTable.setItem(row, 3, QTableWidgetItem(row_total))

        sum_price = 0
        for i in range(0, self.resultTable.rowCount()):
            price_pack = self.resultTable.item(i, 3)
            price_pack = price_pack.text()
            sum_price += float(price_pack)
        self.result_label.setText(str(sum_price))
        '''
Beispiel #26
0
class PrenotazioneForm(QDialog):
    '''
    Widget per l'inserimento di un acquisto immediato.
    '''
    def __init__(self, conn):
        '''
        Parameters:
            conn : connection
                Connection to the database.
        '''
        super().__init__()
        self.setWindowTitle('Aggiungi Prenotazione')
        self.setWindowFlag(QtCore.Qt.WindowContextHelpButtonHint, False)

        self.conn = conn
        self.cursor = conn.cursor()

        self.books = dict()
        self.books_qt = dict()

        self.gen_layout = QVBoxLayout()

        self.form_layout = QFormLayout()
        self.form_layout.setRowWrapPolicy(QFormLayout.WrapLongRows)

        # Widgets
        self.client_field = QLineEdit()
        self.form_layout.addRow('Numero Cliente: ', self.client_field)

        self.dip_field = QLineEdit()
        self.form_layout.addRow('Dipendente (CF): ', self.dip_field)

        self.date_picker = QDateEdit(QDate.currentDate())
        self.date_picker.setDisplayFormat("MM/dd/yyyy")
        self.date_picker.setCalendarPopup(True)
        self.form_layout.addRow('Data (mm/gg/aaaa): ', self.date_picker)

        self.importo_field = QDoubleSpinBox()
        self.importo_field.setMaximum(999999999.99)
        self.form_layout.addRow('Importo: ', self.importo_field)

        self.ins_layout = QHBoxLayout()
        self.libro_field = QLineEdit()
        self.quantita_field = QSpinBox()
        self.quantita_field.setMinimum(1)
        self.libro_button = QPushButton('Aggiungi')
        self.libro_button.clicked.connect(self.aggiungi_libro)
        self.ins_layout.addWidget(QLabel('Libro (ISBN): '))
        self.ins_layout.addWidget(self.libro_field)
        self.ins_layout.addWidget(QLabel('Quantità: '))
        self.ins_layout.addWidget(self.quantita_field)
        self.ins_layout.addWidget(self.libro_button)

        self.labels = ['ISBN', 'Quantità', '']
        self.table = QTableWidget(0, 3)
        self.table.setHorizontalHeaderLabels(self.labels)
        self.table.horizontalHeader().setSectionResizeMode(
            0, QHeaderView.Stretch)
        self.table.horizontalHeader().setSectionResizeMode(
            1, QHeaderView.Stretch)
        self.table.horizontalHeader().setSectionResizeMode(
            2, QHeaderView.ResizeToContents)

        self.info_label = QLabel(
            'Le prenotazioni non riducono i libri disponibili.')
        self.confirm_button = QPushButton('Conferma')
        self.confirm_button.clicked.connect(self.post_prenotazione)

        self.gen_layout.addLayout(self.form_layout)
        self.gen_layout.addLayout(self.ins_layout)
        self.gen_layout.addWidget(self.table)
        self.gen_layout.addWidget(self.info_label)
        self.gen_layout.addWidget(self.confirm_button)
        self.setLayout(self.gen_layout)

    def aggiungi_libro(self):
        self.books[self.libro_field.text()] = self.quantita_field.value()
        self.update_table()

    def remove_libro(self, book):
        self.books.pop(book, None)
        self.update_table()

    def update_table(self):
        self.table.clearContents()
        while self.table.rowCount() > 0:
            self.table.removeRow(0)
        for book in self.books.keys():
            row = self.table.rowCount()
            button = QPushButton('Rimuovi')
            button.clicked.connect(lambda: self.remove_libro(book))
            self.table.insertRow(row)
            self.table.setItem(row, 0, QTableWidgetItem(book))
            self.table.setItem(row, 1, QTableWidgetItem(str(self.books[book])))
            self.table.setCellWidget(row, 2, button)

    def verif_libri(self):
        '''
        Shows error messages based on the validity of inserted books, and
        returns False, or returns True if all the books are ok to sell right now.
        '''
        if len(self.books.items()) == 0:
            self._show_error('Non ci sono libri nell\'acquisto')
            return False
        for book in self.books.keys():
            self.cursor.execute('SELECT * FROM libro WHERE isbn = %s',
                                (book, ))
            result = self.cursor.fetchall()
            if not result:
                self._show_error('\'{}\' non è un libro valido.'.format(book))
                return False
        return True

    def verif_client_dip(self):
        '''
        Returns false and displays and error message if cliente and dipendente are
        not valid tuples in the database, returns true if they are ok
        '''
        cliente = self.client_field.text()
        if not cliente:
            self._show_error('Il Cliente è necessario per una Prenotazione.')
            return False
        if cliente:
            if not cliente.isdigit():
                self._show_error('Il codice del Cliente deve essere numerico.')
                return False
            self.cursor.execute(FIND_CLIENTE, (cliente, ))
            result = self.cursor.fetchall()
            if not result or not result[0][0]:
                self._show_error('Cliente {} non esiste.'.format(cliente))
                return False

        dipendente = self.dip_field.text()
        if not dipendente:
            self._show_error('Il Dipendente non può essere vuoto.')
            return False
        self.cursor.execute(FIND_DIPENDENTE, (dipendente, ))
        result = self.cursor.fetchall()
        if not result or not result[0][0]:
            self._show_error('Dipendente {} non esiste.'.format(dipendente))
            return False
        return True

    def post_prenotazione(self):
        if not self.verif_libri():
            return
        if not self.verif_client_dip():
            return
        cliente = self.client_field.text().strip() if self.client_field.text(
        ).strip() else None
        importo = self.importo_field.value()
        self.cursor.execute(
            INSERT_ACQUISTO,
            (self.date_picker.date().toString('MM/dd/yyyy'),
             self.importo_field.value(), self.dip_field.text()))
        new_id = self.cursor.fetchall()[0][0]
        self.cursor.execute(INSERT_PRENOTAZIONE, (new_id, cliente))
        for book in self.books.keys():
            self.cursor.execute(INSERT_COMPRENDE,
                                (new_id, book, self.books[book]))
        self.conn.commit()
        self._show_confirm()
        self.accept()

    def _show_confirm(self):
        dialog = QMessageBox()
        dialog.setWindowTitle('Conferma Prenotazione')
        msg = 'Registrato prenotazione per i seguenti libri:\n{}'
        dialog.setText(
            msg.format('\n'.join(
                ['{} x {}'.format(k, v) for k, v in self.books.items()])))
        dialog.exec_()

    def _show_error(self, msg=''):
        dialog = QMessageBox()
        dialog.setWindowTitle('ERRORE')
        dialog.setText(msg)
        dialog.exec_()
Beispiel #27
0
class MainWidget(QWidget):
    def __init__(self, parent=None):
        super(MainWidget, self).__init__(parent)
        self.chart = QtCharts.QChart()
        self.series = QtCharts.QBarSeries()

        self.main_layout = QGridLayout()
        self.button_layout = QGridLayout()
        self.font_layout = QFormLayout()

        self.font_size = QDoubleSpinBox()

        self.legend_posx = QDoubleSpinBox()
        self.legend_posy = QDoubleSpinBox()
        self.legend_width = QDoubleSpinBox()
        self.legend_height = QDoubleSpinBox()

        self.detach_legend_button = QPushButton("Toggle attached")
        self.detach_legend_button.clicked.connect(self.toggle_attached)
        self.button_layout.addWidget(self.detach_legend_button, 0, 0)

        self.add_set_button = QPushButton("add barset")
        self.add_set_button.clicked.connect(self.add_barset)
        self.button_layout.addWidget(self.add_set_button, 2, 0)

        self.remove_barset_button = QPushButton("remove barset")
        self.remove_barset_button.clicked.connect(self.remove_barset)
        self.button_layout.addWidget(self.remove_barset_button, 3, 0)

        self.align_button = QPushButton("Align (Bottom)")
        self.align_button.clicked.connect(self.set_legend_alignment)
        self.button_layout.addWidget(self.align_button, 4, 0)

        self.bold_button = QPushButton("Toggle bold")
        self.bold_button.clicked.connect(self.toggle_bold)
        self.button_layout.addWidget(self.bold_button, 8, 0)

        self.italic_button = QPushButton("Toggle italic")
        self.italic_button.clicked.connect(self.toggle_italic)
        self.button_layout.addWidget(self.italic_button, 9, 0)

        self.legend_posx.valueChanged.connect(self.update_legend_layout)
        self.legend_posy.valueChanged.connect(self.update_legend_layout)
        self.legend_width.valueChanged.connect(self.update_legend_layout)
        self.legend_height.valueChanged.connect(self.update_legend_layout)

        legend_layout = QFormLayout()
        legend_layout.addRow("HPos", self.legend_posx)
        legend_layout.addRow("VPos", self.legend_posy)
        legend_layout.addRow("Width", self.legend_width)
        legend_layout.addRow("Height", self.legend_height)

        self.legend_settings = QGroupBox("Detached legend")
        self.legend_settings.setLayout(legend_layout)
        self.button_layout.addWidget(self.legend_settings)
        self.legend_settings.setVisible(False)

        # Create chart view with the chart
        self.chart_view = QtCharts.QChartView(self.chart, self)

        # Create spinbox to modify font size
        self.font_size.setValue(self.chart.legend().font().pointSizeF())
        self.font_size.valueChanged.connect(self.font_size_changed)

        self.font_layout.addRow("Legend font size", self.font_size)

        # Create layout for grid and detached legend
        self.main_layout.addLayout(self.button_layout, 0, 0)
        self.main_layout.addLayout(self.font_layout, 1, 0)
        self.main_layout.addWidget(self.chart_view, 0, 1, 3, 1)
        self.setLayout(self.main_layout)

        self.create_series()

    def create_series(self):
        self.add_barset()
        self.add_barset()
        self.add_barset()
        self.add_barset()

        self.chart.addSeries(self.series)
        self.chart.setTitle("Legend detach example")
        self.chart.createDefaultAxes()

        self.chart.legend().setVisible(True)
        self.chart.legend().setAlignment(Qt.AlignBottom)

        self.chart_view.setRenderHint(QPainter.Antialiasing)

    def show_legend_spinbox(self):
        self.legend_settings.setVisible(True)
        chart_viewrect = self.chart_view.rect()

        self.legend_posx.setMinimum(0)
        self.legend_posx.setMaximum(chart_viewrect.width())
        self.legend_posx.setValue(150)

        self.legend_posy.setMinimum(0)
        self.legend_posy.setMaximum(chart_viewrect.height())
        self.legend_posy.setValue(150)

        self.legend_width.setMinimum(0)
        self.legend_width.setMaximum(chart_viewrect.width())
        self.legend_width.setValue(150)

        self.legend_height.setMinimum(0)
        self.legend_height.setMaximum(chart_viewrect.height())
        self.legend_height.setValue(75)

    def hideLegendSpinbox(self):
        self.legend_settings.setVisible(False)

    def toggle_attached(self):
        legend = self.chart.legend()
        if legend.isAttachedToChart():
            legend.detachFromChart()
            legend.setBackgroundVisible(True)
            legend.setBrush(QBrush(QColor(128, 128, 128, 128)))
            legend.setPen(QPen(QColor(192, 192, 192, 192)))

            self.show_legend_spinbox()
            self.update_legend_layout()
        else:
            legend.attachToChart()
            legend.setBackgroundVisible(False)
            self.hideLegendSpinbox()
        self.update()

    def add_barset(self):
        series_count = self.series.count()
        bar_set = QtCharts.QBarSet("set {}".format(series_count))
        delta = series_count * 0.1
        bar_set.append([1 + delta, 2 + delta, 3 + delta, 4 + delta])
        self.series.append(bar_set)

    def remove_barset(self):
        sets = self.series.barSets()
        len_sets = len(sets)
        if len_sets > 0:
            self.series.remove(sets[len_sets - 1])

    def set_legend_alignment(self):
        button = self.sender()
        legend = self.chart.legend()
        alignment = legend.alignment()

        if alignment == Qt.AlignTop:
            legend.setAlignment(Qt.AlignLeft)
            if button:
                button.setText("Align (Left)")
        elif alignment == Qt.AlignLeft:
            legend.setAlignment(Qt.AlignBottom)
            if button:
                button.setText("Align (Bottom)")
        elif alignment == Qt.AlignBottom:
            legend.setAlignment(Qt.AlignRight)
            if button:
                button.setText("Align (Right)")
        else:
            if button:
                button.setText("Align (Top)")
            legend.setAlignment(Qt.AlignTop)

    def toggle_bold(self):
        legend = self.chart.legend()
        font = legend.font()
        font.setBold(not font.bold())
        legend.setFont(font)

    def toggle_italic(self):
        legend = self.chart.legend()
        font = legend.font()
        font.setItalic(not font.italic())
        legend.setFont(font)

    def font_size_changed(self):
        legend = self.chart.legend()
        font = legend.font()
        font_size = self.font_size.value()
        if font_size < 1:
            font_size = 1
        font.setPointSizeF(font_size)
        legend.setFont(font)

    def update_legend_layout(self):
        legend = self.chart.legend()

        rect = QRectF(self.legend_posx.value(), self.legend_posy.value(),
                      self.legend_width.value(), self.legend_height.value())
        legend.setGeometry(rect)

        legend.update()
class AddIngredientWidget(QWidget):
    def __init__(self, database, parent, ingredient_name):
        QWidget.__init__(self)
        self.add_ingredient_win = QWidget()
        self.add_ingredient_win.setFixedWidth(250)
        self.add_ingredient_win.setWindowTitle("Add Ingredient")
        self.add_ingredient_main_layout = QHBoxLayout()
        self.meal_planner_db = database

        self.setParent(parent)
        self.ingredient_name_input = QLineEdit()
        self.ingredient_name_input.setText(ingredient_name)

        self.calories_spinbox = QDoubleSpinBox()
        self.calories_spinbox.setMaximum(9999)
        self.carbs_spinbox = QDoubleSpinBox()
        self.carbs_spinbox.setMaximum(9999)
        self.sugar_spinbox = QDoubleSpinBox()
        self.sugar_spinbox.setMaximum(9999)
        self.fats_spinbox = QDoubleSpinBox()
        self.fats_spinbox.setMaximum(9999)
        self.protein_spinbox = QDoubleSpinBox()
        self.protein_spinbox.setMaximum(9999)

        self.add_ingredient_btn = QPushButton("Create Ingredient")
        self.add_ingredient_btn.clicked.connect(self.add_ingredient_to_recipe)
        self.cancel_win_btn = QPushButton("Cancel")
        self.cancel_win_btn.clicked.connect(self.add_ingredient_win.close)

        self.left_layout = QVBoxLayout()
        self.left_layout.addWidget(QLabel("Ingredient name"))
        self.left_layout.addWidget(self.ingredient_name_input)
        self.left_layout.addWidget(QLabel("Ingredient nutrition values"))
        self.left_layout.addWidget(QLabel("kCal/100g"))
        self.left_layout.addWidget(self.calories_spinbox)
        self.left_layout.addWidget(QLabel("carb/100g"))
        self.left_layout.addWidget(self.carbs_spinbox)
        self.left_layout.addWidget(QLabel("sugar/100g"))
        self.left_layout.addWidget(self.sugar_spinbox)
        self.left_layout.addWidget(QLabel("fat/100g"))
        self.left_layout.addWidget(self.fats_spinbox)
        self.left_layout.addWidget(QLabel("protein/100g"))
        self.left_layout.addWidget(self.protein_spinbox)
        self.left_layout.addWidget(self.add_ingredient_btn)
        self.left_layout.addWidget(self.cancel_win_btn)

        self.add_ingredient_main_layout.addLayout(self.left_layout)

        self.add_ingredient_win.setLayout(self.add_ingredient_main_layout)
        self.add_ingredient_win.show()

    @Slot()
    def add_ingredient_to_recipe(self):
        self.parent().ingredient_name_input.setText(
            self.ingredient_name_input.text())
        ing_id = self.meal_planner_db.get_table_len("ingredients")
        self.meal_planner_db.add_ingredient(ing_id,
                                            self.ingredient_name_input.text(),
                                            self.calories_spinbox.value(),
                                            self.carbs_spinbox.value(),
                                            self.sugar_spinbox.value(),
                                            self.fats_spinbox.value(),
                                            self.protein_spinbox.value(),
                                            False)

        self.add_ingredient_win.close()
Beispiel #29
0
class SubtitleInfoDialog(QDialog):
    def __init__(self,
                 subtitle_name="Test",
                 subtitle_delay=0.0,
                 subtitle_language=Default_Subtitle_Language,
                 subtitle_track_name="Test",
                 subtitle_set_default=False,
                 subtitle_set_forced=False,
                 subtitle_default_value_delay=0.0,
                 subtitle_default_value_language=Default_Subtitle_Language,
                 subtitle_default_value_track_name="Test",
                 subtitle_default_value_set_default=False,
                 subtitle_default_value_set_forced=False,
                 subtitle_set_default_disabled=False,
                 subtitle_set_forced_disabled=False,
                 disable_edit=False,
                 parent=None):
        super().__init__(parent)
        self.window_title = "Subtitle Info"
        self.state = "no"
        self.messageIcon = QLabel()

        self.disable_edit = disable_edit

        self.current_subtitle_language = str(subtitle_language)
        self.current_subtitle_delay = str(subtitle_delay)
        self.current_subtitle_track_name = str(subtitle_track_name)
        self.current_subtitle_set_default = subtitle_set_default
        self.current_subtitle_set_forced = subtitle_set_forced

        self.default_subtitle_language = str(subtitle_default_value_language)
        self.default_subtitle_delay = str(subtitle_default_value_delay)
        self.default_subtitle_track_name = str(
            subtitle_default_value_track_name)
        self.default_subtitle_set_default = subtitle_default_value_set_default
        self.default_subtitle_set_forced = subtitle_default_value_set_forced

        self.subtitle_set_default_disabled = subtitle_set_default_disabled
        self.subtitle_set_forced_disabled = subtitle_set_forced_disabled

        self.subtitle_name_label = QLabel("Subtitle Name:")
        self.subtitle_name_value = QLabel(str(subtitle_name))

        self.subtitle_delay_label = QLabel("Subtitle Delay:")
        self.subtitle_delay_spin = QDoubleSpinBox()
        self.setup_subtitle_delay_spin()

        self.subtitle_language_label = QLabel("Subtitle Language:")
        self.subtitle_language_comboBox = QComboBox()
        self.setup_subtitle_language_comboBox()

        self.subtitle_track_name_label = QLabel("Subtitle Track Name:")
        self.subtitle_track_name_lineEdit = QLineEdit()
        self.setup_subtitle_track_name_lineEdit()

        self.subtitle_set_forced_label = QLabel("Subtitle Forced State:")
        self.subtitle_set_forced_checkBox = QCheckBox()
        self.setup_subtitle_set_forced_checkBox()

        self.subtitle_set_default_label = QLabel("Subtitle Default State:")
        self.subtitle_set_default_checkBox = QCheckBox()
        self.setup_subtitle_set_default_checkBox()

        self.yes_button = QPushButton("OK")
        self.no_button = QPushButton("Cancel")
        self.reset_button = QPushButton("Reset To Default")

        self.buttons_layout = QHBoxLayout()
        self.subtitle_delay_layout = QHBoxLayout()
        self.subtitle_language_layout = QHBoxLayout()
        self.subtitle_track_name_layout = QHBoxLayout()
        self.subtitle_set_default_layout = QHBoxLayout()
        self.subtitle_set_forced_layout = QHBoxLayout()
        self.buttons_layout.addWidget(QLabel(""), stretch=3)
        self.buttons_layout.addWidget(self.reset_button, stretch=2)
        self.buttons_layout.addWidget(self.yes_button, stretch=2)
        self.buttons_layout.addWidget(self.no_button, stretch=2)
        self.buttons_layout.addWidget(QLabel(""), stretch=3)
        self.subtitle_setting_layout = QGridLayout()
        self.subtitle_changeble_setting_layout = QFormLayout()
        self.subtitle_changeble_setting_layout.addRow(self.subtitle_name_label,
                                                      self.subtitle_name_value)
        self.subtitle_changeble_setting_layout.addRow(
            self.subtitle_track_name_label, self.subtitle_track_name_lineEdit)
        self.subtitle_changeble_setting_layout.addRow(
            self.subtitle_language_label, self.subtitle_language_comboBox)
        self.subtitle_changeble_setting_layout.addRow(
            self.subtitle_delay_label, self.subtitle_delay_spin)
        self.subtitle_changeble_setting_layout.addRow(
            self.subtitle_set_default_label,
            self.subtitle_set_default_checkBox)
        self.subtitle_changeble_setting_layout.addRow(
            self.subtitle_set_forced_label, self.subtitle_set_forced_checkBox)

        self.subtitle_setting_layout.addLayout(
            self.subtitle_changeble_setting_layout, 0, 0, 5, 2)
        self.subtitle_setting_layout.addWidget(self.messageIcon, 0, 3, 5, -1)

        self.main_layout = QGridLayout()
        self.main_layout.addLayout(self.subtitle_setting_layout, 0, 0, 2, 3)
        self.main_layout.addLayout(self.buttons_layout, 2, 0, 1, -1)
        self.main_layout.setContentsMargins(20, 20, 20, 20)
        self.setLayout(self.main_layout)

        self.setup_ui()
        self.signal_connect()

    def setup_ui(self):
        self.disable_question_mark_window()
        self.messageIcon.setPixmap(
            QtGui.QPixmap(GlobalFiles.SubtitleIconPath).scaledToHeight(100))
        self.set_dialog_values()
        self.set_default_buttons()
        if self.subtitle_set_default_disabled:
            self.subtitle_set_default_disable()
        if self.subtitle_set_forced_disabled:
            self.subtitle_set_forced_disable()
        if self.disable_edit:
            self.subtitle_track_name_lineEdit.setEnabled(False)
            self.subtitle_language_comboBox.setEnabled(False)
            self.subtitle_delay_spin.setEnabled(False)
            self.subtitle_set_default_checkBox.setEnabled(False)
            self.subtitle_set_forced_checkBox.setEnabled(False)
            self.reset_button.setEnabled(False)

        self.setup_tool_tip_hint_subtitle_set_default()
        self.setup_tool_tip_hint_subtitle_set_forced()

    def signal_connect(self):
        self.subtitle_track_name_lineEdit.textEdited.connect(
            self.update_current_subtitle_track_name)
        self.subtitle_delay_spin.editingFinished.connect(
            self.update_current_subtitle_delay)
        self.subtitle_language_comboBox.currentTextChanged.connect(
            self.update_current_subtitle_language)
        self.subtitle_set_default_checkBox.stateChanged.connect(
            self.update_current_subtitle_set_default)
        self.subtitle_set_forced_checkBox.stateChanged.connect(
            self.update_current_subtitle_set_forced)
        self.yes_button.clicked.connect(self.click_yes)
        self.no_button.clicked.connect(self.click_no)
        self.reset_button.clicked.connect(self.reset_subtitle_setting)

    def click_yes(self):
        self.state = "yes"
        self.close()

    def click_no(self):
        self.close()

    def set_dialog_values(self):
        self.setWindowTitle(self.window_title)
        self.setWindowIcon(GlobalFiles.InfoSettingIcon)

    def disable_question_mark_window(self):
        self.setWindowFlag(Qt.WindowContextHelpButtonHint, on=False)

    def increase_message_font_size(self, value):
        message_font = self.message.font()
        message_font.setPointSize(self.message.fontInfo().pointSize() + value)
        self.message.setFont(message_font)

    def set_default_buttons(self):
        self.yes_button.setDefault(True)
        self.yes_button.setFocus()

    def showEvent(self, a0: QtGui.QShowEvent) -> None:
        super().showEvent(a0)
        self.setFixedSize(self.size())

    def setup_subtitle_track_name_lineEdit(self):
        self.subtitle_track_name_lineEdit.setClearButtonEnabled(True)
        self.subtitle_track_name_lineEdit.setText(
            self.current_subtitle_track_name)

    def setup_subtitle_language_comboBox(self):
        self.subtitle_language_comboBox.addItems(AllSubtitlesLanguages)
        self.subtitle_language_comboBox.setCurrentIndex(
            AllSubtitlesLanguages.index(self.current_subtitle_language))
        self.subtitle_language_comboBox.setMaxVisibleItems(8)
        self.subtitle_language_comboBox.setStyleSheet(
            "QComboBox { combobox-popup: 0; }")

    def setup_subtitle_delay_spin(self):
        # self.subtitle_delay_spin.setMaximumWidth(screen_size.width() // 16)
        self.subtitle_delay_spin.setDecimals(3)
        self.subtitle_delay_spin.setMinimum(-9999.0)
        self.subtitle_delay_spin.setMaximum(9999.0)
        self.subtitle_delay_spin.setSingleStep(0.5)
        self.subtitle_delay_spin.setValue(float(self.current_subtitle_delay))

    def setup_subtitle_set_default_checkBox(self):
        self.subtitle_set_default_checkBox.setText("Set Default")
        self.subtitle_set_default_checkBox.setChecked(
            bool(self.current_subtitle_set_default))

    def setup_subtitle_set_forced_checkBox(self):
        self.subtitle_set_forced_checkBox.setText("Set Forced")
        self.subtitle_set_forced_checkBox.setChecked(
            bool(self.current_subtitle_set_forced))

    def update_current_subtitle_track_name(self):
        self.current_subtitle_track_name = str(
            self.subtitle_track_name_lineEdit.text())

    def update_current_subtitle_delay(self):
        self.current_subtitle_delay = round(self.subtitle_delay_spin.value(),
                                            5)

    def update_current_subtitle_language(self):
        self.current_subtitle_language = str(
            self.subtitle_language_comboBox.currentText())

    def update_current_subtitle_set_default(self):
        self.current_subtitle_set_default = (
            self.subtitle_set_default_checkBox.checkState() == Qt.Checked)

    def update_current_subtitle_set_forced(self):
        self.current_subtitle_set_forced = (
            self.subtitle_set_forced_checkBox.checkState() == Qt.Checked)

    def reset_subtitle_setting(self):
        self.current_subtitle_language = self.default_subtitle_language
        self.current_subtitle_delay = self.default_subtitle_delay
        self.current_subtitle_track_name = self.default_subtitle_track_name
        self.current_subtitle_set_default = self.default_subtitle_set_default
        self.current_subtitle_set_forced = self.default_subtitle_set_forced

        self.subtitle_language_comboBox.setCurrentIndex(
            AllSubtitlesLanguages.index(self.current_subtitle_language))
        self.subtitle_delay_spin.setValue(float(self.current_subtitle_delay))
        self.subtitle_track_name_lineEdit.setText(
            self.current_subtitle_track_name)
        self.subtitle_set_default_checkBox.setChecked(
            bool(self.current_subtitle_set_default))
        self.subtitle_set_forced_checkBox.setChecked(
            bool(self.current_subtitle_set_forced))

    def subtitle_set_default_disable(self):
        self.subtitle_set_default_checkBox.setDisabled(True)

    def subtitle_set_forced_disable(self):
        self.subtitle_set_forced_checkBox.setDisabled(True)

    def setup_tool_tip_hint_subtitle_set_default(self):
        if self.subtitle_set_default_checkBox.isEnabled():
            self.subtitle_set_default_checkBox.setToolTip(
                "<nobr>set this subtitle to be the default subtitle track "
                "when play")
            self.subtitle_set_default_checkBox.setToolTipDuration(12000)
        else:
            self.subtitle_set_default_checkBox.setToolTip(
                "<nobr>set this subtitle to be the default subtitle track when play<br><b>Disabled</b> because "
                "option "
                "<b>make this subtitle default</b> is enabled on mux setting tab "
            )
            self.subtitle_set_default_checkBox.setToolTipDuration(12000)

    def setup_tool_tip_hint_subtitle_set_forced(self):
        if self.subtitle_set_forced_checkBox.isEnabled():
            self.subtitle_set_forced_checkBox.setToolTip(
                "<nobr>set this subtitle to be the forced subtitle track when "
                "play")
            self.subtitle_set_forced_checkBox.setToolTipDuration(12000)
        else:
            self.subtitle_set_forced_checkBox.setToolTip(
                "<nobr>set this subtitle to be the forced subtitle track when play<br><b>Disabled</b> because "
                "option "
                "<b>make this subtitle default and forced</b> is enabled on mux setting tab "
            )
            self.subtitle_set_forced_checkBox.setToolTipDuration(12000)

    def execute(self):
        self.exec_()
class AddRecipeWidget(QWidget):
    def __init__(self, database, parent):
        QWidget.__init__(self)
        self.recipe_ingredients = 0
        self.add_recipe_win = QWidget()
        self.add_recipe_win.setFixedWidth(400)
        self.add_recipe_win.setWindowTitle("Add Recipe")
        add_rec_main_layout = QVBoxLayout()

        self.meal_planner_db = database
        self.setParent(parent)

        self.rec_name_input = QLineEdit()
        self.rec_desc_input = QLineEdit()
        self.rec_source_input = QLineEdit()
        rec_form_layout = QFormLayout()
        rec_form_layout.addRow(QLabel("Recipe Name"), self.rec_name_input)
        rec_form_layout.addRow(QLabel("Recipe Desc"), self.rec_desc_input)
        rec_form_layout.addRow(QLabel("Recipe Source"), self.rec_source_input)
        add_rec_main_layout.addLayout(rec_form_layout)

        rec_info_layout = QHBoxLayout()
        difficulty_layout = QVBoxLayout()
        self.difficulty_spinbox = QSpinBox()
        self.difficulty_spinbox.setRange(1, 5)
        self.difficulty_spinbox.setValue(3)
        difficulty_layout.addWidget(QLabel("Difficulty"))
        difficulty_layout.addWidget(self.difficulty_spinbox)

        prep_time_layout = QVBoxLayout()
        self.prep_time_spinbox = QSpinBox()
        self.prep_time_spinbox.setRange(0, 600)
        self.prep_time_spinbox.setValue(30)
        prep_time_layout.addWidget(QLabel("Prep. Time (min.)"))
        prep_time_layout.addWidget(self.prep_time_spinbox)

        rating_layout = QVBoxLayout()
        self.rating_spinbox = QSpinBox()
        self.rating_spinbox.setRange(1, 5)
        self.rating_spinbox.setValue(3)
        rating_layout.addWidget(QLabel("Rating"))
        rating_layout.addWidget(self.rating_spinbox)

        rec_info_layout.addLayout(difficulty_layout)
        rec_info_layout.addLayout(prep_time_layout)
        rec_info_layout.addLayout(rating_layout)

        add_rec_main_layout.addLayout(rec_info_layout)

        rec_tags_layout = QHBoxLayout()
        rec_cuisine_layout = QVBoxLayout()
        cuisines = [
            "Mexican", "Swedish", "Austrian", "Italian", "Spanish", "American",
            "British", "Thai", "Greek", "Vietnamese", "Caribbean", "Japanese",
            "Chinese", "Indian", "French", "Swiss", "Portuguese", "Korean",
            "Turkish", "Moroccan", "Russian", "Malaysian", "Philippines",
            "Ethiopian", "Lebanese", "Arab", "Peruvian", "Brazilian", "Asian",
            "Middle Eastern", "South American", "African", "-"
        ]
        cuisines.sort()
        self.tag_cuisine = QComboBox()
        self.tag_cuisine.addItems(cuisines)
        rec_cuisine_layout.addWidget(QLabel("Cuisine"))
        rec_cuisine_layout.addWidget(self.tag_cuisine)
        rec_tags_layout.addLayout(rec_cuisine_layout)

        rec_category_layout = QVBoxLayout()
        categories = [
            "Beef & Calf", "Chicken & Poultry", "Lamb", "Pork", "Preservation",
            "Salad", "Sandwich", "Soup", "Stew", "Pasta", "Rice",
            "Grain & Beans", "Fish & Seafood", "Vegetables", "Eggs & Cheese",
            "BBQ", "Fruits", "Cake & Pie (Sweet)", "Pie", "Bread", "Beverage",
            "Cookies & Sweets", "Sauce", "-"
        ]
        categories.sort()
        self.tag_category = QComboBox()
        self.tag_category.addItems(categories)
        rec_category_layout.addWidget(QLabel("Category"))
        rec_category_layout.addWidget(self.tag_category)
        rec_tags_layout.addLayout(rec_category_layout)

        rec_meal_type_layout = QVBoxLayout()
        meal_types = [
            "Breakfast", "Brunch", "Lunch", "Dinner", "Dessert", "Starter",
            "Side", "Buffet", "Snack", "-"
        ]
        meal_types.sort()
        self.tag_meal_types = QComboBox()
        self.tag_meal_types.addItems(meal_types)
        rec_meal_type_layout.addWidget(QLabel("Meal Type"))
        rec_meal_type_layout.addWidget(self.tag_meal_types)
        rec_tags_layout.addLayout(rec_meal_type_layout)

        add_rec_main_layout.addLayout(rec_tags_layout)

        self.ingredient_name_input = QLineEdit()
        self.ingredient_qty_input = QDoubleSpinBox()
        self.ingredient_qty_input.setValue(1.0)
        self.ingredient_qty_input.setMaximum(1000)
        self.ingredient_qty_input.setDecimals(2)
        self.ingredient_unit_input = QComboBox()
        self.ingredient_unit_input.addItems(
            ["g", "ml", "dl", "l", "msk", "tsk", "st", "-"])
        add_ingredient_btn = QPushButton("Create ingredient")
        add_ingredient_btn.clicked.connect(self.create_ingredient)
        add_ingredient_layout = QHBoxLayout()
        add_rec_main_layout.addWidget(QLabel("Ingredient"))
        add_ingredient_layout.addWidget(self.ingredient_name_input)
        add_ingredient_layout.addWidget(self.ingredient_qty_input)
        add_ingredient_layout.addWidget(self.ingredient_unit_input)
        add_ingredient_layout.addWidget(add_ingredient_btn)
        add_rec_main_layout.addLayout(add_ingredient_layout)

        btn_layout = QHBoxLayout()
        add_ingredient_to_recipe_btn = QPushButton("Add ingredient")
        add_ingredient_to_recipe_btn.clicked.connect(
            self.add_ingredient_to_recipe)

        del_ingredient_from_recipe_btn = QPushButton("Remove ingredient")
        del_ingredient_from_recipe_btn.clicked.connect(
            self.del_ingredient_from_recipe)
        btn_layout.addWidget(add_ingredient_to_recipe_btn)
        btn_layout.addWidget(del_ingredient_from_recipe_btn)
        add_rec_main_layout.addLayout(btn_layout)

        self.rec_ingredient_table = QTableWidget()
        self.rec_ingredient_table.setColumnCount(3)
        self.rec_ingredient_table.setHorizontalHeaderLabels(
            ["Amount", "Unit", "Ingredient"])
        header = self.rec_ingredient_table.horizontalHeader()
        header.setSectionResizeMode(0, QtWidgets.QHeaderView.ResizeToContents)
        header.setSectionResizeMode(1, QtWidgets.QHeaderView.ResizeToContents)
        header.setSectionResizeMode(2, QtWidgets.QHeaderView.Stretch)
        add_rec_main_layout.addWidget(self.rec_ingredient_table)

        self.step_count = 0
        self.add_recipe_step_btn = QPushButton("Add recipe instruction")
        add_rec_main_layout.addWidget(self.add_recipe_step_btn)
        self.add_recipe_step_btn.clicked.connect(self.add_recipe_step_win)
        self.rec_step_table = QTableWidget()
        self.rec_step_table.setColumnCount(1)
        self.rec_step_table.setHorizontalHeaderLabels(["Instructions"])
        self.rec_step_table.setWordWrap(True)
        header = self.rec_step_table.horizontalHeader()
        header.setSectionResizeMode(0, QtWidgets.QHeaderView.Stretch)
        add_rec_main_layout.addWidget(self.rec_step_table)

        bottom_btn_layout = QHBoxLayout()
        self.add_rec_btn = QPushButton("Add recipe")
        self.add_rec_btn.clicked.connect(self.add_recipe_to_db)

        self.back_btn = QPushButton("Cancel")
        self.back_btn.clicked.connect(self.add_recipe_win.close)

        bottom_btn_layout.addWidget(self.add_rec_btn)
        bottom_btn_layout.addWidget(self.back_btn)
        add_rec_main_layout.addLayout(bottom_btn_layout)

        self.add_recipe_win.setLayout(add_rec_main_layout)
        self.add_recipe_win.show()

    @Slot()
    def add_recipe_step_win(self):
        self.rec_step_table.insertRow(self.step_count)
        self.step_count += 1

    @Slot()
    def create_ingredient(self):
        self.add_ingredient_widget = AddIngredientWidget(
            self.meal_planner_db, self, self.ingredient_name_input.text())

    @Slot()
    def add_ingredient_to_recipe(self):
        if self.meal_planner_db.ingredient_exists(
                self.ingredient_name_input.text()):
            self.rec_ingredient_table.insertRow(self.recipe_ingredients)
            self.rec_ingredient_table.setItem(
                self.recipe_ingredients, 0,
                QTableWidgetItem(str(self.ingredient_qty_input.value())))
            self.rec_ingredient_table.setItem(
                self.recipe_ingredients, 1,
                QTableWidgetItem(self.ingredient_unit_input.currentText()))
            self.rec_ingredient_table.setItem(
                self.recipe_ingredients, 2,
                QTableWidgetItem(self.ingredient_name_input.text()))
            self.recipe_ingredients += 1

            self.ingredient_name_input.clear()
            self.ingredient_qty_input.setValue(1.0)
            self.ingredient_unit_input.setCurrentIndex(0)
        else:
            print("Ingredient does not exist in database, please add it first")

    @Slot()
    def del_ingredient_from_recipe(self):
        recipe = self.rec_name_input.text()
        ingredient = self.rec_ingredient_table.currentItem().text()
        self.rec_ingredient_table.removeRow(
            self.rec_ingredient_table.currentRow())
        self.meal_planner_db.del_recipe_ingredient(recipe, ingredient)

    @Slot()
    def add_recipe_to_db(self):
        rec_id = self.meal_planner_db.get_table_len("recipes")
        self.meal_planner_db.add_recipe(rec_id, self.rec_name_input.text(),
                                        self.rec_desc_input.text(),
                                        self.rec_source_input.text(),
                                        self.difficulty_spinbox.value(),
                                        self.prep_time_spinbox.value(),
                                        self.rating_spinbox.value(),
                                        "2000-01-01", 0)

        for row in range(self.rec_ingredient_table.rowCount()):
            qty_id = self.meal_planner_db.get_table_len("measurement_qty")
            qty_id = self.meal_planner_db.add_qty(
                qty_id,
                self.rec_ingredient_table.item(row, 0).text())
            unit_id = self.meal_planner_db.get_table_len("measurement_units")
            unit_id = self.meal_planner_db.add_measurement(
                unit_id,
                self.rec_ingredient_table.item(row, 1).text())
            ing_id = self.meal_planner_db.get_ingredient_id(
                self.rec_ingredient_table.item(row, 2).text())
            if ing_id == -1:
                print("INGREDIENT DOES NOT EXIST! WE F****D UP!")
                break
            self.meal_planner_db.add_recipe_ingredient(rec_id, ing_id, unit_id,
                                                       qty_id)

        for row in range(self.step_count):
            print(row, self.rec_step_table.item(row, 0).text())
            self.meal_planner_db.add_step(
                rec_id, row,
                self.rec_step_table.item(row, 0).text())

        # Cuisine tag
        tag_id = self.meal_planner_db.get_table_len("tags")
        tag_id = self.meal_planner_db.add_tag(tag_id,
                                              self.tag_cuisine.currentText())
        self.meal_planner_db.add_recipe_tag(tag_id, rec_id)
        # Category tag
        tag_id = self.meal_planner_db.get_table_len("tags")
        tag_id = self.meal_planner_db.add_tag(tag_id,
                                              self.tag_category.currentText())
        self.meal_planner_db.add_recipe_tag(tag_id, rec_id)
        # Meal type tag
        tag_id = self.meal_planner_db.get_table_len("tags")
        tag_id = self.meal_planner_db.add_tag(
            tag_id, self.tag_meal_types.currentText())
        self.meal_planner_db.add_recipe_tag(tag_id, rec_id)

        self.rec_name_input.clear()
        self.rec_desc_input.clear()
        self.rec_ingredient_table.setRowCount(0)
        self.rec_step_table.setRowCount(0)
        self.step_count = 0

        self.parent().update_recipe_table()