Exemple #1
0
class Traveller:
    def __init__(self, id, speed):
        self.speed = float(speed)
        self.id = int(id)

        # Časovač opuštění oblasti
        self.timer = QTimer()
        self.timer.timeout.connect(self.done)

        # Cestovatel je aktivní
        self.active = True

    # Cestovatel vstupuje do sledovaného úseku
    def start(self, crossing):
        self.crossing = crossing

        # Časovač je v milisekundách
        self.timer.start(1000 * self.roadLength / self.speed)

    # Obsluha časovače: cestovatel opouští úsek
    def done(self):
        # Pokud jsme se mezitím stihli odpojit a připojit, neposílej nic
        if self.active:
            self.crossing.sendBack(self)

    # Převod argumentů zpět na řetězec
    def strArgs(self):
        return ("id=" + str(self.id) + " speed=" + str(self.speed))

    def position(self):
        return self.roadLength - self.timer.remainingTime() * self.speed / 1000
Exemple #2
0
class Executer:
    def __init__(self, serialObj, loggerObj):
        self.serialPort = serial.Serial()
        self.serialPort = serialObj
        self.logger = loggerObj
        self.log = self.logger.log
        self.execState = ExecState.NotConnected
            

        self.serialTimeoutTimer = QTimer()
        self.serialTimeoutTimer.setSingleShot(True)
        self.serialTimeoutTimer.setInterval(SERIAL_COMMAND_TIMEOUT)

        # self.checkStopRequestTimer = QTimer()
        # self.checkStopRequestTimer.setInterval(500)
        # self.checkStopRequestTimer.setSingleShot(False)
        # self.checkStopRequestTimer.timeout.connect(self.processCheckStopRequest)
        # self.checkStopRequestTimer.start()
        self._stopRequested  = False


    def execute(self, labCode, inputDataFrame, outputFolder, inputFields=None, outputField=None, progressBar=None, model=None):
        # self.logger.disableLogging()

        self.serialPort.flushInput()
        self.serialPort.flushOutput()
        startTime = time.time()

        # progressBar = None
        if progressBar is not None:
            progressBar.setValue(0)
        try:
            if self.execState == ExecState.NotConnected:
                if self.serialPort.isOpen():
                    self.execState = ExecState.Connected
                else:
                    #This should never happen because this function is called after serial is connected
                    self.log("Execution failed because serial port is not open, something is wrong", type="ERROR")
                    return ExecutionResult.FAILED

            if self.execState == ExecState.Connected:
                if self._sendCommand("SELECT_LAB", labCode, max_retry=SERIAL_COMMAND_MAX_TRIALS*4) == FAILURE_CODE:
                        self.log("Error occured with lab selection", type="ERROR")
                        return ExecutionResult.FAILED
                else:
                    self.execState = ExecState.LabSelected
    
            if self.execState == ExecState.LabSelected:
                if model is not None and not model.startswith("RPI:"):
                    self.log("Started sending the model, this could take a while, please wait", type="INFO")
                    if self._sendSaveModelCommand(model) == FAILURE_CODE:
                        self.log("Failed to send the selected model", type="ERROR")
                        return ExecutionResult.FAILED
                else:
                    if not model:
                        modelName = lab_default_models[labCode]
                    elif model.startswith("RPI:"):
                        modelName = model[4:]
                    if self._sendCommand("LOAD_MODEL", modelName, timeout=SERIAL_COMMAND_TIMEOUT*3) == FAILURE_CODE:
                        self.log("Failed to load the required model", type="ERROR")
                        return ExecutionResult.FAILED

                self.execState = ExecState.ModelLoaded

            if self.execState == ExecState.ModelLoaded:
                #load the inputs
                if inputFields is not None:
                    inputs = inputDataFrame[inputFields]
                else:
                    inputs = inputDataFrame
                if outputField:
                    trueOutput = inputDataFrame[outputField]
                else:
                    trueOutput = None
                self.execState = ExecState.Processing

            if self.execState == ExecState.Processing:
                if labCode == "LabTest":
                    executionResult = self._executeLab(inputs, outputFolder,
                        progressBar=progressBar, plotter=None)
                elif labCode == "Lab1":
                    executionResult = self._executeLab(inputs, outputFolder, outputHeader = "Prediction",
                        progressBar=progressBar, plotter=None, trueOutput=trueOutput, labCode=labCode)
                elif labCode == "Lab2":
                    executionResult = self._executeLab(inputs, outputFolder, outputHeader = "Prediction",
                        progressBar=progressBar, plotter=None, trueOutput=trueOutput, labCode=labCode)
                else:
                    raise ValueError("Lab Code should be one of the implemented lab codes for processing to work")
                    return ExecutionResult.FAILED
                if executionResult == FAILURE_CODE:
                    return ExecutionResult.FAILED
                else:
                    self.execState = ExecState.Done

            if self.execState == ExecState.Done:
                if (self._sendCommand("PROCESSING_DONE", "None") != FAILURE_CODE):
                    if progressBar is not None:
                        progressBar.setValue(100)
                    # self.logger.enableLogging()
                    self.log("Processing completed in {} ms".format((time.time()-startTime)*1000))
                    return ExecutionResult.COMPLETED
                else:
                    self.log("Failed to let RPi know that processing is done", "ERROR")
                    return ExecutionResult.FAILED

        except StopProcessingRequested:
            if progressBar is not None:
                progressBar.setValue(0)
            return ExecutionResult.INTERRUPTED
        except Exception as e:
            self.logger.enableLogging()
            self.log("Caught exception: {}".format(e), type="ERROR")
            self.log(traceback.format_exc())
            print(traceback.format_stack())
            return ExecutionResult.FAILED

    def executeOther(self, function, payload = "None"):
        if function not in SERIAL_COMMANDS:
            return ExecutionResult.FAILED
        result = self._sendCommand(function, payload)
        if result == FAILURE_CODE:
            return ExecutionResult.FAILED
        else:
            return result

    def reset(self):
        try:
            startBytes = bytes([STARTING_BYTE]*50)
            self.serialPort.write(startBytes)
            result = self._sendCommand("RESET", "None")
            if result is FAILURE_CODE:
                return ExecutionResult.FAILED
            else:
                return ExecutionResult.COMPLETED
        except SerialTimeoutException as e:
            self.logger.enableLogging()
            self.log("Please try again or reboot the RPi if the problem persists, Caught exception: {}".format(e), type="ERROR")
            return ExecutionResult.FAILED
        except Exception as e:
            self.logger.enableLogging()
            self.log("Caught exception: {}".format(e), type="ERROR")
            self.log(traceback.format_exc())
            print(traceback.format_stack())
            return ExecutionResult.FAILED

    def _executeLab(self, inputs, outputFolder, trueOutput= None, labCode= None, outputHeader= None, progressBar= None, plotter= None):
        if progressBar is not None:
            progressBarIncrements = 100/len(inputs.index)
            currentProgressBarValue = progressBar.value()
        
        outputFilePath = os.path.join(outputFolder, datetime.now().strftime("%d-%m_%H-%M-%S"))

        if trueOutput is None:
            outputDataFrame = copy.deepcopy(inputs)
        else:
            outputDataFrame = copy.deepcopy(pd.concat([inputs, trueOutput], axis=1))

        with open(outputFilePath+"_OutputsOnly.csv", 'a') as outFile:
            headers = []
            if outputHeader is not None:
                outFile.write(outputHeader+"\n")
                headers = outputHeader.split(",")
            for i in range(len(inputs.index)):
                inputStringParts = [str(n) for n in inputs.iloc[i].values.tolist()]
                inputString = ", ".join(inputStringParts)
                self.log("Now processing: {}".format(inputString), type="INFO")
                result = self._sendCommand("PROCESS", inputString)
                if result is FAILURE_CODE:
                    self.log("Error processing line number {}, possible serial communication issues".format(i+1), type="ERROR")
                    return FAILURE_CODE
                else:                
                    self.log("Output is: {}".format(result), type="SUCCESS")
                    outFile.write(result+"\n")
                if plotter is not None:
                    plotter.addNewData(inputs.iloc[i, 0], float(result.rstrip(' \t\r\n\0').split(',')[0]))
                if progressBar is not None:
                    currentProgressBarValue += progressBarIncrements
                    progressBar.setValue(currentProgressBarValue)
                # print(result)
                outputs = [float(i) for i in result.rstrip(' \t\r\n\0').split(',')]
                for index, output in enumerate(outputs):
                    if index < len(headers):
                        header = headers[index]
                    else:
                        header = f"Unknown_{index+1}"

                    outputDataFrame.loc[i, header] = output
            outputDataFrame.to_csv(outputFilePath+"_Full.csv", index=False)
            self.log(f"Outputs Saved in {outputFilePath+'_OutputsOnly.csv'}\nComplete data saved in {outputFilePath+'_Full.csv'}")

            # calculate accuracy
            if trueOutput is not None and labCode:
                try:
                    if labCode == "Lab1":
                        from sklearn.metrics import r2_score, mean_squared_error
                        r2Score = r2_score(outputDataFrame.iloc[:, -2], outputDataFrame.iloc[:, -1])
                        RMSE = mean_squared_error(outputDataFrame.iloc[:, -2], outputDataFrame.iloc[:, -1], squared=False)
                        self.log(f"Regression R2 Score Calculated is {r2Score :.3f} and RMSE is {RMSE :.3f}")
                    elif labCode == "Lab2":
                        from sklearn.metrics import accuracy_score, recall_score, f1_score
                        accuracyScore = accuracy_score(outputDataFrame.iloc[:, -2], outputDataFrame.iloc[:, -1])
                        recallScore = recall_score(outputDataFrame.iloc[:, -2], outputDataFrame.iloc[:, -1])
                        f1Score = f1_score(outputDataFrame.iloc[:, -2], outputDataFrame.iloc[:, -1])
                        self.log(f"Classification Metrics\n Accuracy: {accuracyScore*100 :.2f}%, Recall: {recallScore:.2f}, F1-Score: {f1Score:.2f}")
                except Exception as e:
                    self.log(f"Failed to calculate accuracy metrics because of exception: {e}", type="ERROR")

            return SUCCESS_CODE

    def _sendCommand(self, command, payload, timeout=SERIAL_COMMAND_TIMEOUT, max_retry=SERIAL_COMMAND_MAX_TRIALS):
        if not command in SERIAL_COMMANDS:
            print("The command provided {} is not a valid serial command".format(command))
            return FAILURE_CODE
        sendBuffer = bytearray()
        sendBuffer.append(STARTING_BYTE)
        sendString = command + ":" + payload
        sendBuffer.extend(sendString.encode("utf-8"))
        sendBuffer.append(0x00)
        newChecksum = Crc16()
        # print("Checksum Calc based on {}".format(sendBuffer[1:]))
        newChecksum.process(sendBuffer[1:])
        checksumBytes = newChecksum.finalbytes()
        sendBuffer.extend(checksumBytes)
        # print(len(sendBuffer))
        for attempt in range(max_retry):
            if attempt != 0:
                self.log(f"Attempt #{attempt+1} to send the command {command} with payload {payload}", type="DEBUG")
                QCoreApplication.processEvents()
            # t = time.time()
            try:
                self.serialPort.flushInput()
                self.serialPort.write(sendBuffer)
            except SerialTimeoutException:
                self.serialPort.flushOutput()
                continue
            self.serialTimeoutTimer.setInterval(timeout)
            self.serialTimeoutTimer.start()
            succeeded, string = self.getSerialAck()
            # print("The time spent from sending a command to receiving a reply (or timeouting) is ",time.time()-t)
            if succeeded:
                return string
            elif not succeeded and "EXCEPTION" in string:
                break 
        return FAILURE_CODE

    def _sendSaveModelCommand(self, model):
        with open(model, 'rb') as modelFile:
            fileToBeSent = modelFile.read()
        fileToBeSent = zlib.compress(fileToBeSent, level=9)
        fileToBeSentStr = " ".join(map(str,fileToBeSent))
        self.log(f"Estimated time for model to be sent is {int(len(fileToBeSentStr)/2000)} seconds", type="INFO")
        return self._sendCommand("SAVE_MODEL", fileToBeSentStr, timeout=SAVE_MODEL_COMMAND_TIMEOUT)
        
    def getSerialAck(self):
        string = ""
        succeeded = False

        self.serialState = SerialState.WaitingToStart
        currentSerialString = ""
        currentCheckSum = bytearray(2)

        while(self.serialTimeoutTimer.remainingTime()>0):
            QCoreApplication.processEvents()
            self.processCheckStopRequest()
            if self.serialState == SerialState.WaitingToStart:
                newByte = self.serialPort.read()
                if len(newByte) == 1:
                    if newByte[0] == STARTING_BYTE:
                        self.serialState = SerialState.WaitingForString
            
            if self.serialState == SerialState.WaitingForString:
                newBytes = self.serialPort.read_until(b'\0')
                if len(newBytes) >= 1:
                    for i in range (len(newBytes)):
                        if newBytes[i] == STARTING_BYTE:
                            pass
                        else:
                            currentSerialString = currentSerialString + newBytes[i:].decode("utf-8")
                            if newBytes[-1] == 0x00:
                                self.serialState = SerialState.WaitingForChecksum1
                            break
            
            if self.serialState == SerialState.WaitingForChecksum1:
                newByte = self.serialPort.read()
                if len(newByte) == 1:
                    currentCheckSum[0] = newByte[0]
                    self.serialState = SerialState.WaitingForChecksum2
                
            if self.serialState == SerialState.WaitingForChecksum2:
                newByte = self.serialPort.read()
                if len(newByte) == 1:
                    currentCheckSum[1] = newByte[0]
                    self.serialState = SerialState.CommandDone

            if self.serialState == SerialState.CommandDone:
                # check the message integrity
                receivedCommandCrc = Crc16()
                receivedCommandCrc.process(currentSerialString.encode('utf-8'))
                receivedCommandCrcBytes = receivedCommandCrc.finalbytes()
                
                # print("Checksum Calc based on {}".format(currentSerialString.encode('utf-8')))
                # print("Checksum Received: {}, Calculated: {}".format(currentCheckSum, receivedCommandCrcBytes))
                if receivedCommandCrcBytes == currentCheckSum:
                    succeeded = True
                    string = currentSerialString.split(":")[1].rstrip(' \t\r\n\0')
                    if string == "None":
                        string = ""
                else:
                    self.log("Acknowledgment Failed, received: {}".format(currentSerialString.rstrip("\t\r\n\0")), type="ERROR")
                    string = currentSerialString
                break

        return succeeded, string

    def processCheckStopRequest(self):
        if not self._stopRequested:
            return
        else:
            self._stopRequested = False
            raise StopProcessingRequested

    def requestStop(self):
        self._stopRequested = True

    @property
    def execState(self):
        return self._execState

    @execState.setter
    def execState(self, newVal):
        # print("Switched to Exec State: {}".format(newVal))
        self._execState = newVal

    @property
    def serialState(self):
        return self._serialState

    @serialState.setter
    def serialState(self, newVal):
        # print("Switched to Serial State: {}".format(newVal))
        self._serialState = newVal
Exemple #3
0
class Notification(QWidget):
    """Custom pop-up notification widget with fade-in and fade-out effect."""
    def __init__(self, parent, txt, anim_duration=500, life_span=2000):
        """

        Args:
            parent (QWidget): Parent widget
            txt (str): Text to display in notification
            anim_duration (int): Duration of the animation in msecs
            life_span (int): How long does the notification stays in place in msecs
        """
        super().__init__()
        self.setWindowFlags(Qt.Popup)
        self.setParent(parent)
        self._parent = parent
        self.label = QLabel(txt)
        self.label.setAlignment(Qt.AlignCenter)
        self.label.setWordWrap(True)
        self.label.setMargin(8)
        font = QFont()
        font.setBold(True)
        self.label.setFont(font)
        layout = QHBoxLayout()
        layout.addWidget(self.label)
        layout.setSizeConstraint(QLayout.SetFixedSize)
        layout.setContentsMargins(1, 0, 1, 0)
        self.setLayout(layout)
        self.adjustSize()
        # Move to the top right corner of the parent
        x = self._parent.size().width() - self.width() - 2
        self.move(x, 0)
        self.setAttribute(Qt.WA_DeleteOnClose)
        self.setAttribute(Qt.WA_TranslucentBackground)
        ss = ("QWidget{background-color: rgba(255, 194, 179, 0.8);"
              "border-width: 2px;"
              "border-color: #ffebe6;"
              "border-style: groove; border-radius: 8px;}")
        self.setStyleSheet(ss)
        self.effect = QGraphicsOpacityEffect()
        self.setGraphicsEffect(self.effect)
        self.effect.setOpacity(0.0)
        self._opacity = 0.0
        self.timer = QTimer(self)
        self.timer.setInterval(life_span)
        self.timer.timeout.connect(self.start_self_destruction)
        # Fade in animation
        self.fade_in_anim = QPropertyAnimation(self, b"opacity")
        self.fade_in_anim.setDuration(anim_duration)
        self.fade_in_anim.setStartValue(0.0)
        self.fade_in_anim.setEndValue(1.0)
        self.fade_in_anim.valueChanged.connect(self.update_opacity)
        self.fade_in_anim.finished.connect(self.timer.start)
        # Fade out animation
        self.fade_out_anim = QPropertyAnimation(self, b"opacity")
        self.fade_out_anim.setDuration(anim_duration)
        self.fade_out_anim.setStartValue(1.0)
        self.fade_out_anim.setEndValue(0)
        self.fade_out_anim.valueChanged.connect(self.update_opacity)
        self.fade_out_anim.finished.connect(self.close)
        # Start fade in animation
        self.fade_in_anim.start(QPropertyAnimation.DeleteWhenStopped)

    def get_opacity(self):
        """opacity getter."""
        return self._opacity

    def set_opacity(self, op):
        """opacity setter."""
        self._opacity = op

    @Slot(float)
    def update_opacity(self, value):
        """Updates graphics effect opacity."""
        self.effect.setOpacity(value)

    def start_self_destruction(self):
        """Starts fade-out animation and closing of the notification."""
        self.fade_out_anim.start(QPropertyAnimation.DeleteWhenStopped)

    def enterEvent(self, e):
        super().enterEvent(e)
        self.start_self_destruction()
        self.setAttribute(Qt.WA_TransparentForMouseEvents)

    def remaining_time(self):
        if self.timer.isActive():
            return self.timer.remainingTime()
        if self.fade_out_anim.state() == QPropertyAnimation.Running:
            return 0
        return self.timer.interval()

    opacity = Property(float, get_opacity, set_opacity)
Exemple #4
0
class MainGame(QMainWindow):
    def init_global_fields(self):
        self.user_name = ''
        self.user_settings = dict()
        self.game_time = 0
        self.game_step = 0
        self.solved = False

        self.actions_queue = queue.Queue()
        self.current_combination = list()
        self.action_name = ''

        self.stat_session = None

    def __init__(self, ui_file, parent=None):

        self.init_global_fields()

        super(MainGame, self).__init__(parent)
        ui_file = QFile(ui_file)
        ui_file.open(QFile.ReadOnly)

        loader = QUiLoader()
        self.window = loader.load(ui_file)
        ui_file.close()

        self.btn_clear = self.window.findChild(QPushButton, "btn_clear")
        self.btn_skip = self.window.findChild(QPushButton, "btn_skip")
        self.btn_undo = self.window.findChild(QPushButton, "btn_undo")

        self.label_timer = self.window.findChild(QLabel, "label_timer")
        self.log: QPlainTextEdit = self.window.findChild(QPlainTextEdit, "log")
        self.log.clear()
        self.log.setReadOnly(True)
        self.picture_holder: QLabel = self.window.findChild(
            QLabel, "picture_holder")
        self.picture_holder.setPixmap(
            QtGui.QPixmap("actions/pictures/default.png"))

        self.global_timer = QTimer(self)
        self.second_timer = QTimer(self)
        self.second_timer.timeout.connect(self.redraw_label_timer)
        self.global_timer.timeout.connect(self.stop_game)
        self.log.textChanged.connect(self.scroll_log_to_max)
        self.btn_undo.clicked.connect(self.undo_action)
        self.btn_skip.clicked.connect(self.skip)
        self.btn_clear.clicked.connect(self.reset)

        undo_shortcut = QShortcut(QKeySequence("Ctrl+Z"), self.window)
        undo_shortcut.activated.connect(lambda: self.undo_action())

        self.window.installEventFilter(self)
        self.window.adjustSize()
        self.window.setWindowTitle('{} Game'.format(
            GlobalParams.application_name))

        self.logging_window = LoggingWindow('ui\LoggingDialog.ui')
        self.logging_window.btn_login.clicked.connect(self.login_in_game)

    def eventFilter(self, obj, event):
        if obj == self.window:
            if event.type(
            ) == QEvent.MouseButtonPress:  #and self.is_cursor_in_game_zone()
                key = hook_mouse_event(event)
                self.draw_key(key)

            elif event.type() == QEvent.KeyPress and not event.isAutoRepeat():
                key = hook_key_event(event)
                if key == True:
                    return True

                self.draw_key(key)

            elif event.type() == QEvent.KeyRelease and event.key(
            ) == Qt.Key.Key_Tab:
                self.draw_key(event.key())
            else:
                return False
        return QMainWindow.eventFilter(self, obj, event)

    def login_in_game(self):
        self.user_name = self.logging_window.le_username.text()
        self.game_time = int(self.logging_window.le_game_time.text()) * 60000
        self.user_settings = SettingsParser.parse_to_key_combination(
            self.logging_window.settings_path)

        self.logging_window.hide()
        self.window.show()
        self.show_start_countdown_dialog()

    def start_game(self):
        cur_time = datetime.datetime.fromtimestamp(self.game_time / 1000)
        self.label_timer.setText(
            form_timer_label(cur_time.minute, cur_time.second))
        self.global_timer.start(self.game_time)
        self.second_timer.start(1000)

        self.stat_session = SessionStat(self.user_name, self.game_time, self)

        queue_size = math.trunc((self.game_time / 60000) * 2)
        self.build_action_queue(queue_size)
        self.next_combination()

    def next_combination(self):
        if self.actions_queue.empty():
            self.stop_game()
        else:
            self.action_name = self.actions_queue.get()
            current_action = self.action_by_name(self.action_name)
            new_picture = QtGui.QPixmap(current_action.picture_path)
            self.picture_holder.setPixmap(self.scale_picture(new_picture))
            self.current_combination = current_action.combination
            self.log.clear()
            self.game_step = 0
            print(self.current_combination)

    def draw_key(self, key):
        def key_to_text(coded_key):
            if coded_key in KeyShortcuts.mouse_shorts_map:
                return KeyShortcuts.mouse_shorts_map.get(coded_key)
            else:
                return QKeySequence(coded_key).toString()

        last_key_pos = len(self.current_combination)
        if self.game_step < last_key_pos:
            role = self.current_combination[self.game_step] == key
            self.insert_key_to_log(key_to_text(key), role)
            self.solved = False
            if self.game_step + 1 == last_key_pos and role:
                self.solved = True
                self.insert_key_to_log(
                    'Решено! Нажмите любую клавишу, чтобы продолжить.', True)
                self.stat_session.solve()
        elif self.solved:
            self.next_combination()
            return
        else:
            self.insert_key_to_log(key_to_text(key), False)
        self.game_step += 1

    def show_start_countdown_dialog(self):
        def get_cur_time():
            time = over_timer.remainingTime()
            return math.trunc(time / 1000) + 1

        def timeout():
            msg_box.close()
            over_timer.stop()
            countdown_timer.stop()
            self.start_game()

        msg_box = QMessageBox()
        msg_box.setIcon(QMessageBox.Information)
        msg_box.setText('Игра начнется через 5 секунд')
        msg_box.addButton('Начать сейчас', QMessageBox.YesRole)
        countdown_timer = QTimer(msg_box)
        over_timer = QTimer(msg_box)
        countdown_timer.timeout.connect(lambda: msg_box.setText(
            'Игра начнется через {} секунд'.format(get_cur_time())))
        over_timer.timeout.connect(timeout)
        msg_box.buttonClicked.connect(timeout)
        over_timer.start(5000)
        countdown_timer.start(1000)
        msg_box.exec_()

    def skip(self):
        self.stat_session.skip()
        self.actions_queue.put(self.action_name)
        self.next_combination()

    def reset(self):
        self.stat_session.reset()
        self.game_step = 0
        self.log.clear()

    def undo_action(self):
        if self.game_step > 0:
            self.stat_session.reset()
            self.game_step -= 1
            self.log.undo()
            self.scroll_log_to_max()

    def redo_action(self):
        self.game_step += 1
        self.log.redo()

    def refresh_game_zone(self):
        self.game_step = 0
        self.log.clear()

    def is_cursor_in_game_zone(self):
        pos = QtGui.QCursor.pos()
        name_obj = QApplication.widgetAt(pos).objectName()
        return name_obj == 'picture_holder'

    def redraw_label_timer(self):
        self.game_time -= 1000
        cur_time = datetime.datetime.fromtimestamp(self.game_time / 1000)
        self.label_timer.setText(
            form_timer_label(cur_time.minute, cur_time.second))

    def stop_game(self):
        self.stat_session.finish_game(self.global_timer.remainingTime())
        self.global_timer.stop()
        self.second_timer.stop()

    def insert_key_to_log(self, key, role: bool):
        if role:
            self.log.appendHtml('<font color="#1cb845">{}</font>'.format(key))
        else:
            self.stat_session.make_mistake()
            self.log.appendHtml('<font color="#cf0a0a">{}</font>'.format(key))

    def build_action_queue(self, quantity):
        action_files = [
            file for file in os.listdir('actions/')
            if os.path.splitext(file)[1] == '.csv'
        ]
        if len(action_files) == 0:
            self.show_error_dialog()
            self.close()
        if quantity >= len(action_files):
            quantity = len(action_files)
            random.shuffle(action_files)

        for action in random.sample(action_files, quantity):
            self.actions_queue.put(action)

    def show_error_dialog(self):
        msg_box = QMessageBox()
        msg_box.setDefaultButton(QMessageBox.Ok)
        msg_box.setIcon(QMessageBox.Critical)
        msg_box.setText('Файлы комбинаций не найдены.')
        msg_box.setInformativeText('Будет произведен выход из приложения.')
        msg_box.exec_()

    def action_by_name(self, name):
        return KeyAction('actions/{}'.format(name), self.user_settings)

    def scale_picture(self, picture: QPixmap):
        max_W = self.picture_holder.maximumWidth()
        max_H = self.picture_holder.maximumHeight()
        if picture.height() > max_H or picture.width() > max_W:
            return picture.scaled(max_W, max_H, Qt.KeepAspectRatio)
        else:
            return picture

    def scroll_log_to_max(self):
        self.log.verticalScrollBar().setValue(
            self.log.verticalScrollBar().maximum())
Exemple #5
0
class AsemblerIDE(QMainWindow):
    def __init__(self):
        super(AsemblerIDE, self).__init__()
        self.workspace = None
        self.backupTimer = 300000
        PathManager.START_DIRECTORY = os.getcwd()
        self.workspaceConfiguration = WorkspaceConfiguration.loadConfiguration(
        )
        self.snippetManager = SnippetManager.loadSnippetConfiguration()
        self.tooltipManager = TooltipManager.loadTooltipConfiguration()
        self.configurationManager = ConfigurationManager()
        self.editorTabs = EditorTabWidget(self.snippetManager,
                                          self.tooltipManager)
        self.menuBar = MenuBar()
        self.terminal = Terminal()
        self.toolBar = ToolBar(self.configurationManager)
        self.statusBar = StatusBar()
        self.treeView = TreeView(self.configurationManager)
        self.help = HelpWidget()
        self.ascii = AsciiTableWidget()
        self.setStatusBar(self.statusBar)
        self.addToolBar(self.toolBar)
        self.addDockWidget(Qt.BottomDockWidgetArea, self.terminal)
        self.addDockWidget(Qt.RightDockWidgetArea, self.help)
        self.addDockWidget(Qt.RightDockWidgetArea, self.ascii)
        self.splitDockWidget(self.help, self.ascii, Qt.Vertical)
        self.treeDock = QDockWidget()
        self.treeDock.setAllowedAreas(Qt.LeftDockWidgetArea
                                      | Qt.RightDockWidgetArea)
        self.treeDock.setStyleSheet("background-color: #2D2D30; color: white;")
        self.treeDock.setFeatures(QDockWidget.DockWidgetMovable
                                  | QDockWidget.DockWidgetClosable)
        self.treeDock.setWindowTitle("Workspace explorer")
        self.treeDock.setWidget(self.treeView)
        header = QLabel("Workspace explorer")
        # header.setStyleSheet("background-color: #007ACC;")
        self.treeDock.setTitleBarWidget(header)
        self.addDockWidget(Qt.LeftDockWidgetArea, self.treeDock)
        self.setMenuBar(self.menuBar)
        self.setMinimumSize(1200, 800)
        self.setWindowTitle("i386 Assembly Integrated Development Environment")
        self.setCentralWidget(self.editorTabs)
        self.setStyleSheet("background-color: #3E3E42; color: white;")
        self.setWindowIcon(QIcon(resource_path("resources/app_icon.ico")))

        self.addTabWidgetEventHandlers()
        self.addMenuBarEventHandlers()
        self.addToolBarEventHandlers()
        self.addTreeViewEventHandlers()
        self.checkWorkspaceConfiguration()
        #self.populateTreeView()
        #self.statusBar.comboBox.currentTextChanged.connect(self.changeEditorSyntax)
        self.statusBar.tabWidthComboBox.currentTextChanged.connect(
            self.changeEditorTabWidth)
        self.timer = QTimer()
        self.timer.start(self.backupTimer)
        self.timer.timeout.connect(self.makeBackupSave)

        self.terminal.console.setFocus()
        self.tabSwitcher = TabSwitcher(self.editorTabs)
        self.tabSwitcher.hide()
        self.projectSwitcher = ProjectSwitcher(self.configurationManager,
                                               self.toolBar.projectComboBox)
        self.projectSwitcher.hide()
        self.terminal.projectSwitchRequested.connect(self.showProjectSwitcher)
        self.terminal.tabSwitchRequested.connect(self.showTabSwitcher)
        self.editorTabs.tabSwitchRequested.connect(self.showTabSwitcher)
        self.editorTabs.projectSwitchRequested.connect(
            self.showProjectSwitcher)

        self.helpDocsDialog = GettingStartedDialog()
        self.helpDocsDialog.hide()

    def makeBackupSave(self):
        self.workspace.saveBackup()
        self.timer.setInterval(
            self.backupTimer
        )  # interval has to be reset cause the timer may have been paused

    def changeEditorTabWidth(self, text):
        currentTab: EditorTab = self.editorTabs.getCurrentTab()
        if currentTab:
            currentTab.widget.editor.tabSize = int(text)

    def changeEditorSyntax(self, text):
        currentTab: EditorTab = self.editorTabs.getCurrentTab()
        if currentTab:
            if text == "Assembly":
                currentTab.widget.editor.sintaksa = AsemblerSintaksa(
                    currentTab.widget.editor.document())
            elif text == "C":
                currentTab.widget.editor.sintaksa = CSyntax(
                    currentTab.widget.editor.document())
            currentTab.widget.editor.update()

    def checkWorkspaceConfiguration(self):
        defaultWorkspace = self.workspaceConfiguration.getDefaultWorkspace()
        if defaultWorkspace:
            if self.openWorkspaceAction(defaultWorkspace):
                self.show()
                return
            else:
                self.workspaceConfiguration.removeWorkspace(defaultWorkspace)
        dialog = WorkspaceConfigurationEditor(self.workspaceConfiguration,
                                              self)
        if dialog.exec_():
            self.show()
        else:
            sys.exit(0)

    def addTabWidgetEventHandlers(self):
        self.editorTabs.currentChanged.connect(self.activeTabChanged)

    def addTreeViewEventHandlers(self):
        self.treeView.fileDoubleCliked.connect(self.loadFileText)
        self.treeView.workspaceReload.connect(
            lambda wsProxy: self.openWorkspaceAction(wsProxy.path,
                                                     updateWorkspace=True))
        self.treeView.workspaceRename.connect(
            lambda oldPath, wsProxy: self.workspaceConfiguration.
            replaceWorkpsace(oldPath, wsProxy.path))
        self.treeView.newProjectAdded.connect(
            lambda: self.toolBar.updateComboBox())
        self.treeView.projectCompile.connect(
            lambda proxy: self.compileAction(proxy))
        self.treeView.projectDebug.connect(
            lambda proxy: self.debugAction(proxy))
        self.treeView.projectRun.connect(lambda proxy: self.runAction(proxy))
        self.treeView.projectRemove.connect(
            lambda proxy: self.removeProject(proxy))
        self.treeView.projectRename.connect(
            lambda oldPath, project: self.renameProject(oldPath, project))
        self.treeView.fileRemove.connect(
            lambda fileProxy: self.removeFile(fileProxy))
        self.treeView.fileRename.connect(
            lambda oldPath, fileProxy: self.renameFile(oldPath, fileProxy))
        self.treeView.fileSave.connect(
            lambda fileProxy: self.updateEditorTrie(fileProxy))
        self.treeView.invalidWorkspace.connect(self.invalidWorkspace)
        self.treeView.projectSave.connect(self.saveProject)
        self.treeView.quickAssemblyFile.connect(self.loadFileText)
        self.treeView.newFile.connect(self.loadFileText)

    def saveProject(self, projectProxy: ProjectProxy):
        self.saveAllFiles(projectProxy)

    def invalidWorkspace(self, workspace: WorkspaceNode):
        workspace.deleted = True
        msg = QMessageBox()
        msg.setStyleSheet("background-color: #2D2D30; color: white;")
        msg.setModal(True)
        msg.setIcon(QMessageBox.Critical)
        msg.setText("Workspace '{}' has been deleted from the disk.".format(
            workspace.path))
        msg.setWindowTitle("Invalid workspace")
        msg.exec_()
        if workspace.path in self.workspaceConfiguration.getWorkspaces():
            self.workspaceConfiguration.removeWorkspace(workspace.path)
        self.switchWorkspaceAction()

    def renameFile(self, oldPath: str, fileProxy: FileProxy):
        # fileProxy.text = None
        key = "{}/{}".format(fileProxy.parent.path, oldPath)
        if key in self.editorTabs.projectTabs:
            newKey = "{}/{}".format(fileProxy.parent.path, fileProxy.path)
            tab = self.editorTabs.projectTabs.pop(key)
            self.editorTabs.projectTabs[newKey] = tab
            tab.tabName = newKey
            index = self.editorTabs.tabs.index(fileProxy)
            tabText = newKey + "*" if fileProxy.hasUnsavedChanges else newKey
            self.editorTabs.setTabText(index, tabText)

    def renameProject(self, oldPath: str, project: ProjectNode):
        self.toolBar.updateComboBox()
        for fileProxy in project.proxy.files:
            oldKey = "{}/{}".format(oldPath, fileProxy.path)
            if oldKey in self.editorTabs.projectTabs:
                newKey = "{}/{}".format(project.proxy.path, fileProxy.path)
                tab = self.editorTabs.projectTabs.pop(oldKey)
                self.editorTabs.projectTabs[newKey] = tab
                tab.tabName = newKey
                index = self.editorTabs.tabs.index(fileProxy)
                tabText = newKey + "*" if fileProxy.hasUnsavedChanges else newKey
                self.editorTabs.setTabText(index, tabText)

    def removeFile(self, proxy: FileProxy):
        key = "{}/{}".format(proxy.parent.path, proxy.path)
        if key in self.editorTabs.projectTabs:
            self.editorTabs.closeTab(self.editorTabs.tabs.index(proxy),
                                     askToSave=False)

    def removeProject(self, proxy: ProjectProxy):
        for file in proxy.files:
            if file in self.editorTabs.tabs:
                self.editorTabs.closeTab(self.editorTabs.tabs.index(file),
                                         askToSave=False)
        self.toolBar.updateComboBox()

    def checkIfWorkspaceDeleted(self):
        if not os.path.exists(self.workspace.path):
            self.workspace.deleted = True

    def activeTabChanged(self, index):
        if index == -1:
            self.statusBar.tabWidthComboBox.setCurrentText('4')
            return
        syntax = "Assembly" if self.editorTabs.tabs[index].path[-1].lower(
        ) == "s" else "C"
        proxy = self.editorTabs.tabs[index]
        key = "{}/{}".format(proxy.parent.path, proxy.path)
        self.statusBar.comboBox.setCurrentText(syntax)
        self.statusBar.tabWidthComboBox.setCurrentText(
            str(self.editorTabs.projectTabs[key].widget.editor.tabSize))
        #self.changeEditorSyntax(syntax)

    def populateTreeView(self):
        workspace = WorkspaceNode()
        workspace.setText(0, "My workspace")
        self.treeView.setRoot(workspace)
        for i in range(5):
            project = ProjectNode()
            project.setText(0, "My Project {}".format(i + 1))
            assemblyFile = AssemblyFileNode()
            assemblyFile.setText(0, "procedure_{}.S".format(i + 1))
            cFile = CFileNode()
            cFile.setText(0, "main_{}.c".format(i + 1))
            self.treeView.addNode(workspace, project)
            self.treeView.addNode(project, assemblyFile)
            self.treeView.addNode(project, cFile)
            project.setExpanded(True)
        self.workspace = workspace

    def closeEvent(self, event):
        if self.tabSwitcher.isActiveWindow():
            self.tabSwitcher.hide()
        if self.helpDocsDialog.isVisible():
            self.helpDocsDialog.hide()
        for proxy in self.editorTabs.tabs:
            if proxy.hasUnsavedChanges:
                msg = QMessageBox()
                msg.setStyleSheet("background-color: #2D2D30; color: white;")
                msg.setParent(None)
                msg.setModal(True)
                msg.setWindowTitle("Confirm Exit")
                msg.setText("The file {}/{} has been modified.".format(
                    proxy.parent.path, proxy.path))
                msg.setInformativeText("Do you want to save the changes?")
                msg.setStandardButtons(QMessageBox.Save | QMessageBox.Discard
                                       | QMessageBox.Cancel)
                msg.setDefaultButton(QMessageBox.Save)
                retValue = msg.exec_()
                if retValue == QMessageBox.Save:
                    if not self.saveFileAction():
                        event.ignore()
                        return
                elif retValue == QMessageBox.Discard:
                    pass
                else:
                    event.ignore()
                    return
        self.workspaceConfiguration.saveConfiguration()
        self.snippetManager.saveConfiguration()
        self.tooltipManager.saveConfiguration()
        self.checkIfWorkspaceDeleted()
        if not self.workspace.deleted:
            self.workspace.proxy.closedNormally = True
            self.saveWorkspaceAction()
        else:
            if self.workspace.path in self.workspaceConfiguration.getWorkspaces(
            ):
                self.workspaceConfiguration.removeWorkspace(
                    self.workspace.path)
        super(AsemblerIDE, self).closeEvent(event)

    def addMenuBarEventHandlers(self):
        self.menuBar.quickAssemblyProjectAction.triggered.connect(
            self.quickAssemblyProject)
        self.menuBar.newWorkspaceAction.triggered.connect(
            self.newWorkspaceAction)
        self.menuBar.saveWorkspaceAction.triggered.connect(
            self.saveWorkpsaceAllFiles)
        self.menuBar.openWorkspaceAction.triggered.connect(
            self.openWorkspaceAction)
        self.menuBar.switchWorkspaceAction.triggered.connect(
            self.switchWorkspaceAction)

        self.menuBar.saveAction.triggered.connect(self.saveFileAction)
        self.menuBar.findAction.triggered.connect(self.findAction)
        self.menuBar.editDefaultWorkspace.triggered.connect(
            self.editDefaultWorkspaceConfiguration)
        self.menuBar.editCodeSnippets.triggered.connect(self.editCodeSnippets)
        self.menuBar.editSettings.triggered.connect(self.editSettings)

        self.menuBar.aboutAction.triggered.connect(self.showAbout)
        self.menuBar.helpAction.triggered.connect(self.showGettingStarted)

        self.menuBar.view.addAction(self.terminal.toggleViewAction())
        self.menuBar.view.addAction(self.treeDock.toggleViewAction())
        self.menuBar.view.addAction(self.help.toggleViewAction())
        self.menuBar.view.addAction(self.ascii.toggleViewAction())
        self.menuBar.view.addAction(self.toolBar.toggleViewAction())

    def quickAssemblyProject(self):
        if self.workspace:
            self.workspace.createQuickAssemblyProject()

    def showAbout(self):
        dialog = AboutDialog()
        dialog.exec_()

    def showGettingStarted(self):
        if self.helpDocsDialog.isHidden():
            self.helpDocsDialog.show()

    def findAction(self):
        currentTab: EditorTabWidget = self.editorTabs.getCurrentTab()
        if not currentTab:
            msg = QMessageBox()
            msg.setStyleSheet("background-color: #2D2D30; color: white;")
            msg.setModal(True)
            msg.setIcon(QMessageBox.Critical)
            msg.setText(
                "Cannot open file and replace window because there is no open file at the moment."
            )
            msg.setWindowTitle("Find & Replace error")
            msg.exec_()
            return
        currentTab.widget.find.setVisible(True)
        if currentTab.widget.editor.lastFind:
            currentTab.widget.find.findLabel.setText(
                currentTab.widget.editor.lastFind)
        currentTab.widget.find.findLabel.setFocus()

    def switchWorkspaceAction(self):
        remaining = self.timer.remainingTime()
        self.timer.stop(
        )  # timer for creating backups needs to be paused when switching ws
        dialog = WorkspaceConfigurationEditor(self.workspaceConfiguration,
                                              self,
                                              switch=True)
        if dialog.exec_() and dialog.workspaceDirectory:
            self.workspace.proxy.closedNormally = True
            self.saveWorkspaceAction()
            if not self.editorTabs.closeAllTabs():
                self.timer.start(
                    remaining)  # timer for saving backups is resumed
                return
            self.openWorkspaceAction(dialog.workspaceDirectory)
        self.timer.start(remaining)  # timer for saving backups is resumed

    def editDefaultWorkspaceConfiguration(self):
        editor = DefaultWorkspaceEditor(self.workspaceConfiguration)
        if editor.exec_():
            self.workspaceConfiguration.saveConfiguration()

    def editCodeSnippets(self):
        editor = SnippetEditor(self.snippetManager)
        if editor.exec_():
            self.snippetManager.saveConfiguration()

    def editSettings(self):
        editor = SettingsEditor(self.tooltipManager)
        if editor.exec_():
            self.tooltipManager.saveConfiguration()

    def newWorkspaceAction(self):
        remaining = self.timer.remainingTime()
        self.timer.stop(
        )  # timer for creating backups needs to be paused when switching ws
        if not self.editorTabs.closeAllTabs():
            self.timer.start(remaining)  # timer for saving backups is resumed
            return False
        self.workspace.proxy.closedNormally = True
        self.saveWorkspaceAction()
        workspace = WorkspaceNode()
        name = QFileDialog.getExistingDirectory(
            self, "New workspace", "select new workspace directory")
        if name:
            path = os.path.join(name, ".metadata")
            backup_path = os.path.join(name, ".backup")
            if os.path.isdir(path) or os.path.isdir(backup_path):
                self.msgInvalidFolderError(name)
                self.timer.start(
                    remaining)  # timer for saving backups is resumed
                return
            wsname = name[name.rindex(os.path.sep) + 1:]
            regex = re.compile('[@!#$%^&*()<>?/\|}{~:]')
            if ' ' in name or regex.search(wsname):
                msg = QMessageBox()
                msg.setStyleSheet("background-color: #2D2D30; color: white;")
                msg.setModal(True)
                msg.setIcon(QMessageBox.Critical)
                msg.setText(
                    "Workspace path/name cannot contain whitespace or special characters."
                )
                msg.setWindowTitle("Workspace creation error")
                msg.exec_()
                self.timer.start(
                    remaining)  # timer for saving backups is resumed
                return False
            workspace.path = name
            proxy = WorkspaceProxy()
            proxy.path = name
            workspace.proxy = proxy
            workspace.setIcon(0,
                              QIcon(resource_path("resources/workspace.png")))
            workspace.setText(0, wsname)
            self.workspace = workspace
            self.treeView.setRoot(self.workspace)
            self.saveWorkspaceAction()
            self.configurationManager.allProjects = []
            self.configurationManager.currentProject = None
            self.toolBar.updateComboBox()
            self.terminal.executeCommand("cd {}".format(self.workspace.path))
            self.workspaceConfiguration.addWorkspace(self.workspace.proxy.path)
            self.timer.start(remaining)  # timer for saving backups is resumed
            return True
        self.timer.start(remaining)  # timer for saving backups is resumed
        return False

    def saveWorkspaceAction(self, workspacePath=None):
        if self.workspace:
            self.workspace.saveWorkspace(workspacePath)

    def saveWorkpsaceAllFiles(self):
        self.saveAllFiles()
        self.saveWorkspaceAction()

    def updateProjectList(self):
        self.configurationManager.allProjects = []
        self.configurationManager.currentProject = None
        projects = self.treeView.getProjects()
        if len(projects):
            self.configurationManager.allProjects = self.treeView.getProjects()
            self.configurationManager.currentProject = projects[0]
        self.toolBar.updateComboBox()

    def openWorkspaceAction(self, workspacePath=None, updateWorkspace=False):
        if not self.editorTabs.closeAllTabs():
            return
        if not workspacePath:
            workspacePath = QFileDialog.getExistingDirectory(
                self, "Open workspace", "select new workspace directory")
            if not workspacePath:
                return
        regex = re.compile('[@!#$%^&*()<>?/\|}{~:]')
        if ' ' in workspacePath or regex.search(
                os.path.basename(workspacePath)):
            msg = QMessageBox()
            msg.setStyleSheet("background-color: #2D2D30; color: white;")
            msg.setModal(True)
            msg.setIcon(QMessageBox.Critical)
            msg.setText(
                "Workspace path/name cannot contain whitespace or special characters."
            )
            msg.setWindowTitle("Workspace creation error")
            msg.exec_()
            return False
        path = os.path.join(workspacePath, ".metadata")
        backup_path = os.path.join(workspacePath, ".backup")
        if os.path.isdir(path) or os.path.isdir(backup_path):
            self.msgInvalidFolderError(workspacePath)
            return
        workspace = WorkspaceProxy()
        self.workspace = WorkspaceNode()
        if os.path.exists(path):
            try:  # in try block in case there is a corrupted .metadata file on the path
                with open(path, 'rb') as file:
                    workspace = pickle.load(file)
            except:
                workspace.closedNormally = False  # set it to false to trigger backup msg in case .metadata is corrupted
        elif not os.path.exists(
                backup_path
        ):  # creates a .metadata file for a clean new workspace
            self.workspace.proxy = workspace
            self.applyWsCompatibilityFix(workspacePath)
            self.saveWorkspaceAction(workspacePath)
        self.workspace.proxy = workspace
        self.applyWsCompatibilityFix(workspacePath)
        attempted_backup = False
        if not self.workspace.proxy.closedNormally:
            if self.restoreBackupMessage(workspacePath,
                                         updateWorkspace=updateWorkspace):
                attempted_backup = True
                if self.loadWorkspaceAction(
                        workspacePath, backup=True):  # attempt to load backup
                    self.updateProjectList()
                    return True
                else:
                    self.messageBackupError("closedAbruptly")

        if self.loadWorkspaceAction(
                workspacePath,
                backup=False):  # attempt to load regular ws file
            self.updateProjectList()
            return True
        # If the regular file won't load for some reason and there was no backup attempt, ask to load the backup file
        elif not attempted_backup and self.restoreBackupMessage(
                workspacePath, failedToLoad=True):
            if self.loadWorkspaceAction(
                    workspacePath,
                    backup=True):  # attempt to load the backup file
                self.updateProjectList()
                return True
            else:
                self.messageBackupError()
        return False

    def msgInvalidFolderError(self, path):
        msg = QMessageBox()
        msg.setStyleSheet("background-color: #2D2D30; color: white;")
        msg.setModal(True)
        msg.setIcon(QMessageBox.Critical)
        msg.setText(
            "Invalid folder for a workspace."
            "\nEnsure there are no .metadata and .backup folders in \n{}".
            format(path))
        msg.setWindowTitle("Failed to load workspace.")
        msg.exec_()

    def messageBackupError(self, msgType=None):
        msg = QMessageBox()
        msg.setStyleSheet("background-color: #2D2D30; color: white;")
        msg.setModal(True)
        msg.setIcon(QMessageBox.Critical)
        if msgType == "closedAbruptly":
            msg.setText("Failed to load {}."
                        "\nRegular workspace save will be restored.".format(
                            ".backup workspace file"))
        else:
            msg.setText("Failed to load {}.".format(".backup workspace file"))
        msg.setWindowTitle("Failed to load backup workspace.")
        msg.exec_()

    def applyWsCompatibilityFix(self, workspacePath):
        try:
            closedNormally = self.workspace.proxy.closedNormally
        except AttributeError:
            closedNormally = True
        self.workspace.proxy.closedNormally = closedNormally  # adds attribute to old ws files
        self.workspace.path = workspacePath  # changes the path to currently selected dir, in case it was moved
        self.workspace.proxy.path = workspacePath

    def restoreBackupMessage(self,
                             wsName,
                             failedToLoad=False,
                             updateWorkspace=False):
        try:
            msg = QMessageBox()
            msg.setStyleSheet("background-color: #2D2D30; color: white;")
            msg.setParent(None)
            msg.setModal(True)
            msg.setWindowTitle("Workspace recovery")
            time = strftime(
                '%m/%d/%Y %H:%M:%S',
                localtime(os.path.getmtime(os.path.join(wsName, ".backup"))))
            if failedToLoad:
                msg.setText("The workplace {} could not be loaded.\n"
                            "\nTime the backup was created: {}".format(
                                wsName, time))
            elif updateWorkspace:
                msg.setText(
                    "Choose if you want to reload workspace or to recover from backup.\n"
                    "\nTime the backup was created: {}".format(wsName, time))
            else:
                msg.setText("The workplace {} was closed unexpectedly.\n"
                            "\nTime the backup was created: {}".format(
                                wsName, time))
            if not updateWorkspace:
                msg.setInformativeText(
                    "Would you like to recover from backup?")
            else:
                msg.setInformativeText(
                    "Would you like to recover from backup? Select No if you just want to update the workspace."
                )
            msg.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
            msg.setDefaultButton(QMessageBox.Yes)
            retValue = msg.exec_()
            if retValue == QMessageBox.Yes:
                return True
            else:
                return
        except:
            return False

    def loadWorkspaceAction(self, workspacePath, backup=False):
        if backup:
            path = os.path.join(workspacePath, ".backup")
        else:
            path = os.path.join(workspacePath, ".metadata")
        if os.path.exists(path):
            try:  # in try block in case there is a corrupted .metadata file on the path
                with open(path, 'rb') as file:
                    workspace = pickle.load(file)
            except:
                return False
        else:
            return False
        self.workspace = WorkspaceNode()
        self.workspace.proxy = workspace
        self.applyWsCompatibilityFix(workspacePath)
        self.workspace.setIcon(0,
                               QIcon(resource_path("resources/workspace.png")))
        self.workspace.setText(
            0, workspacePath[workspacePath.rindex(os.path.sep) + 1:])
        self.workspace.proxy.closedNormally = False
        if backup:
            success = self.workspace.loadBackupWorkspace(workspacePath)
        else:
            success = self.workspace.loadWorkspace()
        if not success:
            return False
        self.treeView.setRoot(self.workspace)
        projects = self.treeView.getProjects()
        if projects:
            self.configurationManager.allProjects.clear()
            self.configurationManager.allProjects.extend(projects)
        self.toolBar.updateComboBox()
        #self.treeView.expandAll()
        self.terminal.executeCommand("cd {}".format(self.workspace.path))
        self.workspaceConfiguration.addWorkspace(self.workspace.proxy.path)
        if workspacePath:
            self.saveWorkspaceAction(workspacePath)
        return True

    def addToolBarEventHandlers(self):
        self.toolBar.compile.triggered.connect(self.compileAction)
        self.toolBar.run.triggered.connect(self.runAction)
        self.toolBar.debug.triggered.connect(self.debugAction)

    def debugAction(self, projectProxy=None):
        currentProject: ProjectNode = self.configurationManager.currentProject
        if not currentProject:
            self.showNoCurrentProjectMessage("Debug")
            return
        if not os.path.exists(currentProject.proxy.getProjectPath()):
            currentProject.eventManager.invalidProject.emit(currentProject)
            return
        proxy = None
        if projectProxy:
            proxy = projectProxy
        else:
            if currentProject:
                proxy = currentProject.proxy
        if proxy:
            commandString = proxy.getProjectDebugCommand()
            self.terminal.console.setFocus()
            if self.terminal.executeCommand(proxy.getProjectCompileCommand()):
                copmileString = proxy.getProjectCompileCommand()
                if ' -g ' not in copmileString:
                    msg = QMessageBox()
                    msg.setStyleSheet(
                        "background-color: #2D2D30; color: white;")
                    msg.setModal(True)
                    msg.setIcon(QMessageBox.Warning)
                    msg.setText(
                        "Please set '-g' option in compiler configuration to be able to debug your project."
                    )
                    msg.setWindowTitle("Debug warning")
                    msg.exec_()
                    return False
                self.terminal.executeCommand(commandString)
            self.toolBar.projectComboBox.setCurrentText(proxy.path)

    def runAction(self, projectProxy=None):
        currentProject: ProjectNode = self.configurationManager.currentProject
        if not currentProject:
            self.showNoCurrentProjectMessage("Run")
            return
        if not os.path.exists(currentProject.proxy.getProjectPath()):
            currentProject.eventManager.invalidProject.emit(currentProject)
            return
        proxy = None
        if projectProxy:
            proxy = projectProxy
        else:
            if currentProject:
                proxy = currentProject.proxy
        if proxy:
            commandString = proxy.getProjectRunCommand()
            self.terminal.console.setFocus()
            if self.terminal.executeCommand(proxy.getProjectCompileCommand()):
                self.terminal.executeCommand(commandString)
            self.toolBar.projectComboBox.setCurrentText(proxy.path)

    def compileAction(self, projectProxy=None):
        currentProject: ProjectNode = self.configurationManager.currentProject
        if not currentProject:
            self.showNoCurrentProjectMessage("Compile")
            return
        if not os.path.exists(currentProject.proxy.getProjectPath()):
            currentProject.eventManager.invalidProject.emit(currentProject)
            return
        proxy = None
        if projectProxy:
            proxy = projectProxy
        else:
            if currentProject:
                proxy = currentProject.proxy
        if proxy:
            commandString = proxy.getProjectCompileCommand()
            self.terminal.console.setFocus()
            self.terminal.executeCommand(commandString)
            self.toolBar.projectComboBox.setCurrentText(proxy.path)

    def showNoCurrentProjectMessage(self, action: str):
        msg = QMessageBox()
        msg.setStyleSheet("background-color: #2D2D30; color: white;")
        msg.setModal(True)
        msg.setIcon(QMessageBox.Critical)
        msg.setText("You have to select a project first.")
        msg.setWindowTitle("{} error".format(action.capitalize()))
        msg.exec_()

    def checkExecutable(self):
        if self.editor.filePath:
            destination = self.editor.filePath[:-1] + "out"
            return os.path.exists(destination)
        return None

    def loadFileText(self, fileProxy):
        key = "{}/{}".format(fileProxy.parent.path, fileProxy.path)
        if key in self.editorTabs.projectTabs:
            self.editorTabs.setCurrentIndex(
                self.editorTabs.tabs.index(fileProxy))
            return
        text = self.openFileAction(fileProxy)
        fileProxy.text = text
        fileProxy.hasUnsavedChanges = False
        if fileProxy.getFilePath()[-1].lower() == "c":
            currentText = "C"
        else:
            currentText = "Assembly"
        update = True
        if len(self.editorTabs.tabs) == 0:
            self.editorTabs.tabs.append(fileProxy)
            update = False
        self.editorTabs.addNewTab(fileProxy, update)
        self.statusBar.comboBox.setCurrentText(currentText)
        if currentText == "Assembly":
            self.editorTabs.projectTabs[
                key].widget.editor.sintaksa = AsemblerSintaksa(
                    self.editorTabs.projectTabs[key].widget.editor.document())
        elif currentText == "C":
            self.editorTabs.projectTabs[key].widget.editor.sintaksa = CSyntax(
                self.editorTabs.projectTabs[key].widget.editor.document())

    def updateEditorTrie(self, proxy: FileProxy):
        key = "{}/{}".format(proxy.parent.path, proxy.path)
        if key in self.editorTabs.projectTabs:
            self.editorTabs.projectTabs[key].widget.editor.updateTrie()
            self.editorTabs.removeChangeIdentificator(proxy)

    def saveAllFiles(self, projectProxy=None):
        couldNotBeSaved = []
        for i in range(len(self.editorTabs.tabs)):
            fileProxy = self.editorTabs.tabs[i]
            try:
                if fileProxy and fileProxy.hasUnsavedChanges:
                    if projectProxy and fileProxy.parent.path != projectProxy.path:
                        continue
                    with open(fileProxy.getFilePath(), 'w') as file:
                        file.write(fileProxy.text)
                        fileProxy.hasUnsavedChanges = False
                    self.updateEditorTrie(fileProxy)
            except:
                couldNotBeSaved.append(fileProxy.path)
        self.saveWorkspaceAction()
        if couldNotBeSaved:
            msg = QMessageBox()
            msg.setStyleSheet("background-color: #2D2D30; color: white;")
            msg.setModal(True)
            msg.setIcon(QMessageBox.Critical)
            msg.setText("The following file(s) could not be saved: {}".format(
                ','.join(couldNotBeSaved)))
            msg.setWindowTitle("File save error")
            msg.exec_()

    def saveFileAction(self):
        if len(self.editorTabs.tabs):
            proxy = self.editorTabs.getCurrentFileProxy()
            if proxy and proxy.hasUnsavedChanges:
                try:
                    with open(proxy.getFilePath(), 'w') as file:
                        file.write(proxy.text)
                        proxy.hasUnsavedChanges = False
                    self.updateEditorTrie(proxy)
                except:
                    msg = QMessageBox()
                    msg.setStyleSheet(
                        "background-color: #2D2D30; color: white;")
                    msg.setModal(True)
                    msg.setIcon(QMessageBox.Critical)
                    msg.setText(
                        "The following file could not be saved: {}".format(
                            proxy.path))
                    msg.setWindowTitle("File save error")
                    msg.exec_()
            self.saveWorkspaceAction()
            return True

    def openFileAction(self, fileName: FileProxy):
        text = None
        # if fileName.text:
        #     return fileName.text
        with open(fileName.getFilePath(), 'r') as file:
            text = file.read()
        return text

    def keyPressEvent(self, event):
        if event.modifiers() == Qt.ControlModifier:
            if event.key() == Qt.Key_Tab:
                self.showTabSwitcher()
            elif event.key() == Qt.Key_E:
                self.showProjectSwitcher()
        super(AsemblerIDE, self).keyPressEvent(event)

    def showProjectSwitcher(self):
        if self.projectSwitcher.isHidden() and len(
                self.configurationManager.allProjects):
            self.projectSwitcher.showSwitcher()
            self.projectSwitcher.setFocus()

    def showTabSwitcher(self):
        if self.tabSwitcher.isHidden() and len(self.editorTabs.tabs):
            self.tabSwitcher.showSwitcher()
            self.tabSwitcher.setFocus()
class Notification(QFrame):
    """Custom pop-up notification widget with fade-in and fade-out effect."""
    def __init__(self,
                 parent,
                 txt,
                 anim_duration=500,
                 life_span=None,
                 word_wrap=True,
                 corner=Qt.TopRightCorner):
        """

        Args:
            parent (QWidget): Parent widget
            txt (str): Text to display in notification
            anim_duration (int): Duration of the animation in msecs
            life_span (int): How long does the notification stays in place in msecs
            word_wrap (bool)
            corner (Qt.Corner)
        """
        super().__init__()
        if life_span is None:
            word_count = len(txt.split(" "))
            mspw = 60000 / 140  # Assume people can read ~140 words per minute
            life_span = mspw * word_count
        self.setFocusPolicy(Qt.NoFocus)
        self.setWindowFlags(Qt.Popup)
        self.setParent(parent)
        self._parent = parent
        self._corner = corner
        self.label = QLabel(txt)
        self.label.setMaximumSize(parent.size())
        self.label.setAlignment(Qt.AlignCenter)
        self.label.setWordWrap(word_wrap)
        self.label.setMargin(8)
        self.label.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
        font = QFont()
        font.setBold(True)
        self.label.setFont(font)
        layout = QHBoxLayout()
        layout.addWidget(self.label)
        layout.setSizeConstraint(QLayout.SetMinimumSize)
        layout.setContentsMargins(3, 3, 3, 3)
        self.setLayout(layout)
        self.adjustSize()
        self.setAttribute(Qt.WA_DeleteOnClose)
        self.setObjectName("Notification")
        self._background_color = "#e6ffc2b3"
        ss = ("QFrame#Notification{"
              f"background-color: {self._background_color};"
              "border-width: 2px;"
              "border-color: #ffebe6;"
              "border-style: groove; border-radius: 8px;}")
        self.setStyleSheet(ss)
        self.setAcceptDrops(True)
        self.effect = QGraphicsOpacityEffect()
        self.setGraphicsEffect(self.effect)
        self.effect.setOpacity(0.0)
        self._opacity = 0.0
        self.timer = QTimer(self)
        self.timer.setInterval(life_span)
        self.timer.timeout.connect(self.start_self_destruction)
        # Fade in animation
        self.fade_in_anim = QPropertyAnimation(self, b"opacity")
        self.fade_in_anim.setDuration(anim_duration)
        self.fade_in_anim.setStartValue(0.0)
        self.fade_in_anim.setEndValue(1.0)
        self.fade_in_anim.valueChanged.connect(self.update_opacity)
        self.fade_in_anim.finished.connect(self.timer.start)
        # Fade out animation
        self.fade_out_anim = QPropertyAnimation(self, b"opacity")
        self.fade_out_anim.setDuration(anim_duration)
        self.fade_out_anim.setStartValue(1.0)
        self.fade_out_anim.setEndValue(0)
        self.fade_out_anim.valueChanged.connect(self.update_opacity)
        self.fade_out_anim.finished.connect(self.close)
        # Start fade in animation
        self.fade_in_anim.start(QPropertyAnimation.DeleteWhenStopped)

    def show(self):
        # Move to the top right corner of the parent
        super().show()
        if self._corner in (Qt.TopRightCorner, Qt.BottomRightCorner):
            x = self._parent.size().width() - self.width() - 2
        else:
            x = self.pos().x()
        if self._corner in (Qt.BottomLeftCorner, Qt.BottomRightCorner):
            y = self._parent.size().height() - self.height() - 2
        else:
            y = self.pos().y()
        self.move(x, y)

    def get_opacity(self):
        """opacity getter."""
        return self._opacity

    def set_opacity(self, op):
        """opacity setter."""
        self._opacity = op

    @Slot(float)
    def update_opacity(self, value):
        """Updates graphics effect opacity."""
        self.effect.setOpacity(value)

    def start_self_destruction(self):
        """Starts fade-out animation and closing of the notification."""
        self.fade_out_anim.start(QPropertyAnimation.DeleteWhenStopped)
        self.setAttribute(Qt.WA_TransparentForMouseEvents)

    def enterEvent(self, e):
        super().enterEvent(e)
        self.start_self_destruction()

    def dragEnterEvent(self, e):
        super().dragEnterEvent(e)
        self.start_self_destruction()

    def remaining_time(self):
        if self.timer.isActive():
            return self.timer.remainingTime()
        if self.fade_out_anim.state() == QPropertyAnimation.Running:
            return 0
        return self.timer.interval()

    opacity = Property(float, get_opacity, set_opacity)
Exemple #7
0
class Executer:
    def __init__(self, serialObj, loggerObj):
        self.serialPort = serial.Serial()
        self.serialPort = serialObj
        self.logger = loggerObj
        self.log = self.logger.log
        self.execState = ExecState.NotConnected

        self.serialTimeoutTimer = QTimer()
        self.serialTimeoutTimer.setSingleShot(True)
        self.serialTimeoutTimer.setInterval(SERIAL_COMMAND_TIMEOUT)

        # self.checkStopRequestTimer = QTimer()
        # self.checkStopRequestTimer.setInterval(500)
        # self.checkStopRequestTimer.setSingleShot(False)
        # self.checkStopRequestTimer.timeout.connect(self.processCheckStopRequest)
        # self.checkStopRequestTimer.start()
        self._stopRequested = False

    def execute(self,
                labCode,
                inputDataFrame,
                outputFolder,
                inputFields=None,
                outputField=None,
                progressBar=None,
                model=None):
        # self.logger.disableLogging()

        self.serialPort.flushInput()
        self.serialPort.flushOutput()
        startTime = time.time()

        # progressBar = None
        if progressBar is not None:
            progressBar.setValue(0)
        try:
            if self.execState == ExecState.NotConnected:
                if self.serialPort.isOpen():
                    self.execState = ExecState.Connected
                else:
                    #This should never happen because this function is called after serial is connected
                    self.log(
                        "Execution failed because serial port is not open, something is wrong",
                        type="ERROR")
                    return ExecutionResult.FAILED

            if self.execState == ExecState.Connected:
                if self._sendCommand("SELECT_LAB",
                                     labCode,
                                     max_retry=SERIAL_COMMAND_MAX_TRIALS *
                                     4) == FAILURE_CODE:
                    self.log("Error occured with lab selection", type="ERROR")
                    return ExecutionResult.FAILED
                else:
                    self.execState = ExecState.LabSelected

            if self.execState == ExecState.LabSelected:
                if model is not None and not model.startswith("RPI:"):
                    self.log(
                        "Started sending the model, this could take a while, please wait",
                        type="INFO")
                    if self._sendSaveModelCommand(model) == FAILURE_CODE:
                        self.log("Failed to send the selected model",
                                 type="ERROR")
                        return ExecutionResult.FAILED
                else:
                    if not model:
                        modelName = lab_default_models[labCode]
                    elif model.startswith("RPI:"):
                        modelName = model[4:]
                    if self._sendCommand("LOAD_MODEL",
                                         modelName,
                                         timeout=SERIAL_COMMAND_TIMEOUT *
                                         3) == FAILURE_CODE:
                        self.log("Failed to load the required model",
                                 type="ERROR")
                        return ExecutionResult.FAILED

                self.execState = ExecState.ModelLoaded

            if self.execState == ExecState.ModelLoaded:
                #load the inputs
                if inputFields is not None:
                    inputs = inputDataFrame[inputFields]
                else:
                    inputs = inputDataFrame
                if outputField:
                    trueOutput = inputDataFrame[outputField]
                else:
                    trueOutput = None
                self.execState = ExecState.Processing

            if self.execState == ExecState.Processing:
                if labCode == "LabTest":
                    executionResult = self._executeLab(inputs,
                                                       outputFolder,
                                                       progressBar=progressBar,
                                                       plotter=None)
                elif labCode == "Lab1":
                    executionResult = self._executeLab(
                        inputs,
                        outputFolder,
                        outputHeader="Prediction",
                        progressBar=progressBar,
                        plotter=None,
                        trueOutput=trueOutput,
                        labCode=labCode)
                elif labCode == "Lab2":
                    executionResult = self._executeLab(
                        inputs,
                        outputFolder,
                        outputHeader="Prediction",
                        progressBar=progressBar,
                        plotter=None,
                        trueOutput=trueOutput,
                        labCode=labCode)
                else:
                    raise ValueError(
                        "Lab Code should be one of the implemented lab codes for processing to work"
                    )
                    return ExecutionResult.FAILED
                if executionResult == FAILURE_CODE:
                    return ExecutionResult.FAILED
                else:
                    self.execState = ExecState.Done

            if self.execState == ExecState.Done:
                if (self._sendCommand("PROCESSING_DONE", "None") !=
                        FAILURE_CODE):
                    if progressBar is not None:
                        progressBar.setValue(100)
                    # self.logger.enableLogging()
                    self.log("Processing completed in {} ms".format(
                        (time.time() - startTime) * 1000))
                    return ExecutionResult.COMPLETED
                else:
                    self.log("Failed to let RPi know that processing is done",
                             "ERROR")
                    return ExecutionResult.FAILED

        except StopProcessingRequested:
            if progressBar is not None:
                progressBar.setValue(0)
            return ExecutionResult.INTERRUPTED
        except Exception as e:
            self.logger.enableLogging()
            self.log("Caught exception: {}".format(e), type="ERROR")
            self.log(traceback.format_exc())
            print(traceback.format_stack())
            return ExecutionResult.FAILED

    def executeOther(self, function, payload="None"):
        if function not in SERIAL_COMMANDS:
            return ExecutionResult.FAILED
        result = self._sendCommand(function, payload)
        if result == FAILURE_CODE:
            return ExecutionResult.FAILED
        else:
            return result

    def executeProject(self, mainWindow, project_name):
        if project_name == "SmartGroceries":
            self.referenceItemsLabels = {}
            self.itemInHand = None
            self.itemsInCart = list()
            self.currentOrderReferenceItems = list()
            self.currentOrderUserId = 0
            self.recommendedItemsButtons = list()
            self.previousRecommendations = list()
            self.numberOfGoodRecommendations = 0
            self.supportedUsers = range(1, 1001)

            products = pd.read_csv(
                os.path.join(
                    os.path.dirname(os.path.dirname(
                        os.path.abspath(__file__))), "projects",
                    "SmartGroceries", "data", "products.csv"))
            aisles = pd.read_csv(
                os.path.join(
                    os.path.dirname(os.path.dirname(
                        os.path.abspath(__file__))), "projects",
                    "SmartGroceries", "data", "aisles.csv"))
            test_data = pd.read_csv(
                os.path.join(
                    os.path.dirname(os.path.dirname(
                        os.path.abspath(__file__))), "projects",
                    "SmartGroceries", "data", "test_set.csv"))
            # print(products)
            # print(aisles)
            # print(test_data)
            aisle_name_to_id = {
                k: v
                for k, v in zip(aisles.aisle, aisles.aisle_id)
            }
            product_name_to_id = {
                k: v
                for k, v in zip(products.product_name, products.product_id)
            }
            product_id_to_name = {
                k: v
                for k, v in zip(products.product_id, products.product_name)
            }

            def changeCurrentitem(itemName):
                if itemName == "":
                    self.itemInHand = None
                    currentItemLabel.setText(
                        f"<b>Select an item from the list or from the recommendations</b>s"
                    )
                    addToCartButton.setEnabled(False)
                else:
                    self.itemInHand = product_name_to_id[itemName]
                    currentItemLabel.setText(f"Add <b>{itemName}</b> to Cart")
                    addToCartButton.setEnabled(True)
                    addToCartButton.setFocus()

            def handleNewOrderButtonClicked():
                grouped = test_data.groupby('order_id')
                while True:
                    order_number = random.sample(grouped.indices.keys(), 1)[0]
                    currentOrder = grouped.get_group(order_number)
                    self.currentOrderReferenceItems = currentOrder.product_id.tolist(
                    )
                    self.currentOrderUserId = currentOrder.user_id.iloc[0]
                    if len(
                            self.currentOrderReferenceItems
                    ) > 1 and self.currentOrderUserId in self.supportedUsers:
                        break
                print(self.currentOrderReferenceItems)
                orderInfo = f"<b>Order ID: </b>{currentOrder.order_id.iloc[0]}<br>"
                orderInfo += f"<b>User ID: </b>{self.currentOrderUserId} | <b>DOW: </b>{calendar.day_name[currentOrder.order_dow.iloc[0]]} | <b>Hour of Day: </b>{currentOrder.order_hour_of_day.iloc[0]}  | <b>Number of Items: </b>{len(self.currentOrderReferenceItems)}"
                orderInfo += "<br><b>Items in the Reference Order:</b>"

                for widget in self.referenceItemsLabels.values():
                    item = referenceItemsLayout.itemAt(0)
                    widget.setVisible(False)
                    referenceItemsLayout.removeItem(item)
                    del item
                self.referenceItemsLabels.clear()
                currentCartItems.clear()
                self.itemsInCart.clear()
                self.previousRecommendations.clear()
                self.numberOfGoodRecommendations = 0

                updateCurrentRecommendations(list())
                for product in self.currentOrderReferenceItems:
                    refItemName = product_id_to_name[product]
                    refItemLabel = QPushButton(refItemName)
                    refItemLabel.setContentsMargins(QMargins(0, 0, 0, 0))
                    refItemLabel.setStyleSheet("Text-align:left")
                    refItemLabel.setFlat(False)
                    refItemLabel.clicked.connect(
                        partial(changeCurrentitem, refItemName))
                    self.referenceItemsLabels[product] = refItemLabel
                    orderInfoLabel.setText(
                        f"<b>Order Information</b><br>{orderInfo}")

                for referenceItemLabel in self.referenceItemsLabels.values():
                    referenceItemsLayout.addWidget(referenceItemLabel)

                runAutoButton.setFocus()

            def handleRunAutomatically():
                for referenceItemLabel in self.referenceItemsLabels.values():
                    referenceItemLabel.click()
                    addToCartButton.click()

            def updateCurrentRecommendations(recommendations):
                for widget in self.recommendedItemsButtons:
                    item = recommendationsLayout.itemAt(0)
                    widget.setVisible(False)
                    recommendationsLayout.removeItem(item)
                    del item
                self.recommendedItemsButtons.clear()
                for product in recommendations:
                    recItemName = product_id_to_name[product]
                    recItemButton = QPushButton(recItemName)
                    recItemButton.setContentsMargins(QMargins(0, 0, 0, 0))
                    recItemButton.setStyleSheet("Text-align:left;")
                    if product not in self.currentOrderReferenceItems:
                        recItemButton.setFlat(True)
                    recItemButton.clicked.connect(
                        partial(changeCurrentitem, recItemName))
                    self.recommendedItemsButtons.append(recItemButton)
                for recItemButton in self.recommendedItemsButtons:
                    recommendationsLayout.addWidget(recItemButton)
                if len(recommendations) > 0:
                    currentRecommendationsLabel.setVisible(True)
                else:
                    currentRecommendationsLabel.setVisible(False)
                self.previousRecommendations += recommendations

            def handleAddToCartButtonClicked():
                print(self.currentOrderReferenceItems)
                print(self.itemInHand)
                if self.itemInHand not in self.currentOrderReferenceItems:
                    QMessageBox(
                        QMessageBox.Critical, "Error adding item to cart",
                        "You can only add items that exists in the reference order"
                    ).exec_()
                    return
                elif self.itemInHand in self.itemsInCart:
                    QMessageBox(QMessageBox.Critical,
                                "Error adding item to cart",
                                "This item is already in the cart").exec_()
                    return
                self.referenceItemsLabels[self.itemInHand].setFlat(True)
                self.itemsInCart.append(self.itemInHand)
                currentCartItems.addItem(product_id_to_name[self.itemInHand])
                if self.itemInHand in self.previousRecommendations:
                    self.numberOfGoodRecommendations += 1
                    self.referenceItemsLabels[self.itemInHand].setStyleSheet(
                        "Text-align:left; background-color:green;")
                    self.referenceItemsLabels[self.itemInHand].setFlat(False)

                #update recommendations
                result = self._sendCommand(
                    "PROCESS_PROJECT_GROUP_2", ";".join([
                        str(self.currentOrderUserId),
                        ",".join([str(x) for x in self.itemsInCart]),
                        ",".join([
                            str(x) for x in set(self.previousRecommendations)
                        ])
                    ]))
                if result == FAILURE_CODE:
                    self.log(
                        "Processing Failed, error getting recommendations from the RPi"
                    )
                    return
                else:
                    try:
                        recommendations = [int(id) for id in result.split(',')]
                    except:
                        recommendations = []

                updateCurrentRecommendations(recommendations)
                if len(self.itemsInCart) == len(
                        self.currentOrderReferenceItems):
                    completionMessage = QMessageBox(
                        QMessageBox.Information, "Order Completed",
                        f"Order Completed with {self.numberOfGoodRecommendations} Good Recommendation(s)\nPress New Order to start a new order"
                    )
                    if self.numberOfGoodRecommendations == 0:
                        completionMessage.setIconPixmap(
                            QPixmap('images/this_is_fine.jpg'))
                    completionMessage.setWindowIcon(appIcon)
                    completionMessage.exec_()
                    newOrderButton.setFocus()

            def aisleChanged():
                aisle_number = aisle_name_to_id[
                    selectAisleCombobox.currentText()]
                products_in_aisle = products[
                    products.aisle_id == aisle_number].product_name.tolist()
                selectproductCombobox.clear()
                selectproductCombobox.addItem("")
                selectproductCombobox.addItems(products_in_aisle)

            def itemChanged():
                current_item = selectproductCombobox.currentText()
                changeCurrentitem(current_item)

            dialog = QDialog(mainWindow)
            appIcon = QIcon("images/this_is_fine.jpg")
            dialog.setWindowIcon(appIcon)
            dialog.setMinimumWidth(600)
            dialog.setWindowTitle("Smart Groceries Demo")
            layout = QVBoxLayout()
            newOrderButton = QPushButton("New Order")
            orderInfoLabel = QLabel()
            orderInfoLabel.setTextFormat(Qt.RichText)
            chooseItemLayout = QHBoxLayout()
            verticalSpacer = QSpacerItem(20, 20)
            currentCartItems = QListWidget()

            layoutWidget = QWidget()
            referenceItemsLayout = QVBoxLayout(layoutWidget)
            referenceItemsLayout.setSpacing(0)
            referenceItemsLayout.setMargin(0)
            scroll = QScrollArea(dialog)
            scroll.setWidgetResizable(True)
            scroll.setMinimumHeight(150)
            scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
            scroll.setWidget(layoutWidget)

            selectAisleLabel = QLabel("Aisle: ")
            selectProductLabel = QLabel("Product: ")
            selectAisleCombobox = QComboBox()
            selectproductCombobox = QComboBox()
            chooseItemLayout.addWidget(selectAisleLabel, 0, Qt.AlignLeft)
            chooseItemLayout.addWidget(selectAisleCombobox, 0, Qt.AlignLeft)
            chooseItemLayout.addWidget(selectProductLabel, 0, Qt.AlignLeft)
            chooseItemLayout.addWidget(selectproductCombobox, 0, Qt.AlignLeft)

            addToCartButton = QPushButton("Add to Cart")
            currentItemLabel = QLabel()
            currentItemLabel.setTextFormat(Qt.RichText)
            if self.itemInHand is None:
                currentItemLabel.setText(
                    f"<b>Select an item from the list or from the recommendations</b>"
                )
            addToCartButton.setDisabled(True)
            currentItemLayout = QHBoxLayout()
            currentItemLayout.addWidget(currentItemLabel)
            currentItemLayout.addWidget(addToCartButton)
            recommendationsLayout = QVBoxLayout()
            recommendationsLayout.setSpacing(0)
            recommendationsLayout.setMargin(0)

            newOrderButton.clicked.connect(handleNewOrderButtonClicked)
            addToCartButton.clicked.connect(handleAddToCartButtonClicked)
            selectproductCombobox.currentIndexChanged.connect(itemChanged)
            selectAisleCombobox.currentIndexChanged.connect(aisleChanged)
            selectAisleCombobox.addItems(aisles.aisle.tolist())

            layout.addWidget(newOrderButton)
            layout.addSpacerItem(verticalSpacer)
            layout.addWidget(orderInfoLabel)
            layout.addWidget(scroll)
            layout.addSpacerItem(verticalSpacer)
            layout.addLayout(chooseItemLayout)
            layout.addSpacerItem(verticalSpacer)
            itemsInTheCartLabel = QLabel("<b>Items in the Cart<b>")
            layout.addWidget(itemsInTheCartLabel)
            itemsInTheCartLabel.setTextFormat(Qt.RichText)
            layout.addWidget(currentCartItems)
            layout.addSpacerItem(verticalSpacer)
            currentRecommendationsLabel = QLabel(
                "<b>Current Recommendations<b>")
            layout.addWidget(currentRecommendationsLabel)
            currentRecommendationsLabel.setTextFormat(Qt.RichText)
            currentRecommendationsLabel.setVisible(False)
            layout.addLayout(recommendationsLayout)
            layout.addSpacerItem(verticalSpacer)
            layout.addLayout(currentItemLayout)
            runAutoButton = QPushButton("Run and Watch. TRUST ME, IT IS FUN!")
            layout.addWidget(runAutoButton)
            runAutoButton.clicked.connect(handleRunAutomatically)
            dialog.setLayout(layout)
            handleNewOrderButtonClicked()
            dialog.exec_()
            return

    def reset(self):
        try:
            startBytes = bytes([STARTING_BYTE] * 50)
            self.serialPort.write(startBytes)
            result = self._sendCommand("RESET", "None")
            if result is FAILURE_CODE:
                return ExecutionResult.FAILED
            else:
                return ExecutionResult.COMPLETED
        except SerialTimeoutException as e:
            self.logger.enableLogging()
            self.log(
                "Please try again or reboot the RPi if the problem persists, Caught exception: {}"
                .format(e),
                type="ERROR")
            return ExecutionResult.FAILED
        except Exception as e:
            self.logger.enableLogging()
            self.log("Caught exception: {}".format(e), type="ERROR")
            self.log(traceback.format_exc())
            print(traceback.format_stack())
            return ExecutionResult.FAILED

    def _executeLab(self,
                    inputs,
                    outputFolder,
                    trueOutput=None,
                    labCode=None,
                    outputHeader=None,
                    progressBar=None,
                    plotter=None):
        if progressBar is not None:
            progressBarIncrements = 100 / len(inputs.index)
            currentProgressBarValue = progressBar.value()

        outputFilePath = os.path.join(
            outputFolder,
            datetime.now().strftime("%d-%m_%H-%M-%S"))

        if trueOutput is None:
            outputDataFrame = copy.deepcopy(inputs)
        else:
            outputDataFrame = copy.deepcopy(
                pd.concat([inputs, trueOutput], axis=1))

        with open(outputFilePath + "_OutputsOnly.csv", 'a') as outFile:
            headers = []
            if outputHeader is not None:
                outFile.write(outputHeader + "\n")
                headers = outputHeader.split(",")
            for i in range(len(inputs.index)):
                inputStringParts = [
                    str(n) for n in inputs.iloc[i].values.tolist()
                ]
                inputString = ", ".join(inputStringParts)
                self.log("Now processing: {}".format(inputString), type="INFO")
                result = self._sendCommand("PROCESS", inputString)
                if result is FAILURE_CODE:
                    self.log(
                        "Error processing line number {}, possible serial communication issues"
                        .format(i + 1),
                        type="ERROR")
                    return FAILURE_CODE
                else:
                    self.log("Output is: {}".format(result), type="SUCCESS")
                    outFile.write(result + "\n")
                if plotter is not None:
                    plotter.addNewData(
                        inputs.iloc[i, 0],
                        float(result.rstrip(' \t\r\n\0').split(',')[0]))
                if progressBar is not None:
                    currentProgressBarValue += progressBarIncrements
                    progressBar.setValue(currentProgressBarValue)
                # print(result)
                outputs = [
                    float(i) for i in result.rstrip(' \t\r\n\0').split(',')
                ]
                for index, output in enumerate(outputs):
                    if index < len(headers):
                        header = headers[index]
                    else:
                        header = f"Unknown_{index+1}"

                    outputDataFrame.loc[i, header] = output
            outputDataFrame.to_csv(outputFilePath + "_Full.csv", index=False)
            self.log(
                f"Outputs Saved in {outputFilePath+'_OutputsOnly.csv'}\nComplete data saved in {outputFilePath+'_Full.csv'}"
            )

            # calculate accuracy
            if trueOutput is not None and labCode:
                try:
                    if labCode == "Lab1":
                        from sklearn.metrics import r2_score, mean_squared_error
                        r2Score = r2_score(outputDataFrame.iloc[:, -2],
                                           outputDataFrame.iloc[:, -1])
                        RMSE = mean_squared_error(outputDataFrame.iloc[:, -2],
                                                  outputDataFrame.iloc[:, -1],
                                                  squared=False)
                        self.log(
                            f"Regression R2 Score Calculated is {r2Score :.3f} and RMSE is {RMSE :.3f}"
                        )
                    elif labCode == "Lab2":
                        from sklearn.metrics import accuracy_score, recall_score, f1_score
                        accuracyScore = accuracy_score(
                            outputDataFrame.iloc[:, -2],
                            outputDataFrame.iloc[:, -1])
                        recallScore = recall_score(outputDataFrame.iloc[:, -2],
                                                   outputDataFrame.iloc[:, -1])
                        f1Score = f1_score(outputDataFrame.iloc[:, -2],
                                           outputDataFrame.iloc[:, -1])
                        self.log(
                            f"Classification Metrics\n Accuracy: {accuracyScore*100 :.2f}%, Recall: {recallScore:.2f}, F1-Score: {f1Score:.2f}"
                        )
                except Exception as e:
                    self.log(
                        f"Failed to calculate accuracy metrics because of exception: {e}",
                        type="ERROR")

            return SUCCESS_CODE

    def _sendCommand(self,
                     command,
                     payload,
                     timeout=SERIAL_COMMAND_TIMEOUT,
                     max_retry=SERIAL_COMMAND_MAX_TRIALS):
        if not command in SERIAL_COMMANDS:
            print(
                "The command provided {} is not a valid serial command".format(
                    command))
            return FAILURE_CODE
        sendBuffer = bytearray()
        sendBuffer.append(STARTING_BYTE)
        sendString = command + ":" + payload
        sendBuffer.extend(sendString.encode("utf-8"))
        sendBuffer.append(0x00)
        newChecksum = Crc16()
        # print("Checksum Calc based on {}".format(sendBuffer[1:]))
        newChecksum.process(sendBuffer[1:])
        checksumBytes = newChecksum.finalbytes()
        sendBuffer.extend(checksumBytes)
        # print(len(sendBuffer))
        for attempt in range(max_retry):
            if attempt != 0:
                self.log(
                    f"Attempt #{attempt+1} to send the command {command} with payload {payload}",
                    type="DEBUG")
                QCoreApplication.processEvents()
            # t = time.time()
            try:
                self.serialPort.flushInput()
                self.serialPort.write(sendBuffer)
            except SerialTimeoutException:
                self.serialPort.flushOutput()
                continue
            self.serialTimeoutTimer.setInterval(timeout)
            self.serialTimeoutTimer.start()
            succeeded, string = self.getSerialAck()
            # print("The time spent from sending a command to receiving a reply (or timeouting) is ",time.time()-t)
            if succeeded:
                return string
            elif not succeeded and "EXCEPTION" in string:
                break
        return FAILURE_CODE

    def _sendSaveModelCommand(self, model):
        with open(model, 'rb') as modelFile:
            fileToBeSent = modelFile.read()
        fileToBeSent = zlib.compress(fileToBeSent, level=9)
        fileToBeSentStr = " ".join(map(str, fileToBeSent))
        self.log(
            f"Estimated time for model to be sent is {int(len(fileToBeSentStr)/2000)} seconds",
            type="INFO")
        return self._sendCommand("SAVE_MODEL",
                                 fileToBeSentStr,
                                 timeout=SAVE_MODEL_COMMAND_TIMEOUT)

    def getSerialAck(self):
        string = ""
        succeeded = False

        self.serialState = SerialState.WaitingToStart
        currentSerialString = ""
        currentCheckSum = bytearray(2)

        while (self.serialTimeoutTimer.remainingTime() > 0):
            QCoreApplication.processEvents()
            self.processCheckStopRequest()
            if self.serialState == SerialState.WaitingToStart:
                newByte = self.serialPort.read()
                if len(newByte) == 1:
                    if newByte[0] == STARTING_BYTE:
                        self.serialState = SerialState.WaitingForString

            if self.serialState == SerialState.WaitingForString:
                newBytes = self.serialPort.read_until(b'\0')
                if len(newBytes) >= 1:
                    for i in range(len(newBytes)):
                        if newBytes[i] == STARTING_BYTE:
                            pass
                        else:
                            currentSerialString = currentSerialString + newBytes[
                                i:].decode("utf-8")
                            if newBytes[-1] == 0x00:
                                self.serialState = SerialState.WaitingForChecksum1
                            break

            if self.serialState == SerialState.WaitingForChecksum1:
                newByte = self.serialPort.read()
                if len(newByte) == 1:
                    currentCheckSum[0] = newByte[0]
                    self.serialState = SerialState.WaitingForChecksum2

            if self.serialState == SerialState.WaitingForChecksum2:
                newByte = self.serialPort.read()
                if len(newByte) == 1:
                    currentCheckSum[1] = newByte[0]
                    self.serialState = SerialState.CommandDone

            if self.serialState == SerialState.CommandDone:
                # check the message integrity
                receivedCommandCrc = Crc16()
                receivedCommandCrc.process(currentSerialString.encode('utf-8'))
                receivedCommandCrcBytes = receivedCommandCrc.finalbytes()

                # print("Checksum Calc based on {}".format(currentSerialString.encode('utf-8')))
                # print("Checksum Received: {}, Calculated: {}".format(currentCheckSum, receivedCommandCrcBytes))
                if receivedCommandCrcBytes == currentCheckSum:
                    succeeded = True
                    string = currentSerialString.split(":")[1].rstrip(
                        ' \t\r\n\0')
                    if string == "None":
                        string = ""
                else:
                    self.log("Acknowledgment Failed, received: {}".format(
                        currentSerialString.rstrip("\t\r\n\0")),
                             type="ERROR")
                    string = currentSerialString
                break

        return succeeded, string

    def processCheckStopRequest(self):
        if not self._stopRequested:
            return
        else:
            self._stopRequested = False
            raise StopProcessingRequested

    def requestStop(self):
        self._stopRequested = True

    @property
    def execState(self):
        return self._execState

    @execState.setter
    def execState(self, newVal):
        # print("Switched to Exec State: {}".format(newVal))
        self._execState = newVal

    @property
    def serialState(self):
        return self._serialState

    @serialState.setter
    def serialState(self, newVal):
        # print("Switched to Serial State: {}".format(newVal))
        self._serialState = newVal