Exemplo n.º 1
0
class TimeoutProgressBar(QProgressBar):

    timeout = pyqtSignal()

    def __init__(self, parent=None, timeout=10000):
        super().__init__(parent)
        self.timeout_interval = timeout
        self.timer = QTimer()
        self.timer.setSingleShot(False)
        self.timer.setInterval(100)  # update the progress bar tick

        connect(self.timer.timeout, self._update)
        self.setMaximum(self.timeout_interval)

    def _update(self):
        self.setValue(self.value() + self.timer.interval())
        if self.value() >= self.maximum():
            self.timer.stop()
            self.timeout.emit()

    def start(self):
        self.setValue(0)
        self.timer.start()

    def stop(self):
        self.setValue(0)
        self.timer.stop()
Exemplo n.º 2
0
class Wpm(QLabel):
    def __init__(self, *args, **kwargs):
        super(Wpm, self).__init__("0 wpm", *args, **kwargs)
        self._correct = 0
        self._seconds = 0
        self._timer = QTimer()
        self._timer.timeout.connect(self._update)

    def _update(self):
        self._seconds += self._timer.interval() / 1000
        wpm = round((self._correct / 5) / (self._seconds / 60))
        self.setText(f"{wpm} wpm")

    def begin(self):
        self._timer.start(100) # update every tenth of a second

    def reset(self):
        self.setText("0 wpm")
        self._correct = 0
        self._seconds = 0
        self._timer.stop()

    def finish(self):
        self._timer.stop()

    def addCorrect(self, chars):
        self._correct += chars
Exemplo n.º 3
0
class RandomTimer(QObject):
    timeout = Signal()
    intervalChanged = Signal()
    activeChanged = Signal()

    def __init__(self, parent=None):
        super(RandomTimer, self).__init__()

        self.timer = QTimer()
        self.timer.timeout.connect(self.timeout)

    @Slot()
    def start(self):
        print("timer start")
        if not self.timer.isActive():
            self.timer.start()
            self.activeChanged.emit()

    @Slot()
    def stop(self):
        print("timer stop")
        if self.timer.isActive():
            self.timer.stop()
            self.activeChanged.emit()

    @Slot(int, int, result=int)
    def randomInterval(self, min, max):
        range = max - min
        msec = min + random.randint(0, range)
        return msec

    @pyqtProperty(int, notify=intervalChanged)
    def interval(self):
        return self.timer.interval()

    @interval.setter
    def interval(self, msec):
        if self.timer.interval() != msec:
            self.timer.setInterval(msec)
            self.intervalChanged.emit()
            print("interval = {}".format(self.timer.interval()))

    @pyqtProperty(bool, notify=activeChanged)
    def active(self):
        return self.timer.isActive()
Exemplo n.º 4
0
class StatusWatcher(Extension):
    def __init__(self, application: CuraApplication):
        super().__init__()

        self._app = application
        self._path = Path(
            "~/cura-print-status.txt"
        ).expanduser()  # should be configurable ­ЪциРђЇРЎђ№ИЈ
        self._path.write_text("\n")

        self._timer = QTimer()
        self._timer.timeout.connect(self.dump_status)
        self._timer.start(15000)

    def dump_status(self):
        try:
            job_name = ""
            total_time = -1337
            elapsed_time = -1337
            remaining_time = -1337

            info = self._app.getPrintInformation()
            if info is not None:
                job_name = info.jobName
                total_time = info.currentPrintTime

            devices = self._app.getMachineManager().printerOutputDevices
            if devices:
                printer = devices[0].activePrinter
                if printer is not None:
                    job = printer.activePrintJob
                    if job is not None:
                        total_time = job.timeTotal
                        elapsed_time = job.timeElapsed
                        remaining_time = job.timeTotal - job.timeElapsed

            tpl = """
                Job Name: {job_name}
                Total Time: {total_time}
                Elapsed: {elapsed_time}
                Remaining: {remaining_time}
            """
            content = tpl.format(
                job_name=job_name,
                total_time=duration_text(total_time),
                elapsed_time=duration_text(elapsed_time),
                remaining_time=duration_text(remaining_time),
            )
            self._path.write_text(textwrap.dedent(content))

            if self._timer.interval() > 1000:
                self._timer.setInterval(1000)

        except Exception as e:
            Logger.log("w", "failed to write status file: {}".format(e))
Exemplo n.º 5
0
class Example(QMainWindow, Ui_MainWindow):
    def __init__(self):
        super().__init__()
        self.setupUi(self)  # Ui_MainWindow 의 초기화 매소드
        self.timer = QTimer()
        # connect 함수를 사용하여 주기적으로 실행할 check 함수에 연결해줌
        self.timer.timeout.connect(self.check)
        self.timer.setInterval(5 * 60 * 1000)
        self.rb_5m.setChecked(True)  # UI초기 설정
        self.show()

    def setCycle(self):
        if self.rb_10m.isChecked():
            # self.rb_10m.setChecked()
            self.timer.setInterval(10 * 60 * 1000)
        elif self.rb_5m.isChecked():
            self.timer.setInterval(5 * 60 * 1000)
        elif self.rb_1m.isChecked():
            print('clecked')
            self.timer.setInterval(1 * 60 * 1000)
        elif self.rb_30s.isChecked():
            self.timer.setInterval(0.5 * 60 * 1000)
        elif self.rb_5s.isChecked():
            print('clecke')
            self.timer.setInterval(5 * 1000)
        else:
            self.timer.setInterval(1000)

    def startChk(self):
        self.url.setEnabled(False)
        self.timer.start()

    def stopChk(self):
        self.url.setEnabled(True)
        self.timer.stop()

    def check(self):
        # print('인터벌 :' , self.timer.interval())
        self.rsp = req.urlopen(self.url.text())
        self.html = bs(self.rsp, 'html.parser')
        try:
            self.test = self.html.find(alt="UP").attrs['alt']
        except:
            self.test = "XX"

        self.t = time.localtime()
        self.lbl_print.setText("인터벌:{}, {}:{}:{} 채크결과: {}".format(
            self.timer.interval(), self.t.tm_hour, self.t.tm_min,
            self.t.tm_sec, self.test))
Exemplo n.º 6
0
class Timer(QLabel):
    def __init__(self, *args, **kwargs):
        super(Timer, self).__init__("00:00", *args, **kwargs)
        self._seconds = 0
        self._timer = QTimer()
        self._timer.timeout.connect(self._update)

    def _update(self):
        self._seconds += self._timer.interval() / 1000
        m = int(self._seconds // 60)
        s = int(self._seconds % 60)
        self.setText(f"{m:02}:{s:02}")

    def begin(self):
        self._timer.start(500) # update every half second

    def reset(self):
        self.setText("00:00")
        self._seconds = 0
        self._timer.stop()

    def finish(self):
        self._timer.stop()
Exemplo n.º 7
0
    def __init__(self, width, height, parent=None):
      super(DrawerCanvas, self).__init__(parent)

      self.time = QTime.currentTime()

      # Create and bind timeout callback for 20ms update rate invoke of Refresh methos        
      timer = QTimer(self)
      timer.interval = 20
      timer.timeout.connect(self.Refresh)
      timer.start(20)

      self.setWindowTitle("PDrawer")
      self.resize(width, height)
      self.scale = 1 # scaling member for rendered objects
      self.shapes = [] # object rendering collection
      self.lockShapes = threading.Lock() # explicit lock object for add/clear/access marshalling

      self.lastMouseClickValid_L = False # init to no valid click yet
      self.lastMouseClickValid_R = False # init to no valid click yet
      # Save actual last event, provides more than just position - future use TODO
      self.lastMouseClickEvent = QMouseEvent( QMouseEvent.MouseMove, QPointF(), Qt.MouseButton(), Qt.MouseButtons(), Qt.NoModifier )
      self.lastMouseClickEvent_L = QMouseEvent( QMouseEvent.MouseMove, QPointF(), Qt.MouseButton(), Qt.MouseButtons(), Qt.NoModifier )
      self.lastMouseClickEvent_R = QMouseEvent( QMouseEvent.MouseMove, QPointF(), Qt.MouseButton(), Qt.MouseButtons(), Qt.NoModifier )
      self.lastMouseMoveEvent = QMouseEvent( QMouseEvent.MouseMove, QPointF(), Qt.MouseButton(), Qt.MouseButtons(), Qt.NoModifier )
Exemplo n.º 8
0
class Window(QWidget):
    def __init__(self, data, kwargs):
        super().__init__()
        self.initUI(data, kwargs)

    def initUI(self, data, kwargs, intvl=64):
        self.timer = QTimer(self)
        self.timer.timeout.connect(
            self.tick)  # QWidget.update() fires a "signal"
        self.timer.setInterval(intvl)

        self.dsp = Display(data, kwargs)  ## make a Display instance

        self.stpLbl = QLabel("{:d}".format(self.dsp.stepCt))
        self.stpBtn = QPushButton("Step")
        self.rnpBtn = QPushButton("Run to")

        self.limTbx = QLineEdit(self)
        self.limTbx.setFixedWidth(85)
        self.limTbx.setAlignment(Qt.AlignCenter)
        self.limTbx.setText("{:d}".format(self.dsp.runLim))

        self.fstBtn = QPushButton("Faster")
        self.slwBtn = QPushButton("Slower")
        self.outBtn = QPushButton("Zoom out")
        self.zInBtn = QPushButton("Zoom in")
        self.dmpBtn = QPushButton("Dump")
        self.loadBtn = QPushButton("Load")
        self.clrBtn = QPushButton("Clear Info")
        self.hideBtn = QPushButton("Hide Info")
        self.showBtn = QPushButton("Show Info")
        self.fineGrd = QCheckBox("Fine grid")
        self.showCoh = QCheckBox("Show COH lns")
        self.quitBtn = QPushButton("Quit")

        self.tmrLbl = QLabel("{:d}".format(self.timer.interval()))
        self.tmrLbl.setAlignment(Qt.AlignCenter)
        self.scfLbl = QLabel("{:.1f}".format(self.dsp.scaleFact))
        self.scfLbl.setAlignment(Qt.AlignCenter)

        vbox = QVBoxLayout()  ## these buttons laid out vertically,
        vbox.addStretch(1)  ## centred by means of a stretch at each end
        vbox.addWidget(self.stpLbl)
        vbox.addWidget(self.stpBtn)
        vbox.addWidget(self.rnpBtn)
        vbox.addWidget(self.limTbx)
        vbox.addWidget(self.fstBtn)
        vbox.addWidget(self.tmrLbl)
        vbox.addWidget(self.slwBtn)
        vbox.addWidget(self.outBtn)
        vbox.addWidget(self.scfLbl)
        vbox.addWidget(self.zInBtn)
        vbox.addWidget(self.dmpBtn)
        vbox.addWidget(self.loadBtn)
        vbox.addWidget(self.clrBtn)
        vbox.addWidget(self.hideBtn)
        vbox.addWidget(self.showBtn)
        vbox.addWidget(self.fineGrd)
        vbox.addWidget(self.showCoh)
        vbox.addWidget(self.quitBtn)
        vbox.addStretch(1)

        scrollArea = QScrollArea()  ## a scroll pane for the graphical display;
        scrollArea.setWidget(self.dsp)  ## put the display in the scroll pane
        scrollArea.horizontalScrollBar().setValue(500)
        scrollArea.verticalScrollBar().setValue(600)

        hbox = QHBoxLayout()  ## Put button panel to left of the scrollArea
        hbox.addLayout(vbox)
        hbox.addWidget(scrollArea)
        self.setLayout(hbox)

        self.setGeometry(50, 50, 1200,
                         700)  # Initally 1200x700 px with 50 px offsets

        # Register handlers - methods in Display instance
        self.rnpBtn.clicked.connect(self.stopStart)  #run-to/pause btn
        self.limTbx.editingFinished.connect(
            self.updtRunLim)  #run limit text box edited
        self.fstBtn.clicked.connect(self.faster)
        self.slwBtn.clicked.connect(self.slower)
        self.stpBtn.clicked.connect(self.step)
        self.outBtn.clicked.connect(self.zoomOut)
        self.zInBtn.clicked.connect(self.zoomIn)
        self.dmpBtn.clicked.connect(self.dumpState)
        self.loadBtn.clicked.connect(self.loadSwarm)
        self.clrBtn.clicked.connect(self.clearInfo)
        self.hideBtn.clicked.connect(self.hideInfo)
        self.showBtn.clicked.connect(self.showInfo)
        self.fineGrd.clicked.connect(self.toggle)
        self.showCoh.clicked.connect(self.toggle)
        self.quitBtn.clicked.connect(self.quit)

    # "slot" for editingFinished signal on run-limit text box:
    # -- update run limit from edited contents of the box
    def updtRunLim(self):
        try:
            self.dsp.runLim = int(self.limTbx.text())
        except ValueError:
            self.limTbx.setText("{:d}".format(self.dsp.runLim))
            self.limTbx.setFocus()
            print("Enter a valid positive integer in the box.")
            msg = QMessageBox()
            msg.setText("Enter a valid positive integer in the box.")
            msg.setStandardButtons(QMessageBox.Retry)
            x = msg.exec_()

    # Handle a timer tick by stepping the model and firing a repaint
    def tick(self):
        if self.dsp.stepCt < self.dsp.runLim:
            self.dsp.step()
        else:
            self.timer.stop()
            self.dsp.running = False
            self.rnpBtn.setText("Run to")
        self.stpLbl.setText("{:d}".format(self.dsp.stepCt))

    def step(self):
        self.dsp.step()
        self.stpLbl.setText("{:d}".format(self.dsp.stepCt))

    ## Timer control methods
    def faster(self):
        i = self.timer.interval()
        if i > 16:
            i //= 2
            self.timer.setInterval(i)
            self.tmrLbl.setText("{:d}".format(self.timer.interval()))

    def slower(self):
        i = self.timer.interval()
        if i < 1024:
            i *= 2
            self.timer.setInterval(i)
            self.tmrLbl.setText("{:d}".format(self.timer.interval()))

    def stopStart(self):
        if self.timer.isActive():
            self.timer.stop()
            self.dsp.running = False
            self.rnpBtn.setText("Run to")
        else:
            self.timer.start()
            self.dsp.running = True
            self.rnpBtn.setText("Pause")

    ## Zoom control methods
    def zoomOut(self):
        self.dsp.scaleFact *= self.dsp.scaleMultplr  # initially x2
        if self.dsp.scaleMultplr == 2:  # alternately x5, x2
            self.dsp.scaleMultplr = 5
        else:
            self.dsp.scaleMultplr = 2
        self.dsp.update()
        self.scfLbl.setText("{:.1f}".format(self.dsp.scaleFact))

    def zoomIn(self):
        if self.dsp.scaleMultplr == 2:  # alternate multiplier BEFORE using
            self.dsp.scaleMultplr = 5
        else:
            self.dsp.scaleMultplr = 2
        self.dsp.scaleFact /= self.dsp.scaleMultplr
        self.dsp.update()
        self.scfLbl.setText("{:.1f}".format(self.dsp.scaleFact))

    # persistence
    def dumpState(self):
        path, ok = QInputDialog.getText(self, "Dump", "Path:",
                                        QLineEdit.Normal, "")
        if ok and path != '':
            mdl.dump_swarm_txt(self.dsp.dta, self.dsp.kwargs, path)

    def loadSwarm(self):
        path, ok = QInputDialog.getText(self, "Load", "Path:",
                                        QLineEdit.Normal, "")
        if ok and path != '':
            self.dsp.dta, self.dsp.kwargs = mdl.load_swarm(path)
            self.dsp.stepCt = 1  # reset step count
            self.stpLbl.setText("{:d}".format(self.dsp.stepCt))
            self.dsp.update()  # repaint

    # Info pane
    def clearInfo(self):
        self.dsp.infoDsp.tArea.setPlainText("")

    def hideInfo(self):
        self.dsp.infoDsp.hide()

    def showInfo(self):
        self.dsp.infoDsp.show()

    # Pass grid, cohesion lines options to display window
    def toggle(self):
        self.dsp.fineGrdOn = self.fineGrd.isChecked()
        self.dsp.showCohOn = self.showCoh.isChecked()
        self.dsp.update()

    def quit(self):
        self.dsp.infoDsp.close()
        self.close()
Exemplo n.º 9
0
class RPiHardware(QWidget):
    sub_bot = pyqtSignal()
    sub_top = pyqtSignal()
    target_changed = pyqtSignal()
    laser_finished = pyqtSignal()
    laser_time_to_completion = pyqtSignal(int)

    def __init__(self, laser: CompexLaser, arduino: LaserBrainArduino):
        super().__init__()
        # Set up access to the passed laser control object and get current params
        self.laser = laser
        self.arduino = arduino

        # Set up class variables
        self.homing_sub = False  # Status flag that indicates if the substrate is being homed.
        self.laser_start_delay_msec = 4500
        # Setup timer to check when external trigger pulses finish.
        self.timer_check_laser_finished = QTimer()
        self.timer_check_laser_finished.timeout.connect(
            self.check_laser_finished)

        # Define Pin Dictionaries
        hold_time = 0.01
        self.buttons = {
            'sub_bot':
            NamedButton(22,
                        pull_up=False,
                        hold_time=hold_time,
                        dev_name='sub_bot'),
            'sub_top':
            NamedButton(18,
                        pull_up=False,
                        hold_time=hold_time,
                        dev_name='sub_top'),
            'sub_run':
            NamedButton(21,
                        pull_up=False,
                        hold_time=hold_time,
                        dev_name='sub_run'),
            'target_run':
            NamedButton(20,
                        pull_up=False,
                        hold_time=hold_time,
                        dev_name='target_run'),
            'laser_run':
            NamedButton(19,
                        pull_up=False,
                        hold_time=hold_time,
                        dev_name='laser_run')
        }

        self.high_pins = {
            'ard_rst':
            NamedOutputDevice(16, initial_value=True, dev_name='ard_rst'),
            'top_hi':
            NamedOutputDevice(27, initial_value=True, dev_name='top_hi'),
            'bot_hi':
            NamedOutputDevice(23, initial_value=True, dev_name='bot_hi')
        }

        self.gpio_handler = GPIOHandler(buttons=self.buttons,
                                        high_pins=self.high_pins,
                                        sig_print=False,
                                        parent=self)

        self.gpio_handler.sig_gpio_input.connect(self.gpio_act)

    def start_laser(self, num_pulses=None):
        self.laser.on()  # All cases need the laser in on mode.
        if num_pulses is None:
            self.laser_time_to_completion.emit(-9999)
        else:
            self.laser_time_to_completion.emit(
                ceil(self.laser_start_delay_msec / 1000 +
                     num_pulses / self.laser.reprate))
        if self.laser.trigger_src == 'INT':
            if num_pulses is None:
                pass
            elif isinstance(num_pulses, int):
                time = float(num_pulses / self.laser.reprate)
                warn(
                    "The number of pulses parameter can only be used accurately with an external trigger, internal "
                    "triggering of the laser will continue for {time} seconds then cease."
                    .format(time=time))
                QTimer.singleShot(
                    self.laser_start_delay_msec - 500 + time * 1000,
                    self.laser_finished.emit)
            else:
                raise TypeError(
                    "Num pulses was not an integer, partial pulses are not possible."
                )
        elif self.laser.trigger_src == 'EXT':
            # Turn the laser on so that it can be ready for when pulses start
            # Set the reprate on the arduino first so that we don't have any issues with that.
            self.arduino.update_laser_param('reprate', self.laser.reprate)
            if num_pulses is None:
                self.arduino.update_laser_param('start')
            elif isinstance(num_pulses, int):
                # Start the laser after 3 seconds of warmup
                QTimer.singleShot(
                    self.laser_start_delay_msec,
                    lambda: self.arduino.update_laser_param(
                        'goal', num_pulses))
                QTimer.singleShot(self.laser_start_delay_msec,
                                  lambda: print("laser pulsing started"))
                # Start a timer that will kick off looking for the laser to go dormant
                self.timer_check_laser_finished.start(
                    self.laser_start_delay_msec + 500)
            else:
                raise TypeError(
                    "Num pulses was not an integer, partial pulses are not possible."
                )

    def check_laser_finished(self):
        if self.buttons[
                'laser_run'].is_active and self.timer_check_laser_finished.interval(
                ) > 1000:
            self.timer_check_laser_finished.setInterval(200)
        elif self.buttons['laser_run'].is_active:
            pass
        elif not self.buttons['laser_run'].is_active:
            print("Laser activity finished, emitting signal")
            self.laser_finished.emit()
            self.timer_check_laser_finished.stop()

    def stop_laser(self):
        # Stop the laser from generating or accepting trigger pulses.
        self.laser.off()
        if self.laser.trigger_src == 'EXT':
            self.arduino.halt_laser()

    def set_reprate(self, reprate):
        self.laser.set_reprate(reprate)
        self.arduino.update_laser_param('reprate', reprate)

    # def set_energy(self, ):

    def substrate_limit(self, channel=None):
        # Halt the substrate if it is at the limit
        self.arduino.halt_motor('substrate')

    def home_sub(self):
        self.arduino.update_motor_param(
            'substrate', 'start',
            Global.SUB_DOWN)  # Start moving the substrate down without a goal
        self.homing_sub = True

    def set_sub_home(self):
        # ToDo: connect this to the sub bot signal so that it runs when the bottom is hit
        # If the user was running the substrate home routine, set zero position and reset homing_sub flag
        self.substrate_limit()
        if self.homing_sub:
            self.arduino.update_motor_param('substrate', 'position', 0)
            sleep(Global.OP_DELAY)
            self.arduino.update_motor_param('substrate', 'goal', 40350)
            self.homing_sub = False
        # else warn that the substrate has bottomed out
        else:
            # ToDo: Find a way to constrain substrate to only positive values in arduino code?
            warn(
                "Substrate has reached its lower limit and will not move further."
            )

    def move_sub_to(self, mm_tts: float):
        goal_position = (mm_tts - Global.SUB_D0) * Global.SUB_STEPS_PER_MM
        self.arduino.update_motor_param('substrate', 'goal',
                                        int(goal_position))

    def set_sub_speed(self, mm_spd: float):
        sub_spd = mm_spd * Global.SUB_STEPS_PER_MM
        self.arduino.update_motor_param('substrate', 'max speed', sub_spd)

    def set_sub_acceleration(self, mmpss: float):
        sub_acc = mmpss * Global.SUB_STEPS_PER_MM
        self.arduino.update_motor_param('substrate', 'acceleration', sub_acc)

    def set_carousel_home(self):
        home_carousel = HomeTargetCarouselDialog(self)
        home_carousel.exec_()
        if home_carousel == QDialog.accepted:
            self.arduino.update_motor_param('carousel', 'position', 0)
        elif home_carousel == QDialog.rejected:
            warn(
                "User canceled target carousel homing process, previous home value is preserved."
            )

    def set_carousel_rps(self, rps_speed: float):
        carousel_spd = rps_speed * Global.CAROUSEL_STEPS_PER_REV
        self.arduino.update_motor_param('carousel', 'max speed', carousel_spd)

    def set_carousel_acceleration(self, rpss: float):
        carousel_acc = rpss * Global.CAROUSEL_STEPS_PER_REV
        self.arduino.update_motor_param('carousel', 'acceleration',
                                        carousel_acc)

    def move_to_target(self, target_num: int):
        """
        Moves to the target indicated by target_num. Target numbers are zero indexed and can be kept track of
        of in the upper level GUI
        """
        goal = (target_num % 6) * (Global.CAROUSEL_STEPS_PER_REV / 6)
        self.arduino.update_motor_param('carousel', 'goal', goal)

    def current_target(self):
        # This calculates the current position as a fraction of the total rotation, then multiplies by the number of
        #  of steps and rounds to get to an integer target positon, finally takes the modulus by the number of positions
        #  to account for the circle (position 6 = position 0)
        # print(self.arduino.query_motor_parameters('carousel', 'position'))
        current_pos = int(
            self.arduino.query_motor_parameters('carousel', 'position'))

        return int(
            np.around(((current_pos % Global.CAROUSEL_STEPS_PER_REV) /
                       Global.CAROUSEL_STEPS_PER_REV) * 6) % 6)

    def raster_target(self):
        # ToDo: implement this and test the arduino code attached to it.
        pass

    def set_target_carousel_speed(self, dps: float):
        # ToDo: move these hardcoded values somewhere else so they are easier to change
        speed = (Global.CAROUSEL_STEPS_PER_REV) * (
            dps / 360
        )  # (steps per rev) * (degrees per second / degrees per rev)
        # Naming convention in arduino stepper library uses max speed as target speed and "speed" as instantaneous speed
        self.arduino.update_motor_param('carousel', 'max speed', speed)

    def is_sub_running(self):
        return self.buttons['sub_run'].is_active

    def is_carousel_running(self):
        return self.buttons['target_run'].is_active

    def is_laser_running(self):
        return self.buttons['laser_run'].is_active

    def is_idle(self):
        return not (self.is_carousel_running() or self.is_laser_running()
                    or self.is_sub_running())

    @pyqtSlot(str)
    def gpio_act(self, channel):
        # {'sub_bot': 22,
        #  'sub_top': 18,
        #  'sub_run': 21,
        #  'target_run': 20,
        #  'laser_run': 19}
        if channel == 'GPIO22':
            self.set_sub_home()
        elif channel == 'GPIO18':
            self.substrate_limit()
            warn(
                "Substrate has reached upper limit, make sure your entered position is within bounds "
                "and that the substrate is homed correctly.")
        print(
            "GPIO Signal Emitted and acted on from channel {}".format(channel))
Exemplo n.º 10
0
class Character(QWidget):

    def __init__(self, parent):
        super().__init__()
        self.parent = parent
        self.color = self.parent.character_color
        self.label = QLabel(self.parent)
        self._angle = math.radians(0)
        self.radio = self.parent.radio
        self.anim_angle = 0
        self.velocidad = 1
        self.update_Pixmap()
        self._x = 0
        self._y = 0
        self.isAlive = True
        self.drawing = True

        self.fps = self.parent.fps
        self.sum_intervals = 0
        self.seconds_passed = 0

        self.cada_tiempo = self.parent.cada_tiempo
        self.tiempo_corte = self.parent.tiempo_corte

        self.centro = Point(self.x+self.pixmap.width()/2, \
                            self.y+self.pixmap.height()/2)
        self.distancia_hitbox = 8
        self.hitbox_poderes = 10
        self.has_power = False
        self.x = random.randint(32*6, self.parent.display_x-32*6)
        self.y = random.randint(32*6, self.parent.display_y-32*6)
        position = { "x" : self.x, "y" : self.y }
        msg_to_send = { "type" : "position_update", \
                        "user" : self.color, \
                        "info" : position}
        self.parent.send_position.emit(msg_to_send)


    @property
    def x(self):
        return self._x


    @x.setter
    def x(self, value):
        self._x = value
        self.label.move(self.x, self.y)


    @property
    def y(self):
        return self._y


    @y.setter
    def y(self, value):
        self._y = value
        self.label.move(self.x, self.y)


    @property
    def angle(self):
        return self._angle


    @angle.setter
    def angle(self, value):
        self._angle = value
        self.update_Pixmap()


    def update_Pixmap(self, dead=None):
        if dead is True:
            self.pixmap = QPixmap(f'sprites/{self.color}_ball_dead.png')
        else:
            self.pixmap = QPixmap(f'sprites/{self.color}_ball.png')
        self.label.setMinimumSize(self.pixmap.width(), self.pixmap.height())
        self.label.setAlignment(Qt.AlignCenter)
        self.label.setFixedSize(self.pixmap.width(), self.pixmap.height())
        self.label.setPixmap(self.pixmap)


    def pos(self):
        return QPoint(self.x+8, self.y+8)


    def run(self):
        self.timer = QTimer(self.parent)
        self.timer.timeout.connect(self.animation)
        self.timer.start(1000/self.parent.fps)



    def cut_track_checker(self):
        self.time_checker = QTimer(self.parent)
        self.time_checker.timeout.connect(self.cut_track)
        self.time_checker.start(1000)


    def pickup_power_checker(self):
        self.power_timer = QTimer(self.parent)
        self.power_timer.timeout.connect(self.pickup_power)
        self.power_timer.start(1000/self.parent.fps)


    def pickup_power(self):
        if self.has_power is False:
            for power in self.parent.powers_in_screen:
                distancia = \
                        math.sqrt(  abs(self.centro.x - power.centro.x)**2 + \
                                    abs(self.centro.y - power.centro.y)**2    )
                if distancia <= self.hitbox_poderes:
                    power.taken(self)
                    print(self.velocidad)
                    self.has_power = True


    def cut_track(self):
        if self.isAlive is True:
            if self.drawing:
                if self.seconds_passed == self.cada_tiempo:
                    self.drawing = False
                    self.seconds_passed = 0
                else:
                    self.seconds_passed += self.time_checker.interval()/1000
            else:
                if self.seconds_passed == self.tiempo_corte:
                    self.drawing = True
                    self.seconds_passed = 0
                else:
                    self.seconds_passed += self.time_checker.interval()/1000
        else:
            self.update_Pixmap(True)


    def animation(self):
        if self.isAlive is True:
            if self.parent.is_paused is False:
                self.x += self.radio*self.velocidad*\
                            math.cos(self.angle*self.velocidad)
                self.y += self.radio*\
                            self.velocidad*math.sin(self.angle*self.velocidad)
                self.manipulate_position()
                self.centro = Point(self.x+self.pixmap.width()/2, \
                                    self.y+self.pixmap.height()/2)
                painter = QPainter(self.parent.image)
                painter.setPen(QPen(self.parent.brush_color, \
                                    self.parent.brush_size, \
                                    Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin))
                if self.drawing is True:
                    point_to_save = \
                        Point(self.centro.x-17*math.cos(self.angle), \
                            self.centro.y-17*math.sin(self.angle))
                    painter.drawLine(self.centro.x-17*math.cos(self.angle), \
                                    self.centro.y-17*math.sin(self.angle), \
                                    self.centro.x-16*math.cos(self.angle), \
                                    self.centro.y-16*math.sin(self.angle)   )
                    self.parent.tracks.add(point_to_save)
                self.parent.update()
                if  self.x < 0 or self.parent.display_x < self.x+16 or \
                    self.y < 0 or self.parent.display_y < self.y+16:
                    self.isAlive = False
                for point in self.parent.tracks:
                    distancia = math.sqrt(  abs(self.centro.x - point.x)**2 + \
                                    abs(self.centro.y - point.y)**2    )
                    if distancia <= self.distancia_hitbox:
                        self.isAlive = False
        else:
            self.timer.stop()
            self.points_to_give = 1


    def manipulate_position(self):
        position = { "x" : self.x, "y" : self.y }
        msg_to_send = { "type" : "position_update", \
                        "user" : self.color, \
                        "info" : position}
        self.parent.send_position.emit(msg_to_send)
Exemplo n.º 11
0
class PomoTimer:
    def __init__(self, times, label, tray, rpc, subject):
        self.subject = subject
        self.tray = tray
        self.label = label
        self.main_time = times[0]
        self.time = QTime(0, self.main_time, 0)
        self.timer = QTimer()
        self.timer.setInterval(1000)
        self.timer.timeout.connect(self.timerEvent)
        self.rep = 0
        self.RPC = rpc
        self.short_break = self.Interval_timer(times[1], self.label, self.tray,
                                               self, self.RPC, self.subject)
        self.long_break = self.Interval_timer(times[2], self.label, self.tray,
                                              self, self.RPC, self.subject)
        self.round = 1

    class Interval_timer:
        def __init__(self, main_time, label, tray, outer_class, rpc, subject):
            self.outer_class = outer_class
            self.tray = tray
            self.label = label
            self.main_time = main_time
            self.time = QTime(0, main_time, 0)
            self.RPC = rpc
            self.subject = subject

        def timerEvent(self):
            self.time = self.time.addSecs(-1)
            self.label.setText(self.time.toString("mm:ss"))
            if self.time.secsTo(QTime(0, 0, 0)) == 0:

                print("Break timer stopped")
                self.tray.showMessage("Tomatime", "Break time's up",
                                      self.tray.icon(), 4000)

                self.clearTimer()
                self.outer_class.timer.timeout.disconnect(self.timerEvent)
                self.outer_class.timer.timeout.connect(
                    self.outer_class.timerEvent)
                print("Returning to focus timer")
                self.outer_class.round += 1
                self.outer_class.updateDiscord("Studying")

            return print(self.time.toString("mm:ss"), "   Break timer")

        def startTimer(self):
            print("Starting secondary timer")
            self.outer_class.timer.timeout.connect(self.timerEvent)
            self.outer_class.updateDiscord("Taking a break")

        def clearTimer(self):
            print("Clearing break timer")
            self.time = QTime(0, self.main_time, 0)
            self.label.setText(self.time.toString("mm:ss"))

    def timerEvent(self):
        self.time = self.time.addSecs(-1)
        self.label.setText(self.time.toString("mm:ss"))
        if self.time.secsTo(QTime(0, 0, 0)) == 0:
            self.rep += 1

            self.timer.timeout.disconnect(self.timerEvent)
            self.clearTimer()

            print("Focus time's up")
            self.tray.showMessage("Tomatime", "Focus time's up",
                                  self.tray.icon(), 4000)
            if self.rep == 3:
                self.rep = 0

                self.long_break.startTimer()
                return print("Starting long break timer")

            else:

                self.short_break.startTimer()
                return print("Starting short break timer")

        return print(self.time.toString("mm:ss"), ("   Focus Timer Ticking"))

    def startTimer(self):
        self.timer.start()
        print(self.timer.interval())
        self.updateDiscord("Studying")

    def clearTimer(self):
        self.time = QTime(0, self.main_time, 0)

    def pauseTimer(self):
        self.timer.stop()
        try:
            self.RPC.update(state=f"Studying - Round {self.round}",
                            details="Paused",
                            large_image="fsidfsd")
        except:
            print("No Discord app running")

    def resetTimer(self):
        self.pauseTimer()
        self.short_break.clearTimer()
        self.long_break.clearTimer()
        self.clearTimer()
        self.label.setText(str(self.main_time) + ":00")
        try:
            self.timer.timeout.disconnect(self.short_break.timerEvent)
        except:
            pass

        try:
            self.timer.timeout.disconnect(self.long_break.timerEvent)
        except:
            pass
        try:
            self.timer.timeout.disconnect(self.timerEvent)
        except:
            pass
        self.timer.timeout.connect(self.timerEvent)

    def epochTime(self, mins, second):
        orig = datetime.datetime.fromtimestamp(time.time())
        new = orig + datetime.timedelta(minutes=mins, seconds=second)
        return time.mktime(new.timetuple())

    def updateDiscord(self, info):
        try:
            self.RPC.update(
                state=f"Studying {self.subject} - Round {self.round}",
                details=info,
                large_image="fsidfsd",
                end=self.epochTime(self.time.minute(), self.time.second()))
        except:
            print("No Discord app running")
Exemplo n.º 12
0
class E5SideBar(QWidget):
    """
    Class implementing a sidebar with a widget area, that is hidden or shown,
    if the current tab is clicked again.
    """
    Version = 1

    North = 0
    East = 1
    South = 2
    West = 3

    def __init__(self, orientation=None, delay=200, parent=None):
        """
        Constructor
        
        @param orientation orientation of the sidebar widget (North, East,
            South, West)
        @param delay value for the expand/shrink delay in milliseconds
            (integer)
        @param parent parent widget (QWidget)
        """
        super(E5SideBar, self).__init__(parent)

        self.__tabBar = QTabBar()
        self.__tabBar.setDrawBase(True)
        self.__tabBar.setShape(QTabBar.RoundedNorth)
        self.__tabBar.setUsesScrollButtons(True)
        self.__tabBar.setDrawBase(False)
        self.__stackedWidget = QStackedWidget(self)
        self.__stackedWidget.setContentsMargins(0, 0, 0, 0)
        self.__autoHideButton = QToolButton()
        self.__autoHideButton.setCheckable(True)
        self.__autoHideButton.setIcon(
            UI.PixmapCache.getIcon("autoHideOff.png"))
        self.__autoHideButton.setChecked(True)
        self.__autoHideButton.setToolTip(
            self.tr("Deselect to activate automatic collapsing"))
        self.barLayout = QBoxLayout(QBoxLayout.LeftToRight)
        self.barLayout.setContentsMargins(0, 0, 0, 0)
        self.layout = QBoxLayout(QBoxLayout.TopToBottom)
        self.layout.setContentsMargins(0, 0, 0, 0)
        self.layout.setSpacing(0)
        self.barLayout.addWidget(self.__autoHideButton)
        self.barLayout.addWidget(self.__tabBar)
        self.layout.addLayout(self.barLayout)
        self.layout.addWidget(self.__stackedWidget)
        self.setLayout(self.layout)

        # initialize the delay timer
        self.__actionMethod = None
        self.__delayTimer = QTimer(self)
        self.__delayTimer.setSingleShot(True)
        self.__delayTimer.setInterval(delay)
        self.__delayTimer.timeout.connect(self.__delayedAction)

        self.__minimized = False
        self.__minSize = 0
        self.__maxSize = 0
        self.__bigSize = QSize()

        self.splitter = None
        self.splitterSizes = []

        self.__hasFocus = False
        # flag storing if this widget or any child has the focus
        self.__autoHide = False

        self.__tabBar.installEventFilter(self)

        self.__orientation = E5SideBar.North
        if orientation is None:
            orientation = E5SideBar.North
        self.setOrientation(orientation)

        self.__tabBar.currentChanged[int].connect(
            self.__stackedWidget.setCurrentIndex)
        e5App().focusChanged.connect(self.__appFocusChanged)
        self.__autoHideButton.toggled[bool].connect(self.__autoHideToggled)

    def setSplitter(self, splitter):
        """
        Public method to set the splitter managing the sidebar.
        
        @param splitter reference to the splitter (QSplitter)
        """
        self.splitter = splitter
        self.splitter.splitterMoved.connect(self.__splitterMoved)
        self.splitter.setChildrenCollapsible(False)
        index = self.splitter.indexOf(self)
        self.splitter.setCollapsible(index, False)

    def __splitterMoved(self, pos, index):
        """
        Private slot to react on splitter moves.
        
        @param pos new position of the splitter handle (integer)
        @param index index of the splitter handle (integer)
        """
        if self.splitter:
            self.splitterSizes = self.splitter.sizes()

    def __delayedAction(self):
        """
        Private slot to handle the firing of the delay timer.
        """
        if self.__actionMethod is not None:
            self.__actionMethod()

    def setDelay(self, delay):
        """
        Public method to set the delay value for the expand/shrink delay in
        milliseconds.
        
        @param delay value for the expand/shrink delay in milliseconds
            (integer)
        """
        self.__delayTimer.setInterval(delay)

    def delay(self):
        """
        Public method to get the delay value for the expand/shrink delay in
        milliseconds.
        
        @return value for the expand/shrink delay in milliseconds (integer)
        """
        return self.__delayTimer.interval()

    def __cancelDelayTimer(self):
        """
        Private method to cancel the current delay timer.
        """
        self.__delayTimer.stop()
        self.__actionMethod = None

    def shrink(self):
        """
        Public method to record a shrink request.
        """
        self.__delayTimer.stop()
        self.__actionMethod = self.__shrinkIt
        self.__delayTimer.start()

    def __shrinkIt(self):
        """
        Private method to shrink the sidebar.
        """
        self.__minimized = True
        self.__bigSize = self.size()
        if self.__orientation in [E5SideBar.North, E5SideBar.South]:
            self.__minSize = self.minimumSizeHint().height()
            self.__maxSize = self.maximumHeight()
        else:
            self.__minSize = self.minimumSizeHint().width()
            self.__maxSize = self.maximumWidth()
        if self.splitter:
            self.splitterSizes = self.splitter.sizes()

        self.__stackedWidget.hide()

        if self.__orientation in [E5SideBar.North, E5SideBar.South]:
            self.setFixedHeight(self.__tabBar.minimumSizeHint().height())
        else:
            self.setFixedWidth(self.__tabBar.minimumSizeHint().width())

        self.__actionMethod = None

    def expand(self):
        """
        Public method to record a expand request.
        """
        self.__delayTimer.stop()
        self.__actionMethod = self.__expandIt
        self.__delayTimer.start()

    def __expandIt(self):
        """
        Private method to expand the sidebar.
        """
        self.__minimized = False
        self.__stackedWidget.show()
        self.resize(self.__bigSize)
        if self.__orientation in [E5SideBar.North, E5SideBar.South]:
            minSize = max(self.__minSize, self.minimumSizeHint().height())
            self.setMinimumHeight(minSize)
            self.setMaximumHeight(self.__maxSize)
        else:
            minSize = max(self.__minSize, self.minimumSizeHint().width())
            self.setMinimumWidth(minSize)
            self.setMaximumWidth(self.__maxSize)
        if self.splitter:
            self.splitter.setSizes(self.splitterSizes)

        self.__actionMethod = None

    def isMinimized(self):
        """
        Public method to check the minimized state.
        
        @return flag indicating the minimized state (boolean)
        """
        return self.__minimized

    def isAutoHiding(self):
        """
        Public method to check, if the auto hide function is active.
        
        @return flag indicating the state of auto hiding (boolean)
        """
        return self.__autoHide

    def eventFilter(self, obj, evt):
        """
        Public method to handle some events for the tabbar.
        
        @param obj reference to the object (QObject)
        @param evt reference to the event object (QEvent)
        @return flag indicating, if the event was handled (boolean)
        """
        if obj == self.__tabBar:
            if evt.type() == QEvent.MouseButtonPress:
                pos = evt.pos()
                for i in range(self.__tabBar.count()):
                    if self.__tabBar.tabRect(i).contains(pos):
                        break

                if i == self.__tabBar.currentIndex():
                    if self.isMinimized():
                        self.expand()
                    else:
                        self.shrink()
                    return True
                elif self.isMinimized():
                    self.expand()
            elif evt.type() == QEvent.Wheel:
                if qVersion() >= "5.0.0":
                    delta = evt.angleDelta().y()
                else:
                    delta = evt.delta()
                if delta > 0:
                    self.prevTab()
                else:
                    self.nextTab()
                return True

        return QWidget.eventFilter(self, obj, evt)

    def addTab(self, widget, iconOrLabel, label=None):
        """
        Public method to add a tab to the sidebar.
        
        @param widget reference to the widget to add (QWidget)
        @param iconOrLabel reference to the icon or the label text of the tab
            (QIcon, string)
        @param label the labeltext of the tab (string) (only to be
            used, if the second parameter is a QIcon)
        """
        if label:
            index = self.__tabBar.addTab(iconOrLabel, label)
            self.__tabBar.setTabToolTip(index, label)
        else:
            index = self.__tabBar.addTab(iconOrLabel)
            self.__tabBar.setTabToolTip(index, iconOrLabel)
        self.__stackedWidget.addWidget(widget)
        if self.__orientation in [E5SideBar.North, E5SideBar.South]:
            self.__minSize = self.minimumSizeHint().height()
        else:
            self.__minSize = self.minimumSizeHint().width()

    def insertTab(self, index, widget, iconOrLabel, label=None):
        """
        Public method to insert a tab into the sidebar.
        
        @param index the index to insert the tab at (integer)
        @param widget reference to the widget to insert (QWidget)
        @param iconOrLabel reference to the icon or the labeltext of the tab
            (QIcon, string)
        @param label the labeltext of the tab (string) (only to be
            used, if the second parameter is a QIcon)
        """
        if label:
            index = self.__tabBar.insertTab(index, iconOrLabel, label)
            self.__tabBar.setTabToolTip(index, label)
        else:
            index = self.__tabBar.insertTab(index, iconOrLabel)
            self.__tabBar.setTabToolTip(index, iconOrLabel)
        self.__stackedWidget.insertWidget(index, widget)
        if self.__orientation in [E5SideBar.North, E5SideBar.South]:
            self.__minSize = self.minimumSizeHint().height()
        else:
            self.__minSize = self.minimumSizeHint().width()

    def removeTab(self, index):
        """
        Public method to remove a tab.
        
        @param index the index of the tab to remove (integer)
        """
        self.__stackedWidget.removeWidget(self.__stackedWidget.widget(index))
        self.__tabBar.removeTab(index)
        if self.__orientation in [E5SideBar.North, E5SideBar.South]:
            self.__minSize = self.minimumSizeHint().height()
        else:
            self.__minSize = self.minimumSizeHint().width()

    def clear(self):
        """
        Public method to remove all tabs.
        """
        while self.count() > 0:
            self.removeTab(0)

    def prevTab(self):
        """
        Public slot used to show the previous tab.
        """
        ind = self.currentIndex() - 1
        if ind == -1:
            ind = self.count() - 1

        self.setCurrentIndex(ind)
        self.currentWidget().setFocus()

    def nextTab(self):
        """
        Public slot used to show the next tab.
        """
        ind = self.currentIndex() + 1
        if ind == self.count():
            ind = 0

        self.setCurrentIndex(ind)
        self.currentWidget().setFocus()

    def count(self):
        """
        Public method to get the number of tabs.
        
        @return number of tabs in the sidebar (integer)
        """
        return self.__tabBar.count()

    def currentIndex(self):
        """
        Public method to get the index of the current tab.
        
        @return index of the current tab (integer)
        """
        return self.__stackedWidget.currentIndex()

    def setCurrentIndex(self, index):
        """
        Public slot to set the current index.
        
        @param index the index to set as the current index (integer)
        """
        self.__tabBar.setCurrentIndex(index)
        self.__stackedWidget.setCurrentIndex(index)
        if self.isMinimized():
            self.expand()

    def currentWidget(self):
        """
        Public method to get a reference to the current widget.
        
        @return reference to the current widget (QWidget)
        """
        return self.__stackedWidget.currentWidget()

    def setCurrentWidget(self, widget):
        """
        Public slot to set the current widget.
        
        @param widget reference to the widget to become the current widget
            (QWidget)
        """
        self.__stackedWidget.setCurrentWidget(widget)
        self.__tabBar.setCurrentIndex(self.__stackedWidget.currentIndex())
        if self.isMinimized():
            self.expand()

    def indexOf(self, widget):
        """
        Public method to get the index of the given widget.
        
        @param widget reference to the widget to get the index of (QWidget)
        @return index of the given widget (integer)
        """
        return self.__stackedWidget.indexOf(widget)

    def isTabEnabled(self, index):
        """
        Public method to check, if a tab is enabled.
        
        @param index index of the tab to check (integer)
        @return flag indicating the enabled state (boolean)
        """
        return self.__tabBar.isTabEnabled(index)

    def setTabEnabled(self, index, enabled):
        """
        Public method to set the enabled state of a tab.
        
        @param index index of the tab to set (integer)
        @param enabled enabled state to set (boolean)
        """
        self.__tabBar.setTabEnabled(index, enabled)

    def orientation(self):
        """
        Public method to get the orientation of the sidebar.
        
        @return orientation of the sidebar (North, East, South, West)
        """
        return self.__orientation

    def setOrientation(self, orient):
        """
        Public method to set the orientation of the sidebar.

        @param orient orientation of the sidebar (North, East, South, West)
        """
        if orient == E5SideBar.North:
            self.__tabBar.setShape(QTabBar.RoundedNorth)
            self.__tabBar.setSizePolicy(QSizePolicy.Expanding,
                                        QSizePolicy.Preferred)
            self.barLayout.setDirection(QBoxLayout.LeftToRight)
            self.layout.setDirection(QBoxLayout.TopToBottom)
            self.layout.setAlignment(self.barLayout, Qt.AlignLeft)
        elif orient == E5SideBar.East:
            self.__tabBar.setShape(QTabBar.RoundedEast)
            self.__tabBar.setSizePolicy(QSizePolicy.Preferred,
                                        QSizePolicy.Expanding)
            self.barLayout.setDirection(QBoxLayout.TopToBottom)
            self.layout.setDirection(QBoxLayout.RightToLeft)
            self.layout.setAlignment(self.barLayout, Qt.AlignTop)
        elif orient == E5SideBar.South:
            self.__tabBar.setShape(QTabBar.RoundedSouth)
            self.__tabBar.setSizePolicy(QSizePolicy.Expanding,
                                        QSizePolicy.Preferred)
            self.barLayout.setDirection(QBoxLayout.LeftToRight)
            self.layout.setDirection(QBoxLayout.BottomToTop)
            self.layout.setAlignment(self.barLayout, Qt.AlignLeft)
        elif orient == E5SideBar.West:
            self.__tabBar.setShape(QTabBar.RoundedWest)
            self.__tabBar.setSizePolicy(QSizePolicy.Preferred,
                                        QSizePolicy.Expanding)
            self.barLayout.setDirection(QBoxLayout.TopToBottom)
            self.layout.setDirection(QBoxLayout.LeftToRight)
            self.layout.setAlignment(self.barLayout, Qt.AlignTop)
        self.__orientation = orient

    def tabIcon(self, index):
        """
        Public method to get the icon of a tab.
        
        @param index index of the tab (integer)
        @return icon of the tab (QIcon)
        """
        return self.__tabBar.tabIcon(index)

    def setTabIcon(self, index, icon):
        """
        Public method to set the icon of a tab.
        
        @param index index of the tab (integer)
        @param icon icon to be set (QIcon)
        """
        self.__tabBar.setTabIcon(index, icon)

    def tabText(self, index):
        """
        Public method to get the text of a tab.
        
        @param index index of the tab (integer)
        @return text of the tab (string)
        """
        return self.__tabBar.tabText(index)

    def setTabText(self, index, text):
        """
        Public method to set the text of a tab.
        
        @param index index of the tab (integer)
        @param text text to set (string)
        """
        self.__tabBar.setTabText(index, text)

    def tabToolTip(self, index):
        """
        Public method to get the tooltip text of a tab.
        
        @param index index of the tab (integer)
        @return tooltip text of the tab (string)
        """
        return self.__tabBar.tabToolTip(index)

    def setTabToolTip(self, index, tip):
        """
        Public method to set the tooltip text of a tab.
        
        @param index index of the tab (integer)
        @param tip tooltip text to set (string)
        """
        self.__tabBar.setTabToolTip(index, tip)

    def tabWhatsThis(self, index):
        """
        Public method to get the WhatsThis text of a tab.
        
        @param index index of the tab (integer)
        @return WhatsThis text of the tab (string)
        """
        return self.__tabBar.tabWhatsThis(index)

    def setTabWhatsThis(self, index, text):
        """
        Public method to set the WhatsThis text of a tab.
        
        @param index index of the tab (integer)
        @param text WhatsThis text to set (string)
        """
        self.__tabBar.setTabWhatsThis(index, text)

    def widget(self, index):
        """
        Public method to get a reference to the widget associated with a tab.
        
        @param index index of the tab (integer)
        @return reference to the widget (QWidget)
        """
        return self.__stackedWidget.widget(index)

    def saveState(self):
        """
        Public method to save the state of the sidebar.
        
        @return saved state as a byte array (QByteArray)
        """
        if len(self.splitterSizes) == 0:
            if self.splitter:
                self.splitterSizes = self.splitter.sizes()
            self.__bigSize = self.size()
            if self.__orientation in [E5SideBar.North, E5SideBar.South]:
                self.__minSize = self.minimumSizeHint().height()
                self.__maxSize = self.maximumHeight()
            else:
                self.__minSize = self.minimumSizeHint().width()
                self.__maxSize = self.maximumWidth()

        data = QByteArray()
        stream = QDataStream(data, QIODevice.WriteOnly)
        stream.setVersion(QDataStream.Qt_4_6)

        stream.writeUInt16(self.Version)
        stream.writeBool(self.__minimized)
        stream << self.__bigSize
        stream.writeUInt16(self.__minSize)
        stream.writeUInt16(self.__maxSize)
        stream.writeUInt16(len(self.splitterSizes))
        for size in self.splitterSizes:
            stream.writeUInt16(size)
        stream.writeBool(self.__autoHide)

        return data

    def restoreState(self, state):
        """
        Public method to restore the state of the sidebar.
        
        @param state byte array containing the saved state (QByteArray)
        @return flag indicating success (boolean)
        """
        if state.isEmpty():
            return False

        if self.__orientation in [E5SideBar.North, E5SideBar.South]:
            minSize = self.layout.minimumSize().height()
            maxSize = self.maximumHeight()
        else:
            minSize = self.layout.minimumSize().width()
            maxSize = self.maximumWidth()

        data = QByteArray(state)
        stream = QDataStream(data, QIODevice.ReadOnly)
        stream.setVersion(QDataStream.Qt_4_6)
        stream.readUInt16()  # version
        minimized = stream.readBool()

        if minimized and not self.__minimized:
            self.shrink()

        stream >> self.__bigSize
        self.__minSize = max(stream.readUInt16(), minSize)
        self.__maxSize = max(stream.readUInt16(), maxSize)
        count = stream.readUInt16()
        self.splitterSizes = []
        for i in range(count):
            self.splitterSizes.append(stream.readUInt16())

        self.__autoHide = stream.readBool()
        self.__autoHideButton.setChecked(not self.__autoHide)

        if not minimized:
            self.expand()

        return True

    #######################################################################
    ## methods below implement the autohide functionality
    #######################################################################

    def __autoHideToggled(self, checked):
        """
        Private slot to handle the toggling of the autohide button.
        
        @param checked flag indicating the checked state of the button
            (boolean)
        """
        self.__autoHide = not checked
        if self.__autoHide:
            self.__autoHideButton.setIcon(
                UI.PixmapCache.getIcon("autoHideOn.png"))
        else:
            self.__autoHideButton.setIcon(
                UI.PixmapCache.getIcon("autoHideOff.png"))

    def __appFocusChanged(self, old, now):
        """
        Private slot to handle a change of the focus.
        
        @param old reference to the widget, that lost focus (QWidget or None)
        @param now reference to the widget having the focus (QWidget or None)
        """
        self.__hasFocus = self.isAncestorOf(now)
        if self.__autoHide and not self.__hasFocus and not self.isMinimized():
            self.shrink()
        elif self.__autoHide and self.__hasFocus and self.isMinimized():
            self.expand()

    def enterEvent(self, event):
        """
        Protected method to handle the mouse entering this widget.
        
        @param event reference to the event (QEvent)
        """
        if self.__autoHide and self.isMinimized():
            self.expand()
        else:
            self.__cancelDelayTimer()

    def leaveEvent(self, event):
        """
        Protected method to handle the mouse leaving this widget.
        
        @param event reference to the event (QEvent)
        """
        if self.__autoHide and not self.__hasFocus and not self.isMinimized():
            self.shrink()
        else:
            self.__cancelDelayTimer()

    def shutdown(self):
        """
        Public method to shut down the object.
        
        This method does some preparations so the object can be deleted
        properly. It disconnects from the focusChanged signal in order to
        avoid trouble later on.
        """
        e5App().focusChanged.disconnect(self.__appFocusChanged)
Exemplo n.º 13
0
class PyScientificSpinBox(QDoubleSpinBox):
    # This is emited like valueChanged, but not when using setValue_quiet and not within valueChange_gui_time
    valueChanged_gui = pyqtSignal(float)

    # For PyQt < 5.11: only a class with attributes using integer can be made an enum
    #                  (so simple object subclass)
    #        from 5.11: Enum class can also be used. (also Q_ENUMS, Q_FLAGS are depracated for Q_ENUM and Q_FLAG)
    # Version can be obtain as:
    #   import PyQt5.QtCore
    #   PyQt5.QtCore.PYQT_VERSION_STR
    #class Sci_Mode(Enum):
    class Sci_Mode(object):
        ENG_UNIT, ENG, SCI = range(3)

    Q_ENUMS(Sci_Mode)

    class Enabled_Controls(object):
        none = 0x00
        minimum = 0x01
        maximum = 0x02
        min_increment = 0x04
        precision = 0x08
        display_mode = 0x10
        log_increment_mode = 0x20
        fixed_exponent = 0x40
        all = 0x7f
        minmax = 0x03
        no_minmax = all & ~minmax

    Q_FLAGS(Enabled_Controls)

    def __init__(self, *args, **kwargs):
        """ display_mode can be any of the Sci_Mode enum: ENG_UNIT (default), ENG or SCI
            To have a unit, set the suffix.
            keyboardTracking is disabled by default.
            accelerated is enabled by default
            decimals is overriden and redirected to precision.
            A new signal, valueChanged_gui, is emitted when the value is updated but at a restricted rate,
                 valueChange_gui_time, and also when pressing enter. It is not emitted when using
                 setValue_quiet
            option disable_context_menu can be used to disable the context menu. It is False by default.
            enabled_controls property can be used to disable some entries in the context_menu
        """
        self._initializing = True
        self._in_setValue_quiet = False
        key_track = kwargs.pop('keyboardTracking', False)
        kwargs['keyboardTracking'] = key_track
        # need to handle min/max before decimal
        self._disable_context_menu = kwargs.pop('disable_context_menu', False)
        maximum = kwargs.pop('maximum', np.inf)
        minimum = kwargs.pop('minimum', -np.inf)
        accelerated = kwargs.pop('accelerated', True)
        decimals = kwargs.pop('decimals', 5)
        precision = kwargs.pop('precision', decimals)
        self.text_formatter = Format_Float()
        self.text_formatter.precision = precision
        self.validator = FloatValidator()
        self._min_increment = 1e-15
        self._log_increment_mode = False
        self._log_increment = 0.01
        self._display_mode = self.Sci_Mode.ENG_UNIT
        self._enabled_controls = self.Enabled_Controls.all
        self._last_enabled_controls = None
        self._valueChange_timer = QTimer()
        self._valueChange_timer.setInterval(100)  # ms
        # We override decimals. This should set the parent value.
        kwargs[
            'decimals'] = 1000  # this get adjusted to max double  precision (323)
        kwargs['accelerated'] = accelerated
        kwargs['minimum'] = minimum
        kwargs['maximum'] = maximum
        # Note that this will call all properties that are left in kwargs
        super(PyScientificSpinBox, self).__init__(*args, **kwargs)
        # decimals is used when changing min/max, on setValue and on converting value to Text (internally, it is overriden here)
        #super(PyScientificSpinBox, self).setDecimals(1000) # this get adjusted to max double  precision (323)
        # make sure parent class decimals is set properly
        if super(PyScientificSpinBox, self).decimals() < 323:
            raise RuntimeError('Unexpected parent decimals value.')
        # The suffix/prefix change in the above QDoubleSpinBox.__init__ did not call our override
        # lets update them now
        self.setSuffix(self.suffix())
        self.setPrefix(self.prefix())
        # pyqt5 5.6 on windows (at least) QAction requires the parent value.
        self.copyAction = QAction('&Copy',
                                  None,
                                  shortcut=QKeySequence(QKeySequence.Copy),
                                  triggered=self.handle_copy)
        self.pasteAction = QAction('&Paste',
                                   None,
                                   shortcut=QKeySequence(QKeySequence.Paste),
                                   triggered=self.handle_paste)
        self.addActions((self.copyAction, self.pasteAction))
        self._create_menu_init()
        self.clipboard = QApplication.instance().clipboard()
        self._last_valueChanged_gui = self.value()
        self._current_valueChanged_gui = self._last_valueChanged_gui
        self.valueChanged.connect(self._valueChanged_helper)
        self._valueChange_timer.timeout.connect(self._do_valueChanged_gui_emit)
        self._last_focus_reason_mouse = False
        self.lineEdit().installEventFilter(self)
        self.text_is_being_changed_state = False
        self.lineEdit().textEdited.connect(self.text_is_being_changed)
        self._last_key_pressed_is_enter = False
        self.editingFinished.connect(self.editFinished_slot)
        self._in_config_menu = False
        self._initializing = False

    def _create_menu_init(self):
        if self._disable_context_menu:
            return
        # pyqt5 5.6 on windows (at least) QAction requires the parent value.
        self.accel_action = QAction('&Acceleration',
                                    None,
                                    checkable=True,
                                    checked=self.isAccelerated(),
                                    triggered=self.setAccelerated)
        self.log_increment_mode_action = QAction(
            '&Log increment',
            None,
            checkable=True,
            checked=self.log_increment_mode,
            triggered=self.log_increment_mode_change)
        self.help_action = QAction('&Help', None, triggered=self.help_dialog)
        self.stepUpAction = QAction('Step &up', None)
        self.stepUpAction.triggered.connect(self.stepUp)
        self.stepDownAction = QAction('Step &down', None)
        self.stepDownAction.triggered.connect(self.stepDown)
        self.display_mode_grp = QActionGroup(None)
        self.display_mode_eng_unit = QAction('Engineering with &units',
                                             None,
                                             checkable=True)
        self.display_mode_eng = QAction('&Engineering', None, checkable=True)
        self.display_mode_sci = QAction('&Scientific', None, checkable=True)
        self.display_mode_option = {
            self.Sci_Mode.ENG_UNIT: self.display_mode_eng_unit,
            self.Sci_Mode.ENG: self.display_mode_eng,
            self.Sci_Mode.SCI: self.display_mode_sci
        }
        self.display_mode_grp.addAction(self.display_mode_eng_unit)
        self.display_mode_grp.addAction(self.display_mode_eng)
        self.display_mode_grp.addAction(self.display_mode_sci)
        self.display_mode_grp.triggered.connect(self.handle_display_mode)
        # fixed exponent
        self.fixed_exponent_menu = submenu = QMenu('&Fixed exponent')
        self.fixed_exponent_group = subgroup = QActionGroup(None)
        self.fixed_exponent_actions = actions = {}
        for i in range(-15, 9 + 1, 3)[::-1]:
            if i == 0:
                act = QAction('%i: %s' % (i, ''), None, checkable=True)
            else:
                act = QAction('%i: %s' % (i, units_inverse[i]),
                              None,
                              checkable=True)
            submenu.addAction(act)
            subgroup.addAction(act)
            actions[i] = act
        act = QAction('&Custom', None, checkable=True)
        submenu.addAction(act)
        subgroup.addAction(act)
        actions['custom'] = act
        self.fixed_exponent_custom_action = QWidgetAction(None)
        self.fixed_exponent_custom_widget = QSpinBox(value=0,
                                                     minimum=-MAX_FIXED_EXP,
                                                     maximum=MAX_FIXED_EXP)
        self.fixed_exponent_custom_action.setDefaultWidget(
            self.fixed_exponent_custom_widget)
        submenu.addAction(self.fixed_exponent_custom_action)
        submenu.addSeparator()
        act = QAction('&Disabled', None, checkable=True)
        submenu.addAction(act)
        subgroup.addAction(act)
        actions['disabled'] = act
        subgroup.triggered.connect(self.handle_fixed_exponent)
        self.fixed_exponent_custom_widget.valueChanged.connect(
            self.handle_fixed_exponent)
        # precision
        self.precision_menu = submenu = QMenu('&Precision')
        self.precision_group = subgroup = QActionGroup(None)
        self.precision_actions = actions = {}
        for i in range(0, 10):
            act = QAction('&%i' % i, None, checkable=True)
            submenu.addAction(act)
            subgroup.addAction(act)
            actions[i] = act
        act = QAction('&Custom', None, checkable=True)
        submenu.addAction(act)
        subgroup.addAction(act)
        actions['custom'] = act
        self.precision_custom_action = QWidgetAction(None)
        self.precision_custom_widget = QSpinBox(value=0,
                                                minimum=0,
                                                maximum=MAX_PRECISION)
        self.precision_custom_action.setDefaultWidget(
            self.precision_custom_widget)
        submenu.addAction(self.precision_custom_action)
        subgroup.triggered.connect(self.handle_precision)
        self.precision_custom_widget.valueChanged.connect(
            self.handle_precision)
        # min, max, min_increment
        self.min_custom_action = QWidgetAction(None)
        self.min_custom_widget = PyScientificSpinBox(prefix='min: ',
                                                     disable_context_menu=True)
        self.min_custom_action.setDefaultWidget(self.min_custom_widget)
        self.min_custom_widget.valueChanged.connect(
            self.handle_min_custom_widget)
        self.max_custom_action = QWidgetAction(None)
        self.max_custom_widget = PyScientificSpinBox(prefix='max: ',
                                                     disable_context_menu=True)
        self.max_custom_action.setDefaultWidget(self.max_custom_widget)
        self.max_custom_widget.valueChanged.connect(
            self.handle_max_custom_widget)
        self.min_incr_custom_action = QWidgetAction(None)
        self.min_incr_custom_widget = PyScientificSpinBox(
            prefix='min incr: ', disable_context_menu=True)
        self.min_incr_custom_action.setDefaultWidget(
            self.min_incr_custom_widget)
        self.min_incr_custom_widget.valueChanged.connect(
            self.handle_min_incr_custom_widget)
        self.display_mode_sep = QAction('Display mode', None)
        self.display_mode_sep.setSeparator(True)

    def text_is_being_changed(self):
        self.text_is_being_changed_state = True

    def _create_menu(self):
        ec = self.enabled_controls
        EC = self.Enabled_Controls
        if ec == self._last_enabled_controls:
            return self.modified_context_menu
        self._last_enabled_controls = ec
        #self.modified_context_menu = menu = self.lineEdit().createStandardContextMenu()
        self.modified_context_menu = menu = QMenu('Main Menu')
        menu.addActions((self.copyAction, self.pasteAction))
        menu.addSeparator()
        menu.addActions((self.stepUpAction, self.stepDownAction))
        menu.addSeparator()
        if ec & EC.display_mode:
            menu.addActions((self.display_mode_sep, self.display_mode_eng_unit,
                             self.display_mode_eng, self.display_mode_sci))
            menu.addSeparator()
        menu.addAction(self.accel_action)
        if ec & EC.log_increment_mode:
            menu.addAction(self.log_increment_mode_action)
        if ec & EC.fixed_exponent:
            menu.addMenu(self.fixed_exponent_menu)
        if ec & EC.precision:
            menu.addMenu(self.precision_menu)
        menu.addActions((self.min_custom_action, self.max_custom_action,
                         self.min_incr_custom_action))
        self.min_custom_action.setEnabled(ec & EC.minimum)
        self.max_custom_action.setEnabled(ec & EC.maximum)
        self.min_incr_custom_action.setEnabled(ec & EC.min_increment)
        menu.addSeparator()
        menu.addAction(self.help_action)
        return menu

    #def __del__(self):
    #    print('Deleting up PyScientificSpinBox')

    def pyqtConfigure(self, **kwargs):
        # this is necessary to override pyqtConfigure decimals
        # otherwise it calls the parent one directly.
        decimals = kwargs.pop('decimals', None)
        if decimals is not None:
            self.setDecimals(decimals)
        # same thing for suffix/prefix
        prefix = kwargs.pop('prefix', None)
        if prefix is not None:
            self.setPrefix(prefix)
        suffix = kwargs.pop('suffix', None)
        if suffix is not None:
            self.setSuffix(suffix)
        if len(kwargs):
            super(PyScientificSpinBox, self).pyqtConfigure(**kwargs)

    def get_config(self):
        """ The return value can by used in pyqtConfigure.
        """
        return dict(display_mode=self.display_mode,
                    precision=self.precision,
                    min_increment=self.min_increment,
                    minimum=self.minimum(),
                    maximum=self.maximum(),
                    log_increment_mode=self.log_increment_mode,
                    log_increment=self.log_increment,
                    fixed_exponent=self.fixed_exponent,
                    suffix=self.suffix(),
                    prefix=self.prefix(),
                    singleStep=self.singleStep(),
                    accelerated=self.isAccelerated(),
                    wrapping=self.wrapping(),
                    keyboardTracking=self.keyboardTracking())

    def force_update(self):
        val = self.value()
        self.setValue(val)
        self.selectAll()

    def decimals(self):
        return self.precision

    def setDecimals(self, val):
        self.precision = val

    @pyqtProperty(Enabled_Controls)
    def enabled_controls(self):
        return self._enabled_controls

    @enabled_controls.setter
    def enabled_controls(self, val):
        self._enabled_controls = val

    @pyqtProperty(Sci_Mode)
    #@pyqtProperty(int)
    def display_mode(self):
        return self._display_mode

    @display_mode.setter
    def display_mode(self, val):
        self._display_mode = val
        self.force_update()
        self.updateGeometry()

    @pyqtProperty(int)
    def min_increment(self):
        return self._min_increment

    @min_increment.setter
    def min_increment(self, val):
        self._min_increment = val
        if self.singleStep() < val:
            self.setSingleStep(val)

    @pyqtProperty(int)
    def precision(self):
        return self.text_formatter.precision

    @precision.setter
    def precision(self, val):
        val = min(max(val, 0), MAX_PRECISION)
        self.text_formatter.precision = val
        self.force_update()
        self.updateGeometry()

    @pyqtProperty(bool)
    def log_increment_mode(self):
        return self._log_increment_mode

    @log_increment_mode.setter
    def log_increment_mode(self, val):
        self._log_increment_mode = val

    @pyqtProperty(float)
    def log_increment(self):
        return self._log_increment

    @log_increment.setter
    def log_increment(self, val):
        self._log_increment = val

    @pyqtProperty(int)
    def fixed_exponent(self):
        """ 9999 disables fixed exponent mode """
        val = self.text_formatter.fixed_exponent
        if val is None:
            return 9999
        return val

    @fixed_exponent.setter
    def fixed_exponent(self, val):
        if val == 9999:
            val = None
        else:
            # keep within -308 to 308: MAX_FIXED_EXP = 308
            val = max(val, -MAX_FIXED_EXP)
            val = min(val, MAX_FIXED_EXP)
        self.text_formatter.fixed_exponent = val
        self.force_update()
        self.updateGeometry()

    @pyqtProperty(float)
    def valueChange_gui_time(self):
        return self._valueChange_timer.interval() / 1e3

    @valueChange_gui_time.setter
    def valueChange_gui_time(self, val):
        val = int(val * 1e3)
        if val <= 0:
            self._valueChange_timer.stop()
        self._valueChange_timer.setInterval(val)

    def keyPressEvent(self, event):
        key = event.key()
        self._last_key_pressed_is_enter = False
        if event.matches(QKeySequence.Copy):
            #print('doing copy')
            self.copyAction.trigger()
        elif event.matches(QKeySequence.Paste):
            #print('doing paste')
            self.pasteAction.trigger()
        elif key == QtCore.Qt.Key_Escape:
            if self.text_is_being_changed_state:
                #print('doing escape')
                self.setValue(self.value())
            else:
                #print('letting escape propagate')
                event.ignore()
        elif key in [QtCore.Qt.Key_Enter, QtCore.Qt.Key_Return]:
            self._last_key_pressed_is_enter = True
            text_was_being_changed_state = self.text_is_being_changed_state
            super(PyScientificSpinBox, self).keyPressEvent(event)
            # default handling does event.ignore() so event, after doing something here
            # propagate. only do that if test is not being changed.
            if text_was_being_changed_state:
                event.accept()
        else:
            super(PyScientificSpinBox, self).keyPressEvent(event)

    # Can't seem to do the same thing (stop short timer) for wheelEvent
    def keyReleaseEvent(self, event):
        if not event.isAutoRepeat() and event.key() in [
                QtCore.Qt.Key_Up, QtCore.Qt.Key_Down, QtCore.Qt.Key_PageUp,
                QtCore.Qt.Key_PageDown
        ]:
            self._valueChange_timer.stop()
            self._do_valueChanged_gui_emit()
        super(PyScientificSpinBox, self).keyReleaseEvent(event)

    def mouseReleaseEvent(self, event):
        if event.button() == QtCore.Qt.LeftButton:
            self._valueChange_timer.stop()
            self._do_valueChanged_gui_emit()
        super(PyScientificSpinBox, self).mouseReleaseEvent(event)

    def mousePressEvent(self, event):
        self._last_focus_reason_mouse = False
        super(PyScientificSpinBox, self).mousePressEvent(event)

    def focusInEvent(self, event):
        if event.reason() == QtCore.Qt.MouseFocusReason:
            self._last_focus_reason_mouse = True
        l = self.lineEdit()
        super(PyScientificSpinBox, self).focusInEvent(event)
        # Using tab to activate widget already does this.
        # It is necessary so that pressing the up/down buttons directly from another widget
        # does not increment before the unit (because position is 0, selection is False)
        self.selectAll()

    def editFinished_slot(self):
        self.text_is_being_changed_state = False
        if self._last_key_pressed_is_enter:
            self._last_key_pressed_is_enter = False
            self._do_valueChanged_gui_emit(force=True)

    def eventFilter(self, watched_obj, event):
        if event.type(
        ) == QEvent.MouseButtonPress and self._last_focus_reason_mouse:
            self._last_focus_reason_mouse = False
            return True
        return False

    def handle_copy(self):
        #print('handling copy')
        self.clipboard.setText('%r' % self.value())

    def handle_paste(self):
        #print('handling paste')
        text = self.clipboard.text()
        # let user decide if it is ok by pressing ok (not esc)
        self.lineEdit().setText(text)
        self.text_is_being_changed_state = True
        # update value immediately
        #try:
        #    self.setValue(float(text))
        #except ValueError: # maybe text is not pure number (contains units)
        #    self.lineEdit().setText(text)
        #    self.interpretText()

    def handle_display_mode(self, action):
        if self._in_config_menu:
            return
        mode = [k for k, v in self.display_mode_option.items()
                if v == action][0]
        self.display_mode = mode

    def handle_fixed_exponent(self, action_or_val):
        if self._in_config_menu:
            return
        if isinstance(action_or_val, QAction):
            enable = False
            action = [
                k for k, v in self.fixed_exponent_actions.items()
                if v == action_or_val
            ][0]
            if action == 'disabled':
                self.fixed_exponent = 9999
            elif action == 'custom':
                enable = True
                self.fixed_exponent = self.fixed_exponent_custom_widget.value()
            else:
                self.fixed_exponent = action
            self.fixed_exponent_custom_action.setEnabled(enable)
        else:
            self.fixed_exponent = action_or_val

    def handle_precision(self, action_or_val):
        if self._in_config_menu:
            return
        if isinstance(action_or_val, QAction):
            enable = False
            action = [
                k for k, v in self.precision_actions.items()
                if v == action_or_val
            ][0]
            if action == 'custom':
                enable = True
                self.precision = self.precision_custom_widget.value()
            else:
                self.precision = action
            self.precision_custom_action.setEnabled(enable)
        else:
            self.precision = action_or_val

    def handle_min_custom_widget(self, val):
        if self._in_config_menu:
            return
        self.setMinimum(val)

    def handle_max_custom_widget(self, val):
        if self._in_config_menu:
            return
        self.setMaximum(val)

    def handle_min_incr_custom_widget(self, val):
        if self._in_config_menu:
            return
        self.min_increment = val

    def setSuffix(self, string):
        self.validator.suffix = string
        super(PyScientificSpinBox, self).setSuffix(string)
        self.updateGeometry()

    def setPrefix(self, string):
        self.validator.prefix = string
        super(PyScientificSpinBox, self).setPrefix(string)
        self.updateGeometry()

    def contextMenuEvent(self, event):
        if self._disable_context_menu:
            return
        self._in_config_menu = True
        menu = self._create_menu()
        se = self.stepEnabled()
        self.stepUpAction.setEnabled(se & QAbstractSpinBox.StepUpEnabled)
        self.stepDownAction.setEnabled(se & QAbstractSpinBox.StepDownEnabled)
        mode = self.display_mode
        self.display_mode_option[mode].setChecked(True)
        self.log_increment_mode_action.setChecked(self.log_increment_mode)
        self.accel_action.setChecked(self.isAccelerated())
        # fixed_exponent
        fe = self.fixed_exponent
        if fe == 9999:
            fe = 'disabled'
        action = self.fixed_exponent_actions.get(fe, None)
        fe_action_custom = self.fixed_exponent_actions['custom']
        if fe_action_custom.isChecked() or action is None:
            action = fe_action_custom
            self.fixed_exponent_custom_action.setEnabled(True)
            self.fixed_exponent_custom_widget.setValue(fe)
        else:
            self.fixed_exponent_custom_action.setEnabled(False)
        action.setChecked(True)
        # precision
        prec = self.precision
        action = self.precision_actions.get(prec, None)
        prec_action_custom = self.precision_actions['custom']
        if prec_action_custom.isChecked() or action is None:
            action = prec_action_custom
            self.precision_custom_action.setEnabled(True)
            self.precision_custom_widget.setValue(prec)
        else:
            self.precision_custom_action.setEnabled(False)
        action.setChecked(True)
        #min
        conf = self.get_config()
        del conf['prefix'], conf['maximum'], conf['minimum'], conf[
            'min_increment']
        conf['keyboardTracking'] = False
        self.min_custom_widget.pyqtConfigure(value=self.minimum(), **conf)
        self.max_custom_widget.pyqtConfigure(value=self.maximum(), **conf)
        self.min_incr_custom_widget.pyqtConfigure(value=self.min_increment,
                                                  minimum=0.,
                                                  **conf)
        self._in_config_menu = False
        menu.exec_(event.globalPos())

    def validate(self, text, position):
        if self._initializing:
            return QValidator.Invalid, text, position
        return self.validator.validate(text, position)

    def fixup(self, text):
        return self.validator.fixup(text)

    def valueFromText(self, text):
        val, unit, scale = self.validator.valueFromText(text)
        if unit:
            if val == 0:
                self.text_formatter.decode_eng(scale)  # update last_exp
        return val

    def textFromValue(self, value, tmp=False):
        #mode = self.display_mode_grp.checkedAction()
        mode = self.display_mode
        fmt = self.text_formatter
        fmt_func_d = {
            self.Sci_Mode.ENG_UNIT: fmt.to_eng_unit,
            self.Sci_Mode.ENG: fmt.to_eng,
            self.Sci_Mode.SCI: fmt.to_float
        }
        suffix = self.suffix()
        ret = fmt_func_d[mode](value, suffix, tmp)
        return ret

    # this is needed because setAccelerated is not a slot, which makes
    # using it connect block deletes. wrapping it in python makes it work properly.
    def setAccelerated(self, val):
        if self._in_config_menu:
            return
        super(PyScientificSpinBox, self).setAccelerated(val)

    def log_increment_mode_change(self, val):
        if self._in_config_menu:
            return
        self.log_increment_mode = val

    def setSingleStep_bounded(self, incr):
        incr = max(incr, self.min_increment)
        self.setSingleStep(incr)

    @pyqtSlot(int)
    def stepBy(self, steps):
        pos = self.lineEdit().cursorPosition()
        if not self.lineEdit().hasSelectedText():
            text = self.lineEdit().text()
            special = self.specialValueText()
            if not (special and special == text):
                incr, log_incr = self.validator.find_increment(text, pos)
                self.setSingleStep_bounded(incr)
                if log_incr is not None:
                    self.log_increment = log_incr
        if self.log_increment_mode:
            #frac = self._adapative_step_frac
            #frac = .01
            frac = self.log_increment
            frac_exp = np.floor(np.log10(frac))
            cval = self.value()
            step_dir = np.sign(steps)
            val_dir = np.sign(cval)
            min_increment = self.min_increment
            for i in range(np.abs(steps)):
                cval = self.value()
                val_dir = np.sign(cval)
                abs_cval = np.abs(cval)
                abs_increase = val_dir == step_dir
                if cval == 0:
                    incr = min_increment
                elif abs_cval < min_increment:
                    if abs_increase:
                        incr = min_increment - cval
                    else:
                        #incr = abs_cval
                        self.setValue(0)
                        continue
                else:
                    incr = abs_cval * frac
                    cval_exp = np.log10(abs_cval)
                    incr_exp = np.floor(cval_exp + frac_exp)
                    incr = 10.**incr_exp
                    if not abs_increase:
                        incr_1 = 10.**(incr_exp - 1)
                        if np.log10(np.abs(cval) -
                                    incr_1) < np.floor(cval_exp):
                            incr = incr_1
                self.setSingleStep_bounded(incr)
                super(PyScientificSpinBox, self).stepBy(step_dir)
        else:
            super(PyScientificSpinBox, self).stepBy(steps)

    def _do_valueChanged_gui_emit(self, val=None, force=False):
        if val is None:
            val = self._current_valueChanged_gui
        if val != self._last_valueChanged_gui or force:
            self._last_valueChanged_gui = val
            self.valueChanged_gui.emit(val)
        else:
            self._valueChange_timer.stop()

    @pyqtSlot(float)
    def _valueChanged_helper(self, val):
        if not self._in_setValue_quiet:
            self._current_valueChanged_gui = val
            if not self._valueChange_timer.isActive():
                self._do_valueChanged_gui_emit(val)
                if self.valueChange_gui_time > 0:
                    self._valueChange_timer.start()

    def setValue_quiet(self, val):
        self._in_setValue_quiet = True
        self.setValue(val)
        self._in_setValue_quiet = False

    def _sizeHint_helper(self, minimum=False):
        self.ensurePolished()
        # code from widgets/qabstractspinbox.cpp
        fm = self.fontMetrics()  # migth need a copy
        try:
            fmw = fm.horizontalAdvance
        except AttributeError:
            fmw = fm.width
        h = self.lineEdit().sizeHint().height()
        w = 0
        if minimum:
            fixedContent = self.prefix() + " "
        else:
            fixedContent = self.prefix() + self.suffix() + " "
        val = -988.888e-99
        if self.fixed_exponent != 9999:
            if self.maximum() != np.inf:
                val = self.maximum()
            else:
                val = -999888. * 10.**self.fixed_exponent
        s = self.textFromValue(val, tmp=True)
        s += fixedContent
        w = fmw(s)
        special = self.specialValueText()
        if special:
            w = max(w, fmw(special))
        w += 2  # cursor blinking space
        hint = QSize(w, h)
        opt = QStyleOptionSpinBox()
        self.initStyleOption(opt)
        style = self.style()
        hint = style.sizeFromContents(style.CT_SpinBox, opt, QSize(w, h), self)
        qapp = QApplication.instance()
        hint = hint.expandedTo(qapp.globalStrut())
        return hint

    def minimumSizeHint(self):
        return self._sizeHint_helper(minimum=True)

    def sizeHint(self):
        return self._sizeHint_helper()

# Observations from QDoubleSpinBox code:
#  minimumSizeHint is the same as SizeHint less the space for suffix
#  sizeHint
#     # internal commands is space of prefix+suffix+max(min, max, special)+graphical elements
#     # note that text is limited to 18 char.
# sizeHint is recalculated and advertised when calling setSuffix (doubleSpinBox.setPrefix seems to only update text)
#  aslo setMinimum, setMaximum, setRange all update the geometry
# setSpecialValueText, setValue, setSingleStep and setSuffix all update the text

    help_text = u"""
For entry you can use scientific entry (like 1e-3) or units. You can add spaces.
The available units are (they are case sensitive):
     Y(1e24), Z, E, P, T, G, M, k(1e3), h(100), da(10), d(0.1), c, m, µ, n, p, f, a, z, y(1e-24)
you can use u instead of µ, and b for no unit (1)

The arrows, or the mouse wheel steps the value.
Page up/down steps the value 10 times faster. Using the CTRL key also goes 10 times faster but only for wheel events.
If you change the cursor position (left/right keys or mouse click) before stepping, the number
to the left of the cursor is incremented by 1.
Adapatative option will increase in a logarithmic way.
Acceleration is a speed up of the steps when pressing the GUI button.
"""

    def help_dialog(self):
        dialog = QMessageBox.information(self, 'Entry Help', self.help_text)
Exemplo n.º 14
0
class Widget(QMainWindow):
    loaded_signal = pyqtSignal()
    progress_bar = pyqtSignal(int)
    exception_signal = pyqtSignal(Exception)

    def __init__(self):
        super().__init__()
        self.pbar = QProgressBar(self)
        self.pbar.setGeometry(30, 40, 200, 25)
        self.pbar.setValue(0)
        self.pbar.hide()
        self.isOpened = False
        self.is_loading = False
        self.img = QLabel()
        self.form_widget = CentralWidget(self)
        self.setCentralWidget(self.form_widget)

        self.progress_bar.connect(self.pbar.setValue)
        self.exception_signal.connect(self.show_error)
        self.loaded_signal.connect(self.set_gif_settings)

        self.gifinfo = None
        self.gif_id = 0

        self.create_menubar()

        self.scrTimer = QTimer(self)
        self.scrTimer.setInterval(50)
        self.scrTimer.timeout.connect(self.timerEvent)

        self.pal = self.form_widget.isPaused.palette()
        self.pal.setColor(QPalette.WindowText, QColor("red"))
        self.form_widget.isPaused.setPalette(self.pal)
        self.frames = [QPixmap()]

    def show_error(self, e):
        self.test = QMessageBox(self)
        self.test.setWindowModality(Qt.ApplicationModal)
        self.test.setWindowTitle('Error')
        self.test.setIcon(QMessageBox.Critical)
        self.test.setText(str(e))
        self.test.setStandardButtons(QMessageBox.Ok)
        self.test.show()
        self.pbar.hide()
        self.is_loading = False

    def create_menubar(self):
        main_menu = self.menuBar()

        file_menu = main_menu.addMenu('File')
        open_button = QAction('Open', self)
        open_button.setShortcut('Ctrl+O')
        open_button.triggered.connect(self.open_gif)
        close_button = QAction('Close', self)
        close_button.setShortcut('Ctrl+C')
        close_button.triggered.connect(self.close_gif)
        file_menu.addAction(open_button)
        file_menu.addAction(close_button)

        frame_menu = main_menu.addMenu('Frame')
        prev_button = QAction('Prev', self)
        prev_button.setShortcut('Left')
        prev_button.triggered.connect(self.prev_frame)
        pause_button = QAction('Play/Pause', self)
        pause_button.setShortcut('Space')
        pause_button.triggered.connect(self.pause_gif)
        next_button = QAction('Next', self)
        next_button.setShortcut('Right')
        next_button.triggered.connect(self.next_frame)
        frame_menu.addAction(prev_button)
        frame_menu.addAction(pause_button)
        frame_menu.addAction(next_button)

        play_menu = main_menu.addMenu('Play')
        dspeed_button = QAction('Speed -', self)
        dspeed_button.setShortcut('-')
        dspeed_button.triggered.connect(self.dspeed_gif)
        uspeed_button = QAction('Speed +', self)
        uspeed_button.setShortcut('+')
        uspeed_button.triggered.connect(self.uspeed_gif)
        play_menu.addAction(dspeed_button)
        play_menu.addAction(uspeed_button)

        return main_menu

    def open_gif(self):
        if self.is_loading:
            return
        self.is_loading = True
        self.fname = QFileDialog.getOpenFileName(
            self,
            'Open file',
            options=QFileDialog.DontUseNativeDialog)[0]
        if not self.fname:
            self.is_loading = False
            return
        self.close_gif()
        self.pbar.show()
        self.update()
        self.t = SomeThread(self.fname, self)
        self.t.start()

    def set_gif_settings(self):
        self.form_widget.isPaused.setText('Play')
        self.pal.setColor(QPalette.WindowText, QColor("green"))
        self.form_widget.isPaused.setPalette(self.pal)
        fps = round(1000 / self.scrTimer.interval())
        self.form_widget.fps.setText('FPS: {}'.format(fps))
        frames_length = len(self.gifinfo.frames)
        self.form_widget.frames.setText('Frame: 1/{}'.format(frames_length))
        self.isOpened = True
        width = int(self.gifinfo.width, 16)
        height = int(self.gifinfo.height, 16)
        width = width + 18 if width + 18 > 300 else 300
        height = height + 74 if height + 74 > 300 else 300
        self.setMinimumSize(width, height)
        self.resize(width, height)
        self.setWindowTitle(self.fname)
        self.pbar.hide()
        self.pbar.setValue(0)
        self.scrTimer.start()
        self.is_loading = False

    def close_gif(self):
        self.scrTimer.stop()
        self.gif_id = 0
        self.isOpened = False
        self.frames = [QPixmap(QImage().fill(QColor('#ffffff')))]
        self.form_widget.fps.setText('FPS: 0')
        self.form_widget.frames.setText('Frames: 0/0')
        self.form_widget.isPaused.setText('Pause')
        self.pal.setColor(QPalette.WindowText, QColor("red"))
        self.form_widget.isPaused.setPalette(self.pal)
        self.setMinimumSize(300, 300)
        self.resize(300, 300)
        self.setWindowTitle('GIF')
        self.update()

    def pause_gif(self):
        if not self.isOpened:
            return
        if self.scrTimer.isActive():
            self.scrTimer.stop()
        else:
            self.scrTimer.start()
        self.change_pause_text()

    def change_pause_text(self):
        if self.scrTimer.isActive():
            self.form_widget.isPaused.setText('Play')
            self.form_widget.isPaused.adjustSize()
            self.pal.setColor(QPalette.WindowText, QColor("green"))
            self.form_widget.isPaused.setPalette(self.pal)
        else:
            self.form_widget.isPaused.setText('Pause')
            self.form_widget.isPaused.adjustSize()
            self.pal.setColor(QPalette.WindowText, QColor("red"))
            self.form_widget.isPaused.setPalette(self.pal)

    def prev_frame(self):
        if not self.isOpened:
            return
        self.scrTimer.stop()
        if self.gif_id:
            self.gif_id -= 1
        else:
            self.gif_id = len(self.gifinfo.images) - 1
        self.set_frames_text()
        self.change_pause_text()
        self.update()

    def next_frame(self):
        if not self.isOpened:
            return
        self.scrTimer.stop()
        self.change_pause_text()
        if self.gif_id != len(self.gifinfo.frames) - 1:
            self.gif_id += 1
        else:
            self.gif_id = 0
        self.set_frames_text()
        self.update()

    def set_frames_text(self):
        frames_text = 'Frame: {}/{}'.format(self.gif_id + 1,
                                            len(self.gifinfo.frames))
        self.form_widget.frames.setText(frames_text)

    def set_fps_text(self):
        fps_text = 'FPS: {}'.format(round(1000 / self.scrTimer.interval()))
        self.form_widget.fps.setText(fps_text)

    def uspeed_gif(self):
        if not self.isOpened:
            return
        current_interval = self.scrTimer.interval()
        possible_fps = 1000 / current_interval + 1
        if possible_fps > 20:
            self.scrTimer.setInterval(50)
        else:
            self.scrTimer.setInterval(round(1000 / possible_fps))
        self.set_fps_text()

    def dspeed_gif(self):
        if not self.isOpened:
            return
        current_interval = self.scrTimer.interval()
        possible_fps = 1000 / current_interval - 1
        if possible_fps < 1:
            self.scrTimer.setInterval(1000)
        else:
            self.scrTimer.setInterval(round(1000 / possible_fps))
        self.set_fps_text()

    def timerEvent(self):
        self.gif_id = (self.gif_id + 1) % len(self.gifinfo.images)
        self.set_frames_text()
        self.update()

    def paintEvent(self, event):
        super().paintEvent(event)
        self.img.setPixmap(self.frames[self.gif_id])

    def get_all_pixmaps(self):
        result = []
        count = len(self.gifinfo.frames)
        all_percents = 70
        z = 0
        all = int(self.gifinfo.height, 16) * count
        for t in self.gifinfo.frames:
            frame = QImage(int(self.gifinfo.width, 16),
                           int(self.gifinfo.height, 16),
                           QImage.Format_RGB32)
            y = 0
            for i in t:
                x = 0
                for j in i:
                    frame.setPixelColor(x, y, QColor('#' + j))
                    x += 1
                y += 1

                z += 1
                self.progress_bar.emit(all_percents * z / all + 30)
            result.append(QPixmap(frame))
            self.progress_bar.emit(all_percents * z / all + 30)
        return result
Exemplo n.º 15
0
class MarkedHistogram(QWidget):
    """Histogram with color-indication markers

       MarkedHistogram shows a histogram of a data set and an optional
       label for the numeric range of the data set.  Color markers can
       be placed on the histogram by the user and moved interactively,
       either with the mouse or by typing in a particular data value.
       A color button is used to control the color of the "current" marker
       (the one most recently selected with the mouse).  Markers can
       either be vertical bars or squares.  Vertical bars can only be
       moved left/right whereas squares can also be moved up/down.
       Squares are also connected from left to right by line segments.

       A row of associated widgets (such as the marker color button) is
       placed below the histogram.  User-specified widgets can also be
       placed in this row with the add_custom_widget() method.

       Individual markers are grouped into HistogramMarkers instances,
       and several HistogramMarkers instances can be associated with
       a single histogram, though only one instance is active/shown at
       a time.

       MarkedHistogram has the following constructor options:
           [Options noted as init options can only be specified at
        widget creation.  Others can be changed later via the corresponding
        property name.]

        color_button --  controls whether a color button is offered in
            the user interface for changing marker colors.
            default: True

        data_source -- either a string or a 3-tuple.  If a string, then
            no histogram is displayed and instead the string is
            displayed in the histogram area as a text message.
            The first 2 components of a 3-tuple should be the
            minimum and maximum values of the histogram,  The
            third component should either be an array of numbers
            (i.e. the histogram) or a callback function that
            takes as its single argument the number of bins to
            histogram into and that returns a histogram array.
            default: 'no data'

        layout -- [init option] how to organize the megawidget layout.
            Choices are 'single', 'top', and 'below'.  'single'
            should be used when you are using a single histogram
            in your GUI, or histograms that aren't arrayed
            vertically.  'top' and 'below' should be used for
            histograms that are laid out in a vertical array
            ('top' for the top-most one, and 'below' for all
            others).  Certain repeated elements will be omitted
            in 'below' histograms (e.g. some widget labels).
            default: single

        max_label/min_label [init options] show the max/min histogram
            values as labels below the histogram on the right/left.
            If neither is True, then the range will be shown in a
            single label widget below the histogram.
            default: False

        redraw_delay -- amount of time (in seconds) to delay between
            needing to redraw and actually starting to redraw.
            Used to avoid multiple (possibly slow) redraws during
            a resize.
            default: 0.25

        scaling -- how to scale the vertical height of the histogram.
            Either 'logarithmic' or 'linear'.
            default: logarithmic

        select_callback -- [init option] function to call when the
            "current" marker changes.  The function receives 4
            argments:  previous active marker set/marker,
            new active marker set/marker.  The marker set can
            be None to indicate no active marker set and the
            marker can be None if no marker was/is current.

        show_marker_help -- [init option] whether to show the help
            text over the histogram describing how to add/delete
            markers.
            default: True

        status_line -- function to use to output messages (such as
            warning when trying to add more markers than allowed).
            The function should take a single string argument.
            default: None

        value_label -- [init option] label to use next to the
            entry widget describing the current marker value.
            default: 'Value'

        value_width -- width of the current marker value entry widget.
            default: 7

       Constructor options that begin with 'Markers_' specify default
       constructor options for HistogramMarkers objects created in the
       add_markers method (e.g. Markers_connect_color='red' will supply
       connect_color='red' to the HistogramMarkers constructor).  Options
       for specific instances can still be provided to the add_ markers()
       method as keyword arguments (without the 'Markers_' prefix).
    """
    def __init__(self,
                 *args,
                 color_button=True,
                 data_source='no data',
                 layout='single',
                 max_label=False,
                 min_label=False,
                 redraw_delay=0.25,
                 scaling='logarithmic',
                 select_callback=None,
                 show_marker_help=True,
                 status_line=None,
                 value_label='Value',
                 value_width=7,
                 **kw):

        # Get HistogramMarkers options and initialise base class
        self._histogram_markers_kw = markers_kw = {}
        for opt_name in list(kw.keys()):
            if opt_name.startswith('Markers_'):
                markers_kw[opt_name[8:]] = kw.pop(opt_name)
        super().__init__(*args, **kw)

        # initialize variables
        self._layout = layout
        self.status_line = status_line
        self._show_marker_help = show_marker_help
        self._active_markers = None
        self._markers = []
        self._markable = False
        self._drag_marker = None
        self._scaling = scaling
        self._select_callback = select_callback
        if select_callback:
            self._prev_markers = None
            self._prev_marker = None

        overall_layout = QVBoxLayout()
        self.setLayout(overall_layout)

        # Create the add/delete marker help
        if show_marker_help and layout != 'below':
            self._marker_help = QLabel(
                "Ctrl-click on histogram to add or delete thresholds")
            self._marker_help.setAlignment(Qt.AlignCenter)
            overall_layout.addWidget(self._marker_help)
        else:
            self._marker_help = None

        # Create the data area
        class HistFrame(QFrame):
            def sizeHint(self):
                return QSize(300, 64)

        data_frame = QFrame()
        data_frame.setLineWidth(1)
        data_frame.setMidLineWidth(2)
        data_frame.setFrameStyle(data_frame.Panel | data_frame.Sunken)
        data_frame.setContentsMargins(0, 0, 0, 0)
        data_frame_layout = QHBoxLayout()
        data_frame.setLayout(data_frame_layout)
        self._data_widgets = QStackedWidget()
        data_frame_layout.addWidget(self._data_widgets)

        # Crate the histogram widget
        self._hist_scene = QGraphicsScene()
        self._hist_bars = self._hist_scene.createItemGroup([])
        self._hist_view = QGraphicsView(self._hist_scene)
        self._hist_view.resizeEvent = self._redraw
        self._hist_scene.mousePressEvent = lambda event: self._add_or_delete_marker_cb(event) \
            if event.modifiers() & mod_key_info("control")[0] else self._select_marker_cb(event)
        self._hist_scene.mouseMoveEvent = lambda event: self._move_marker_cb(event) \
            if self._drag_marker else super().mouseMoveEvent(event)
        self._hist_scene.mouseReleaseEvent = self._button_up_cb
        self._redraw_timer = QTimer()
        self._redraw_timer.timeout.connect(self._redraw_cb)
        self._redraw_timer.start(1000 * redraw_delay)
        self._redraw_timer.stop()
        self._data_widgets.addWidget(self._hist_view)

        # Create the histogram replacement label
        self._no_histogram_label = QLabel()
        self._no_histogram_label.setAlignment(Qt.AlignCenter)
        self._data_widgets.addWidget(self._no_histogram_label)
        overall_layout.addWidget(self._data_widgets, stretch=1)

        # Create range label(s)
        self._widget_area = QWidget()
        self._widget_layout = QHBoxLayout()
        self._min_label = self._max_label = None
        if min_label or max_label:
            min_max_layout = QHBoxLayout()
            if min_label:
                self._min_label = QLabel()
                min_max_layout.addWidget(self._min_label,
                                         alignment=Qt.AlignLeft & Qt.AlignTop)

            if max_label:
                self._max_label = QLabel()
                min_max_layout.addWidget(self._max_label,
                                         alignment=Qt.AlignRight & Qt.AlignTop)
            overall_layout.addLayout(min_max_layout)
        else:
            self._range_label = QLabel()
            if layout == 'below':
                self._widget_layout.addWidget(self._range_label)
            else:
                lab = QLabel("Range")
                if layout == 'single':
                    self._widget_layout.addWidget(lab, alignment=Qt.AlignRight)
                    self._widget_layout.addWidget(self._range_label,
                                                  alignment=Qt.AlignLeft)
                else:  # layout == 'top'
                    range_layout = QVBoxLayout()
                    range_layout.addWidget(lab, alignment=Qt.AlignBottom)
                    range_layout.addWidget(self._range_label,
                                           alignment=Qt.AlignTop)
                    self._widget_layout.addLayout(range_layout)
        self._widget_area.setLayout(self._widget_layout)
        overall_layout.addWidget(self._widget_area)

        # Create value widget
        self._value_entry = QLineEdit()
        self._value_entry.setEnabled(False)
        self._value_entry.returnPressed.connect(self._set_value_cb)
        self.value_width = value_width
        if layout == 'below':
            self._widget_layout.addWidget(self._value_entry)
        else:
            lab = QLabel(value_label)
            if layout == 'single':
                self._widget_layout.addWidget(lab, alignment=Qt.AlignRight)
                self._widget_layout.addWidget(self._value_entry,
                                              alignment=Qt.AlignLeft)
            else:
                value_layout = QVBoxLayout()
                value_layout.addWidget(lab, alignment=Qt.AlignBottom)
                value_layout.addWidget(self._value_entry,
                                       alignment=Qt.AlignTop)
                self._widget_layout.addLayout(value_layout)

        # Create color button widget
        from .color_button import ColorButton
        self._color_button = cb = ColorButton()
        cb.color_changed.connect(
            lambda rgba8: self._color_button_cb([c / 255.0 for c in rgba8]))
        cb.setEnabled(False)
        self._color_button_label = cbl = QLabel("Color")
        if layout == 'below':
            self._widget_layout.addWidget(self._color_button)
        else:
            if layout == 'single':
                self._widget_layout.addWidget(cbl, alignment=Qt.AlignRight)
                self._widget_layout.addWidget(cb, alignment=Qt.AlignLeft)
            else:
                color_layout = QVBoxLayout()
                color_layout.addWidget(cbl, alignment=Qt.AlignBottom)
                color_layout.addWidget(cb, alignment=Qt.AlignTop)
                self._widget_layout.addLayout(color_layout)
        self._color_button_shown = True

        # Show the histogram or the no-data label
        self.data_source = data_source

    def activate(self, markers):
        """Make the given set of markers the currently active set

           Any previously-active set will be hidden.
        """

        if markers is not None and markers not in self._markers:
            raise ValueError("activate() called with bad value")
        if markers == self._active_markers:
            return
        if self._active_markers is not None:
            self._active_markers._hide()
        elif self.layout != 'below' and self._show_marker_help:
            self._marker_help.setHidden(False)
        self._active_markers = markers
        if self._active_markers is not None:
            self._active_markers.shown = True
            self._set_sel_marker(self._active_markers._sel_marker)
        else:
            if self.layout != 'below' and self._show_marker_help:
                self._marker_help.setHidden(True)
            if self._select_callback:
                if self._prev_marker is not None:
                    self._select_callback(self._prev_markers,
                                          self._prev_marker, None, None)
                self._prev_markers = None
                self._prev_marker = None

    def add_custom_widget(self, widget, left_side=True):
        self._widget_layout.addWidget(0 if left_side else -1, widget)

    def add_markers(self, activate=True, **kw):
        """Create and return a new set of markers.

           If 'activate' is true, the new set will also become
           the active set.  Other keyword arguments will be passed
           to the HistogramMarkers constructor.
        """
        final_kw = {k: v for k, v in self._histogram_markers_kw.items()}
        final_kw.update(kw)
        final_kw['histogram'] = self
        markers = HistogramMarkers(**final_kw)
        self._markers.append(markers)
        if activate:
            self.activate(markers)
        return markers

    @property
    def color_button(self):
        return self._color_button_shown

    @color_button.setter
    def color_button(self, show):
        if show == self._color_button_shown:
            return
        if self.layout != 'below':
            self._color_button_label.setHidden(not show)
        self._color_button.setHidden(not show)
        self._color_button_shown = show

    def current_marker_info(self):
        """Identify the marker currently selected by the user.
           
           Returns a HistogramMarkers instance and a marker.
           The instance will be None if no marker set has been
           activated.  The marker will be None if no marker has
           been selected by the user.
        """
        if self._active_markers is None:
            return None, None
        return self._active_markers, self._active_markers._sel_marker

    @property
    def data_source(self):
        return self._data_source

    @data_source.setter
    def data_source(self, data_source):
        self._data_source = data_source
        self._new_data()

    def delete_markers(self, markers):
        """Delete the given set of markers.

           If the markers were active, there will be no active set
           of markers afterward.
        """
        if markers not in self._markers:
            raise ValueError("Bad value for delete()")
        if markers == self._active_markers:
            self.activate(None)
        self._markers.remove(markers)

    @property
    def layout(self):
        return self._layout

    @property
    def redraw_delay(self):
        return self._redraw_timer.interval() / 1000.0

    @redraw_delay.setter
    def redraw_delay(self, secs):
        self._redraw_timer.setInterval(secs * 1000)

    @property
    def scaling(self):
        return self._scaling

    @scaling.setter
    def scaling(self, scaling):
        if self._scaling != scaling:
            self._scaling = scaling
            self._redraw_cb()

    def snapshot_data(self):
        info = {
            'version': 1,
            'draw_min': self._draw_min,
            'draw_max': self._draw_max,
            'markers': [markers.snapshot_data() for markers in self._markers],
        }
        if self._active_markers is None:
            info['active markers'] = None
        else:
            info['active markers'] = self._markers.index(self._active_markers)
        if self['color_button']:
            info['color well'] = self._color_button.color
        else:
            info['color well'] = None
        return info

    def snapshot_restore(self, data):
        self._draw_min = data['draw_min']
        self._draw_max = data['draw_max']
        if data['color well'] is not None:
            self._color_button.color = data['color well']
        if len(data['markers']) != len(self._markers):
            # don't know how to deal with this situation
            return
        for markers, markers_data in zip(self._markers, data['markers']):
            markers.snapshot_restore(markers_data)
        if data['active markers'] is not None:
            self.activate(self._markers[data['active markers']])
            self._set_sel_marker(self._active_markers._sel_marker)

    @property
    def value_width(self):
        return self._value_width

    @value_width.setter
    def value_width(self, vw):
        self._value_width = vw
        ve = self._value_entry
        fm = ve.fontMetrics()
        tm = ve.textMargins()
        cm = ve.contentsMargins()
        w = vw * fm.width('w') + tm.left() + tm.right() + cm.left() + cm.right(
        ) + 8
        ve.setMaximumWidth(w)

    def _abs2rel(self, abs_xy):
        x, y = abs_xy
        rel_x = (x - self._min_val) / float(self._max_val - self._min_val)
        rel_y = y / float(self._ymax)
        return rel_x, rel_y

    def _abs_xy(self, scene_xy):
        scene_x, scene_y = scene_xy
        dy = min(max(self._bottom - scene_y, 0), self._hist_height - 1)
        if self.scaling == 'logarithmic':
            exp = dy / float(self._hist_height - 1)
            abs_y = (self._max_height + 1)**exp - 1
        else:
            abs_y = self._max_height * dy / float(self._hist_height - 1)

        cx = scene_x - self._border
        num_bins = len(self._bins)
        if num_bins == self._hist_width:
            fract = cx / (num_bins - 1)
            abs_x = self._min_val + fract * (self._max_val - self._min_val)
        elif num_bins == 2:
            abs_x = self._min_val + (self._max_val - self._min_val) * (
                2 * cx / self._hist_width - 0.5)
        else:
            extra = self._hist_width / (2.0 * (num_bins - 1))
            abs_x = self._min_val + (self._max_val - self._min_val) * (
                cx - extra) / (self._hist_width - 2.0 * extra)
        abs_x = max(self._min_val, abs_x)
        abs_x = min(self._max_val, abs_x)
        return abs_x, abs_y

    def _add_or_delete_marker_cb(self, event=None):
        if self._active_markers is None:
            return

        marker = self._active_markers._pick_marker(event.scenePos())

        if marker is None:
            max_marks = self._active_markers.max_marks
            if max_marks is not None and len(
                    self._active_markers) >= max_marks:
                if self.status_line:
                    self.status_line("Maximum of %d markers\n" % max_marks)
                return
            xy = self._abs_xy((event.scenePos().x(), event.scenePos().y()))
            if self._active_markers.coord_type == 'relative':
                xy = self._abs2rel(xy)
            sel_marker = self._active_markers._sel_marker
            if sel_marker:
                color = sel_marker.rgba
            else:
                color = self._active_markers.new_color
            marker = self._active_markers.append((xy, color))
            self._set_sel_marker(marker, drag_start=event)
        else:
            min_marks = self._active_markers.min_marks
            if min_marks is not None and len(
                    self._active_markers) <= min_marks:
                if self.status_line:
                    self.status_line("Minimum of %d markers\n" % min_marks)
                return
            self._active_markers.remove(marker)
            self._set_sel_marker(None)

    def _button_up_cb(self, event=None):
        if self._drag_marker:
            self._drag_marker = None
            if self._active_markers.move_callback:
                self._active_markers.move_callback('end')

    def _scene_xy(self, abs_xy):
        # minimum is in the _center_ of the first bin,
        # likewise, maximum is in the center of the last bin

        abs_x, abs_y = abs_xy

        abs_y = max(0, abs_y)
        abs_y = min(self._max_height, abs_y)
        if self.scaling == 'logarithmic':
            import math
            abs_y = math.log(abs_y + 1)
        scene_y = self._bottom - (self._hist_height - 1) * (abs_y /
                                                            self._max_height)

        abs_x = max(self._min_val, abs_x)
        abs_x = min(self._max_val, abs_x)
        num_bins = len(self._bins)
        if num_bins == self._hist_width:
            bin_width = (self._max_val - self._min_val) / float(num_bins - 1)
            left_edge = self._min_val - 0.5 * bin_width
            scene_x = int((abs_x - left_edge) / bin_width)
        else:
            # histogram is effectively one bin wider (two half-empty bins on each end)
            if num_bins == 1:
                scene_x = 0.5 * (self._hist_width - 1)
            else:
                extra = (self._max_val - self._min_val) / (2.0 *
                                                           (num_bins - 1))
                eff_min_val = self._min_val - extra
                eff_max_val = self._max_val + extra
                eff_range = float(eff_max_val - eff_min_val)
                scene_x = (self._hist_width - 1) * (abs_x -
                                                    eff_min_val) / eff_range
        return self._border + scene_x, scene_y

    def _color_button_cb(self, rgba):
        m = self._active_markers._sel_marker
        if not m:
            if self.status_line:
                self.status_line("No marker selected")
            return
        m.rgba = rgba

    def _marker2abs(self, marker):
        if self._active_markers.coord_type == 'absolute':
            return marker.xy
        else:
            return self._rel2abs(marker.xy)

    def _move_cur_marker(self, x, yy=None):
        #
        # Don't allow dragging out of the scene box.
        #
        m = self._active_markers._sel_marker
        if x < self._min_val:
            x = self._min_val
        elif x > self._max_val:
            x = self._max_val
        if yy is None:
            y = m.xy[1]
        else:
            y = yy
            if y < 0:
                y = 0
            elif y > self._ymax:
                y = self._ymax

        if self._active_markers.coord_type == 'absolute':
            m.xy = (x, y)
        else:
            m.xy = self._abs2rel((x, y))
        if yy is None:
            m.xy = (m.xy[0], y)

        self._set_value_entry(x)

        self._active_markers._update_plot()

        if self._active_markers.move_callback:
            self._active_markers.move_callback(m)

    def _move_marker_cb(self, event):
        mouse_xy = self._abs_xy((event.scenePos().x(), event.scenePos().y()))
        dx = mouse_xy[0] - self._last_mouse_xy[0]
        dy = mouse_xy[1] - self._last_mouse_xy[1]
        self._last_mouse_xy = mouse_xy

        if event.modifiers() & mod_key_info("shift")[0]:
            dx *= .1
            dy *= .1

        m = self._drag_marker
        mxy = self._marker2abs(m)
        x, y = mxy[0] + dx, mxy[1] + dy

        self._move_cur_marker(x, y)

    def _new_data(self):
        ds = self.data_source
        if isinstance(ds, str):
            self._no_histogram_label.setText(ds)
            self._data_widgets.setCurrentWidget(self._no_histogram_label)
            if self._min_label:
                self._min_label.setText("")
            if self._max_label:
                self._max_label.setText("")
            if self.layout != 'below' and self._show_marker_help:
                self._marker_help.setHidden(True)
            self._widget_area.setHidden(True)
        else:
            self._data_widgets.setCurrentWidget(self._hist_view)
            if self.layout != 'below' and self._show_marker_help:
                self._marker_help.setHidden(False)
            self._widget_area.setHidden(False)
        self._draw_min = self._draw_max = None
        self._redraw_cb()

    def _redraw(self, event=None):
        self._markable = False
        self._redraw_timer.start()

    def _redraw_cb(self):
        self._redraw_timer.stop()
        ds = self.data_source
        if isinstance(ds, str):
            # displaying a text label right now
            return
        view = self._hist_view
        scene = self._hist_scene
        hist_size = view.viewport().size()
        self._hist_width, self._hist_height = hist_width, hist_height = hist_size.width(
        ), hist_size.height()
        self._min_val, self._max_val, self._bins = ds
        filled_range = self._max_val - self._min_val
        empty_ranges = [0, 0]
        if self._draw_min != None:
            empty_ranges[0] = self._min_val - self._draw_min
            self._min_val = self._draw_min
        if self._draw_max != None:
            empty_ranges[1] = self._draw_max - self._max_val
            self._max_val = self._draw_max
        if callable(self._bins):
            if empty_ranges[0] or empty_ranges[1]:
                full_range = filled_range + empty_ranges[0] + empty_ranges[1]
                filled_bins = self._bins(
                    int(hist_width * filled_range / full_range))
                left = [0] * int(hist_width * empty_ranges[0] / full_range)
                right = [0] * (hist_width - len(filled_bins) - len(left))
                self._bins = left + filled_bins + right
            else:
                self._bins = self._bins(hist_width)
        elif empty_ranges[0] or empty_ranges[1]:
            full_range = filled_range + empty_ranges[0] + empty_ranges[1]
            left = [0] * int(len(self._bins) * empty_ranges[0] / full_range)
            right = [0] * int(len(self._bins) * empty_ranges[1] / full_range)
            self._bins = left + self._bins + right
        if self._min_label:
            self._min_label.setText(self._str_val(self._min_val))
        if self._max_label:
            self._max_label.setText(self._str_val(self._max_val))
        if not self._min_label and not self._max_label:
            self._range_label.setText(
                "%s - %s" %
                (self._str_val(self._min_val), self._str_val(self._max_val)))

        bars = self._hist_bars.childItems()
        for bar in bars:
            self._hist_bars.removeFromGroup(bar)
            self._hist_scene.removeItem(bar)

        self._ymax = max(self._bins)
        if self.scaling == 'logarithmic':
            from numpy import array, log, float32
            self._bins = array(self._bins, float32)
            self._bins += 1.0
            log(self._bins, self._bins)

        max_height = max(self._bins)
        self._max_height = max_height
        h_scale = float(hist_height - 1) / max_height
        self._border = border = 0
        bottom = hist_height + border - 1
        self._bottom = bottom

        num_bins = len(self._bins)
        if num_bins == hist_width:
            for b, n in enumerate(self._bins):
                x = border + b
                h = int(h_scale * n)
                line = self._hist_scene.addLine(x, bottom, x, bottom - h)
                self._hist_bars.addToGroup(line)
                line.setZValue(-1)  # keep bars below markers
        else:
            x_scale = (hist_width - 1) / float(num_bins)
            for b, n in enumerate(self._bins):
                x1 = border + b * x_scale
                x2 = border + (b + 1) * x_scale
                h = int(h_scale * n)
                rect = self._hist_scene.addRect(x1, bottom - h, x2 - x1, h)
                self._hist_bars.addToGroup(rect)
                rect.setZValue(-1)  # keep bars below markers
        self._markable = True
        if self._active_markers is not None:
            self._active_markers._update_plot()
            marker = self._active_markers._sel_marker
            if marker:
                self._set_value_entry(self._marker2abs(marker)[0])
        self._hist_scene.setSceneRect(self._hist_scene.itemsBoundingRect())

    def _rel2abs(self, rel_xy):
        x, y = rel_xy
        abs_x = self._min_val * (1 - x) + x * self._max_val
        abs_y = y * self._ymax
        return abs_x, abs_y

    def _select_marker_cb(self, event=None):
        if self._active_markers is not None:
            marker = self._active_markers._pick_marker(event.scenePos())
            self._set_sel_marker(marker, drag_start=event)
            if marker is not None:
                return
        # show value where histogram clicked
        self._set_value_entry(self._abs_xy((event.scenePos().x(), 0))[0])

    def _set_sel_marker(self, marker, drag_start=None):
        self._active_markers._sel_marker = marker
        if not marker:
            self._color_button.color = "gray"
            self._color_button.setEnabled(False)
            self._set_value_entry("")
            self._value_entry.setEnabled(False)
        else:
            self._color_button.setEnabled(True)
            self._color_button.color = marker.rgba
            self._value_entry.setEnabled(True)
            self._set_value_entry(self._marker2abs(marker)[0])
        if self._select_callback:
            if marker is not None or self._prev_marker is not None:
                self._select_callback(self._prev_markers, self._prev_marker,
                                      self._active_markers, marker)
            self._prev_markers = self._active_markers
            self._prev_marker = marker
        if not drag_start:
            return
        self._drag_marker = marker
        if not marker:
            return

        self._last_mouse_xy = self._abs_xy(
            (drag_start.scenePos().x(), drag_start.scenePos().y()))
        if self._active_markers.move_callback:
            self._active_markers.move_callback('start')

    def _set_value_cb(self):
        try:
            v = eval(self._value_entry.text())
        except Exception:
            raise ValueError("Invalid histogram value")
        if type(self._min_val) != type(v):
            v = type(self._min_val)(v)
        if v < self._min_val:
            self._draw_min = v
            self._redraw_cb()
        elif v > self._max_val:
            self._draw_max = v
            self._redraw_cb()
        self._move_cur_marker(v)

    def _set_value_entry(self, val):
        if isinstance(val, str):
            self._value_entry.setText(val)
            return
        if isinstance(self._min_val, int):
            val = int(val + 0.5)
        self._value_entry.setText("%g" % val)

    def _str_val(self, val):
        if isinstance(val, (int, bool)):
            return str(val)
        return "%g" % val
Exemplo n.º 16
0
class Player(QWidget):
    # signal that there is a new frame for the selected universe
    new_pix = pyqtSignal(QPixmap)
    new_image = pyqtSignal(QImage)
    new_load = pyqtSignal()
    new_frame = pyqtSignal()
    new_frame_index = pyqtSignal(int)
    clear = pyqtSignal()
    __players__ = []

    def __init__(self, name, filepath):
        super(QWidget, self).__init__()
        self.name = name
        self._loop = 'repeat'
        self.frames = 0
        self.loop_points = None
        # set openCV capture
        self.cap = cv2.VideoCapture()
        self.autostart = True
        self.current_frame = None
        self.timer = QTimer()
        self.fps = 25
        self._play = False
        self.timer.timeout.connect(self.render_frame)
        #self.filepath = filepath
        if filepath:
            self.load(filepath)
        else:
            self.filepath = None
        self.__players__.append(self)

    def __repr__(self):
        return self.name

    @property
    def fps(self):
        return 1000. / self.timer.interval()

    @fps.setter
    def fps(self, fps):
        self.timer.setInterval(1000. / fps)

    def render_frame(self):
        ret, frame = self.cap.read()
        #frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
        if ret:
            img = QImage(frame, frame.shape[1], frame.shape[0],
                         QImage.Format_RGB888)
            img = img.rgbSwapped()
            self.image = img
            self.new_image.emit(img)
            pix = QPixmap.fromImage(img)
            current_frame = self.cap.get(1)
            #print('render frame : ' + str(current_frame))
            # emit the new frame signal
            self.new_pix.emit(pix)
            self.new_frame.emit()
            self.new_frame_index.emit(current_frame)
            if current_frame == self.frames:
                print(self.loop)
                if self.loop == 'repeat':
                    self.seek(0)
                elif self.loop == 'one-shot':
                    self.end_action()
                else:
                    self.end_action()
        else:
            print('nothing to play')

    def end_action(self, action='eject'):
        if action == 'loop':
            self.seek(0)
        elif action == 'freeze':
            self.play = False
        elif action == 'eject':
            self.eject()

    def load(self, filepath):
        # release first the previous playhead if existing
        if self.cap:
            self.cap.release()
        # store filepath
        self.filepath = filepath
        try:
            # open the capture
            self.cap.open(self.filepath)
            ret, frame = self.cap.read()
            if ret:
                try:
                    # get properties of the movie
                    self.width = self.cap.get(3)
                    self.height = self.cap.get(4)
                    self.frames = self.cap.get(7)
                    self.fps = (self.cap.get(5))
                    self.loop_points = [0, self.frames]
                    print(
                        self.filepath.split('/')[-1], self.fps, self.width,
                        self.height)
                    self.render_frame()
                    if self.autostart:
                        self.play = True
                    self.new_load.emit()
                except:
                    print('cannot access to the movie')
        except:
            print('cannot open the file')

    @property
    def loop(self):
        return self._loop

    @loop.setter
    def loop(self, mode):
        self._loop = mode

    @property
    def loop_points(self):
        return self._loop_points

    @loop_points.setter
    def loop_points(self, points):
        print('loop_point setter : ', points)
        self._loop_points = points

    @property
    def autostart(self):
        """
        if True, player will start when load a movie
        if False, player will load the movie and 
        """
        return self._autostart

    @autostart.setter
    def autostart(self, state):
        self._autostart = state

    def seek(self, frame):
        # check that the frame exists
        if frame <= self.frames:
            # set playhead to the desired frame
            self.cap.set(1, frame)
            # render the frame
            self.render_frame()
        else:
            print('frame ' + str(frame) + ' does not exist')

    def pause(self):
        """
        calling this method will pause the media
        same effect than self.play = 0
        """
        self.play = False

    def resume(self):
        """
        calling this methid will play the media
        same effect than self.play = 1
        """
        self.play = True

    @property
    def play(self):
        """
        The play property
        if set to True, media is playing
        if set to False, media is pausing
        """
        return self._play

    @play.setter
    def play(self, state):
        self._play = state
        if state:
            self.timer.start()
        else:
            self.timer.stop()

    def eject(self):
        """
        unload the media from the player
        """
        # release the player
        self.cap.release()
        self.pause()
        self.clear.emit()
Exemplo n.º 17
0
class ImageSlider(QWidget):
    def __init__(self):
        super().__init__()

        # setting title
        self.setWindowTitle("Python ")

        self.setGeometry(1000, 1000, 2000, 100)

        stylesheett = """
            QPushbutton{
            height: 50px;
            width: 50px;
            background-color: rgb(54,57,63);
            background-image: url("./Images/left.png");
            }

            QPushbutton#pic:hover{
            background-color: rgb(54,57,63);
            background-image: url("./Images/left_hover.png");
            }

            QPushbutton#pic2{
            height: 50px;
            width: 50px;
            background-color: rgb(54,57,63);
            background-image: url("./Images/right.png");
            }

            QPushbutton#pic2:hover{
            background-color: rgb(54,57,63);
            background-image: url("./Images/right_hover.png");
            }
        """

        self.setStyleSheet(stylesheett)
        self.hbox = QHBoxLayout()
        self.list_widget = QListWidget()
        self.items = []

        # setting flow
        self.list_widget.setFlow(QListView.LeftToRight)
        self.list_widget.setIconSize(QtCore.QSize(190, 190))
        self.list_widget.hasAutoScroll()

        self.list_widget.setAutoFillBackground(False)

        self.pic = QPushButton()
        self.pic.setObjectName("pic")
        self.pic.clicked.connect(lambda: self.goleftSmooth())

        #use full ABSOLUTE path to the image, not relative

        self.hbox.addWidget(self.pic)

        self.hbox.addWidget(self.list_widget)

        self.pic2 = QPushButton()
        self.pic2.setObjectName("pic2")
        self.pic2.clicked.connect(lambda: self.gorightSmooth())

        self.hbox.addWidget(self.pic2)

        self.list_widget.horizontalScrollBar().setDisabled(True)
        self.list_widget.horizontalScrollBar().hide()

        self.list_widget.verticalScrollBar().setDisabled(True)
        self.list_widget.verticalScrollBar().hide()

        self.list_widget.setHorizontalScrollMode(
            QAbstractItemView.ScrollPerPixel)

        self.setLayout(self.hbox)

        self.atCurrentRight = 16
        self.atCurrentLeft = 0

        self.timerBaseInterval = 25
        self.floorInterval = 5

        self.timer = QTimer(self)
        self.timer.setInterval(self.timerBaseInterval)
        self.timer.timeout.connect(self.goRight)

        self.timer2 = QTimer(self)
        self.timer2.setInterval(self.timerBaseInterval)
        self.timer2.timeout.connect(self.goLeft)

        self.rightCounter = 0
        self.leftCounter = 0

        self.incrementalStep = 1
        self.counterSize = 410

        self.lingertime = 1
        self.lingertimeCounter = 0

        self.show()

    #Takes list of images and adds them to the image container of this class
    def setImages(self, images, urls, movieIDs, titles):

        self.list_widget.clear()
        self.items = []
        self.list_widget.setViewMode(QListWidget.IconMode)
        #        self.list_widget.setResizeMode(QListWidget.Adjust);
        #        self.list_widget.setIconSize(QSize(150,150));
        #        self.list_widget.setAcceptDrops(True);
        #        self.list_widget.setDragEnabled(False);
        for i in range(len(images)):
            self.items.append(ClickableThumbnail(urls[i], movieIDs[i]))
            self.items[i].setText(titles[i])

            self.list_widget.addItem(self.items[i])

            pm = QPixmap()
            pm.loadFromData(base64.b64decode(images[i]))
            ic = QIcon()
            pm = pm.scaled(QSize(220, 150), Qt.IgnoreAspectRatio)
            ic.addPixmap(pm)
            if (ic.isNull() == False):
                self.items[i].setIcon(ic)

    #movies images to the right to slide left by initiating a timer that moves the images smoothly by pixels
    def goleftSmooth(self):
        self.timer2.start()
        self.pic.setDisabled(True)
        self.pic2.setDisabled(True)

    #movies images to the left to slide right by initiating a timer that moves the images smoothly by pixels
    def gorightSmooth(self):
        self.timer.start()
        self.pic.setDisabled(True)
        self.pic2.setDisabled(True)

    #Function that timer2 uses to movie images right
    #It works by modifying the timer interval (time needed until this function is called again)
    #starts by high time interval to low then to high (slow fast slow)
    def goLeft(self):
        if (self.leftCounter != self.counterSize):

            if (self.leftCounter < math.ceil(self.counterSize * 0.4)):
                if (self.lingertime > self.lingertimeCounter):

                    self.lingertimeCounter += 1

                else:
                    if (self.timer2.interval() > self.floorInterval):
                        self.timer2.setInterval(self.timer2.interval() - 1)
                        self.lingertime = self.timerBaseInterval - self.timer2.interval(
                        )
                        self.lingertimeCounter = 5
            elif (self.leftCounter >
                  self.counterSize - math.ceil(self.counterSize * 0.4)):

                if (self.lingertime > self.lingertimeCounter):

                    self.lingertimeCounter += 1

                else:
                    if (self.timer2.interval() < self.timerBaseInterval):
                        self.timer2.setInterval(self.timer2.interval() + 1)
                        self.lingertime = self.timerBaseInterval - self.timer2.interval(
                        )
                        self.lingertimeCounter = 5

            self.list_widget.horizontalScrollBar().setValue(
                self.list_widget.horizontalScrollBar().value() -
                self.incrementalStep)

            self.leftCounter += 1

            self.repaint()

            if (self.leftCounter == math.ceil(self.counterSize / 2)):
                self.lingertimeCounter = 5
                self.lingertime = self.timerBaseInterval

        else:

            self.timer2.setInterval(self.timerBaseInterval)
            self.leftCounter = 0
            self.timer2.stop()
            self.pic.setEnabled(True)
            self.pic2.setEnabled(True)

        if (self.list_widget.horizontalScrollBar().value() == 0):
            self.timer2.setInterval(self.timerBaseInterval)
            self.leftCounter = 0
            self.timer2.stop()
            self.pic.setEnabled(True)
            self.pic2.setEnabled(True)

    #functions similarly to goLeft but adds scrolls to the right by adding pixels to the scrollbar value rather than subtracting
    #TODO: integrate goleft and goright into 1 function, no need for 2
    def goRight(self):

        #        print(self.timer.interval())
        if (self.rightCounter != self.counterSize):

            if (self.rightCounter < math.ceil(self.counterSize * 0.4)):
                if (self.lingertime > self.lingertimeCounter):

                    self.lingertimeCounter += 1

                else:
                    if (self.timer.interval() > self.floorInterval):
                        self.timer.setInterval(self.timer.interval() - 1)
                        self.lingertime = self.timerBaseInterval - self.timer.interval(
                        )
                        self.lingertimeCounter = 5
            elif (self.rightCounter >
                  self.counterSize - math.ceil(self.counterSize * 0.4)):

                if (self.lingertime > self.lingertimeCounter):

                    self.lingertimeCounter += 1

                else:
                    if (self.timer.interval() < self.timerBaseInterval):
                        self.timer.setInterval(self.timer.interval() + 1)
                        self.lingertime = self.timerBaseInterval - self.timer.interval(
                        )
                        self.lingertimeCounter = 5

            self.list_widget.horizontalScrollBar().setValue(
                self.list_widget.horizontalScrollBar().value() +
                self.incrementalStep)

            self.rightCounter += 1

            self.repaint()

            if (self.rightCounter == math.ceil(self.counterSize / 2)):
                self.lingertimeCounter = 5
                self.lingertime = self.timerBaseInterval

        else:

            self.timer.setInterval(self.timerBaseInterval)
            self.rightCounter = 0
            self.timer.stop()
            self.pic.setEnabled(True)
            self.pic2.setEnabled(True)

        if (self.list_widget.horizontalScrollBar().value() ==
                self.list_widget.horizontalScrollBar().maximum()):
            self.timer.setInterval(self.timerBaseInterval)
            self.rightCounter = 0
            self.timer.stop()
            self.pic.setEnabled(True)
            self.pic2.setEnabled(True)
Exemplo n.º 18
0
class Main(QMainWindow):
    current_x = 0.
    current_y = 0.
    current_z = 0.
    changes = dict()

    def __init__(self, *args):
        super(Main, self).__init__(*args)

        loadUi('mainwindow.ui', self)

        self.flavor = config.get('flavor', default='smoothie')
        self['connection.host'] = config.get('server_host', 'localhost')
        self['connection.port'] = str(config.get('server_port', '11011'))
        #self.prompt.setText("M114")
        self.prompt.addItem("M114")
        self.prompt.addItem("MULTILINE")

        self.conn = QHcClient(self)
        self.conn.readyRead.connect(self.readSocket)
        self.conn.error.connect(self.socketError)
        self.conn.connected.connect(self.socketConnect)
        self.conn.disconnected.connect(self.socketDisconnect)

        self.connected = False

        self.actionSave_log.triggered.connect(self.save_log_dialog)

        self.actionSave_probe_data.triggered.connect(self.save_probe_data_dialog)
        self.actionLoad_probe_data.triggered.connect(self.load_probe_data_dialog)

        self.actionLoad_G_code.triggered.connect(self.load_gcode_dialog)
        self.actionSave_G_code.triggered.connect(self.save_gcode_dialog)

        self.actionSave_probe_G_code.triggered.connect(self.save_probe_gcode_dialog)

        self.prompt.setFocus()
        self.prompt.lineEdit().returnPressed.connect(self.on_send_clicked)

        # paramtree handlers
        self.ptree.params.sigTreeStateChanged.connect(self.pchange)
        self.ptree.params.param('Connection', 'Connect').sigActivated.connect(self.do_connect)
        self.ptree.params.param('Probe', 'Run probe').sigActivated.connect(self.run_probe)
        self.ptree.params.param('Probe', 'Process').sigActivated.connect(self.process)
        self.ptree.params.param('Probe', 'Save processed G-code').sigActivated.connect(self.save_gcode_dialog)
        self.ptree.params.param('GCode', 'Load G-code').sigActivated.connect(self.load_gcode_dialog)

        # alias
        self.p = self.ptree.get_param
        self.ptree

        self.do_connect()
        self.update_probe()
        self.update_grid()

        #self.commodel = QStandardItemModel(self.comlist)
        #self.comlist.setModel(self.commodel)
        self.comtree.setColumnCount(3)
        self.comtree.setColumnWidth(0, 200)
        self.comtree.setColumnWidth(1, 100)
        self.comtree.setHeaderLabels(['Time', 'Command', 'Response'])
        #self.comtree.header().setSectionResizeMode(2, QHeaderView.Stretch)
        self.comtree.header().setSectionResizeMode(2, QHeaderView.ResizeToContents)
        self.comtree.header().setStretchLastSection(False)

        self.reconnect_timer = QTimer()
        self.reconnect_timer.timeout.connect(self.do_connect)

        joystick = False

        if joystick:
            self.joy = Joystick()
            self.joy.button_down.connect(self.button_down)
            self.joy.axis_moving.connect(self.axis_moving)

        self.init_timer = QTimer()
        self.init_timer.timeout.connect(self.late_init)
        self.init_timer.setSingleShot(True)
        self.init_timer.start(333)

    def late_init(self):
        self.ptree.changing(self.changing)
        self.tool_ipython()

    def tool_ipython(self):
        self.ipy.start()
        self.ipy.push({"self": self})
        #self.ipy.execute('%pylab inline')

    def __getitem__(self, attr):
        """
        Access HCParameTree values via self['some.path']

        Returns cached value from self.changes if parameter is changing
        """

        param = self.ptree.get_param(attr)
        path = self.ptree.params.childPath(param)
        if path is not None:
            pl = '.'.join(path).lower()
            if pl in self.changes:
                return self.changes[pl]

        return param.value()

    def __setitem__(self, attr, val):
        """
        Set HCParameTree values via self['some.path']
        """
        param = self.ptree.get_param(attr)
        param.setValue(val)

    def changing(self, param, value):
        '''
        Keep track of changing parameters
        and store these in self.changes dict
        '''
        path = self.ptree.params.childPath(param)
        if path is not None:
            pl = '.'.join(path).lower()
            # cache changes, eval `value` to param.type
            try:
                # will fail for complex types like colormap so just ignore
                self.changes[pl] = eval('{}({})'.format(param.type(), value))
            except:
                pass
            self.handle_updates(pl)

    def pchange(self, param, changes):
        """
        HCParamTreechange handler
        """

        for param, change, data in changes:
            path = self.ptree.params.childPath(param)
            if path is not None:
                pl = '.'.join(path).lower()
                if pl in self.changes:
                    del self.changes[pl]

                self.handle_updates(pl)

    def handle_updates(self, path=None):
        if path:
            if path.startswith('probe'):
                self.update_probe()

            if path.startswith('grid'):
                self.update_grid()

            if path.startswith('cross'):
                self.update_cross()

            if path.startswith('gcode'):
                self.update_gcode()

            if path.startswith('probe result'):
                self.update_result()

            self.gl.paintGL()
            self.gl.repaint()
            proc_events()

    def load_gcode_dialog(self):
        fname, mask = QFileDialog.getOpenFileName(None, "Load G-Code", "",
                "GCode (*.ngc *.gcode);;All files (*.*)")
        if not fname:
            return
        try:
            self.gl.gcode.load_gcode(fname)

            # prefill probe width / height
            xmin, xmax = self.gl.gcode.limits['X']
            xlen = xmax - xmin
            ymin, ymax = self.gl.gcode.limits['Y']
            ylen = ymax - ymin
            zmin, zmax = self.gl.gcode.limits['Z']
            zlen = zmax - zmin
            self['gcode.limits.x min'] = xmin
            self['gcode.limits.x max'] = xmax
            self['gcode.limits.x len'] = xlen
            self['gcode.limits.y min'] = ymin
            self['gcode.limits.y max'] = ymax
            self['gcode.limits.y len'] = ylen
            self['gcode.limits.z min'] = zmin
            self['gcode.limits.z max'] = zmax
            self['gcode.limits.z len'] = zlen

            self['probe.width'] = xlen
            self['probe.height'] = ylen

            self['grid.x origin'] = xmin
            self['grid.y origin'] = ymin
            self['grid.width'] = xlen
            self['grid.height'] = ylen
            o = 3

            self.gl.ruler.x = xmin
            self.gl.ruler.size = xmax - xmin
            self.gl.ruler.reset()
            self.gl.ruler.translate(xmin, ymin - o, zmin)
            self.gl.ruler.redraw()

            self.gl.yruler.y = ymin
            self.gl.yruler.size = ymax - ymin
            self.gl.yruler.reset()
            self.gl.yruler.translate(xmin - o, ymin, zmin)
            self.gl.yruler.redraw()

            self.gl.zruler.z = zmin
            self.gl.zruler.size = zmax - zmin
            self.gl.zruler.reset()
            self.gl.zruler.translate(xmin - o, ymin - o, zmin)
            self.gl.zruler.redraw()

            self.gcode_path = fname

            self.update_probe()
            self.update_grid()
            self.info('Loaded {}'.format(fname))
        except IOError as e:
            self.info('Unable to load {}'.format(fname))
            self.info(str(e))

    def save_gcode_dialog(self):
        fname, mask = QFileDialog.getSaveFileName(None, "Save G-Code", "",
                "GCode (*.ngc *.gcode);;All files (*.*)")

        if not fname:
            return
        try:
            if self.gl.postgcode.orig:
                self.gl.postgcode.save_gcode(name)
                self.info('Saved post-processed g-code to {}'.format(name))
            elif self.gl.gcode.orig:
                self.gl.gcode.save_gcode(name)
                self.info('Saved original g-code to {}'.format(name))
            else:
                self.info('Nothing to save')

        except IOError as e:
            self.info('Unable to save to {}'.format(name))
            self.info(str(e))

    def append(self, text):
        self.text.append(text)
        c = self.text.textCursor()
        c.movePosition(QTextCursor.End, QTextCursor.MoveAnchor)
        self.text.setTextCursor(c)

    def handle_response(self, idx, txt):
        root = self.comtree.invisibleRootItem()
        item = root.child(idx)

        item.setText(0, time.strftime("%Y.%m.%d. %H:%M:%S", time.localtime()))
        if not txt:
            txt = 'ok'
        item.setText(2, txt)
        item.setCheckState(0, Qt.Checked)
        self.comtree.scrollToItem(item)

        cmd = item.text(1)

        proc_events()

        if 'G0' in cmd or 'G1' in cmd:
            x, y, z = parse.xyz(cmd[2:])
            # should probably emit signals

            self.current_x = x
            self.current_y = y
            self.current_z = z

        if 'G38.2' in cmd:
            try:
                # e.g. Probe not tripped from LinuxCNC
                # (we get error message from backend)
                if 'error' in txt:
                    z = -999.0
                else:
                    z = parse.probe(txt)
            except:
                self.err('Unable to parse probe: {}'.format(txt))
                self.err('Is your flavor ({}) correct?'.format(self.flavor))
                z = -999.0

            self.gl.result.data.append((self.current_x, self.current_y, z))
            self.update_result()
            self['probe result.lowest'] = min(self['probe result.lowest'], z)
            self['probe result.highest'] = max(self['probe result.highest'], z)
            self['probe result.last'] = z

    def save_probe_data_dialog(self):
        if not self.gl.result.data:
            # err not much to save
            return

        fname, sel = QFileDialog.getSaveFileName(
            self,
            'Save Log',)
            #'/path/to/default/directory', FIXME: lastused
            #selectedFilter='*.txt')

        if fname:
            self.save_probe_data(fname)

    def save_probe_data(self, fname):
        with open(fname, 'w') as f:
            for x, y, z in self.gl.result.data:
                f.write("{:04.2f} {:04.2f} {:04.2f}\n".format(x, y, z))

    def load_probe_data_dialog(self):
        fname, mask = QFileDialog.getOpenFileName(None, "Load probe data", "",
            "Log data (*.txt *.log);;All files (*.*)")

        if fname:
            self.load_probe_data(fname)

    def load_probe_data(self, fname):
        with open(fname, 'r') as f:
            d = (map(lambda x: map(float, x.split()), f.readlines()))

        self.gl.result.data = d
        self.update_result()

    @pyqtSlot()
    def on_save_clicked(self):
        root = self.comtree.invisibleRootItem()
        {'name': 'Visible', 'type': 'bool', 'value': 1},
        count = root.childCount()

        parts = []
        for i in range(count):
            item = root.child(i)
            time = item.text(0)
            cmd = item.text(1)
            resp = item.text(2)
            parts.append((time, cmd, resp))

        fname, sel = QFileDialog.getSaveFileName(
            self,
            'Save Log',)
            #'/path/to/default/directory', FIXME: lastused
            #selectedFilter='*.txt')

        if fname:
            with open(fname, 'w') as f:
                for time, cmd, resp in parts:
                    f.write('{}\t{}\t{}\n'.format(time, cmd, resp))

    def readSocket(self):
        def handle(r):
            if not r:
                return

            #print('buffered', r)
            (idx, txt) = dec_msg(r)
            if idx is not None:
                self.handle_response(idx, txt)

        buffer = ''

        while True:
            r = str(self.conn.readLine())

            if not r:
                handle(buffer)
                if self.conn.canReadLine():
                    buffer = ''
                    continue

                break

            if r[0] == '/':
                _, msg = dec_msg(r)
                self.append('{}'.format(msg))
                continue

            if r[0] == '[':
                handle(buffer)
                buffer = r
                continue

            buffer += r

    def info(self, errtext):
        self.text.setTextColor(QColor(20, 20, 20))
        self.append(errtext)

    def err(self, errtext):
        self.text.setTextColor(QColor(100, 0, 0))
        self.append(errtext)

    def socketDisconnect(self):
        self.err("Disconnected")
        self.connected = False
        self.info("Reconnecting")
        self.reconnect_timer.start(1000)

    def socketConnect(self):
        self.connected = True
        self.reconnect_timer.stop()
        self.info("Connected to {}:{}".format(self['connection.host'],
                                              self['connection.port']))
        self.ptree.collapse_group('connection')

    def socketError(self, socketError):
        # backoff
        self.reconnect_timer.setInterval(self.reconnect_timer.interval() * 2)

        if socketError == QAbstractSocket.RemoteHostClosedError:
            pass
        elif socketError == QAbstractSocket.HostNotFoundError:
            self.err("The host was not found. Please check the host name and "
                     "port settings.")

        elif socketError == QAbstractSocket.ConnectionRefusedError:
            self.err("The connection was refused by the peer. Make sure the "
                     "server is running, and check that the host name "
                     "and port settings are correct.")
        else:
            self.err("The following error occurred: {0}"
                     .format(self.conn.errorString()))

    def save_log_dialog(self):
        fname, sel = QFileDialog.getSaveFileName(
            self,
            'Save Log',)
            #'/path/to/default/directory', FIXME: lastused
            #selectedFilter='*.txt')

        if fname:
            with open(fname, 'w') as f:
                f.write(self.text.toPlainText())

    def save_probe_gcode_dialog(self):
        fname, sel = QFileDialog.getSaveFileName(
            self,
            'Save probe G-code',)
            #'/path/to/default/directory', FIXME: lastused
            #selectedFilter='*.txt')

        if fname:
            with open(fname, 'w') as f:
                for code in self.gen_probe_gcode(self.get_probe_points()):
                    f.write(code + '\n')

    def do_connect(self):
        self.conn.abort()
        self.info("Connecting to {}:{}".format(self['connection.host'],
                                               self['connection.port']))
        self.conn.connectToHost(self['connection.host'],
                                int(self['connection.port']))

    @pyqtSlot()
    def on_prompt_activated(self):
        pass

    def run_cmd(self, cmd):
        item = QTreeWidgetItem(self.comtree)

        item.setText(0, time.strftime("%Y.%m.%d. %H:%M:%S", time.localtime()))
        item.setText(1, cmd)

        for i in range(3):
            item.setTextAlignment(i, Qt.AlignTop)

        item.setForeground(1, QtGui.QBrush(green))
        item.setForeground(2, QtGui.QBrush(red))

        self.comtree.scrollToItem(item)
        self.conn.cmd(cmd)
        proc_events()

    @pyqtSlot()
    def on_send_clicked(self):
        if not self.connected:
            self.err("Not connected")
            return
        out = self.prompt.currentText()
        self.run_cmd(out)

    # FIXME: should go to hc lib
    def gen_probe_grid(self, rows, cols,
            w, h,
            x_margin, y_margin,
            x_trans, y_trans,
            start_z):
        w = w - x_margin * 2.
        h = h - y_margin * 2.

        if rows <= 0 or cols <= 0:
            return []

        if cols == 1 or rows == 1:
            return []

        xstep = w / (cols - 1)
        ystep = h / (rows - 1)

        cx = x_margin
        cy = y_margin

        out = []

        for i in range(rows):
            for j in range(cols):
                out.append((cx + x_trans, cy + y_trans, start_z))
                cx += xstep

            cx = x_margin
            cy += ystep

        return out

    def get_probe_points(self):
        mx = self['probe.x margin']
        my = self['probe.y margin']
        probe_points = self.gen_probe_grid(
            self['probe.rows'], self['probe.cols'],
            self['probe.width'], self['probe.height'],
            mx, my,
            self['gcode.limits.x min'], self['gcode.limits.y min'],
            self['probe.start z'])

        return probe_points

    def gen_probe_gcode(self, points):
        feed = self['probe.z feedrate']
        travel_feed = self['probe.travel feedrate']
        depth = self['probe.max depth']
        sz = self['probe.start z']

        yield 'G90'
        yield 'G1 Z{}'.format(sz)
        yield 'G4 P0'

        for point in points:
            yield 'G1 {} F{}'.format(xyzfmt(*point), travel_feed)
            yield probecmd.format(depth, feed)
            yield 'G1 Z{}'.format(sz)
            yield 'G4 P0'

    def run_probe(self):
        # clean probe result
        self.gl.result.data = []

        for code in self.gen_probe_gcode(self.get_probe_points()):
            self.run_cmd(code)

    def update_gcode(self):
        self.gl.gcode.setVisible(self['gcode.visible'])

    def update_probe(self):
        probe_points = self.get_probe_points()
        self.gl.probelist.probe_points = probe_points
        self.gl.probelist.redraw()

    def update_result(self):
        self.gl.result.setVisible(self['probe result.visible'])
        self.gl.result.offset = self['probe result.offset']
        self.gl.result.multiply = self['probe result.multiply']
        self.gl.result.snapz = self['probe result.snap z']
        self.gl.result.colormap = self['probe result.gradient']
        self.gl.result.opts['drawEdges'] = self['probe result.draw edges']

        self.gl.result.update_data()

    def update_grid(self):
        w = self['grid.width']
        h = self['grid.height']
        tx = self['grid.x origin']
        ty = self['grid.y origin']
        self.gl.grid.setSize(w, h, 1)
        self.gl.grid.resetTransform()
        self.gl.grid.translate(w / 2. + tx, h / 2. + ty, -0.05)
        self.gl.grid.setVisible(self['grid.visible'])
        self.gl.grid.redraw()

    def update_cross(self):
        s = self['cross.size']
        pos = []
        for i in ['x', 'y', 'z']:
            pos.append(self['cross.pos.{}'.format(i)])

        self.gl.cross.size = s
        self.gl.cross.redraw()
        self.gl.cross.resetTransform()
        self.gl.cross.translate(*pos)
        self.gl.cross.setVisible(self['cross.visible'])

    def process(self):
        self.info('Processing')
        self.precision = 0.1

        res = self.gl.result.data[:]
        if not res:
            self.err('No probe results')
            return

        gcpath = os.path.abspath(self.gcode_path)
        gcdir = os.path.dirname(gcpath)

        datapath = os.path.join(gcdir, 'probedata.txt')
        outpath = os.path.join(gcdir, 'lvl_{}'.format(os.path.basename(gcpath)))

        with open(datapath, 'w') as f:
            for x, y, z in res:
                f.write("{:04.2f} {:04.2f} {:04.2f}\n".format(x, y, z))

        script = config.get('leveling_tool', 'scale_gcode.py')

        args = '{} 1-999999 --zlevel {} {:.2f}'.format(gcpath,
                                                       datapath,
                                                       self.precision)

        args = args.split()
        self.info('Calling "{}"'.format(" ".join([script] + args)))
        proc_events()

        import subprocess
        proc = subprocess.Popen([script] + args, stdout=subprocess.PIPE,
                                stderr=subprocess.PIPE, close_fds=True)

        (stdout, stderr) = proc.communicate()
        if proc.returncode == 0:
          self.info('Done. Saving to {}'.format(outpath))

          with open(outpath, 'w') as f:
              f.write(stdout)

          self.gl.postgcode.load_gcode(outpath)
          self.info('Loaded post-processed G-code')
        else:
          self.info('Processing failed with return code {}'.format(proc.returncode))
          self.info("Error was:")
          self.info(stderr)

    # joystick handlers
    def axis_moving(self, axis, value):
        #self.run_cmd('G91')
        m = ['x', 'y', 'z', 'a']
        if abs(value) < 0.1:
            return

        value = value / 10
        #self.run_cmd('G0 {}{}'.format(m[axis], value))
        self.run_cmd('/jog {} {:04.2f}'.format(m[axis], value))

    def button_down(self, button):
        pass
Exemplo n.º 19
0
class ScrollLabel(QLabel):
    def __init__(self, parent=None, flags=Qt.WindowFlags()):
        super().__init__(parent=parent, flags=flags)
        self.timer = QTimer(self)
        self.timer.timeout.connect(self.move)

    def move(self):
        if self.countdown > 0:
            self.countdown -= self.timer.interval()
        elif self.reverse:
            if self.x >= 0:
                self.refresh(cooldown=True)
            else:
                self.x += 1
        else:
            if self.textWidth + self.x <= self.width():
                self.refresh(reverse=True, cooldown=True)
            else:
                self.x -= 1

        self.update()

    def refresh(self, reverse=False, cooldown=False):
        self.x = self.width() - self.textWidth if reverse else 0
        self.countdown = 2000 if cooldown else 0
        self.reverse = reverse
        self.update()

    def makeShort(self):
        self.short = self.long
        metrics = QFontMetrics(self.font())
        width = metrics.width(self.short)

        if width >= self.width():
            while width >= self.width():
                self.short = self.short[:-1]
                width = metrics.width(self.short + '...')

            self.short = self.short + '...'

    def setText(self, a0):
        self.long = a0
        self.makeShort()

        self.txt = self.short
        self.refresh()

    def enterEvent(self, event):
        self.txt = self.long
        self.refresh()
        self.timer.start(50)

    def leaveEvent(self, event):
        self.timer.stop()
        self.txt = self.short
        self.refresh()

    def paintEvent(self, a0):
        self.textWidth = QFontMetrics(self.font()).width(self.txt)

        painter = QPainter(self)
        painter.setFont(self.font())
        painter.setPen(self.palette().windowText().color())
        painter.setBrush(self.palette().windowText())

        if self.textWidth > self.width():
            painter.drawText(
                QRect(self.x, 0, self.textWidth, self.height()),
                Qt.AlignHCenter | (self.alignment() & Qt.AlignVCenter),
                self.txt)
        else:
            painter.drawText(QRect(0, 0, self.width(), self.height()),
                             self.alignment(), self.txt)
            self.timer.stop()
Exemplo n.º 20
0
class E5SideBar(QWidget):
    """
    Class implementing a sidebar with a widget area, that is hidden or shown,
    if the current tab is clicked again.
    """
    Version = 1
    
    North = 0
    East = 1
    South = 2
    West = 3
    
    def __init__(self, orientation=None, delay=200, parent=None):
        """
        Constructor
        
        @param orientation orientation of the sidebar widget (North, East,
            South, West)
        @param delay value for the expand/shrink delay in milliseconds
            (integer)
        @param parent parent widget (QWidget)
        """
        super(E5SideBar, self).__init__(parent)
        
        self.__tabBar = QTabBar()
        self.__tabBar.setDrawBase(True)
        self.__tabBar.setShape(QTabBar.RoundedNorth)
        self.__tabBar.setUsesScrollButtons(True)
        self.__tabBar.setDrawBase(False)
        self.__stackedWidget = QStackedWidget(self)
        self.__stackedWidget.setContentsMargins(0, 0, 0, 0)
        self.__autoHideButton = QToolButton()
        self.__autoHideButton.setCheckable(True)
        self.__autoHideButton.setIcon(
            UI.PixmapCache.getIcon("autoHideOff.png"))
        self.__autoHideButton.setChecked(True)
        self.__autoHideButton.setToolTip(
            self.tr("Deselect to activate automatic collapsing"))
        self.barLayout = QBoxLayout(QBoxLayout.LeftToRight)
        self.barLayout.setContentsMargins(0, 0, 0, 0)
        self.layout = QBoxLayout(QBoxLayout.TopToBottom)
        self.layout.setContentsMargins(0, 0, 0, 0)
        self.layout.setSpacing(0)
        self.barLayout.addWidget(self.__autoHideButton)
        self.barLayout.addWidget(self.__tabBar)
        self.layout.addLayout(self.barLayout)
        self.layout.addWidget(self.__stackedWidget)
        self.setLayout(self.layout)
        
        # initialize the delay timer
        self.__actionMethod = None
        self.__delayTimer = QTimer(self)
        self.__delayTimer.setSingleShot(True)
        self.__delayTimer.setInterval(delay)
        self.__delayTimer.timeout.connect(self.__delayedAction)
        
        self.__minimized = False
        self.__minSize = 0
        self.__maxSize = 0
        self.__bigSize = QSize()
        
        self.splitter = None
        self.splitterSizes = []
        
        self.__hasFocus = False
        # flag storing if this widget or any child has the focus
        self.__autoHide = False
        
        self.__tabBar.installEventFilter(self)
        
        self.__orientation = E5SideBar.North
        if orientation is None:
            orientation = E5SideBar.North
        self.setOrientation(orientation)
        
        self.__tabBar.currentChanged[int].connect(
            self.__stackedWidget.setCurrentIndex)
        e5App().focusChanged[QWidget, QWidget].connect(self.__appFocusChanged)
        self.__autoHideButton.toggled[bool].connect(self.__autoHideToggled)
    
    def setSplitter(self, splitter):
        """
        Public method to set the splitter managing the sidebar.
        
        @param splitter reference to the splitter (QSplitter)
        """
        self.splitter = splitter
        self.splitter.splitterMoved.connect(self.__splitterMoved)
        self.splitter.setChildrenCollapsible(False)
        index = self.splitter.indexOf(self)
        self.splitter.setCollapsible(index, False)
    
    def __splitterMoved(self, pos, index):
        """
        Private slot to react on splitter moves.
        
        @param pos new position of the splitter handle (integer)
        @param index index of the splitter handle (integer)
        """
        if self.splitter:
            self.splitterSizes = self.splitter.sizes()
    
    def __delayedAction(self):
        """
        Private slot to handle the firing of the delay timer.
        """
        if self.__actionMethod is not None:
            self.__actionMethod()
    
    def setDelay(self, delay):
        """
        Public method to set the delay value for the expand/shrink delay in
        milliseconds.
        
        @param delay value for the expand/shrink delay in milliseconds
            (integer)
        """
        self.__delayTimer.setInterval(delay)
    
    def delay(self):
        """
        Public method to get the delay value for the expand/shrink delay in
        milliseconds.
        
        @return value for the expand/shrink delay in milliseconds (integer)
        """
        return self.__delayTimer.interval()
    
    def __cancelDelayTimer(self):
        """
        Private method to cancel the current delay timer.
        """
        self.__delayTimer.stop()
        self.__actionMethod = None
    
    def shrink(self):
        """
        Public method to record a shrink request.
        """
        self.__delayTimer.stop()
        self.__actionMethod = self.__shrinkIt
        self.__delayTimer.start()
   
    def __shrinkIt(self):
        """
        Private method to shrink the sidebar.
        """
        self.__minimized = True
        self.__bigSize = self.size()
        if self.__orientation in [E5SideBar.North, E5SideBar.South]:
            self.__minSize = self.minimumSizeHint().height()
            self.__maxSize = self.maximumHeight()
        else:
            self.__minSize = self.minimumSizeHint().width()
            self.__maxSize = self.maximumWidth()
        if self.splitter:
            self.splitterSizes = self.splitter.sizes()
        
        self.__stackedWidget.hide()
        
        if self.__orientation in [E5SideBar.North, E5SideBar.South]:
            self.setFixedHeight(self.__tabBar.minimumSizeHint().height())
        else:
            self.setFixedWidth(self.__tabBar.minimumSizeHint().width())
        
        self.__actionMethod = None
    
    def expand(self):
        """
        Public method to record a expand request.
        """
        self.__delayTimer.stop()
        self.__actionMethod = self.__expandIt
        self.__delayTimer.start()
    
    def __expandIt(self):
        """
        Private method to expand the sidebar.
        """
        self.__minimized = False
        self.__stackedWidget.show()
        self.resize(self.__bigSize)
        if self.__orientation in [E5SideBar.North, E5SideBar.South]:
            minSize = max(self.__minSize, self.minimumSizeHint().height())
            self.setMinimumHeight(minSize)
            self.setMaximumHeight(self.__maxSize)
        else:
            minSize = max(self.__minSize, self.minimumSizeHint().width())
            self.setMinimumWidth(minSize)
            self.setMaximumWidth(self.__maxSize)
        if self.splitter:
            self.splitter.setSizes(self.splitterSizes)
        
        self.__actionMethod = None
    
    def isMinimized(self):
        """
        Public method to check the minimized state.
        
        @return flag indicating the minimized state (boolean)
        """
        return self.__minimized
    
    def isAutoHiding(self):
        """
        Public method to check, if the auto hide function is active.
        
        @return flag indicating the state of auto hiding (boolean)
        """
        return self.__autoHide
    
    def eventFilter(self, obj, evt):
        """
        Public method to handle some events for the tabbar.
        
        @param obj reference to the object (QObject)
        @param evt reference to the event object (QEvent)
        @return flag indicating, if the event was handled (boolean)
        """
        if obj == self.__tabBar:
            if evt.type() == QEvent.MouseButtonPress:
                pos = evt.pos()
                for i in range(self.__tabBar.count()):
                    if self.__tabBar.tabRect(i).contains(pos):
                        break
                
                if i == self.__tabBar.currentIndex():
                    if self.isMinimized():
                        self.expand()
                    else:
                        self.shrink()
                    return True
                elif self.isMinimized():
                    self.expand()
            elif evt.type() == QEvent.Wheel:
                if qVersion() >= "5.0.0":
                    delta = evt.angleDelta().y()
                else:
                    delta = evt.delta()
                if delta > 0:
                    self.prevTab()
                else:
                    self.nextTab()
                return True
        
        return QWidget.eventFilter(self, obj, evt)
    
    def addTab(self, widget, iconOrLabel, label=None):
        """
        Public method to add a tab to the sidebar.
        
        @param widget reference to the widget to add (QWidget)
        @param iconOrLabel reference to the icon or the label text of the tab
            (QIcon, string)
        @param label the labeltext of the tab (string) (only to be
            used, if the second parameter is a QIcon)
        """
        if label:
            index = self.__tabBar.addTab(iconOrLabel, label)
            self.__tabBar.setTabToolTip(index, label)
        else:
            index = self.__tabBar.addTab(iconOrLabel)
            self.__tabBar.setTabToolTip(index, iconOrLabel)
        self.__stackedWidget.addWidget(widget)
        if self.__orientation in [E5SideBar.North, E5SideBar.South]:
            self.__minSize = self.minimumSizeHint().height()
        else:
            self.__minSize = self.minimumSizeHint().width()
    
    def insertTab(self, index, widget, iconOrLabel, label=None):
        """
        Public method to insert a tab into the sidebar.
        
        @param index the index to insert the tab at (integer)
        @param widget reference to the widget to insert (QWidget)
        @param iconOrLabel reference to the icon or the labeltext of the tab
            (QIcon, string)
        @param label the labeltext of the tab (string) (only to be
            used, if the second parameter is a QIcon)
        """
        if label:
            index = self.__tabBar.insertTab(index, iconOrLabel, label)
            self.__tabBar.setTabToolTip(index, label)
        else:
            index = self.__tabBar.insertTab(index, iconOrLabel)
            self.__tabBar.setTabToolTip(index, iconOrLabel)
        self.__stackedWidget.insertWidget(index, widget)
        if self.__orientation in [E5SideBar.North, E5SideBar.South]:
            self.__minSize = self.minimumSizeHint().height()
        else:
            self.__minSize = self.minimumSizeHint().width()
    
    def removeTab(self, index):
        """
        Public method to remove a tab.
        
        @param index the index of the tab to remove (integer)
        """
        self.__stackedWidget.removeWidget(self.__stackedWidget.widget(index))
        self.__tabBar.removeTab(index)
        if self.__orientation in [E5SideBar.North, E5SideBar.South]:
            self.__minSize = self.minimumSizeHint().height()
        else:
            self.__minSize = self.minimumSizeHint().width()
    
    def clear(self):
        """
        Public method to remove all tabs.
        """
        while self.count() > 0:
            self.removeTab(0)
    
    def prevTab(self):
        """
        Public slot used to show the previous tab.
        """
        ind = self.currentIndex() - 1
        if ind == -1:
            ind = self.count() - 1
            
        self.setCurrentIndex(ind)
        self.currentWidget().setFocus()
    
    def nextTab(self):
        """
        Public slot used to show the next tab.
        """
        ind = self.currentIndex() + 1
        if ind == self.count():
            ind = 0
            
        self.setCurrentIndex(ind)
        self.currentWidget().setFocus()
    
    def count(self):
        """
        Public method to get the number of tabs.
        
        @return number of tabs in the sidebar (integer)
        """
        return self.__tabBar.count()
    
    def currentIndex(self):
        """
        Public method to get the index of the current tab.
        
        @return index of the current tab (integer)
        """
        return self.__stackedWidget.currentIndex()
    
    def setCurrentIndex(self, index):
        """
        Public slot to set the current index.
        
        @param index the index to set as the current index (integer)
        """
        self.__tabBar.setCurrentIndex(index)
        self.__stackedWidget.setCurrentIndex(index)
        if self.isMinimized():
            self.expand()
    
    def currentWidget(self):
        """
        Public method to get a reference to the current widget.
        
        @return reference to the current widget (QWidget)
        """
        return self.__stackedWidget.currentWidget()
    
    def setCurrentWidget(self, widget):
        """
        Public slot to set the current widget.
        
        @param widget reference to the widget to become the current widget
            (QWidget)
        """
        self.__stackedWidget.setCurrentWidget(widget)
        self.__tabBar.setCurrentIndex(self.__stackedWidget.currentIndex())
        if self.isMinimized():
            self.expand()
    
    def indexOf(self, widget):
        """
        Public method to get the index of the given widget.
        
        @param widget reference to the widget to get the index of (QWidget)
        @return index of the given widget (integer)
        """
        return self.__stackedWidget.indexOf(widget)
    
    def isTabEnabled(self, index):
        """
        Public method to check, if a tab is enabled.
        
        @param index index of the tab to check (integer)
        @return flag indicating the enabled state (boolean)
        """
        return self.__tabBar.isTabEnabled(index)
    
    def setTabEnabled(self, index, enabled):
        """
        Public method to set the enabled state of a tab.
        
        @param index index of the tab to set (integer)
        @param enabled enabled state to set (boolean)
        """
        self.__tabBar.setTabEnabled(index, enabled)
    
    def orientation(self):
        """
        Public method to get the orientation of the sidebar.
        
        @return orientation of the sidebar (North, East, South, West)
        """
        return self.__orientation
    
    def setOrientation(self, orient):
        """
        Public method to set the orientation of the sidebar.

        @param orient orientation of the sidebar (North, East, South, West)
        """
        if orient == E5SideBar.North:
            self.__tabBar.setShape(QTabBar.RoundedNorth)
            self.__tabBar.setSizePolicy(
                QSizePolicy.Expanding, QSizePolicy.Preferred)
            self.barLayout.setDirection(QBoxLayout.LeftToRight)
            self.layout.setDirection(QBoxLayout.TopToBottom)
            self.layout.setAlignment(self.barLayout, Qt.AlignLeft)
        elif orient == E5SideBar.East:
            self.__tabBar.setShape(QTabBar.RoundedEast)
            self.__tabBar.setSizePolicy(
                QSizePolicy.Preferred, QSizePolicy.Expanding)
            self.barLayout.setDirection(QBoxLayout.TopToBottom)
            self.layout.setDirection(QBoxLayout.RightToLeft)
            self.layout.setAlignment(self.barLayout, Qt.AlignTop)
        elif orient == E5SideBar.South:
            self.__tabBar.setShape(QTabBar.RoundedSouth)
            self.__tabBar.setSizePolicy(
                QSizePolicy.Expanding, QSizePolicy.Preferred)
            self.barLayout.setDirection(QBoxLayout.LeftToRight)
            self.layout.setDirection(QBoxLayout.BottomToTop)
            self.layout.setAlignment(self.barLayout, Qt.AlignLeft)
        elif orient == E5SideBar.West:
            self.__tabBar.setShape(QTabBar.RoundedWest)
            self.__tabBar.setSizePolicy(
                QSizePolicy.Preferred, QSizePolicy.Expanding)
            self.barLayout.setDirection(QBoxLayout.TopToBottom)
            self.layout.setDirection(QBoxLayout.LeftToRight)
            self.layout.setAlignment(self.barLayout, Qt.AlignTop)
        self.__orientation = orient
    
    def tabIcon(self, index):
        """
        Public method to get the icon of a tab.
        
        @param index index of the tab (integer)
        @return icon of the tab (QIcon)
        """
        return self.__tabBar.tabIcon(index)
    
    def setTabIcon(self, index, icon):
        """
        Public method to set the icon of a tab.
        
        @param index index of the tab (integer)
        @param icon icon to be set (QIcon)
        """
        self.__tabBar.setTabIcon(index, icon)
    
    def tabText(self, index):
        """
        Public method to get the text of a tab.
        
        @param index index of the tab (integer)
        @return text of the tab (string)
        """
        return self.__tabBar.tabText(index)
    
    def setTabText(self, index, text):
        """
        Public method to set the text of a tab.
        
        @param index index of the tab (integer)
        @param text text to set (string)
        """
        self.__tabBar.setTabText(index, text)
    
    def tabToolTip(self, index):
        """
        Public method to get the tooltip text of a tab.
        
        @param index index of the tab (integer)
        @return tooltip text of the tab (string)
        """
        return self.__tabBar.tabToolTip(index)
    
    def setTabToolTip(self, index, tip):
        """
        Public method to set the tooltip text of a tab.
        
        @param index index of the tab (integer)
        @param tip tooltip text to set (string)
        """
        self.__tabBar.setTabToolTip(index, tip)
    
    def tabWhatsThis(self, index):
        """
        Public method to get the WhatsThis text of a tab.
        
        @param index index of the tab (integer)
        @return WhatsThis text of the tab (string)
        """
        return self.__tabBar.tabWhatsThis(index)
    
    def setTabWhatsThis(self, index, text):
        """
        Public method to set the WhatsThis text of a tab.
        
        @param index index of the tab (integer)
        @param text WhatsThis text to set (string)
        """
        self.__tabBar.setTabWhatsThis(index, text)
    
    def widget(self, index):
        """
        Public method to get a reference to the widget associated with a tab.
        
        @param index index of the tab (integer)
        @return reference to the widget (QWidget)
        """
        return self.__stackedWidget.widget(index)
    
    def saveState(self):
        """
        Public method to save the state of the sidebar.
        
        @return saved state as a byte array (QByteArray)
        """
        if len(self.splitterSizes) == 0:
            if self.splitter:
                self.splitterSizes = self.splitter.sizes()
            self.__bigSize = self.size()
            if self.__orientation in [E5SideBar.North, E5SideBar.South]:
                self.__minSize = self.minimumSizeHint().height()
                self.__maxSize = self.maximumHeight()
            else:
                self.__minSize = self.minimumSizeHint().width()
                self.__maxSize = self.maximumWidth()
        
        data = QByteArray()
        stream = QDataStream(data, QIODevice.WriteOnly)
        stream.setVersion(QDataStream.Qt_4_6)
        
        stream.writeUInt16(self.Version)
        stream.writeBool(self.__minimized)
        stream << self.__bigSize
        stream.writeUInt16(self.__minSize)
        stream.writeUInt16(self.__maxSize)
        stream.writeUInt16(len(self.splitterSizes))
        for size in self.splitterSizes:
            stream.writeUInt16(size)
        stream.writeBool(self.__autoHide)
        
        return data
    
    def restoreState(self, state):
        """
        Public method to restore the state of the sidebar.
        
        @param state byte array containing the saved state (QByteArray)
        @return flag indicating success (boolean)
        """
        if state.isEmpty():
            return False
        
        if self.__orientation in [E5SideBar.North, E5SideBar.South]:
            minSize = self.layout.minimumSize().height()
            maxSize = self.maximumHeight()
        else:
            minSize = self.layout.minimumSize().width()
            maxSize = self.maximumWidth()
        
        data = QByteArray(state)
        stream = QDataStream(data, QIODevice.ReadOnly)
        stream.setVersion(QDataStream.Qt_4_6)
        stream.readUInt16()  # version
        minimized = stream.readBool()
        
        if minimized and not self.__minimized:
            self.shrink()
        
        stream >> self.__bigSize
        self.__minSize = max(stream.readUInt16(), minSize)
        self.__maxSize = max(stream.readUInt16(), maxSize)
        count = stream.readUInt16()
        self.splitterSizes = []
        for i in range(count):
            self.splitterSizes.append(stream.readUInt16())
        
        self.__autoHide = stream.readBool()
        self.__autoHideButton.setChecked(not self.__autoHide)
        
        if not minimized:
            self.expand()
        
        return True
    
    #######################################################################
    ## methods below implement the autohide functionality
    #######################################################################
    
    def __autoHideToggled(self, checked):
        """
        Private slot to handle the toggling of the autohide button.
        
        @param checked flag indicating the checked state of the button
            (boolean)
        """
        self.__autoHide = not checked
        if self.__autoHide:
            self.__autoHideButton.setIcon(
                UI.PixmapCache.getIcon("autoHideOn.png"))
        else:
            self.__autoHideButton.setIcon(
                UI.PixmapCache.getIcon("autoHideOff.png"))
    
    def __appFocusChanged(self, old, now):
        """
        Private slot to handle a change of the focus.
        
        @param old reference to the widget, that lost focus (QWidget or None)
        @param now reference to the widget having the focus (QWidget or None)
        """
        self.__hasFocus = self.isAncestorOf(now)
        if self.__autoHide and not self.__hasFocus and not self.isMinimized():
            self.shrink()
        elif self.__autoHide and self.__hasFocus and self.isMinimized():
            self.expand()
    
    def enterEvent(self, event):
        """
        Protected method to handle the mouse entering this widget.
        
        @param event reference to the event (QEvent)
        """
        if self.__autoHide and self.isMinimized():
            self.expand()
        else:
            self.__cancelDelayTimer()
    
    def leaveEvent(self, event):
        """
        Protected method to handle the mouse leaving this widget.
        
        @param event reference to the event (QEvent)
        """
        if self.__autoHide and not self.__hasFocus and not self.isMinimized():
            self.shrink()
        else:
            self.__cancelDelayTimer()
    
    def shutdown(self):
        """
        Public method to shut down the object.
        
        This method does some preparations so the object can be deleted
        properly. It disconnects from the focusChanged signal in order to
        avoid trouble later on.
        """
        e5App().focusChanged[QWidget, QWidget].disconnect(
            self.__appFocusChanged)
Exemplo n.º 21
0
class AbstractPLCConnector(QObject, object):
    """Connector with buffered PLC access.

    The access is not done directly via PLC addresses but instead with Tags. Data is
    exchanged with the PLC when the C{poll()} function is called or C{autopoll} is
    enabled.

    :type tags: dict
    :ivar tags: holds the Tag objects, the key is the tag name

    :type poll_interval: int
    :ivar poll_interval: interval for auto-polling in ms
    """

    polled = pyqtSignal()
    connectionError = pyqtSignal(str)

    def __init__(self) -> None:
        super(AbstractPLCConnector, self).__init__()
        self.tags: Dict[str, Tag] = dict()
        self.autopoll_timer = QTimer(self)
        self.autopoll_timer.timeout.connect(self.poll)

    def add_tag(self, tag: Tag) -> Tag:
        """Add a Tag to the list."""
        self.tags[tag.name] = tag
        return tag

    def add_tags(self, tags: Iterable) -> None:
        """Add multiple tags to the internal list."""
        list(map(self.add_tag, tags))

    @property
    def cycletime(self) -> int:
        """Return current cycletime."""
        return self.autopoll_timer.interval()

    def remove_tag(self, tag_name: str) -> None:
        """Remove a Tag from the list."""
        self.tags.pop(tag_name)

    def poll(self) -> None:
        """Exchange data with PLC.

        If a Tag value has been modified in the GUI the value is first written
        to the PLC and then read again.

        The pyqtSignal C{polled()} is emitted when finished.

        """
        for tag in self.tags.values():
            try:
                if tag.dirty:
                    self.write_to_plc(tag.address, tag.raw_value,
                                      tag.plc_datatype)
                    tag.dirty = False

                tag.raw_value = self.read_from_plc(tag.address,
                                                   tag.plc_datatype)

            except ConnectionError as e:
                self.connectionError.emit(str(e))

        self.polled.emit()

    @abstractmethod
    def write_to_plc(self, *args: Any, **kwargs: Any) -> None:
        """Write data to the plc.

        B{Abstract method. Overwrite when inherited.}

        """
        pass

    @abstractmethod
    def read_from_plc(self, *args: Any, **kwargs: Any) -> None:
        """Read data from the plc.

        B{Abstract method. Overwrite when inherited.}

        """
        pass

    def start_autopoll(self, poll_interval: int) -> None:
        """Enable auto-polling data.

        :param poll_interval: interval for autopolling in ms

        """
        self.autopoll_timer.start(poll_interval)

    def stop_autopoll(self) -> None:
        """Disable auto-polling data."""
        self.autopoll_timer.stop()
Exemplo n.º 22
0
Arquivo: main.py Projeto: kip93/3T-VI
class Main(view.Game):
    def __init__(self):
        super().__init__()

        self._board_model = model.Board()
        self._vi_o_model = model.VI('3T-VI O')
        self._vi_x_model = model.VI('3T-VI X')

        self._start = random.choice([-1, +1])
        self._turn = self._start
        self._thinking = True
        self._checking = True

        self._step_timer = QTimer()
        self._step_timer.timeout.connect(self._step)
        self._step_timer.setInterval(int(self.delay() * 1000))

    def refresh(self, board: 'model.Board'):
        super().refresh(board)
        if self._step_timer.interval() != int(self.delay() * 1000):
            self._step_timer.setInterval(int(self.delay() * 1000))

    def _toggle(self):
        super()._toggle()
        if self.is_running():
            self._step_timer.start()
        else:
            self._step_timer.stop()

    def _check(self, result):
        if result < 0:
            self._x_wins += 1
            self.state_o = 'lost'
            self.state_x = 'won'
        elif result > 0:
            self._o_wins += 1
            self.state_o = 'won'
            self.state_x = 'lost'
        else:
            self._ties += 1
            self.state_o = 'tied'
            self.state_x = 'tied'

        self._vi_o_model.train(result)
        self._vi_x_model.train(-result)
        self._vi_o_model.save()
        self._vi_x_model.save()

        self._checking = False

    def _move(self):
        if self._turn == +1:
            move = self._vi_o_model.move(self._board_model)
            self._board_model.set(*move, +1)

        elif self._turn == -1:
            move = self._vi_x_model.move(self._board_model.invert())
            self._board_model.set(*move, -1)

        self._turn *= -1
        self._thinking = True

    def _reset(self):
        self._board_model.clear()
        self._start *= -1
        self._turn = self._start

        self._checking = True

    def _step(self):
        result = self._board_model.result()
        if result is None:
            if self._thinking:
                self._think()
            else:
                self._move()
        else:
            if self._checking:
                self._check(result)
            else:
                self._reset()

    def _think(self):
        if self.delay() != 0:
            if self._turn == +1:
                self.state_o = 'thinking'
                self.state_x = 'waiting'
            else:
                self.state_o = 'waiting'
                self.state_x = 'thinking'
            self._thinking = False
class OctoPrintOutputDevice(NetworkedPrinterOutputDevice):
    def __init__(self,
                 instance_id: str,
                 address: str,
                 port: int,
                 properties: dict,
                 parent=None) -> None:
        super().__init__(device_id=instance_id,
                         address=address,
                         properties=properties,
                         parent=parent)

        self._address = address
        self._port = port
        self._path = properties.get(b"path", b"/").decode("utf-8")
        if self._path[-1:] != "/":
            self._path += "/"
        self._id = instance_id
        self._properties = properties  # Properties dict as provided by zero conf

        self._gcode_stream = StringIO()

        self._auto_print = True
        self._forced_queue = False

        # We start with a single extruder, but update this when we get data from octoprint
        self._number_of_extruders_set = False
        self._number_of_extruders = 1

        # Try to get version information from plugin.json
        plugin_file_path = os.path.join(
            os.path.dirname(os.path.abspath(__file__)), "plugin.json")
        try:
            with open(plugin_file_path) as plugin_file:
                plugin_info = json.load(plugin_file)
                plugin_version = plugin_info["version"]
        except:
            # The actual version info is not critical to have so we can continue
            plugin_version = "Unknown"
            Logger.logException(
                "w", "Could not get version information for the plugin")

        self._user_agent_header = "User-Agent".encode()
        self._user_agent = (
            "%s/%s %s/%s" %
            (CuraApplication.getInstance().getApplicationName(),
             CuraApplication.getInstance().getVersion(), "OctoPrintPlugin",
             plugin_version)
        )  # NetworkedPrinterOutputDevice defines this as string, so we encode this later

        self._api_prefix = "api/"
        self._api_header = "X-Api-Key".encode()
        self._api_key = b""

        self._protocol = "https" if properties.get(
            b'useHttps') == b"true" else "http"
        self._base_url = "%s://%s:%d%s" % (self._protocol, self._address,
                                           self._port, self._path)
        self._api_url = self._base_url + self._api_prefix

        self._basic_auth_header = "Authorization".encode()
        self._basic_auth_data = None
        basic_auth_username = properties.get(b"userName", b"").decode("utf-8")
        basic_auth_password = properties.get(b"password", b"").decode("utf-8")
        if basic_auth_username and basic_auth_password:
            data = base64.b64encode(
                ("%s:%s" % (basic_auth_username,
                            basic_auth_password)).encode()).decode("utf-8")
            self._basic_auth_data = ("basic %s" % data).encode()

        self._monitor_view_qml_path = os.path.join(
            os.path.dirname(os.path.abspath(__file__)), "MonitorItem.qml")

        name = self._id
        matches = re.search(r"^\"(.*)\"\._octoprint\._tcp.local$", name)
        if matches:
            name = matches.group(1)

        self.setPriority(
            2
        )  # Make sure the output device gets selected above local file output
        self.setName(name)
        self.setShortDescription(
            i18n_catalog.i18nc("@action:button", "Print with OctoPrint"))
        self.setDescription(
            i18n_catalog.i18nc("@properties:tooltip", "Print with OctoPrint"))
        self.setIconName("print")
        self.setConnectionText(
            i18n_catalog.i18nc("@info:status",
                               "Connected to OctoPrint on {0}").format(
                                   self._id))

        self._post_reply = None

        self._progress_message = None  # type: Union[None, Message]
        self._error_message = None  # type: Union[None, Message]
        self._connection_message = None  # type: Union[None, Message]

        self._queued_gcode_commands = []  # type: List[str]
        self._queued_gcode_timer = QTimer()
        self._queued_gcode_timer.setInterval(0)
        self._queued_gcode_timer.setSingleShot(True)
        self._queued_gcode_timer.timeout.connect(self._sendQueuedGcode)

        # TODO; Add preference for update intervals
        self._update_fast_interval = 2000
        self._update_slow_interval = 10000
        self._update_timer = QTimer()
        self._update_timer.setInterval(self._update_fast_interval)
        self._update_timer.setSingleShot(False)
        self._update_timer.timeout.connect(self._update)

        self._show_camera = False
        self._camera_mirror = False
        self._camera_rotation = 0
        self._camera_url = ""
        self._camera_shares_proxy = False

        self._sd_supported = False

        self._plugin_data = {}  #type: Dict[str, Any]

        self._output_controller = GenericOutputController(self)

    def getProperties(self) -> Dict[bytes, bytes]:
        return self._properties

    @pyqtSlot(str, result=str)
    def getProperty(self, key: str) -> str:
        key_b = key.encode("utf-8")
        if key_b in self._properties:
            return self._properties.get(key_b, b"").decode("utf-8")
        else:
            return ""

    ##  Get the unique key of this machine
    #   \return key String containing the key of the machine.
    @pyqtSlot(result=str)
    def getId(self) -> str:
        return self._id

    ##  Set the API key of this OctoPrint instance
    def setApiKey(self, api_key: str) -> None:
        self._api_key = api_key.encode()

    ##  Name of the instance (as returned from the zeroConf properties)
    @pyqtProperty(str, constant=True)
    def name(self) -> str:
        return self._name

    ##  Version (as returned from the zeroConf properties)
    @pyqtProperty(str, constant=True)
    def octoprintVersion(self) -> str:
        return self._properties.get(b"version", b"").decode("utf-8")

    ## IPadress of this instance
    @pyqtProperty(str, constant=True)
    def ipAddress(self) -> str:
        return self._address

    ## IPadress of this instance
    #  Overridden from NetworkedPrinterOutputDevice because OctoPrint does not
    #  send the ip address with zeroconf
    @pyqtProperty(str, constant=True)
    def address(self) -> str:
        return self._address

    ## port of this instance
    @pyqtProperty(int, constant=True)
    def port(self) -> int:
        return self._port

    ## path of this instance
    @pyqtProperty(str, constant=True)
    def path(self) -> str:
        return self._path

    ## absolute url of this instance
    @pyqtProperty(str, constant=True)
    def baseURL(self) -> str:
        return self._base_url

    cameraOrientationChanged = pyqtSignal()

    @pyqtProperty("QVariantMap", notify=cameraOrientationChanged)
    def cameraOrientation(self) -> Dict[str, Any]:
        return {
            "mirror": self._camera_mirror,
            "rotation": self._camera_rotation,
        }

    cameraUrlChanged = pyqtSignal()

    @pyqtProperty("QUrl", notify=cameraUrlChanged)
    def cameraUrl(self) -> QUrl:
        return QUrl(self._camera_url)

    def setShowCamera(self, show_camera: bool) -> None:
        if show_camera != self._show_camera:
            self._show_camera = show_camera
            self.showCameraChanged.emit()

    showCameraChanged = pyqtSignal()

    @pyqtProperty(bool, notify=showCameraChanged)
    def showCamera(self) -> bool:
        return self._show_camera

    def _update(self) -> None:
        ## Request 'general' printer data
        self.get("printer", self._onRequestFinished)

        ## Request print_job data
        self.get("job", self._onRequestFinished)

    def _createEmptyRequest(self,
                            target: str,
                            content_type: Optional[str] = "application/json"
                            ) -> QNetworkRequest:
        request = QNetworkRequest(QUrl(self._api_url + target))
        request.setRawHeader(self._user_agent_header,
                             self._user_agent.encode())
        request.setRawHeader(self._api_header, self._api_key)
        if content_type is not None:
            request.setHeader(QNetworkRequest.ContentTypeHeader,
                              "application/json")
        if self._basic_auth_data:
            request.setRawHeader(self._basic_auth_header,
                                 self._basic_auth_data)
        return request

    def close(self) -> None:
        self.setConnectionState(ConnectionState.closed)
        if self._progress_message:
            self._progress_message.hide()
        if self._error_message:
            self._error_message.hide()
        self._update_timer.stop()

    ##  Start requesting data from the instance
    def connect(self) -> None:
        self._createNetworkManager()

        self.setConnectionState(ConnectionState.connecting)
        self._update(
        )  # Manually trigger the first update, as we don't want to wait a few secs before it starts.

        Logger.log("d", "Connection with instance %s with url %s started",
                   self._id, self._base_url)
        self._update_timer.start()

        self._last_response_time = None
        self._setAcceptsCommands(False)
        self.setConnectionText(
            i18n_catalog.i18nc("@info:status",
                               "Connecting to OctoPrint on {0}").format(
                                   self._id))

        ## Request 'settings' dump
        self.get("settings", self._onRequestFinished)

    ##  Stop requesting data from the instance
    def disconnect(self) -> None:
        Logger.log("d", "Connection with instance %s with url %s stopped",
                   self._id, self._base_url)
        self.close()

    def pausePrint(self) -> None:
        self._sendJobCommand("pause")

    def resumePrint(self) -> None:
        if not self._printers[0].activePrintJob:
            return

        if self._printers[0].activePrintJob.state == "paused":
            self._sendJobCommand("pause")
        else:
            self._sendJobCommand("start")

    def cancelPrint(self) -> None:
        self._sendJobCommand("cancel")

    def requestWrite(self,
                     nodes: List["SceneNode"],
                     file_name: Optional[str] = None,
                     limit_mimetypes: bool = False,
                     file_handler: Optional["FileHandler"] = None,
                     **kwargs: str) -> None:
        global_container_stack = CuraApplication.getInstance(
        ).getGlobalContainerStack()
        if not global_container_stack:
            return

        # Make sure post-processing plugin are run on the gcode
        self.writeStarted.emit(self)

        # Get the g-code through the GCodeWriter plugin
        # This produces the same output as "Save to File", adding the print settings to the bottom of the file
        gcode_writer = cast(
            MeshWriter,
            PluginRegistry.getInstance().getPluginObject("GCodeWriter"))
        self._gcode_stream = StringIO()
        if not gcode_writer.write(self._gcode_stream, None):
            Logger.log("e",
                       "GCodeWrite failed: %s" % gcode_writer.getInformation())
            return

        if self._error_message:
            self._error_message.hide()
            self._error_message = None

        if self._progress_message:
            self._progress_message.hide()
            self._progress_message = None

        self._auto_print = parseBool(
            global_container_stack.getMetaDataEntry("octoprint_auto_print",
                                                    True))
        self._forced_queue = False

        if self.activePrinter.state not in ["idle", ""]:
            Logger.log(
                "d", "Tried starting a print, but current state is %s" %
                self.activePrinter.state)
            if not self._auto_print:
                # Allow queueing the job even if OctoPrint is currently busy if autoprinting is disabled
                self._error_message = None
            elif self.activePrinter.state == "offline":
                self._error_message = Message(
                    i18n_catalog.i18nc(
                        "@info:status",
                        "The printer is offline. Unable to start a new job."))
            else:
                self._error_message = Message(
                    i18n_catalog.i18nc(
                        "@info:status",
                        "OctoPrint is busy. Unable to start a new job."))

            if self._error_message:
                self._error_message.addAction(
                    "Queue", i18n_catalog.i18nc("@action:button", "Queue job"),
                    "",
                    i18n_catalog.i18nc(
                        "@action:tooltip",
                        "Queue this print job so it can be printed later"))
                self._error_message.actionTriggered.connect(self._queuePrint)
                self._error_message.show()
                return

        self._startPrint()

    def _queuePrint(self,
                    message_id: Optional[str] = None,
                    action_id: Optional[str] = None) -> None:
        if self._error_message:
            self._error_message.hide()
        self._forced_queue = True
        self._startPrint()

    def _startPrint(self) -> None:
        global_container_stack = CuraApplication.getInstance(
        ).getGlobalContainerStack()
        if not global_container_stack:
            return

        if self._auto_print and not self._forced_queue:
            CuraApplication.getInstance().getController().setActiveStage(
                "MonitorStage")

            # cancel any ongoing preheat timer before starting a print
            try:
                self._printers[0].stopPreheatTimers()
            except AttributeError:
                # stopPreheatTimers was added after Cura 3.3 beta
                pass

        self._progress_message = Message(
            i18n_catalog.i18nc("@info:status", "Sending data to OctoPrint"), 0,
            False, -1)
        self._progress_message.addAction(
            "Cancel", i18n_catalog.i18nc("@action:button", "Cancel"), "", "")
        self._progress_message.actionTriggered.connect(self._cancelSendGcode)
        self._progress_message.show()

        job_name = CuraApplication.getInstance().getPrintInformation(
        ).jobName.strip()
        if job_name is "":
            job_name = "untitled_print"
        file_name = "%s.gcode" % job_name

        ##  Create multi_part request
        post_parts = []  # type: List[QHttpPart]

        ##  Create parts (to be placed inside multipart)
        post_part = QHttpPart()
        post_part.setHeader(QNetworkRequest.ContentDispositionHeader,
                            "form-data; name=\"select\"")
        post_part.setBody(b"true")
        post_parts.append(post_part)

        if self._auto_print and not self._forced_queue:
            post_part = QHttpPart()
            post_part.setHeader(QNetworkRequest.ContentDispositionHeader,
                                "form-data; name=\"print\"")
            post_part.setBody(b"true")
            post_parts.append(post_part)

        post_part = QHttpPart()
        post_part.setHeader(
            QNetworkRequest.ContentDispositionHeader,
            "form-data; name=\"file\"; filename=\"%s\"" % file_name)
        post_part.setBody(self._gcode_stream.getvalue().encode())
        post_parts.append(post_part)

        destination = "local"
        if self._sd_supported and parseBool(
                global_container_stack.getMetaDataEntry(
                    "octoprint_store_sd", False)):
            destination = "sdcard"

        try:
            ##  Post request + data
            post_request = self._createEmptyRequest("files/" + destination)
            self._post_reply = self.postFormWithParts(
                "files/" + destination,
                post_parts,
                on_finished=self._onRequestFinished,
                on_progress=self._onUploadProgress)

        except IOError:
            self._progress_message.hide()
            self._error_message = Message(
                i18n_catalog.i18nc("@info:status",
                                   "Unable to send data to OctoPrint."))
            self._error_message.show()
        except Exception as e:
            self._progress_message.hide()
            Logger.log(
                "e",
                "An exception occurred in network connection: %s" % str(e))

        self._gcode_stream = StringIO()

    def _cancelSendGcode(self,
                         message_id: Optional[str] = None,
                         action_id: Optional[str] = None) -> None:
        if self._post_reply:
            Logger.log("d", "Stopping upload because the user pressed cancel.")
            try:
                self._post_reply.uploadProgress.disconnect(
                    self._onUploadProgress)
            except TypeError:
                pass  # The disconnection can fail on mac in some cases. Ignore that.

            self._post_reply.abort()
            self._post_reply = None
        if self._progress_message:
            self._progress_message.hide()

    def sendCommand(self, command: str) -> None:
        self._queued_gcode_commands.append(command)
        self._queued_gcode_timer.start()

    # Send gcode commands that are queued in quick succession as a single batch
    def _sendQueuedGcode(self) -> None:
        if self._queued_gcode_commands:
            self._sendCommandToApi("printer/command",
                                   self._queued_gcode_commands)
            Logger.log("d", "Sent gcode command to OctoPrint instance: %s",
                       self._queued_gcode_commands)
            self._queued_gcode_commands = []  # type: List[str]

    def _sendJobCommand(self, command: str) -> None:
        self._sendCommandToApi("job", command)
        Logger.log("d", "Sent job command to OctoPrint instance: %s", command)

    def _sendCommandToApi(self, end_point: str,
                          commands: Union[str, List[str]]) -> None:
        if isinstance(commands, list):
            data = json.dumps({"commands": commands})
        else:
            data = json.dumps({"command": commands})
        self.post(end_point, data, self._onRequestFinished)

    ## Overloaded from NetworkedPrinterOutputDevice.post() to backport https://github.com/Ultimaker/Cura/pull/4678
    def post(self,
             target: str,
             data: str,
             on_finished: Optional[Callable[[QNetworkReply], None]],
             on_progress: Callable = None) -> None:
        self._validateManager()
        request = self._createEmptyRequest(target)
        self._last_request_time = time()
        if self._manager is not None:
            reply = self._manager.post(request, data.encode())
            if on_progress is not None:
                reply.uploadProgress.connect(on_progress)
            self._registerOnFinishedCallback(reply, on_finished)
        else:
            Logger.log("e", "Could not find manager.")

    ##  Handler for all requests that have finished.
    def _onRequestFinished(self, reply: QNetworkReply) -> None:
        if reply.error() == QNetworkReply.TimeoutError:
            Logger.log("w", "Received a timeout on a request to the instance")
            self._connection_state_before_timeout = self._connection_state
            self.setConnectionState(ConnectionState.error)
            return

        if self._connection_state_before_timeout and reply.error(
        ) == QNetworkReply.NoError:  #  There was a timeout, but we got a correct answer again.
            if self._last_response_time:
                Logger.log(
                    "d",
                    "We got a response from the instance after %s of silence",
                    time() - self._last_response_time)
            self.setConnectionState(self._connection_state_before_timeout)
            self._connection_state_before_timeout = None

        if reply.error() == QNetworkReply.NoError:
            self._last_response_time = time()

        http_status_code = reply.attribute(
            QNetworkRequest.HttpStatusCodeAttribute)
        if not http_status_code:
            # Received no or empty reply
            return

        error_handled = False

        if reply.operation() == QNetworkAccessManager.GetOperation:
            if self._api_prefix + "printer" in reply.url().toString(
            ):  # Status update from /printer.
                if not self._printers:
                    self._createPrinterList()

                # An OctoPrint instance has a single printer.
                printer = self._printers[0]
                update_pace = self._update_slow_interval

                if http_status_code == 200:
                    update_pace = self._update_fast_interval

                    if not self.acceptsCommands:
                        self._setAcceptsCommands(True)
                        self.setConnectionText(
                            i18n_catalog.i18nc(
                                "@info:status",
                                "Connected to OctoPrint on {0}").format(
                                    self._id))

                    if self._connection_state == ConnectionState.connecting:
                        self.setConnectionState(ConnectionState.connected)
                    try:
                        json_data = json.loads(
                            bytes(reply.readAll()).decode("utf-8"))
                    except json.decoder.JSONDecodeError:
                        Logger.log(
                            "w",
                            "Received invalid JSON from octoprint instance.")
                        json_data = {}

                    if "temperature" in json_data:
                        if not self._number_of_extruders_set:
                            self._number_of_extruders = 0
                            while "tool%d" % self._number_of_extruders in json_data[
                                    "temperature"]:
                                self._number_of_extruders += 1

                            if self._number_of_extruders > 1:
                                # Recreate list of printers to match the new _number_of_extruders
                                self._createPrinterList()
                                printer = self._printers[0]

                            if self._number_of_extruders > 0:
                                self._number_of_extruders_set = True

                        # Check for hotend temperatures
                        for index in range(0, self._number_of_extruders):
                            extruder = printer.extruders[index]
                            if ("tool%d" % index) in json_data["temperature"]:
                                hotend_temperatures = json_data["temperature"][
                                    "tool%d" % index]
                                extruder.updateTargetHotendTemperature(
                                    hotend_temperatures["target"])
                                extruder.updateHotendTemperature(
                                    hotend_temperatures["actual"])
                            else:
                                extruder.updateTargetHotendTemperature(0)
                                extruder.updateHotendTemperature(0)

                        if "bed" in json_data["temperature"]:
                            bed_temperatures = json_data["temperature"]["bed"]
                            actual_temperature = bed_temperatures[
                                "actual"] if bed_temperatures[
                                    "actual"] is not None else -1
                            printer.updateBedTemperature(actual_temperature)
                            target_temperature = bed_temperatures[
                                "target"] if bed_temperatures[
                                    "target"] is not None else -1
                            printer.updateTargetBedTemperature(
                                target_temperature)
                        else:
                            printer.updateBedTemperature(-1)
                            printer.updateTargetBedTemperature(0)

                    printer_state = "offline"
                    if "state" in json_data:
                        flags = json_data["state"]["flags"]
                        if flags["error"] or flags["closedOrError"]:
                            printer_state = "error"
                        elif flags["paused"] or flags["pausing"]:
                            printer_state = "paused"
                        elif flags["printing"]:
                            printer_state = "printing"
                        elif flags["cancelling"]:
                            printer_state = "aborted"
                        elif flags["ready"] or flags["operational"]:
                            printer_state = "idle"
                    printer.updateState(printer_state)

                elif http_status_code == 401:
                    printer.updateState("offline")
                    if printer.activePrintJob:
                        printer.activePrintJob.updateState("offline")
                    self.setConnectionText(
                        i18n_catalog.i18nc(
                            "@info:status",
                            "OctoPrint on {0} does not allow access to print").
                        format(self._id))
                    error_handled = True

                elif http_status_code == 409:
                    if self._connection_state == ConnectionState.connecting:
                        self.setConnectionState(ConnectionState.connected)

                    printer.updateState("offline")
                    if printer.activePrintJob:
                        printer.activePrintJob.updateState("offline")
                    self.setConnectionText(
                        i18n_catalog.i18nc(
                            "@info:status",
                            "The printer connected to OctoPrint on {0} is not operational"
                        ).format(self._id))
                    error_handled = True

                elif http_status_code == 502 or http_status_code == 503:
                    printer.updateState("offline")
                    if printer.activePrintJob:
                        printer.activePrintJob.updateState("offline")
                    self.setConnectionText(
                        i18n_catalog.i18nc(
                            "@info:status",
                            "OctoPrint on {0} is not running").format(
                                self._id))
                    error_handled = True

                else:
                    printer.updateState("offline")
                    if printer.activePrintJob:
                        printer.activePrintJob.updateState("offline")
                    Logger.log("w", "Received an unexpected returncode: %d",
                               http_status_code)

                if update_pace != self._update_timer.interval():
                    self._update_timer.setInterval(update_pace)

            elif self._api_prefix + "job" in reply.url().toString(
            ):  # Status update from /job:
                if not self._printers:
                    return  # Ignore the data for now, we don't have info about a printer yet.
                printer = self._printers[0]

                if http_status_code == 200:
                    try:
                        json_data = json.loads(
                            bytes(reply.readAll()).decode("utf-8"))
                    except json.decoder.JSONDecodeError:
                        Logger.log(
                            "w",
                            "Received invalid JSON from octoprint instance.")
                        json_data = {}

                    if printer.activePrintJob is None:
                        print_job = PrintJobOutputModel(
                            output_controller=self._output_controller)
                        printer.updateActivePrintJob(print_job)
                    else:
                        print_job = printer.activePrintJob

                    print_job_state = "offline"
                    if "state" in json_data:
                        if json_data["state"] == "Error":
                            print_job_state = "error"
                        elif json_data["state"] == "Pausing":
                            print_job_state = "pausing"
                        elif json_data["state"] == "Paused":
                            print_job_state = "paused"
                        elif json_data["state"] == "Printing":
                            print_job_state = "printing"
                        elif json_data["state"] == "Cancelling":
                            print_job_state = "abort"
                        elif json_data["state"] == "Operational":
                            print_job_state = "ready"
                            printer.updateState("idle")
                    print_job.updateState(print_job_state)

                    print_time = json_data["progress"]["printTime"]
                    if print_time:
                        print_job.updateTimeElapsed(print_time)
                        if json_data["progress"][
                                "completion"]:  # not 0 or None or ""
                            print_job.updateTimeTotal(
                                print_time /
                                (json_data["progress"]["completion"] / 100))
                        else:
                            print_job.updateTimeTotal(0)
                    else:
                        print_job.updateTimeElapsed(0)
                        print_job.updateTimeTotal(0)

                    print_job.updateName(json_data["job"]["file"]["name"])
                else:
                    pass  # See generic error handler below

            elif self._api_prefix + "settings" in reply.url().toString(
            ):  # OctoPrint settings dump from /settings:
                if http_status_code == 200:
                    try:
                        json_data = json.loads(
                            bytes(reply.readAll()).decode("utf-8"))
                    except json.decoder.JSONDecodeError:
                        Logger.log(
                            "w",
                            "Received invalid JSON from octoprint instance.")
                        json_data = {}

                    if "feature" in json_data and "sdSupport" in json_data[
                            "feature"]:
                        self._sd_supported = json_data["feature"]["sdSupport"]

                    if "webcam" in json_data and "streamUrl" in json_data[
                            "webcam"]:
                        self._camera_shares_proxy = False
                        stream_url = json_data["webcam"]["streamUrl"]
                        if not stream_url:  #empty string or None
                            self._camera_url = ""
                        elif stream_url[:4].lower() == "http":  # absolute uri
                            self._camera_url = stream_url
                        elif stream_url[:2] == "//":  # protocol-relative
                            self._camera_url = "%s:%s" % (self._protocol,
                                                          stream_url)
                        elif stream_url[:
                                        1] == ":":  # domain-relative (on another port)
                            self._camera_url = "%s://%s%s" % (
                                self._protocol, self._address, stream_url)
                        elif stream_url[:
                                        1] == "/":  # domain-relative (on same port)
                            self._camera_url = "%s://%s:%d%s" % (
                                self._protocol, self._address, self._port,
                                stream_url)
                            self._camera_shares_proxy = True
                        else:
                            Logger.log("w", "Unusable stream url received: %s",
                                       stream_url)
                            self._camera_url = ""

                        Logger.log("d", "Set OctoPrint camera url to %s",
                                   self._camera_url)
                        self.cameraUrlChanged.emit()

                        if "rotate90" in json_data["webcam"]:
                            self._camera_rotation = -90 if json_data["webcam"][
                                "rotate90"] else 0
                            if json_data["webcam"]["flipH"] and json_data[
                                    "webcam"]["flipV"]:
                                self._camera_mirror = False
                                self._camera_rotation += 180
                            elif json_data["webcam"]["flipH"]:
                                self._camera_mirror = True
                                self._camera_rotation += 180
                            elif json_data["webcam"]["flipV"]:
                                self._camera_mirror = True
                            else:
                                self._camera_mirror = False
                            self.cameraOrientationChanged.emit()

                    if "plugins" in json_data:
                        self._plugin_data = json_data["plugins"]

        elif reply.operation() == QNetworkAccessManager.PostOperation:
            if self._api_prefix + "files" in reply.url().toString(
            ):  # Result from /files command:
                if http_status_code == 201:
                    Logger.log(
                        "d", "Resource created on OctoPrint instance: %s",
                        reply.header(
                            QNetworkRequest.LocationHeader).toString())
                else:
                    pass  # See generic error handler below

                reply.uploadProgress.disconnect(self._onUploadProgress)
                if self._progress_message:
                    self._progress_message.hide()

                if self._forced_queue or not self._auto_print:
                    location = reply.header(QNetworkRequest.LocationHeader)
                    if location:
                        file_name = QUrl(
                            reply.header(QNetworkRequest.LocationHeader).
                            toString()).fileName()
                        message = Message(
                            i18n_catalog.i18nc(
                                "@info:status",
                                "Saved to OctoPrint as {0}").format(file_name))
                    else:
                        message = Message(
                            i18n_catalog.i18nc("@info:status",
                                               "Saved to OctoPrint"))
                    message.addAction(
                        "open_browser",
                        i18n_catalog.i18nc("@action:button", "OctoPrint..."),
                        "globe",
                        i18n_catalog.i18nc("@info:tooltip",
                                           "Open the OctoPrint web interface"))
                    message.actionTriggered.connect(self._openOctoPrint)
                    message.show()

            elif self._api_prefix + "job" in reply.url().toString(
            ):  # Result from /job command (eg start/pause):
                if http_status_code == 204:
                    Logger.log("d", "Octoprint job command accepted")
                else:
                    pass  # See generic error handler below

            elif self._api_prefix + "printer/command" in reply.url().toString(
            ):  # Result from /printer/command (gcode statements):
                if http_status_code == 204:
                    Logger.log("d", "Octoprint gcode command(s) accepted")
                else:
                    pass  # See generic error handler below

        else:
            Logger.log("d",
                       "OctoPrintOutputDevice got an unhandled operation %s",
                       reply.operation())

        if not error_handled and http_status_code >= 400:
            # Received an error reply
            error_string = reply.attribute(
                QNetworkRequest.HttpReasonPhraseAttribute)
            if self._error_message:
                self._error_message.hide()
            self._error_message = Message(
                i18n_catalog.i18nc(
                    "@info:status",
                    "OctoPrint returned an error: {0}.").format(error_string))
            self._error_message.show()
            return

    def _onUploadProgress(self, bytes_sent: int, bytes_total: int) -> None:
        if not self._progress_message:
            return

        if bytes_total > 0:
            # Treat upload progress as response. Uploading can take more than 10 seconds, so if we don't, we can get
            # timeout responses if this happens.
            self._last_response_time = time()

            progress = bytes_sent / bytes_total * 100
            previous_progress = self._progress_message.getProgress()
            if progress < 100:
                if previous_progress is not None and progress > previous_progress:
                    self._progress_message.setProgress(progress)
            else:
                self._progress_message.hide()
                self._progress_message = Message(
                    i18n_catalog.i18nc("@info:status",
                                       "Storing data on OctoPrint"), 0, False,
                    -1)
                self._progress_message.show()
        else:
            self._progress_message.setProgress(0)

    def _createPrinterList(self) -> None:
        printer = PrinterOutputModel(
            output_controller=self._output_controller,
            number_of_extruders=self._number_of_extruders)
        printer.updateName(self.name)
        self._printers = [printer]
        self.printersChanged.emit()

    def _openOctoPrint(self,
                       message_id: Optional[str] = None,
                       action_id: Optional[str] = None) -> None:
        QDesktopServices.openUrl(QUrl(self._base_url))
Exemplo n.º 24
0
class MainForm(QWidget):
    def __init__(self):
        super(MainForm, self).__init__()
        self.ui = Ui_mainForm()
        self.ui.setupUi(self)

        self.setWindowTitle("Растительное сообщество")

        self.map = None
        
        self.timer = QTimer(self)
        # Соединение сигнала timeout() со слотом updateTime()
        self.timer.timeout.connect(self.__nextDay)
        self.timer.setInterval(1000)
        # Интервалы с которыми может срабатывать таймер.
        # Первый - самая низкая скорость 
        self._timeIntervals = [2000, 1500, 1000, 750, 500]
        self._time_NORM_INTERVAL = 1000

        # Поле
        self.grid = self.ui.gridLayout_Map

        self.initDialog = InitDialog(self)
        self.initDialog.setWindowTitle("Новая симуляция")

        # Кнопка "начать новую симуляцию"
        self.ui.initButton.clicked.connect(self._showInitDialog)

        # Кнопка "Пауза". Соединение сигнала и слота
        self.ui.pushButton_Pause.clicked.connect(self._timePause)

        # Кнопка "Быстрее". Соединение сигнала и слота
        self.ui.pushButton_Faster.clicked.connect(self._timeFaster)

        # Кнопка "Медленнее". Соединение сигнала и слота
        self.ui.pushButton_Slower.clicked.connect(self._timeSlower)

    def __nextDay(self):

        self.map.oneStep()

        self._redrawMap()

        self._changeInformation()

        if len(self.map.plantsList) == 0 :
            self.timer.stop()
        

    # Реакция на нажатие кнопки Пауза    
    def _timePause(self) :
        if self.timer.isActive() :
            self.timer.stop()
            self.ui.pushButton_Pause.setText("|>")

            self.ui.pushButton_Faster.setText(">")
            self.ui.pushButton_Slower.setText("<")
        else:
            self.timer.setInterval(self._time_NORM_INTERVAL)
            self.timer.start()
            self.ui.pushButton_Pause.setText("||")

    # Реакция на нажатие кнопки Быстрее
    def _timeFaster(self) :
        # Если таймер идет
        if self.timer.isActive() :
            i = self._timeIntervals.index(self.timer.interval())
            # Если скорость можно увеличить, то индекс интервала 
            # будет меньше максимального индекса
            if i < len(self._timeIntervals) - 1 :
                self.timer.setInterval(self._timeIntervals[i + 1])
                # Изменить подписи кнопок перемотки
                str = self.ui.pushButton_Faster.text()
                str += '>'
                self.ui.pushButton_Faster.setText(str)

                self.ui.pushButton_Slower.setText('<')
        else :
            #Если Таймер на паузе, эмитируем нажатие кнопки "Пауза"
            self._timePause()

    # Реакция на нажатие кнопки
    def _timeSlower(self) :
        # Если таймер идет
        if self.timer.isActive() :
            i = self._timeIntervals.index(self.timer.interval())
            # Если скорость можно уменьшить, то индекс интервала 
            # будет больше 0 
            if i  > 0 :
                self.timer.setInterval(self._timeIntervals[i - 1])
                # Изменить подписи кнопок перемотки
                str = self.ui.pushButton_Slower.text()
                str += '<'
                self.ui.pushButton_Slower.setText(str)

                self.ui.pushButton_Faster.setText('>')
        else :
            #Если Таймер на паузе, эмитируем нажатие кнопки "Пауза"
            self._timePause()
        
    def _showInitDialog(self) :
        self.timer.stop()

        # Если пользователь подтвердил, что хочет создать карту
        if self.initDialog.exec() == 1 :

            # Включить кнопки управления скоростью симуляции
            self.ui.pushButton_Slower.setEnabled(True)
            self.ui.pushButton_Pause.setEnabled(True)
            self.ui.pushButton_Faster.setEnabled(True)

            # Считать данные из формы
            nums = self.initDialog.getSpinBoxInf()
            self.xy = self.initDialog.getXY()
            self.partition = self.initDialog.getPartition()
            self.numOfClim = int(self.initDialog.getNumOfClim())

            # Создать интеллектуальную модель карты
            self.map = Map(nums, self.xy, self.partition, self.numOfClim)

            # Отрисовать карту
            self._drawMap()
            self._changeInformation()
        
        if self.map is not None :
            self.timer.start()

    def _redrawMap(self) :
        try :

            for i in range(len(self.map.cells)) :

                for j in range(len(self.map.cells[0])) :
                
                    w = self.grid.itemAtPosition(i, j).widget()
                
                    h = self.map.cells[i][j].getHeighOfHighestPlant()
                    w.checkColor(h)
                
                    msg = self.map.cells[i][j].getNameOfHighestPlant()
                    if h is not None : msg += '\n' + 'Высота: '+ str(h)
                    
                    age = self.map.cells[i][j].getAgeOfHighestPlant()
                    if age is not None : msg += '\n' + 'Процент жизни: '+ str(age)
                
                    w.setToolTip(msg)
        except Exception as e :
            print(e, i, j)



    def _drawMap(self) :
        #Сначала очистить карту, если она уже была создана ранее
        if self.grid.isEmpty() is False :
            for i in range(self.grid.rowCount()) :
                for j in range(self.grid.columnCount()) :
                    w = self.grid.itemAtPosition(i, j).widget()
                    self.grid.removeWidget(w)
                    w.setParent(None)
                    
        self.grid.setSpacing(1)
        self.grid.setSizeConstraint(QLayout.SetFixedSize)

        xSize, ySize = self.xy
        xSize *= self.partition
        ySize *= self.partition

        for i in range(ySize) :
            for j in range(xSize) :
                h = self.map.cells[i][j].getHeighOfHighestPlant()
                w = CellWidget(j, i, self.partition, h)

                self.grid.addWidget(w, i, j)

    def _changeInformation(self) :
        msg = str(len(self.map.plantsList))
        self.ui.label_CountOfPlants.setText(msg)
Exemplo n.º 25
0
class ImageSlider(QWidget):
    def __init__(self):
        super().__init__()

        # setting title
        self.setWindowTitle("Python ")

        self.setGeometry(600, 600, 900, 300)

        self.hbox = QHBoxLayout()

        self.list_widget = QListWidget()
        #        self.list_widget.setGeometry(50, 70, 300, 150)

        #        item1.setIcon(QIcon("key_icon.ico"))
        self.items = []
        #        for i in range(20):
        #                self.items.append(QListWidgetItem(str(i)))
        #                self.list_widget.addItem(self.items[i])
        #                if(i%2 == 0):
        #                    self.items[i].setIcon(QIcon("thumbnail.jpg"))
        #                else:
        #                    self.items[i].setIcon(QIcon("1.jpg"))

        # setting flow
        self.list_widget.setFlow(QListView.LeftToRight)
        self.list_widget.setIconSize(QtCore.QSize(190, 190))
        self.list_widget.hasAutoScroll()

        self.list_widget.setAutoFillBackground(False)

        self.pic = QPushButton()
        self.pic.clicked.connect(lambda: self.goleftSmooth())
        self.pic.setGeometry(10, 10, 50, 50)
        #use full ABSOLUTE path to the image, not relative
        self.pic.setIcon(QIcon("left.png"))

        self.hbox.addWidget(self.pic)

        self.hbox.addWidget(self.list_widget)

        self.pic2 = QPushButton()
        self.pic2.clicked.connect(lambda: self.gorightSmooth())
        self.pic2.setGeometry(10, 10, 50, 50)
        #use full ABSOLUTE path to the image, not relative
        self.pic2.setIcon(QIcon("right.png"))

        self.hbox.addWidget(self.pic2)

        self.list_widget.horizontalScrollBar().setDisabled(True)
        self.list_widget.horizontalScrollBar().hide()

        self.list_widget.verticalScrollBar().setDisabled(True)
        self.list_widget.verticalScrollBar().hide()

        self.list_widget.setHorizontalScrollMode(
            QAbstractItemView.ScrollPerPixel)

        self.setLayout(self.hbox)

        self.atCurrentRight = 16
        self.atCurrentLeft = 0

        self.timerBaseInterval = 25
        self.floorInterval = 5

        self.timer = QTimer(self)
        self.timer.setInterval(self.timerBaseInterval)
        self.timer.timeout.connect(self.goRight)

        self.timer2 = QTimer(self)
        self.timer2.setInterval(self.timerBaseInterval)
        self.timer2.timeout.connect(self.goLeft)

        self.rightCounter = 0
        self.leftCounter = 0

        self.incrementalStep = 2
        self.counterSize = 410

        self.lingertime = 1
        self.lingertimeCounter = 0

        #        self.list_widget.itemPressed.connect(lambda: self.test())

        self.show()

#    def test(self):
#        print(self.list_widget.selectedItems()[0].url)

#Takes list of images and adds them to the image container of this class

    def setImages(self, images, urls, movieIDs, titles):

        self.list_widget.clear()
        self.items = []

        for i in range(len(images)):
            self.items.append(ClickableThumbnail(urls[i], movieIDs[i]))
            self.items[i].setText(titles[i])
            self.list_widget.addItem(self.items[i])

            pm = QPixmap()
            pm.loadFromData(base64.b64decode(images[i]))
            ic = QIcon()
            ic.addPixmap(pm)
            if (ic.isNull() == False):
                self.items[i].setIcon(ic)

    #movies images to the right to slide left by initiating a timer that moves the images smoothly by pixels
    def goleftSmooth(self):
        self.timer2.start()
        self.pic.setDisabled(True)
        self.pic2.setDisabled(True)

    #movies images to the left to slide right by initiating a timer that moves the images smoothly by pixels
    def gorightSmooth(self):
        self.timer.start()
        self.pic.setDisabled(True)
        self.pic2.setDisabled(True)

    #Function that timer2 uses to movie images right
    #It works by modifying the timer interval (time needed until this function is called again)
    #starts by high time interval to low then to high (slow fast slow)
    def goLeft(self):
        if (self.leftCounter != self.counterSize):

            if (self.leftCounter < math.ceil(self.counterSize * 0.4)):
                if (self.lingertime > self.lingertimeCounter):

                    self.lingertimeCounter += 1

                else:
                    if (self.timer2.interval() > self.floorInterval):
                        self.timer2.setInterval(self.timer2.interval() - 1)
                        self.lingertime = self.timerBaseInterval - self.timer2.interval(
                        )
                        self.lingertimeCounter = 5
            elif (self.leftCounter >
                  self.counterSize - math.ceil(self.counterSize * 0.4)):

                if (self.lingertime > self.lingertimeCounter):

                    self.lingertimeCounter += 1

                else:
                    if (self.timer2.interval() < self.timerBaseInterval):
                        self.timer2.setInterval(self.timer2.interval() + 1)
                        self.lingertime = self.timerBaseInterval - self.timer2.interval(
                        )
                        self.lingertimeCounter = 5

            self.list_widget.horizontalScrollBar().setValue(
                self.list_widget.horizontalScrollBar().value() -
                self.incrementalStep)

            self.leftCounter += 1

            self.repaint()

            if (self.leftCounter == math.ceil(self.counterSize / 2)):
                self.lingertimeCounter = 5
                self.lingertime = self.timerBaseInterval

        else:

            self.timer2.setInterval(self.timerBaseInterval)
            self.leftCounter = 0
            self.timer2.stop()
            self.pic.setEnabled(True)
            self.pic2.setEnabled(True)

        if (self.list_widget.horizontalScrollBar().value() == 0):
            self.timer2.setInterval(self.timerBaseInterval)
            self.leftCounter = 0
            self.timer2.stop()
            self.pic.setEnabled(True)
            self.pic2.setEnabled(True)

    #functions similarly to goLeft but adds scrolls to the right by adding pixels to the scrollbar value rather than subtracting
    #TODO: integrate goleft and goright into 1 function, no need for 2
    def goRight(self):

        #        print(self.timer.interval())
        if (self.rightCounter != self.counterSize):

            if (self.rightCounter < math.ceil(self.counterSize * 0.4)):
                if (self.lingertime > self.lingertimeCounter):

                    self.lingertimeCounter += 1

                else:
                    if (self.timer.interval() > self.floorInterval):
                        self.timer.setInterval(self.timer.interval() - 1)
                        self.lingertime = self.timerBaseInterval - self.timer.interval(
                        )
                        self.lingertimeCounter = 5
            elif (self.rightCounter >
                  self.counterSize - math.ceil(self.counterSize * 0.4)):

                if (self.lingertime > self.lingertimeCounter):

                    self.lingertimeCounter += 1

                else:
                    if (self.timer.interval() < self.timerBaseInterval):
                        self.timer.setInterval(self.timer.interval() + 1)
                        self.lingertime = self.timerBaseInterval - self.timer.interval(
                        )
                        self.lingertimeCounter = 5

            self.list_widget.horizontalScrollBar().setValue(
                self.list_widget.horizontalScrollBar().value() +
                self.incrementalStep)

            self.rightCounter += 1

            self.repaint()

            if (self.rightCounter == math.ceil(self.counterSize / 2)):
                self.lingertimeCounter = 5
                self.lingertime = self.timerBaseInterval

        else:

            self.timer.setInterval(self.timerBaseInterval)
            self.rightCounter = 0
            self.timer.stop()
            self.pic.setEnabled(True)
            self.pic2.setEnabled(True)

        if (self.list_widget.horizontalScrollBar().value() ==
                self.list_widget.horizontalScrollBar().maximum()):
            self.timer.setInterval(self.timerBaseInterval)
            self.rightCounter = 0
            self.timer.stop()
            self.pic.setEnabled(True)
            self.pic2.setEnabled(True)
Exemplo n.º 26
0
class GameScene(Scene):
    def __init__(self, parentWindow):
        super().__init__(parentWindow)

    #初始化场景
    #传入从0开始,1为递增量的难度系数
    def init(self, difficulty):
        #获取宽高方向地图线的数目
        self.spaceNum = 20

        #获取宽和高方向的间隔
        self.widthSpace = int(self.width() / self.spaceNum)
        self.heightSpace = int(self.height() / self.spaceNum)

        #设定FPS值
        #平常的按阶梯递增的难度
        if difficulty == 3:
            self.fps = 1000
        #光速难度
        else:
            self.fps = difficulty * 5 + 5

        #初始化蛇
        self.snake = Snake([5, 5], 2, 3)
        #初始化食物
        self.food = Food()
        self.food.bodyGrowth = difficulty + 1
        self.food.updatePos(-1, self.spaceNum, -1, self.spaceNum,
                            self.snake.pos)

        #初始化当前分数
        self.score = 0
        #设置分数增长
        self.scoreGrowth = difficulty + 1

        #设置背景颜色
        self.setAutoFillBackground(True)
        self.setPalette(QPalette(QColor(255, 255, 255)))

        #绘制画面定时器
        self.timer = QTimer(self)
        self.timer.timeout.connect(self.gameCycle)
        self.timer.setInterval(1000 / self.fps)
        self.timer.start()

    #重写按键事件
    def keyPressEvent(self, event):
        if not event.isAutoRepeat():
            if event.key() == Qt.Key_Up and abs(
                    self.snake.pos[0][1] - self.snake.pos[1][1]) != 1:
                self.snake.dir = 0
            elif event.key() == Qt.Key_Down and abs(
                    self.snake.pos[0][1] - self.snake.pos[1][1]) != 1:
                self.snake.dir = 1
            elif event.key() == Qt.Key_Left and abs(
                    self.snake.pos[0][0] - self.snake.pos[1][0]) != 1:
                self.snake.dir = 2
            elif event.key() == Qt.Key_Right and abs(
                    self.snake.pos[0][0] - self.snake.pos[1][0]) != 1:
                self.snake.dir = 3
            elif event.key() == Qt.Key_Space:
                self.timer.setInterval(self.timer.interval() / 3)
        return super().keyPressEvent(event)

    #按键释放的时候的事件
    def keyReleaseEvent(self, event):
        if not event.isAutoRepeat():
            if event.key() == Qt.Key_Space:
                self.timer.setInterval(self.timer.interval() * 3)
        return super().keyReleaseEvent(event)

    #游戏循环
    def gameCycle(self):
        #蛇的移动
        self.snake.move()

        #如果吃到了食物
        if self.snake.eatFood(self.food):
            #刷新分数
            self.score += self.scoreGrowth
            #刷新食物坐标
            self.food.updatePos(-1, self.spaceNum, -1, self.spaceNum,
                                self.snake.pos)

        #判断游戏是否结束
        if self.snake.isDead(0, self.spaceNum - 1, 0, self.spaceNum - 1):
            self.gameEnd()

        #更新界面,触发paintEvent事件重绘界面
        self.update()

    #绘制事件
    def paintEvent(self, event):
        painter = QPainter(self)

        #绘制背景线
        for i in range(self.spaceNum):
            painter.drawLine(i * self.widthSpace, 0, i * self.widthSpace,
                             self.height())
        for i in range(self.spaceNum):
            painter.drawLine(0, i * self.heightSpace, self.width(),
                             i * self.heightSpace)

        #绘制蛇身
        painter.setBrush(QBrush(QColor(0, 0, 255)))
        for i, j in self.snake.pos:
            painter.drawRect(i * self.widthSpace, j * self.heightSpace,
                             self.widthSpace, self.heightSpace)

        #绘制食物
        painter.setBrush(QBrush(QColor(255, 0, 0)))
        painter.drawRect(self.food.x * self.widthSpace,
                         self.food.y * self.heightSpace, self.widthSpace,
                         self.heightSpace)

        #绘制分数
        painter.setPen(Qt.red)
        painter.setFont(QFont("微软雅黑", 13, 5))
        painter.drawText(0, 0, self.width(), 25, Qt.AlignCenter,
                         "分数:%d" % self.score)

        painter.end()
        return super().paintEvent(event)

    #游戏结束
    def gameEnd(self):
        #定时器停止
        self.timer.stop()

        #判断是否是最高分
        #如果是的话,则保存最高分并且录入保持者名字
        if self.score > Config().highestScore:
            Config().highestScore = self.score
            highestScorePlayer = ""
            #必须有纪录保持者
            while highestScorePlayer == "":
                highestScorePlayer = QInputDialog.getText(
                    self, self.tr("最高分!"),
                    self.tr("恭喜你的分数 %d 成为最高分,请留下你的尊姓大名。") % self.score)[0]
            #录入最高分纪录保持者
            Config().highestScorePlayer = highestScorePlayer
        #判断是否要再来一局
        #如果不要的话回到开始场景
        if QMessageBox.information(
                self, self.tr("游戏结束"),
                self.tr("您的得分是:%d,是否要以当前难度再来一局?") % self.score,
                QMessageBox.Yes, QMessageBox.No) == QMessageBox.No:
            startScene = StartScene(Director().window)
            Director().nowScene = startScene
            startScene.init()
            startScene.show()
            self.deleteLater()
        #否则将游戏的各项数据设置复原
        else:
            #重置蛇和食物
            self.snake = Snake([5, 5], 2, 3)
            self.food.updatePos(-1, self.spaceNum, -1, self.spaceNum,
                                self.snake.pos)
            #重置分数
            self.score = 0
            #重置绘制定时器
            self.timer.setInterval(1000 / self.fps)
            self.timer.start()
Exemplo n.º 27
0
class Place(QWidget):
    def __init__(self):
        super().__init__(None, Qt.WindowCloseButtonHint | Qt.WindowMaximizeButtonHint)
        loadUi("gui/Place.ui", self)
        self.services = dict()
        self.queue = dict()
        self.client_count = 0

        self.ticket = Ticket(self.groupBox, self)
        self.ticket.hide()
        self.ticket_anim = QPropertyAnimation(self.ticket, b"pos")
        self.ticket_anim.setEasingCurve(QEasingCurve.OutExpo)
        self.ticket_anim.setDuration(1000)

        self.ticket_time = QTimer(self)
        self.ticket_time.setInterval(1500)
        self.ticket_time.timeout.connect(self.ticket.hide)

        self.emulation = QTimer(self)
        self.emulation.timeout.connect(self.emulate)
        self.emulation_events = None
        self.client_event = RandomEvent(100 - ClientProb, ClientProb)

        self.switch_emul.clicked.connect(self.switch_emulation)
        self.display.mousePressEvent = lambda: None
        self.top_left.setAlignment(Qt.AlignLeft)
        self.delay.setValidator(QIntValidator(0, 99999))
        self.delay.returnPressed.connect(lambda: self.emulation.setInterval(int(self.delay.text())))

    def init(self, services: dict, emulation):
        try:
            if bool(emulation):
                self.init_emulation(len(services), emulation)

            win_index = 0
            for index, service in enumerate(services):
                option = TButton(self, service, index)
                option.setEnabled(services[service] > 0)
                option.clicked.connect(self.select_service)
                self.terminal.addWidget(option, index // 2, index % 2)
                for i in range(services[service]):
                    if not self.services.get(Alpha[index], False):
                        self.services[Alpha[index]] = list()
                    window = Window(self, win_index + 1, index)
                    self.services[Alpha[index]].append(window)
                    self.windows_place.addWidget(window, win_index // 3, win_index % 3)
                    win_index += 1
            self.show()
            self.splitter.setSizes([self.height()//2, self.height()//2])
            self.splitter2.setSizes([self.bottom.height()//2, self.bottom.height()//2])
        except Exception as e:
            print("init", e)

    def init_emulation(self, servicesCount: int, delay):
        global Emulation
        Emulation = True
        self.bottom_area.setEnabled(False)
        self.ticket.setEnabled(False)

        self.switch_emul.setChecked(True)
        self.delay.setText(str(delay))
        self.emulation.setInterval(delay)
        self.emulation.start()
        events = [100 // servicesCount for _ in range(servicesCount)]
        if not (100 / servicesCount).is_integer():
            events.append(100 - sum(events))
        self.emulation_events = RandomEvent(*events, shuffle=True)

    def emulate(self):
        if self.client_event.event() and Emulation:
            index = self.emulation_events.event()
            if index == len(self.services):
                return self.emulate()
            self.select_service(index)

    def switch_emulation(self, turn_on):
        try:
            if self.emulation.interval() == 0:
                self.init_emulation(len(self.services), 1500)
            global Emulation
            Emulation = turn_on
            self.bottom_area.setEnabled(not turn_on)
            self.ticket.setEnabled(not turn_on)
            if turn_on:
                self.emulation.start()
                self.switch_emul.setText("    Pause emulation    ")
            else:
                self.switch_emul.setText("    Continue emulation    ")
                self.emulation.stop()
        except Exception as e:
            print("switch_emulation", e)

    def select_service(self, index):
        try:
            if isinstance(self.sender(), QPushButton):
                service = Alpha[self.sender().index]
            else:
                service = Alpha[index]

            self.client_count += 1
            client = service + str(self.client_count)
            if not self.queue.get(service, False):
                self.queue[service] = Queue()
            self.queue[service].enqueue(client)

            self.print_ticket(client)
            self.check_windows(client)
        except Exception as e:
            print("select_service", e)

    def print_ticket(self, client):
        try:
            if self.groupBox.height() > 0 and self.groupBox.width() > 0:
                y = self.groupBox.height() // 2 - self.ticket.height() // 2 + 10
                x = self.groupBox.width() // 2 - self.ticket.width() // 2

                self.ticket_anim.setStartValue(QPoint(-self.ticket.width(), y))
                self.ticket_anim.setEndValue(QPoint(x, y))
                self.ticket.text.setText("Your number\n" + client)
                self.ticket.show()
                self.ticket_anim.start()
                self.ticket_time.start()
        except Exception as e:
            print("print_ticket", e)

    def check_windows(self, client):
        try:
            for window in self.services[client[0]]:
                if window.client is None:
                    window.makeOrder(client)
                    self.queue[client[0]].dequeue()
                    return
        except Exception as e:
            print("check_windows", e)

    def keyPressEvent(self, event):
        if event.key() == Qt.Key_Escape:
            self.close()
Exemplo n.º 28
0
class MonitorVerb(DisplayVerb):

    """ Base class for Monitor verbs (verbs 11 through 17 inclusive)
    """

    def __init__(self, name, verb_number, noun):

        """ Class constructor
        :param name: name (description) of verb
        :type name: string
        :param verb_number: the verb number
        :type verb_number: str
        :return: None
        """
        
        super().__init__(name, verb_number, noun)
        self.timer = QTimer()
        self.timer.timeout.connect(self._update_display)
        self.is_tooltips_set = False

    def _send_output(self):

        """ Sends the requested output to the DSKY """

        # check if the display update interval needs to be changed
        if self.timer.interval() != config.DISPLAY_UPDATE_INTERVAL:
            # stop and start the timer to change the update interval
            self.timer.stop()
            self.timer.start(config.DISPLAY_UPDATE_INTERVAL)

        if self.noun is None:
            self.noun = Verb.computer.keyboard_state["requested_noun"]
        if self.noun in self.illegal_nouns:
            raise NounNotAcceptableError
        noun_function = Verb.computer.nouns[self.noun]()
        try:
            data = noun_function.return_data()
        except nouns.NounNotImplementedError:
            self.computer.operator_error("Noun {} not implemented yet. Sorry about that...".format(self.noun))
            self.terminate()
            return
        except KSPNotConnected:
            utils.log("KSP not connected, terminating V{}".format(self.number),
                      log_level="ERROR")
            Verb.computer.program_alarm(110)
            self.terminate()
            raise
        except TelemetryNotAvailable:
            utils.log("Telemetry not available, terminating V{}".format(self.number),
                      log_level="ERROR")
            Verb.computer.program_alarm(111)
            self.terminate()
            raise
        if not data:
            # if the noun returns False, the noun *should* have already raised a program alarm, so we just need to
            # terminate and return
            self.terminate()
            return
        output = self._format_output_data(data)
        
        # set tooltips
        if not self.is_tooltips_set:
            Verb.computer.dsky.set_tooltip("data_1", data["tooltips"][0])
            Verb.computer.dsky.set_tooltip("data_2", data["tooltips"][1])
            Verb.computer.dsky.set_tooltip("data_3", data["tooltips"][2])

            self.is_tooltips_set = True

        # display data on DSKY registers
        Verb.computer.dsky.set_register(output[0], "data_1")
        Verb.computer.dsky.set_register(output[1], "data_2")
        Verb.computer.dsky.set_register(output[2], "data_3")

        Verb.computer.dsky.flash_comp_acty()

    def start_monitor(self):

        """ Starts the timer to monitor the verb """

        # if Verb.computer.keyboard_state["backgrounded_update"] is not None:
        #     Verb.computer.keyboard_state["backgrounded_update"].terminate()
        Verb.computer.keyboard_state["display_lock"] = self

        try:
            self._send_output()
        except KSPNotConnected:
            return
        except TelemetryNotAvailable:
            return
        if self.noun is None:   #JRI if monitoring was terminated noun=None and we don't want to start the timer.
            return

        self.timer.start(config.DISPLAY_UPDATE_INTERVAL)

    def _update_display(self):

        """ a simple wrapper to call the display update method """

        # if not self.activity_timer.active():
        #     self.activity_timer.Start(1000)
        self._send_output()

    def terminate(self):

        """ Terminates the verb
        :return: None
        """

        utils.log("Terminating V{}".format(self.number))
        Verb.computer.dsky.stop_annunciator_blink("key_rel")
        Verb.computer.keyboard_state["display_lock"] = None
        Verb.computer.keyboard_state["backgrounded_update"] = None
        self.timer.stop()
        self.noun = None
        # self.activity_timer.Stop()
        # reset tooltips to ""
        Verb.computer.dsky.set_tooltip("data_1", "")
        Verb.computer.dsky.set_tooltip("data_2", "")
        Verb.computer.dsky.set_tooltip("data_3", "")

    def background(self):

        """ Backgrounds verb display updates
        :return: None
        """

        Verb.computer.keyboard_state["backgrounded_update"] = self
        Verb.computer.keyboard_state["display_lock"] = None
        self.timer.stop()
        Verb.computer.dsky.start_annunciator_blink("key_rel")
        

    def resume(self):

        """ Resumes verb display updates
        :return: None
        """

        Verb.computer.keyboard_state["display_lock"] = self
        Verb.computer.keyboard_state["backgrounded_update"] = None
        Verb.computer.dsky.set_register(self.number, "verb")
        Verb.computer.dsky.set_register(self.noun, "noun") 
    
        self.start_monitor()
Exemplo n.º 29
0
class AbcPlayable(ABC):
    def __init__(self):
        super().__init__()
        # self.signals = Signals()
        self.timer = QTimer()
        self.timer.setInterval(50)
        self.signals = self.Signals()

        self._flag_playing = False
        self._flag_cur_index = -1

        self.scheduled = self.Schedule(None, None, None)

        self.timer.timeout.connect(self.flush)

    @property
    @abstractmethod
    def indices(self):
        pass

    @property
    def fps(self):
        return 1000 / self.timer.interval()

    @fps.setter
    def fps(self, fps):
        logger.debug(fps)
        self.timer.setInterval(1000 / fps)

    def is_playing(self):
        return self._flag_playing

    def start(self, clear_schedule=False):
        logger.debug('')
        self._flag_playing = True
        if clear_schedule:
            self.scheduled.clear()
        if not self.timer.isActive():
            self.timer.start()

    def pause(self):
        logger.debug('')
        self._flag_playing = False
        # self.timer.stop()

    @abstractmethod
    def set_viewer(self, view):
        pass

    @abstractmethod
    def flush(self):
        pass

    @abstractmethod
    def to_head(self):
        pass

    @abstractmethod
    def to_tail(self):
        pass

    def schedule(self, jump_to, bias, stop_at, emitter):
        logger.debug(f'{jump_to}, {bias}, {stop_at}, {emitter}, {self._flag_cur_index}')
        last_frame_index = self.indices[-1]
        jump_to = self._flag_cur_index + bias if jump_to == -1 else max(0, min(jump_to, last_frame_index))
        stop_at = None if stop_at == -1 else max(0, min(stop_at, last_frame_index))
        self.scheduled.set(emitter, jump_to, stop_at)

    class Signals(QObject):
        flushed = pyqtSignal(int)

    class Schedule:
        def __init__(self, emitter, jump_to, stop_at):
            self.emitter = emitter
            self.jump_to = jump_to
            self.stop_at = stop_at

        def set(self, emitter, jump_to, stop_at):
            self.emitter = emitter
            self.jump_to = jump_to
            self.stop_at = stop_at
            logger.debug(f'scheduled: {self}')

        def clear(self):
            self.emitter = None
            self.jump_to = None
            self.stop_at = None

        def __str__(self):
            return f'emitter:{self.emitter} jumpTo:{self.jump_to} stopAt:{self.stop_at}'

        def __bool__(self):
            return self.emitter is not None and self.jump_to is not None
Exemplo n.º 30
0
class Main(QMainWindow):
    current_x = 0.
    current_y = 0.
    current_z = 0.

    def __init__(self, *args):
        super(Main, self).__init__(*args)

        loadUi('mainwindow.ui', self)

        self.flavor = config.get('flavor', default='smoothie')
        self['connection.host'] = config.get('server_host', 'localhost')
        self['connection.port'] = str(config.get('server_port', '11011'))
        #self.prompt.setText("M114")
        self.prompt.addItem("M114")
        self.prompt.addItem("MULTILINE")

        #self.conn = QTcpSocket(self)
        self.conn = QHcClient(self)
        self.conn.readyRead.connect(self.readSocket)
        self.conn.error.connect(self.socketError)
        self.conn.connected.connect(self.socketConnect)
        self.conn.disconnected.connect(self.socketDisconnect)

        self.connected = False

        self.actionSave_log.triggered.connect(self.save_log_dialog)

        self.actionSave_probe_data.triggered.connect(
            self.save_probe_data_dialog)
        self.actionLoad_probe_data.triggered.connect(
            self.load_probe_data_dialog)

        self.actionLoad_G_code.triggered.connect(self.load_gcode_dialog)
        self.actionSave_G_code.triggered.connect(self.save_gcode_dialog)

        self.actionSave_probe_G_code.triggered.connect(
            self.save_probe_gcode_dialog)

        self.prompt.setFocus()
        self.prompt.lineEdit().returnPressed.connect(self.on_send_clicked)

        # paramtree handlers
        self.ptree.params.sigTreeStateChanged.connect(self.pchange)
        self.ptree.changing(self.changing)
        self.ptree.params.param('Connection', 'Connect').sigActivated.connect(
            self.do_connect)
        self.ptree.params.param('Probe', 'Run probe').sigActivated.connect(
            self.run_probe)
        self.ptree.params.param('Probe',
                                'Process').sigActivated.connect(self.process)
        self.ptree.params.param('Probe',
                                'Save processed G-code').sigActivated.connect(
                                    self.save_gcode_dialog)
        self.ptree.params.param('GCode', 'Load G-code').sigActivated.connect(
            self.load_gcode_dialog)

        # alias
        self.p = self.ptree.get_param
        self.ptree

        self.do_connect()
        self.update_probe()
        self.update_grid()

        #self.commodel = QStandardItemModel(self.comlist)
        #self.comlist.setModel(self.commodel)
        self.comtree.setColumnCount(3)
        self.comtree.setColumnWidth(0, 200)
        self.comtree.setColumnWidth(1, 350)
        self.comtree.setHeaderLabels(['Time', 'Command', 'Response'])
        #self.comtree.header().setSectionResizeMode(2, QHeaderView.Stretch)
        self.comtree.header().setSectionResizeMode(
            2, QHeaderView.ResizeToContents)
        self.comtree.header().setStretchLastSection(False)

        self.reconnect_timer = QTimer()
        self.reconnect_timer.timeout.connect(self.do_connect)
        #self.otimer = QTimer()
        #self.otimer.timeout.connect(self.gl.autoorbit)
        #self.otimer.start(50)

    def __getitem__(self, attr):
        """
        Access HCParameTree values via self['some.path']
        """

        param = self.ptree.get_param(attr)
        return param.value()

    def __setitem__(self, attr, val):
        """
        Set HCParameTree values via self['some.path']
        """
        param = self.ptree.get_param(attr)
        param.setValue(val)

    def changing(self, param, value):
        print('Changing {} {}'.format(param, value))
        path = self.ptree.params.childPath(param)
        if path is not None:
            pl = '.'.join(path).lower()
            self.handle_updates(pl)

    def pchange(self, param, changes):
        """
        HCParamTreechange handler
        """

        for param, change, data in changes:
            path = self.ptree.params.childPath(param)
            if path is not None:
                pl = '.'.join(path).lower()
                self.handle_updates(pl)

                childName = '.'.join(path)
            else:
                childName = param.name()
            print('  parameter: %s' % childName)
            print('  change:    %s' % change)
            print('  data:      %s' % str(data))
            print('  ----------')

    def handle_updates(self, path=None):
        if path:
            if path.startswith('probe'):
                self.update_probe()

            if path.startswith('grid'):
                self.update_grid()

            if path.startswith('cross'):
                self.update_cross()

            if path.startswith('gcode'):
                self.update_gcode()

            if path.startswith('probe result'):
                self.update_proberesult()

    def load_gcode_dialog(self):
        d = QFileDialog(self)
        d.setNameFilter("GCode (*.ngc *.gcode);;All files (*.*)")
        d.exec_()
        name = d.selectedFiles()[0]
        try:
            self.gl.gcode.load_gcode(name)

            # prefill probe width / height
            xmin, xmax = self.gl.gcode.limits['X']
            ymin, ymax = self.gl.gcode.limits['Y']
            zmin, zmax = self.gl.gcode.limits['Z']
            self['probe.width'] = xmax
            self['probe.height'] = ymax
            self['gcode.width'] = xmax
            self['gcode.height'] = ymax
            self['gcode.min z'] = zmin
            self['gcode.max z'] = zmax

            self.gcode_path = name
            print('Loaded {}'.format(name))
        except IOError as e:
            print('Unable to load {}'.format(name))
            print(e)

    def save_gcode_dialog(self):
        d = QFileDialog(self)
        d.setNameFilter("GCode (*.ngc *.gcode);;All files (*.*)")
        d.exec_()
        name = d.selectedFiles()[0]
        try:
            if self.gl.postgcode.orig:
                self.gl.postgcode.save_gcode(name)
                print('Saved post-processed g-code to {}'.format(name))
            elif self.gl.gcode.orig:
                self.gl.gcode.save_gcode(name)
                print('Saved original g-code to {}'.format(name))
            else:
                print('Nothing to save')

        except IOError as e:
            print('Unable to save to {}'.format(name))
            print(e)

    def append(self, text):
        self.text.append(text)

        #if self.autoscroll.isChecked():
        #    c = self.text.textCursor()
        #    c.movePosition(QTextCursor.End, QTextCursor.MoveAnchor)
        #    self.text.setTextCursor(c)

    def handle_response(self, idx, txt):
        root = self.comtree.invisibleRootItem()
        item = root.child(idx)

        item.setText(0, time.strftime("%Y.%m.%d. %H:%M:%S", time.localtime()))
        if not txt:
            txt = 'ok'
        item.setText(2, txt)
        #item.setBackground(0, QtGui.QBrush(green))
        item.setCheckState(0, Qt.Checked)
        self.comtree.scrollToItem(item)

        cmd = item.text(1)

        if 'G0' in cmd:
            x, y, z = parse.xyz(cmd[2:])
            # should probably emit signals

            self.current_x = x
            self.current_y = y
            self.current_z = z

        if 'G38.2' in cmd:
            try:
                z = parse.probe(txt)
            except:
                log.err('Unable to parse probe: {}'.format(txt))
                log.err('Is your flavor ({}) correct?'.format(self.flavor))
                z = 0.0, 0.0
                return

            self.gl.proberes.probe_results.append(
                (self.current_x, self.current_y, z))
            self.gl.proberes.update_results()
            self['probe result.lowest'] = min(self['probe result.lowest'], z)
            self['probe result.highest'] = max(self['probe result.highest'], z)
            self['probe result.last'] = z

    def save_probe_data_dialog(self):
        if not self.gl.proberes.probe_results:
            # err not much to save
            return

        fname, sel = QFileDialog.getSaveFileName(
            self,
            'Save Log',
        )
        #'/path/to/default/directory', FIXME: lastused
        #selectedFilter='*.txt')

        if fname:
            self.save_probe_data(fname)

    def save_probe_data(self, fname):
        with open(fname, 'w') as f:
            for x, y, z in self.gl.proberes.probe_results:
                f.write("{:04.2f} {:04.2f} {:04.2f}\n".format(x, y, z))

    def load_probe_data_dialog(self):
        d = QFileDialog(self)
        d.setNameFilter("Log data (*.txt *.log);;All files (*.*)")
        d.exec_()
        fname = d.selectedFiles()[0]
        if fname:
            self.load_probe_data(fname)

    def load_probe_data(self, fname):
        with open(fname, 'r') as f:
            d = (map(lambda x: map(float, x.split()), f.readlines()))

        self.gl.proberes.probe_results = d
        self.gl.proberes.update_results()

    @pyqtSlot()
    def on_save_clicked(self):
        root = self.comtree.invisibleRootItem()
        {'name': 'Visible', 'type': 'bool', 'value': 1},
        count = root.childCount()

        parts = []
        for i in range(count):
            item = root.child(i)
            time = item.text(0)
            cmd = item.text(1)
            resp = item.text(2)
            parts.append((time, cmd, resp))

        fname, sel = QFileDialog.getSaveFileName(
            self,
            'Save Log',
        )
        #'/path/to/default/directory', FIXME: lastused
        #selectedFilter='*.txt')

        if fname:
            with open(fname, 'w') as f:
                for time, cmd, resp in parts:
                    f.write('{}\t{}\t{}\n'.format(time, cmd, resp))

    def readSocket(self):
        def handle(r):
            if not r:
                return

            #print('buffered', r)
            (idx, txt) = dec_msg(r)
            if idx is not None:
                self.handle_response(idx, txt)

        buffer = ''

        while True:
            r = str(self.conn.readLine())

            if not r:
                handle(buffer)
                if self.conn.canReadLine():
                    buffer = ''
                    continue

                break

            if r[0] == '/':
                self.append('{}'.format(r.strip()))
                continue

            if r[0] == '[':
                handle(buffer)
                buffer = r
                continue

            buffer += r

    def info(self, errtext):
        self.text.setTextColor(QColor(20, 20, 20))
        self.append(errtext)

    def err(self, errtext):
        self.text.setTextColor(QColor(100, 0, 0))
        self.append(errtext)

    def socketDisconnect(self):
        self.err("Disconnected")
        self.connected = False
        self.info("Reconnecting")
        self.reconnect_timer.start(1000)

    def socketConnect(self):
        self.connected = True
        self.reconnect_timer.stop()
        self.info("Connected to {}:{}".format(self['connection.host'],
                                              self['connection.port']))
        self.ptree.collapse_group('connection')

    def socketError(self, socketError):
        # backoff
        self.reconnect_timer.setInterval(self.reconnect_timer.interval() * 2)

        if socketError == QAbstractSocket.RemoteHostClosedError:
            pass
        elif socketError == QAbstractSocket.HostNotFoundError:
            self.err("The host was not found. Please check the host name and "
                     "port settings.")

        elif socketError == QAbstractSocket.ConnectionRefusedError:
            self.err("The connection was refused by the peer. Make sure the "
                     "server is running, and check that the host name "
                     "and port settings are correct.")
        else:
            self.err("The following error occurred: {0}".format(
                self.conn.errorString()))

    def save_log_dialog(self):
        fname, sel = QFileDialog.getSaveFileName(
            self,
            'Save Log',
        )
        #'/path/to/default/directory', FIXME: lastused
        #selectedFilter='*.txt')

        if fname:
            with open(fname, 'w') as f:
                f.write(self.text.toPlainText())

    def save_probe_gcode_dialog(self):
        fname, sel = QFileDialog.getSaveFileName(
            self,
            'Save probe G-code',
        )
        #'/path/to/default/directory', FIXME: lastused
        #selectedFilter='*.txt')

        if fname:
            with open(fname, 'w') as f:
                for code in self.gen_probe_gcode(self.get_probe_points()):
                    f.write(code + '\n')

    def do_connect(self):
        self.conn.abort()
        self.info("Connecting to {}:{}".format(self['connection.host'],
                                               self['connection.port']))
        self.conn.connectToHost(self['connection.host'],
                                int(self['connection.port']))

    @pyqtSlot()
    def on_prompt_activated(self):
        print("LAA")

    def run_cmd(self, cmd):
        item = QTreeWidgetItem(self.comtree)

        item.setText(0, time.strftime("%Y.%m.%d. %H:%M:%S", time.localtime()))
        item.setText(1, cmd)

        for i in range(3):
            item.setTextAlignment(i, Qt.AlignTop)

        item.setForeground(1, QtGui.QBrush(blue))
        item.setForeground(1, QtGui.QBrush(green))
        item.setForeground(2, QtGui.QBrush(red))

        self.comtree.scrollToItem(item)
        self.conn.cmd(cmd)
        proc_events()

    @pyqtSlot()
    def on_send_clicked(self):
        if not self.connected:
            self.err("Not connected")
            return
        out = self.prompt.currentText()
        self.run_cmd(out)

    # FIXME: should go to hc lib
    def gen_probe_grid(self, rows, cols, w, h, x_margin, y_margin, start_z):
        w = w - x_margin * 2.
        h = h - y_margin * 2.

        if rows <= 0 or cols <= 0:
            return []

        if cols == 1 or rows == 1:
            return []

        xstep = w / (cols - 1)
        ystep = h / (rows - 1)

        cx = x_margin
        cy = y_margin

        out = []

        for i in range(rows):
            for j in range(cols):
                out.append((cx, cy, start_z))
                cx += xstep

            cx = x_margin
            cy += ystep

        return out

    def get_probe_points(self):
        m = self['probe.margin']
        probe_points = self.gen_probe_grid(self['probe.rows'],
                                           self['probe.cols'],
                                           self['probe.width'],
                                           self['probe.height'], m, m,
                                           self['probe.start z'])

        return probe_points

    def gen_probe_gcode(self, points):
        feed = self['probe.feedrate']
        depth = self['probe.max depth']
        sz = self['probe.start z']

        yield 'G90'
        for point in points:
            yield 'G0 {}'.format(xyzfmt(*point))
            yield probecmd.format(depth, feed)
            # go straight up startz/2.
            yield 'G0 Z{}'.format(sz / 2.)

    def run_probe(self):
        # clean probe result
        self.gl.proberes.probe_results = []

        for code in self.gen_probe_gcode(self.get_probe_points()):
            self.run_cmd(code)

    def update_gcode(self):
        self.gl.gcode.setVisible(self['gcode.visible'])

    def update_probe(self):
        probe_points = self.get_probe_points()
        self.gl.probelist.probe_points = probe_points
        self.gl.probelist.update()

    def update_proberesult(self):
        self.gl.proberes.setVisible(self['probe result.visible'])

    def update_grid(self):
        w = self['grid.width']
        h = self['grid.height']
        self.gl.grid.setSize(w, h, 1)
        self.gl.grid.resetTransform()
        self.gl.grid.translate(w / 2., h / 2., -0.05)
        self.gl.grid.setVisible(self['grid.visible'])

    def update_cross(self):
        s = self['cross.size']
        self.gl.cross.setVisible(self['cross.visible'])
        #self.gl.grid.setSize(w, h, 1)
        #self.gl.grid.translate(w/2., h/2., -0.05)

    #@pyqtSlot()
    #def on_normalize_clicked(self):
    #    res = self.gl.proberes.probe_results[:]
    #    print(res)
    #    sres = sorted(res, key=lambda x: x[2])
    #    print(sres)
    #    minz = sres[0][2]
    #    maxz = sres[-1][2]
    #    print("Normalizing, minz {} maxz {}".format(minz, maxz))

    #    nres = []
    #    for x, y, z in res:
    #        nz = z - maxz
    #        nz = abs(nz)
    #        nres.append((x, y, nz))

    #    print(nres)

    #    self.gl.proberes.probe_results = nres
    #    self.gl.proberes.update_results()

    def process(self):
        self.info('Processing')
        self.precision = 0.1

        res = self.gl.proberes.probe_results[:]
        if not res:
            self.err('No probe results')
            return

        datapath = '/tmp/hc_probedata'

        print("RES")
        print(res)
        with open(datapath, 'w') as f:
            for x, y, z in res:
                f.write("{:04.2f} {:04.2f} {:04.2f}\n".format(x, y, z))

        script = config.get('leveling_tool', 'scale_gcode.py')

        gcpath = os.path.abspath(self.gcode_path)
        args = '{} 1-999999 --zlevel {} {:.2f}'.format(gcpath, datapath,
                                                       self.precision)

        args = args.split()
        self.info('Calling "{}"'.format(" ".join([script] + args)))
        proc_events()

        import subprocess
        proc = subprocess.Popen([script] + args,
                                stdout=subprocess.PIPE,
                                stderr=subprocess.PIPE,
                                close_fds=True)

        (stdout, stderr) = proc.communicate()

        print("stderr:")
        print(stderr)
        print("stdout:")
        print(stdout)

        self.info('Done. Saving to /tmp/hc_postgcode')

        fpath = '/tmp/hc_postgcode'
        with open(fpath, 'w') as f:
            f.write(stdout)

        self.gl.postgcode.load_gcode(fpath)
        self.info('Loaded post-processed G-code')