Пример #1
0
class TestTimeoutSignal(UsesQCoreApplication):
    '''Test case to check if the signals are really being caught'''

    def setUp(self):
        #Acquire resources
        UsesQCoreApplication.setUp(self)
        self.watchdog = WatchDog(self)
        self.timer = QTimer()
        self.called = False

    def tearDown(self):
        #Release resources
        del self.watchdog
        del self.timer
        del self.called
        UsesQCoreApplication.tearDown(self)

    def callback(self, *args):
        #Default callback
        self.called = True

    def testTimeoutSignal(self):
        #Test the QTimer timeout() signal
        refCount = sys.getrefcount(self.timer)
        QObject.connect(self.timer, SIGNAL('timeout()'), self.callback)
        self.timer.start(4)
        self.watchdog.startTimer(10)

        self.app.exec_()

        self.assert_(self.called)
        self.assertEqual(sys.getrefcount(self.timer), refCount)
Пример #2
0
class CpuLoadModel(QAbstractListModel):
    def __init__(self):
        QAbstractListModel.__init__(self)
        
        self.__cpu_count = psutil.cpu_count()
        self.__cpu_load = [0] * self.__cpu_count
        
        self.__update_timer = QTimer(self)
        self.__update_timer.setInterval(1000)
        self.__update_timer.timeout.connect(self.__update)
        self.__update_timer.start()
        
        # The first call returns invalid data
        psutil.cpu_percent(percpu=True)
            
    def __update(self):
        self.__cpu_load = psutil.cpu_percent(percpu=True)
        self.dataChanged.emit(self.index(0,0), self.index(self.__cpu_count-1, 0))
        
    def rowCount(self, parent):
        return self.__cpu_count
    
    def data(self, index, role):
        if (role == Qt.DisplayRole and
            index.row() >= 0 and
            index.row() < len(self.__cpu_load) and
            index.column() == 0):
            return self.__cpu_load[index.row()]
        else:
            return None
Пример #3
0
 def testFontInfo(self):
     w = MyWidget()
     w._app = self.app
     w._info = None
     QTimer.singleShot(300, w.show)
     self.app.exec_()
     self.assert_(w._info)
Пример #4
0
    def __init__(self):
        super(Window, self).__init__()

        aliasedLabel = self.createLabel("Aliased")
        antialiasedLabel = self.createLabel("Antialiased")
        intLabel = self.createLabel("Int")
        floatLabel = self.createLabel("Float")

        layout = QGridLayout()
        layout.addWidget(aliasedLabel, 0, 1)
        layout.addWidget(antialiasedLabel, 0, 2)
        layout.addWidget(intLabel, 1, 0)
        layout.addWidget(floatLabel, 2, 0)

        timer = QTimer(self)

        for i in range(2):
            for j in range(2):
                w = CircleWidget()
                w.setAntialiased(j != 0)
                w.setFloatBased(i != 0)

                timer.timeout.connect(w.nextAnimationFrame)

                layout.addWidget(w, i + 1, j + 1)

        timer.start(100)
        self.setLayout(layout)

        self.setWindowTitle("Concentric Circles")
Пример #5
0
 def testPythonSlot(self):
     self._sucess = False
     view = View()
     view.called.connect(self.done)
     view.show()
     QTimer.singleShot(300, QCoreApplication.instance().quit)
     self.app.exec_()
     self.assertTrue(self._sucess)
Пример #6
0
 def testSetPenWithPenStyleEnum(self):
     '''Calls QPainter.setPen with both enum and integer. Bug #511.'''
     w = Painting()
     w.show()
     QTimer.singleShot(1000, self.app.quit)
     self.app.exec_()
     self.assertEqual(w.penFromEnum.style(), Qt.NoPen)
     self.assertEqual(w.penFromInteger.style(), Qt.SolidLine)
Пример #7
0
 def testIt(self):
     app = QGuiApplication([])
     qmlRegisterType(MyClass,'Example',1,0,'MyClass')
     view = QQuickView()
     view.setSource(QUrl.fromLocalFile(adjust_filename('bug_926.qml', __file__)))
     self.assertEqual(len(view.errors()), 0)
     view.show()
     QTimer.singleShot(0, app.quit)
     app.exec_()
Пример #8
0
        def setUp(self, timeout=100):
            '''Setups this Application.

            timeout - timeout in milisseconds'''
            global _timed_instance
            if _timed_instance is None:
                _timed_instance = QApplication([])

            self.app = _timed_instance
            QTimer.singleShot(timeout, self.app.quit)
Пример #9
0
    def testIt(self):
        app = QApplication([])
        self.box = MySpinBox()
        self.box.show()

        QTimer.singleShot(0, self.sendKbdEvent)
        QTimer.singleShot(100, app.quit)
        app.exec_()

        self.assertEqual(self.box.text(), '0')
Пример #10
0
def createNextWebView():
    global functionID

    nListCount = len(FUNCTIONS_LIST) - 1
    functionID = functionID + 1
    print functionID

    if functionID < nListCount:
        createWebView( functionID )
    else:
        QTimer.singleShot(300, QApplication.instance().quit)
Пример #11
0
    def testSignalStarted(self):
        #QThread.started() (signal)
        obj = Dummy()
        QObject.connect(obj, SIGNAL('started()'), self.cb)
        obj.start()

        self._thread = obj
        QTimer.singleShot(1000, self.abort_application)
        self.app.exec_()

        self.assertEqual(obj.qobj.thread(), obj) # test QObject.thread() method
        self.assert_(self.called)
    def test_setParentItem(self):
        global qgraphics_item_painted

        scene = QGraphicsScene()
        scene.addText("test")
        view = QGraphicsView(scene)

        rect = self.createRoundRect(scene)
        view.show()
        QTimer.singleShot(1000, self.quit_app)
        self.app.exec_()
        self.assert_(qgraphics_item_painted)
    def testIt(self):

        self.textEdit = QTextEdit()
        self.textEdit.show()

        interface = Foo()
        self.textEdit.document().documentLayout().registerHandler(QAbstractTextDocumentLayoutTest.objectType, interface)

        QTimer.singleShot(0, self.foo)
        self.app.exec_()

        self.assertTrue(Foo.called)
Пример #14
0
    def generateEvent(self):
        o = QTest.touchEvent(self)
        o.press(0, QPoint(10, 10))
        o.commit()
        del o

        QTest.touchEvent(self).press(0, QPoint(10, 10))
        QTest.touchEvent(self).stationary(0).press(1, QPoint(40, 10))
        QTest.touchEvent(self).move(0, QPoint(12, 12)).move(1, QPoint(45, 5))
        QTest.touchEvent(self).release(0, QPoint(12, 12)).release(1, QPoint(45, 5))

        QTimer.singleShot(200, self.deleteLater)
Пример #15
0
    def testIt(self):
        app = QGuiApplication([])

        qmlRegisterType(PieChart, 'Charts', 1, 0, 'PieChart');
        qmlRegisterType(PieSlice, "Charts", 1, 0, "PieSlice");

        view = QQuickView()
        view.setSource(QUrl.fromLocalFile(helper.adjust_filename('registertype.qml', __file__)))
        view.show()
        QTimer.singleShot(250, view.close)
        app.exec_()
        self.assertTrue(appendCalled)
        self.assertTrue(paintCalled)
Пример #16
0
    def testSignalFinished(self):
        #QThread.finished() (signal)
        obj = Dummy()
        QObject.connect(obj, SIGNAL('finished()'), self.cb)
        mutex.lock()
        obj.start()
        mutex.unlock()

        self._thread = obj
        QTimer.singleShot(1000, self.abort_application)
        self.app.exec_()

        self.assert_(self.called)
Пример #17
0
 def animate(animation: QPropertyAnimation, start: Union[QPointF, int, float], end: Union[QPointF, int, float],
             curve: QEasingCurve=QEasingCurve.Linear, speed: float=1 / 50, delay: int=-1):
     animation.setStartValue(start)
     animation.setEndValue(end)
     animation.setEasingCurve(curve)
     if type(start) == type(end) == QPointF:
         distance = (end - start).manhattanLength()
     else:
         distance = abs(end - start)
     animation.setDuration(round(distance / speed))
     if delay == 0:
         animation.start()
     if delay > 0:
         QTimer.singleShot(delay, animation.start)
Пример #18
0
    def testPlugin(self):
        view = QWebView()
        fac = PluginFactory()
        view.page().setPluginFactory(fac)
        QWebSettings.globalSettings().setAttribute(QWebSettings.PluginsEnabled, True)

        view.load(QUrl(os.path.join(os.path.abspath(os.path.dirname(__file__)), 'qmlplugin', 'index.html')))

        view.resize(840, 600)
        view.show()

        QTimer.singleShot(500, self.app.quit)

        self.app.exec_()
Пример #19
0
    def testBasic(self):
        '''QStateMachine.configuration converting QSet to python set'''
        machine = QStateMachine()
        s1 = QState()
        machine.addState(s1)
        machine.setInitialState(s1)
        machine.start()

        QTimer.singleShot(100, self.app.quit)
        self.app.exec_()

        configuration = machine.configuration()

        self.assert_(isinstance(configuration, set))
        self.assert_(s1 in configuration)
Пример #20
0
    def __init__(self):
        QWidget.__init__(self)
        self.setMinimumSize(800, 600)
        self.donuts = []
        self.chart_view = QtCharts.QChartView()
        self.chart_view.setRenderHint(QPainter.Antialiasing)
        self.chart = self.chart_view.chart()
        self.chart.legend().setVisible(False)
        self.chart.setTitle("Nested donuts demo")
        self.chart.setAnimationOptions(QtCharts.QChart.AllAnimations)

        self.min_size = 0.1
        self.max_size = 0.9
        self.donut_count = 5

        self.setup_donuts()

        # create main layout
        self.main_layout = QGridLayout(self)
        self.main_layout.addWidget(self.chart_view, 1, 1)
        self.setLayout(self.main_layout)

        self.update_timer = QTimer(self)
        self.update_timer.timeout.connect(self.update_rotation)
        self.update_timer.start(1250)
Пример #21
0
 def exposeEvent(self, event):
     if self.isExposed():
         self.render()
         if self.timer is None:
             self.timer = QTimer(self)
             self.timer.timeout.connect(self.slotTimer)
             self.timer.start(10)
Пример #22
0
    def initUI(self):
        self.setWindowTitle(self.tr("Game of Life"))
        self.setLayout(QVBoxLayout())
        self.layout().setSpacing(0)
        self.layout().setContentsMargins(0, 0, 0, 0)

        self.comboBox = QComboBox()
        self.comboBox.addItems([*QGameOfLife.Games.keys()])
        self.comboBox.currentTextChanged.connect(self.select)
        self.layout().addWidget(self.comboBox)

        self.scene = QGraphicsScene()
        self.view = QGraphicsView(self.scene)
        self.view.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.view.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.view.setSizePolicy(QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred))
        self.view.setFrameShape(QFrame.NoFrame)
        self.layout().addWidget(self.view)

        self.item = None
        self.timer = QTimer()
        self.timer.setInterval(10)
        self.timer.timeout.connect(self.tick)
        initialGame = random.choice([*QGameOfLife.Games.keys()])
        self.select(initialGame)
        self.view.fitInView(self.item, Qt.KeepAspectRatioByExpanding)
        self.comboBox.setCurrentText(initialGame)
Пример #23
0
    def testBasic(self):
        self.machine = QStateMachine()
        s1 = QState()
        s2 = QState()
        s3 = QFinalState()

        QObject.connect(self.machine, SIGNAL("started()"), self.cb)

        self.anim = QParallelAnimationGroup()

        self.machine.addState(s1)
        self.machine.addState(s2)
        self.machine.addState(s3)
        self.machine.setInitialState(s1)
        self.machine.addDefaultAnimation(self.anim)
        self.machine.start()

        QTimer.singleShot(100, self.app.quit)
        self.app.exec_()
Пример #24
0
    def testFromData(self):
        picture = QPicture()
        painter = QPainter()
        painter.begin(picture)
        painter.drawEllipse(10, 20, 80, 70)
        painter.end()

        data = picture.data()
        picture2 = QPicture()
        picture2.setData(data)

        self.assertEqual(picture2.data(), picture.data())

        w = MyWidget()
        w._picture = picture2
        w._app = self.app

        QTimer.singleShot(300, w.show)
        self.app.exec_()
Пример #25
0
 def __init__(self):
     QAbstractListModel.__init__(self)
     
     self.__cpu_count = psutil.cpu_count()
     self.__cpu_load = [0] * self.__cpu_count
     
     self.__update_timer = QTimer(self)
     self.__update_timer.setInterval(1000)
     self.__update_timer.timeout.connect(self.__update)
     self.__update_timer.start()
     
     # The first call returns invalid data
     psutil.cpu_percent(percpu=True)
    def testQGraphicsProxyWidget(self):
        scene = QGraphicsScene()

        proxy = QGraphicsProxyWidget(None, Qt.Window)
        widget = QLabel('Widget')
        proxy.setWidget(widget)
        proxy.setCacheMode(QGraphicsItem.DeviceCoordinateCache)
        scene.addItem(proxy)
        scene.setSceneRect(scene.itemsBoundingRect())

        view = QGraphicsView(scene)
        view.setRenderHints(QPainter.Antialiasing|QPainter.SmoothPixmapTransform)
        view.setViewportUpdateMode(QGraphicsView.BoundingRectViewportUpdate)
        view.show()

        timer = QTimer.singleShot(100, self.app.quit)
        self.app.exec_()
Пример #27
0
    def initializeAudio(self):
        self.m_pullTimer = QTimer(self)
        self.m_pullTimer.timeout.connect(self.pullTimerExpired)
        self.m_pullMode = True

        self.m_format = QAudioFormat()
        self.m_format.setSampleRate(self.DataSampleRateHz)
        self.m_format.setChannelCount(1)
        self.m_format.setSampleSize(16)
        self.m_format.setCodec('audio/pcm')
        self.m_format.setByteOrder(QAudioFormat.LittleEndian)
        self.m_format.setSampleType(QAudioFormat.SignedInt)

        info = QAudioDeviceInfo(QAudioDeviceInfo.defaultOutputDevice())
        if not info.isFormatSupported(self.m_format):
            qWarning("Default format not supported - trying to use nearest")
            self.m_format = info.nearestFormat(self.m_format)

        self.m_generator = Generator(self.m_format,
                self.DurationSeconds * 1000000, self.ToneSampleRateHz, self)

        self.createAudioOutput()
Пример #28
0
class Bot(QQuickPaintedItem):
    def __init__(self, parent=None):
        super(Bot, self).__init__(parent)

        self._map = None
        self.image = QImage(300, 300, QImage.Format_RGBA8888)
        self.image.fill('#000000ff')
        self.timer = QTimer()
        self.position = QPoint()
        self.around = None
        self.angle = 0
        self.last_front = False
        self.timer.timeout.connect(lambda: self.drawCircle(self.position))

    def paint(self, painter):
        painter.drawImage(QRect(0, 0, self.width(), self.height()), self.image)

    def setMap(self, map: Map):
        if not map:
            return
        print('Map', map)
        self._map = map
        self._map.clicked.connect(self.handleClick)
        self.image = QImage(self.map.image.width(), self.map.image.height(),
                            QImage.Format_RGBA8888)
        self.image.fill('#000000ff')

    def getMap(self) -> Map:
        return self._map

    map = Property(Map, getMap, setMap)

    @Slot(QPoint)
    def handleClick(self, point: QPoint):
        self.position = point
        self.around = False
        self.drawCircle(point)
        self.timer.start(100)

    def mousePressEvent(self, event):
        a, b = event.pos().x() * self.image.width() / self.width(), event.pos(
        ).y() * self.image.height() / self.height()

    def mouseMoveEvent(self, event):
        a, b = event.pos().x() * self.image.width() / self.width(), event.pos(
        ).y() * self.image.height() / self.height()

    @Slot(QPoint)
    def drawCircle(self, point: QPoint):
        a = point.x()
        b = point.y()
        angle_step_size = 64
        radius = 90
        initPoint = QPoint(-1, -1)
        finalPoint = initPoint
        firstPoint = finalPoint
        self.image.fill('#000000ff')
        painter = QPainter(self.image)
        lidarPoints = []
        painter.setPen('#00ff00')
        painter.drawRect(point.x() - 1, point.y() - 1, 2, 2)
        front = QPoint(math.cos(self.angle), math.sin(self.angle))
        self.image.setPixelColor(point + front, QColor('#0000ff'))

        painter.setPen('#ff0000')
        for step in range(0, angle_step_size - 1):
            angle = 2 * math.pi * step / angle_step_size + self.angle
            initPoint = finalPoint
            for r in range(1, radius):
                x = point.x() + r * math.cos(angle)
                y = point.y() + r * math.sin(angle)
                finalPoint = QPoint(x, y)
                if not self.map.pixel(x, y):
                    break
            if initPoint != QPoint(-1, -1):
                painter.drawLine(initPoint, finalPoint)
            else:
                firstPoint = finalPoint
            lidarPoints.append(finalPoint - point)

        painter.drawLine(finalPoint, firstPoint)
        painter.end()
        self.update()
        self.runObstacleAvoidance(point, lidarPoints)

    def runObstacleAvoidance(self, point, lidarPoints):
        # Calculate distance
        # Target 287, 293
        destiny = QPoint(287, 293)
        ao = destiny - point

        rcoli = 2
        # Get only the front, right, back and left values
        lpoints = [
            lidarPoints[int(i * (len(lidarPoints) + 1) / 4)] for i in range(4)
        ]
        dist = lambda d: (d.x()**2 + d.y()**2)**0.5
        dist2 = lambda d, d2: ((d.x() - d2.x())**2 + (d.y() - d2.y())**2)**0.5
        dists = [dist(p) for p in lpoints]

        # Calculate next point
        nextPoint = point
        if dists[0] < rcoli and not self.around:
            self.around_point = copy.copy(point)
        self.around = dists[0] < rcoli or self.around

        # Bug algorithm
        if not self.around:
            if abs(ao.x()) > abs(ao.y()):
                if ao.x() > 0:
                    nextPoint += QPoint(1, 0)
                    self.angle = 0
                else:
                    nextPoint += QPoint(-1, 0)
                    self.angle = math.pi
            else:
                if ao.y() > 0:
                    nextPoint += QPoint(0, 1)
                    self.angle = math.pi / 2
                else:
                    nextPoint += QPoint(0, -1)
                    self.angle = 3 * math.pi / 2
        else:
            if dist2(self.around_point, point) + dist2(destiny, point) - dist2(
                    destiny, self.around_point) < 3 and dist2(
                        self.around_point, point) > 3:
                self.around = False
            elif dists[3] < rcoli:
                if dists[0] > rcoli:
                    self.position += QPoint(math.cos(self.angle),
                                            math.sin(self.angle))
                else:
                    self.angle += math.pi / 2
            elif dists[0] < rcoli:
                self.position += QPoint(math.cos(self.angle),
                                        math.sin(self.angle))
                self.angle += math.pi / 2
            elif dists[1] < rcoli:
                self.angle += math.pi
            else:
                self.position += QPoint(math.cos(self.angle - math.pi / 2),
                                        math.sin(self.angle - math.pi / 2))
                self.angle -= math.pi / 2
Пример #29
0
class TreeWidgetFilter(QObject):
    change_item = Signal(QModelIndex, bool, int)
    scroll_to_signal = Signal(QModelIndex)

    def __init__(self,
                 ui,
                 widget: QWidget,
                 line_edit: QWidget,
                 columns: Tuple = (0, 1, 2)):
        super(TreeWidgetFilter, self).__init__(widget)
        self.ui = ui
        self.widget = widget
        self.columns = columns
        self.clean = True

        self.filter_timer = QTimer()
        self.filter_timer.setSingleShot(True)
        self.filter_timer.setInterval(1500)
        self.filter_timer.timeout.connect(self.search)

        self.restore_timer = QTimer()
        self.restore_timer.setSingleShot(True)
        self.restore_timer.setInterval(500)
        self.restore_timer.timeout.connect(self.restore)

        self.busy_timer = QTimer()
        self.busy_timer.setSingleShot(True)
        self.busy_timer.setInterval(100)
        self.busy_timer.timeout.connect(self.filtering_finished)

        self.line_edit: QLineEdit = line_edit
        self.line_edit.textChanged.connect(self._line_edit_text_changed)

        self.bgr_animation = BgrAnimation(self.line_edit, (255, 255, 255, 255))

        self.change_item.connect(self.apply_item_change)
        self.scroll_to_signal.connect(self.scroll_to_item)

        self.widget.installEventFilter(self)

    def eventFilter(self, watched: QObject, event: QEvent) -> bool:
        if self.ui.widget_with_focus() is not watched:
            return False

        if not event.type() == QEvent.KeyPress:
            return False

        if event.key() in (Qt.Key_Backspace, Qt.Key_Escape):
            self.line_edit.clear()
            self.restore()
            self.widget.info_overlay.display(f'Clearing filter.', 1500, True)
            return True

        # Send alphanumeric keys to LineEdit filter widget
        filter_keys = [Qt.Key_Space, Qt.Key_Underscore, Qt.Key_Minus]

        if event.text().isalnum() or event.key() in filter_keys:
            filter_txt = self.line_edit.text()
            filter_txt += event.text()

            if filter_txt:
                self.widget.info_overlay.display(f'Filtering: {filter_txt}',
                                                 1500, True)
            self.line_edit.setText(filter_txt)

            return True

        return False

    def start(self):
        if self.line_edit.text():
            self.filter_timer.start()
        else:
            if not self.clean:
                self.restore_timer.start()

    def _line_edit_text_changed(self, txt):
        if self.ui.widget_with_focus() is not self.widget:
            return

        self.start()

    def _prepare_filtering(self):
        LOGGER.debug('Running filter on: %s', self.widget.objectName())

        # Display actual filter
        t = ''
        for word in self.line_edit.text().split(' '):
            t += f'{word} AND '

        if self.line_edit.text():
            self.widget.info_overlay.display(f'Filtering: {t[:-5]}', 4500,
                                             True)
        else:
            self.widget.info_overlay.display(f'Filter Reset', 3000, True)

        self.bgr_animation.blink()
        self.widget.hide()
        self.busy_timer.start()

    def filtering_finished(self):
        LOGGER.debug('Filter operation finished on: %s',
                     self.widget.objectName())
        self.widget.show()

        if self.line_edit.text():
            self.clean = False
        else:
            self.clean = True

    def search(self):
        self._prepare_filtering()

        for item in iterate_widget_items_flat(self.widget):
            txt = ''
            for c in self.columns:
                # Match any column text with OR '|'
                txt += f'{item.text(c)}|'

            if txt:
                txt = txt[:-1]

            # Show everything and collapse parents
            index = self.widget.indexFromItem(item)
            parent_index = self.widget.indexFromItem(item.parent())

            results = list()

            # Match space separated filter strings with AND
            for word in self.line_edit.text().split(' '):
                results.append(True if re.search(
                    word, txt, flags=re.IGNORECASE) else False)

            if not all(results):
                # Hide all non-matching items
                self.change_item.emit(index, True, 0)
                continue

            # Un-hide
            self.change_item.emit(index, False, 0)

            if parent_index:
                # Show and expand parent
                self.change_item.emit(parent_index, False, 1)

            # Scroll to selection
            if item.isSelected():
                self.scroll_to_signal.emit(index)

    def restore(self):
        self._prepare_filtering()

        for item in iterate_widget_items_flat(self.widget):
            # Show everything and collapse parents
            index = self.widget.indexFromItem(item)
            parent_index = self.widget.indexFromItem(item.parent())

            if not parent_index.isValid():
                # Show and collapse top level items
                self.change_item.emit(index, False, 2)
            else:
                # Un-hide all
                self.change_item.emit(index, False, 0)

            # Scroll to selection
            if item.isSelected():
                self.widget.horizontalScrollBar().setSliderPosition(0)
                self.widget.scrollToItem(item)

    def scroll_to_item(self, index: QModelIndex):
        item = self.widget.itemFromIndex(index)
        self.widget.horizontalScrollBar().setSliderPosition(0)
        self.widget.scrollToItem(item)

    def apply_item_change(self, index, hide=False, expand: int = 0):
        """ Receives signal to hide/unhide or expand/collapse items """
        item = self.widget.itemFromIndex(index)

        if not item:
            return

        self.busy_timer.start()

        if hide:
            item.setHidden(True)
        else:
            item.setHidden(False)

        if expand == 1:
            item.setExpanded(True)
        elif expand == 2:
            item.setExpanded(False)
Пример #30
0
class DSRViewer(QObject):
    save_graph_signal = Signal()
    close_window_signal = Signal()
    reset_viewer = Signal(QWidget)

    def __init__(self, window, G, options, main=None):
        super().__init__()
        self.timer = QTimer()
        self.alive_timer = QElapsedTimer()
        self.g = G
        self.window = window
        self.view_menu = QMenu()
        self.file_menu = QMenu()
        self.forces_menu = QMenu()
        self.main_widget = window
        self.docks = {}
        self.widgets = {}
        self.widgets_by_type = {}

        available_geometry = QApplication.desktop().availableGeometry()
        window.move((available_geometry.width() - window.width()) / 2,
                    (available_geometry.height() - window.height()) / 2)
        self.__initialize_file_menu()
        viewMenu = window.menuBar().addMenu(window.tr("&View"))
        forcesMenu = window.menuBar().addMenu(window.tr("&Forces"))
        actionsMenu = window.menuBar().addMenu(window.tr("&Actions"))
        restart_action = actionsMenu.addAction("Restart")

        self.__initialize_views(options, main)
        self.alive_timer.start()
        self.timer.start(500)
        # self.init()  #intialize processor number
        # connect(timer, SIGNAL(timeout()), self, SLOT(compute()))

    def __del__(self):
        settings = QSettings("RoboComp", "DSR")
        settings.beginGroup("MainWindow")
        settings.setValue("size", self.window.size())
        settings.setValue("pos", self.window.pos())
        settings.endGroup()

    def get_widget_by_type(self, widget_type) -> QWidget:
        if widget_type in self.widgets_by_type:
            return self.widgets_by_type[widget_type].widget
        return None

    def get_widget_by_name(self, name) -> QWidget:
        if name in self.widgets:
            return self.widgets[name].widget
        return None

    def add_custom_widget_to_dock(self, name, custom_view):
        widget_c = WidgetContainer()
        widget_c.name = name
        widget_c.type = View.none
        widget_c.widget = custom_view
        self.widgets[name] = widget_c
        self.__create_dock_and_menu(name, custom_view)
        # Tabification of current docks
        previous = None
        for dock_name, dock in self.docks.items():
            if previous and previous != dock:
                self.window.tabifyDockWidget(previous, self.docks[name])
                break
            previous = dock
        self.docks[name].raise_()

    def keyPressEvent(self, event):
        if event.key() == Qt.Key_Escape:
            self.close_window_signal.emit()

    # SLOTS
    def save_graph_slot(self, state):
        self.save_graph_signal.emit()

    def restart_app(self, state):
        pass

    def switch_view(self, state, container):
        widget = container.widget
        dock = container.dock
        if state:
            widget.blockSignals(True)
            dock.hide()
        else:
            widget.blockSignals(False)
            self.reset_viewer.emit(widget)
            dock.show()
            dock.raise_()

    def compute(self):
        pass

    def __create_dock_and_menu(self, name, view):
        # TODO: Check if name exists in docks
        if name in self.docks:
            dock_widget = self.docks[name]
            self.window.removeDockWidget(dock_widget)
        else:
            dock_widget = QDockWidget(name)
            new_action = QAction(name, self)
            new_action.setStatusTip("Create a new file")
            new_action.setCheckable(True)
            new_action.setChecked(True)
            new_action.triggered.connect(
                lambda state: self.switch_view(state, self.widgets[name]))
            self.view_menu.addAction(new_action)
            self.docks[name] = dock_widget
            self.widgets[name].dock = dock_widget
        dock_widget.setWidget(view)
        dock_widget.setAllowedAreas(Qt.AllDockWidgetAreas)
        self.window.addDockWidget(Qt.RightDockWidgetArea, dock_widget)
        dock_widget.raise_()

    def __initialize_views(self, options, central):
        # Create docks view and main widget
        valid_options = [(View.graph, "Graph"), (View.tree, "Tree"),
                         (View.osg, "3D"), (View.scene, "2D")]

        # Creation of docks and mainwidget
        for widget_type, widget_name in valid_options:
            if widget_type == central and central != View.none:
                viewer = self.__create_widget(widget_type)
                self.window.setCentralWidget(viewer)
                widget_c = WidgetContainer()
                widget_c.widget = viewer
                widget_c.name = widget_name
                widget_c.type = widget_type
                self.widgets[widget_name] = widget_c
                self.widgets_by_type[widget_type] = widget_c
                self.main_widget = viewer
            elif options & widget_type:
                viewer = self.__create_widget(widget_type)
                widget_c = WidgetContainer()
                widget_c.widget = viewer
                widget_c.name = widget_name
                widget_c.type = widget_type
                self.widgets[widget_name] = widget_c
                self.widgets_by_type[widget_type] = widget_c
                self.__create_dock_and_menu(widget_name, viewer)
        if View.graph in self.widgets_by_type:
            new_action = QAction("Animation", self)
            new_action.setStatusTip("Toggle animation")
            new_action.setCheckable(True)
            new_action.setChecked(False)
            self.forces_menu.addAction(new_action)
            new_action.triggered.connect(lambda: self.widgets_by_type[
                View.graph].widget.toggle_animation(True))

        # Tabification of current docks
        previous = None
        for dock_name, dock_widget in self.docks.items():
            if previous:
                self.window.tabifyDockWidget(previous, dock_widget)
            previous = dock_widget

        # Connection of tree to graph signals
        if "Tree" in self.docks:
            if self.main_widget:
                graph_widget = self.main_widget
                if graph_widget:
                    tree_widget = self.docks["Tree"].widget()
                    tree_widget.node_check_state_changed_signal.connect(
                        lambda node_id: graph_widget.hide_show_node_SLOT(
                            node_id, 2))
        if len(self.docks) > 0 or central != None:
            self.window.show()
        else:
            self.window.showMinimized()

    def __initialize_file_menu(self):
        file_menu = self.window.menuBar().addMenu(self.window.tr("&File"))
        file_submenu = file_menu.addMenu("Save")
        save_action = QAction("Save", self)
        file_submenu.addAction(save_action)
        rgbd = QAction("RGBD", self)
        rgbd.setCheckable(True)
        rgbd.setChecked(False)
        file_submenu.addAction(rgbd)
        laser = QAction("Laser", self)
        laser.setCheckable(True)
        laser.setChecked(False)
        file_submenu.addAction(laser)
        # save_action
        save_action.triggered.connect(
            lambda: self.__save_json_file(rgbd, laser))

    def __save_json_file(self, rgbd, laser):
        file_name = QFileDialog.getSaveFileName(
            None, "Save file",
            "/home/robocomp/robocomp/components/dsr-graph/etc",
            "JSON Files (*.json)", None,
            QFileDialog.Option.DontUseNativeDialog)
        skip_content = []
        if not rgbd.isChecked():
            skip_content.push_back("rgbd")
        if not laser.isChecked():
            skip_content.push_back("laser")
        self.g.write_to_json_file(file_name.toStdString(), skip_content)
        print("File saved")

    def __create_widget(self, widget_type):
        widget_view = None
        if widget_type == View.graph:
            widget_view = GraphViewer(self.g)
        elif widget_type == View.osg:
            widget_view = OSG3dViewer(self.g, 1, 1)
        elif widget_type == View.tree:
            widget_view = TreeViewer(self.g)
        elif widget_type == View.scene:
            widget_view = QScene2dViewer(self.g)
        elif widget_type == View.none:
            widget_view = None
        # self.reset_viewer.connect(self.reload)
        return widget_view
Пример #31
0
 def recording(self, gesture):
     self.core_controller.write_to_file(self.current_path, gesture)
     self.tips_label.setText('Recording...')
     QTimer.singleShot(RECORDING_DURATION * 1000, self.stop_recording)
Пример #32
0
    def __init__(self, ui):
        """ Dialog to import Datapool items

        :param modules.gui.main_ui.KnechtWindow ui: Main Window
        """
        super(DatapoolDialog, self).__init__(ui)
        SetupWidget.from_ui_file(self, Resource.ui_paths['knecht_datapool'])
        self.setWindowTitle('Datapool Import')

        self._asked_for_close = False
        self._current_project_name = ''
        self.ui = ui

        # Avoid db/thread polling within timeout
        self.action_timeout = QTimer()
        self.action_timeout.setSingleShot(True)
        self.action_timeout.setInterval(300)

        # --- Translations n Style ---
        self.project_icon: QLabel
        self.project_icon.setPixmap(IconRsc.get_pixmap('storage'))
        self.project_title: QLabel
        self.project_title.setText(_('Datapool Projekte'))
        self.image_icon: QLabel
        self.image_icon.setPixmap(IconRsc.get_pixmap('img'))
        self.image_title: QLabel
        self.image_title.setText(_('Bildeinträge'))
        self.details_btn: QPushButton
        self.details_btn.setText(_('Detailspalten anzeigen'))
        self.details_btn.toggled.connect(self.toggle_view_columns)
        self.filter_box: QLineEdit
        self.filter_box.setPlaceholderText(
            _('Im Baum tippen um zu filtern...'))

        # -- Trigger filter update for all views ---
        self.update_filter_timer = QTimer()
        self.update_filter_timer.setInterval(5)
        self.update_filter_timer.setSingleShot(True)
        self.update_filter_timer.timeout.connect(self.update_filter_all_views)

        self.filter_box: QLineEdit
        self.filter_box.textChanged.connect(self.update_filter_timer.start)

        # --- Init Tree Views ---
        self.project_view = KnechtTreeViewCheckable(
            self,
            None,
            filter_widget=self.filter_box,
            replace=self.project_view)
        self.image_view = KnechtTreeViewCheckable(
            self, None, filter_widget=self.filter_box, replace=self.image_view)

        # --- Database Connector ---
        self.dp = DatapoolController(self)
        self.dp.add_projects.connect(self.update_project_view)
        self.dp.add_images.connect(self.update_image_view)
        self.dp.error.connect(self.error)

        # Connection timeout
        self.connection_timeout = QTimer()
        self.connection_timeout.setInterval(self.timeout)
        self.connection_timeout.setSingleShot(True)
        self.connection_timeout.timeout.connect(self.connection_timed_out)

        # Make sure to end thread on App close
        self.ui.is_about_to_quit.connect(self.close)

        # Intercept mouse press events from project view
        self.org_view_mouse_press_event = self.project_view.mousePressEvent
        self.project_view.mousePressEvent = self.view_mouse_press_event

        # Start thread
        QTimer.singleShot(100, self.start_datapool_connection)
Пример #33
0
    def __init__(self,
                 tracker,
                 connectivity_service,
                 priority,
                 obj_id,
                 obj_size,
                 file_path,
                 display_name,
                 file_hash=None,
                 parent=None,
                 files_info=None):
        QObject.__init__(self, parent=parent)
        self._tracker = tracker
        self._connectivity_service = connectivity_service

        self.priority = priority
        self.size = obj_size
        self.id = obj_id
        self.file_path = file_path
        self.file_hash = file_hash
        self.download_path = file_path + '.download'
        self._info_path = file_path + '.info'
        self.display_name = display_name
        self.received = 0
        self.files_info = files_info

        self.hash_is_wrong = False
        self._ready = False
        self._started = False
        self._paused = False
        self._finished = False
        self._no_disk_space_error = False

        self._wanted_chunks = SortedDict()
        self._downloaded_chunks = SortedDict()
        self._nodes_available_chunks = dict()
        self._nodes_requested_chunks = dict()
        self._nodes_last_receive_time = dict()
        self._nodes_downloaded_chunks_count = dict()
        self._nodes_timeouts_count = dict()
        self._total_chunks_count = 0

        self._file = None
        self._info_file = None

        self._started_time = time()

        self._took_from_turn = 0
        self._received_via_turn = 0
        self._received_via_p2p = 0

        self._retry = 0

        self._limiter = None

        self._init_wanted_chunks()

        self._on_downloaded_cb = None
        self._on_failed_cb = None
        self.download_complete.connect(self._on_downloaded)
        self.download_failed.connect(self._on_failed)

        self._timeout_timer = QTimer(self)
        self._timeout_timer.setInterval(15 * 1000)
        self._timeout_timer.setSingleShot(False)
        self._timeout_timer.timeout.connect(self._on_check_timeouts)

        self._leaky_timer = QTimer(self)
        self._leaky_timer.setInterval(1000)
        self._leaky_timer.setSingleShot(True)
        self._leaky_timer.timeout.connect(self._download_chunks)

        self._network_limited_error_set = False
Пример #34
0
 def _single_shot(self, timeout_ms: int, func: Callable[[], Any]):
     timer = QTimer(parent=self.test_qobj)
     timer.setSingleShot(True)
     timer.setInterval(timeout_ms)
     timer.timeout.connect(func)
     timer.start()
Пример #35
0
    def __init__(self):
        super(BoidsApp, self).__init__()
        self.setWindowTitle('Boids')

        self._boids_environment = BoidsEnvironment()

        layout = QHBoxLayout()
        self._boids_widget = BoidsWidget()
        self._boids_widget.set_boids_environment(self._boids_environment)
        self._boids_widget.setMinimumWidth(550)
        self._form_layout = QFormLayout()

        self._is_running_checkbox = QCheckBox()
        self._is_running_checkbox.setChecked(True)
        self._is_running_checkbox.stateChanged.connect(
            self.__is_running_changed)

        self._tick_button = QPushButton()
        self._tick_button.setText('Tick')
        self._tick_button.clicked.connect(self.__do_tick)

        self._total_spinbox = _make_spinbox(TOTAL_BOIDS, 0, 9999)
        self._total_spinbox.valueChanged.connect(self.__total_changed)

        self._max_speed_spinbox = _make_double_spinbox(DEFAULT_MAX_SPEED, 0.0,
                                                       9999.0, 0.1)
        self._max_speed_spinbox.valueChanged.connect(self.__max_speed_changed)

        self._eyesight_radius_spinbox = _make_double_spinbox(
            DEFAULT_MAX_DISTANCE, 0.0, 9999.0, 0.5)
        self._eyesight_radius_spinbox.valueChanged.connect(
            self.__eyesight_radius_changed)

        self._eyesight_angle_spinbox = _make_double_spinbox(
            DEFAULT_EYE_SIGHT_ANGLE, 0.0, 360.0, 1.0)
        self._eyesight_angle_spinbox.valueChanged.connect(
            self.__eyesight_angle_changed)

        self._separation_distance_spinbox = _make_double_spinbox(
            SEPARATION_DISTANCE, 0.0, 9999.0, 1.0)
        self._separation_distance_spinbox.valueChanged.connect(
            self.__separation_distance_changed)

        self._separation_value_spinbox = _make_double_spinbox(
            SEPARATION_VALUE, 0.0, 1.0, 0.1)
        self._separation_value_spinbox.valueChanged.connect(
            self.__separation_value_changed)

        self._cohesion_value_spinbox = _make_double_spinbox(
            COHESION_VALUE, 0.0, 1.0, 0.1)
        self._cohesion_value_spinbox.valueChanged.connect(
            self.__cohesion_value_changed)

        self._alignment_value_spinbox = _make_double_spinbox(
            ALIGNMENT_VALUE, 0.0, 1.0, 0.1)
        self._alignment_value_spinbox.valueChanged.connect(
            self.__alignment_value_changed)

        self._generate_button = QPushButton()
        self._generate_button.setText('Generate')

        self._generate_button.clicked.connect(self._generate_environment)

        self._form_layout.addRow('Total Boids', self._total_spinbox)
        self._form_layout.addRow('Separation', self._separation_value_spinbox)
        self._form_layout.addRow('Cohesion', self._cohesion_value_spinbox)
        self._form_layout.addRow('Alignment', self._alignment_value_spinbox)

        self._form_layout.addRow('Max Speed', self._max_speed_spinbox)
        self._form_layout.addRow('Eyesight Radius',
                                 self._eyesight_radius_spinbox)
        self._form_layout.addRow('Eyesight Angle (degrees)',
                                 self._eyesight_angle_spinbox)
        self._form_layout.addRow('Separation Distance',
                                 self._separation_distance_spinbox)

        self._form_layout.addRow('Running', self._is_running_checkbox)
        self._form_layout.addWidget(self._generate_button)
        self._form_layout.addWidget(self._tick_button)

        layout.addWidget(self._boids_widget)
        layout.addLayout(self._form_layout)
        self.setLayout(layout)

        self.tick_timer = QTimer()
        self.tick_timer.setInterval(int(1000 / 24))
        self.tick_timer.timeout.connect(self._tick)
        self.tick_timer.start()
Пример #36
0
def execute_1():
    """
    after 3 seconds, deactivate and re-activate the app
    after 3 seconds, re-open and re-activate the app
    after 3 more seconds, quit
    :return:
    """
    logger.info("execute_1:begin")
    cfg = Services.getService("Configuration")
    rc = Services.getService("RecordingControl")

    logger.info("app activated")
    app = cfg.configuration().applicationByName("myApp")

    t = QTimer()
    t.setSingleShot(True)
    t.setInterval(3000)
    t.start()
    waitForSignal(t.timeout)

    execute_1.i = MethodInvoker(cfg.deactivate, Qt.QueuedConnection)
    waitForSignal(app.activeApplication.stateChanged,
                  lambda s: s == FilterState.CONSTRUCTED)
    logger.info("app deactivated")

    execute_1.i = MethodInvoker(cfg.activate, Qt.QueuedConnection)
    waitForSignal(cfg.configuration().appActivated)
    if app.activeApplication.getState() != FilterState.ACTIVE:
        waitForSignal(app.activeApplication.stateChanged,
                      lambda s: s == FilterState.ACTIVE)

    execute_1.i = MethodInvoker(rc.startRecording, Qt.QueuedConnection, ".")
    logger.info("app activated")

    t = QTimer()
    t.setSingleShot(True)
    t.setInterval(3000)
    t.start()
    waitForSignal(t.timeout)

    execute_1.i = MethodInvoker(cfg.deactivate, Qt.QueuedConnection)
    waitForSignal(app.activeApplication.stateChanged,
                  lambda s: s == FilterState.CONSTRUCTED)
    logger.info("app deactivated")

    # re-open this application
    execute_1.i = MethodInvoker(cfg.loadConfig, Qt.QueuedConnection,
                                "basicworkflow.json")
    waitForSignal(cfg.configuration().configNameChanged)
    logger.info("config loaded")

    # activate
    execute_1.i = MethodInvoker(cfg.changeActiveApp, Qt.QueuedConnection,
                                "myApp")
    waitForSignal(cfg.configuration().appActivated)
    execute_1.i = MethodInvoker(cfg.activate, Qt.QueuedConnection)
    waitForSignal(cfg.configuration().appActivated)
    if app.activeApplication.getState() != FilterState.ACTIVE:
        waitForSignal(app.activeApplication.stateChanged,
                      lambda s: s == FilterState.ACTIVE)
    logger.info("app activated")

    t = QTimer()
    t.setSingleShot(True)
    t.setInterval(3000)
    t.start()
    waitForSignal(t.timeout)

    execute_1.i = MethodInvoker(QCoreApplication.quit, Qt.QueuedConnection)
    logger.info("execute_1:end")
Пример #37
0
def start(_exit: bool = False) -> None:
    app = QApplication(sys.argv)

    first_start = False
    if not os.path.isfile(STATE_FILE):
        first_start = True

    logo = QIcon(LOGO)
    main_window = MainWindow()
    ui = main_window.ui
    main_window.setWindowIcon(logo)
    tray = QSystemTrayIcon(logo, app)
    tray.activated.connect(main_window.systray_clicked)

    menu = QMenu()
    action_show = QAction("Show")
    action_show.triggered.connect(main_window.show)
    action_exit = QAction("Exit")
    action_exit.triggered.connect(app.exit)
    menu.addAction(action_show)
    menu.addAction(action_exit)

    tray.setContextMenu(menu)

    ui.text.textChanged.connect(partial(queue_text_change, ui))
    ui.command.textChanged.connect(partial(update_button_command, ui))
    ui.keys.textChanged.connect(partial(update_button_keys, ui))
    ui.write.textChanged.connect(partial(update_button_write, ui))
    ui.change_brightness.valueChanged.connect(partial(update_change_brightness, ui))
    ui.switch_page.valueChanged.connect(partial(update_switch_page, ui))
    ui.imageButton.clicked.connect(partial(select_image, main_window))
    ui.brightness.valueChanged.connect(partial(set_brightness, ui))

    items = api.open_decks().items()
    print("wait for device(s)")

    while len(items) == 0:
        time.sleep(3)
        items = api.open_decks().items()

    print("found " + str(len(items)))

    for deck_id, deck in items:
    ui.information.currentIndexChanged.connect(partial(set_information, ui))
    for deck_id, deck in api.open_decks().items():
        ui.device_list.addItem(f"{deck['type']} - {deck_id}", userData=deck_id)

    build_device(ui)
    ui.device_list.currentIndexChanged.connect(partial(build_device, ui))

    ui.pages.currentChanged.connect(partial(change_page, ui))

    ui.actionExport.triggered.connect(partial(export_config, main_window))
    ui.actionImport.triggered.connect(partial(import_config, main_window))
    ui.actionExit.triggered.connect(app.exit)

    timer = QTimer()
    timer.timeout.connect(partial(sync, ui))
    timer.start(1000)

    api.init_http_images()
    api.render()
    tray.show()
    if first_start:
        main_window.show()

    if _exit:
        return
    else:
        sys.exit(app.exec_())


if __name__ == "__main__":
    start()
    def __init__(self,
                 name,
                 skeleton,
                 share_widget,
                 parent=None,
                 enable_line_edit=False,
                 skeleton_model=None):
        self.initialized = False
        QDialog.__init__(self, parent)
        Ui_Dialog.setupUi(self, self)
        self.view = SceneViewerWidget(parent, share_widget, size=(400, 400))
        self.view.setObjectName("left")
        self.view.setMinimumSize(400, 400)
        self.view.initializeGL()
        self.nameLineEdit.setText(name)
        self.nameLineEdit.setEnabled(enable_line_edit)
        self.name = name
        self.view.enable_mouse_interaction = True
        self.view.mouse_click.connect(self.on_mouse_click)
        self.viewerLayout.addWidget(self.view)

        self.radius = 1.0
        self.fps = 60
        self.dt = 1 / 60
        self.timer = QTimer()
        self.timer.timeout.connect(self.draw)
        self.timer.start(0)
        self.timer.setInterval(1000.0 / self.fps)
        self.skeleton = skeleton
        self.view.makeCurrent()
        self.scene = EditorScene(True)
        self.scene.enable_scene_edit_widget = True

        if skeleton_model is not None:
            self.skeleton_model = skeleton_model
        elif skeleton.skeleton_model is not None:
            self.skeleton_model = skeleton.skeleton_model
        else:
            self.skeleton_model = dict()
            print("create new skeleton model")
        if "cos_map" not in self.skeleton_model:
            self.skeleton_model["cos_map"] = dict()
        if "joints" not in self.skeleton_model:
            self.skeleton_model["joints"] = dict()
        if "joint_constraints" not in self.skeleton_model:
            self.skeleton_model["joint_constraints"] = dict()

        motion_vector = MotionVector()
        self.reference_frame = skeleton.reference_frame
        print(self.reference_frame[:3])
        motion_vector.frames = [skeleton.reference_frame]
        motion_vector.n_frames = 1
        o = self.scene.object_builder.create_object("animation_controller",
                                                    "skeleton", skeleton,
                                                    motion_vector,
                                                    skeleton.frame_time)
        self.controller = o._components["animation_controller"]
        self.skeleton = self.controller.get_skeleton()
        self.skeleton_vis = o._components["skeleton_vis"]
        self.init_joints(self.controller)
        self.fill_joint_map()

        self.selectButton.clicked.connect(self.slot_accept)
        self.cancelButton.clicked.connect(self.slot_reject)
        self.applyTwistRotationButton.clicked.connect(self.slot_set_twist)
        self.applySwingRotationButton.clicked.connect(self.slot_set_swing)

        self.setOrthogonalTwistButton.clicked.connect(
            self.slot_set_orthogonal_twist)
        self.setOrthogonalSwingButton.clicked.connect(
            self.slot_set_orthogonal_swing)
        self.rotateTwistButton.clicked.connect(self.slot_rotate_twist)
        self.rotateSwingButton.clicked.connect(self.slot_rotate_swing)

        self.flipTwistButton.clicked.connect(self.slot_flip_twist)
        self.flipSwingButton.clicked.connect(self.slot_flip_swing)

        self.flipZAxisButton.clicked.connect(self.slot_flip_z_axis)
        self.alignToUpAxisButton.clicked.connect(self.slot_align_to_up_axis)
        self.alignToForwardAxisButton.clicked.connect(
            self.slot_align_to_forward_axis)

        self.guessSelectedButton.clicked.connect(
            self.slot_guess_selected_cos_map)
        self.resetSelectedCosButton.clicked.connect(
            self.slot_reset_selected_cos_map)
        self.guessAllCosButton.clicked.connect(self.slot_guess_cos_map)
        self.resetAllCosButton.clicked.connect(self.slot_reset_cos_map)
        self.loadDefaultPoseButton.clicked.connect(self.slot_load_default_pose)
        self.applyScaleButton.clicked.connect(self.slot_apply_scale)
        self.jointMapComboBox.currentIndexChanged.connect(
            self.slot_update_joint_map)
        self.aligningRootComboBox.currentIndexChanged.connect(
            self.slot_update_aligning_root_joint)

        self.mirrorLeftButton.clicked.connect(self.slot_mirror_left_to_right)
        self.mirrorRightButton.clicked.connect(self.slot_mirror_right_to_left)

        self.is_updating_joint_info = False
        self.success = False
        self.initialized = False
        self.skeleton_data = None
        self.precision = 3
        self.aligning_root_node = self.skeleton.aligning_root_node
        self.fill_root_combobox()
        self.init_aligning_root_node()
class SkeletonEditorDialog(QDialog, Ui_Dialog):
    def __init__(self,
                 name,
                 skeleton,
                 share_widget,
                 parent=None,
                 enable_line_edit=False,
                 skeleton_model=None):
        self.initialized = False
        QDialog.__init__(self, parent)
        Ui_Dialog.setupUi(self, self)
        self.view = SceneViewerWidget(parent, share_widget, size=(400, 400))
        self.view.setObjectName("left")
        self.view.setMinimumSize(400, 400)
        self.view.initializeGL()
        self.nameLineEdit.setText(name)
        self.nameLineEdit.setEnabled(enable_line_edit)
        self.name = name
        self.view.enable_mouse_interaction = True
        self.view.mouse_click.connect(self.on_mouse_click)
        self.viewerLayout.addWidget(self.view)

        self.radius = 1.0
        self.fps = 60
        self.dt = 1 / 60
        self.timer = QTimer()
        self.timer.timeout.connect(self.draw)
        self.timer.start(0)
        self.timer.setInterval(1000.0 / self.fps)
        self.skeleton = skeleton
        self.view.makeCurrent()
        self.scene = EditorScene(True)
        self.scene.enable_scene_edit_widget = True

        if skeleton_model is not None:
            self.skeleton_model = skeleton_model
        elif skeleton.skeleton_model is not None:
            self.skeleton_model = skeleton.skeleton_model
        else:
            self.skeleton_model = dict()
            print("create new skeleton model")
        if "cos_map" not in self.skeleton_model:
            self.skeleton_model["cos_map"] = dict()
        if "joints" not in self.skeleton_model:
            self.skeleton_model["joints"] = dict()
        if "joint_constraints" not in self.skeleton_model:
            self.skeleton_model["joint_constraints"] = dict()

        motion_vector = MotionVector()
        self.reference_frame = skeleton.reference_frame
        print(self.reference_frame[:3])
        motion_vector.frames = [skeleton.reference_frame]
        motion_vector.n_frames = 1
        o = self.scene.object_builder.create_object("animation_controller",
                                                    "skeleton", skeleton,
                                                    motion_vector,
                                                    skeleton.frame_time)
        self.controller = o._components["animation_controller"]
        self.skeleton = self.controller.get_skeleton()
        self.skeleton_vis = o._components["skeleton_vis"]
        self.init_joints(self.controller)
        self.fill_joint_map()

        self.selectButton.clicked.connect(self.slot_accept)
        self.cancelButton.clicked.connect(self.slot_reject)
        self.applyTwistRotationButton.clicked.connect(self.slot_set_twist)
        self.applySwingRotationButton.clicked.connect(self.slot_set_swing)

        self.setOrthogonalTwistButton.clicked.connect(
            self.slot_set_orthogonal_twist)
        self.setOrthogonalSwingButton.clicked.connect(
            self.slot_set_orthogonal_swing)
        self.rotateTwistButton.clicked.connect(self.slot_rotate_twist)
        self.rotateSwingButton.clicked.connect(self.slot_rotate_swing)

        self.flipTwistButton.clicked.connect(self.slot_flip_twist)
        self.flipSwingButton.clicked.connect(self.slot_flip_swing)

        self.flipZAxisButton.clicked.connect(self.slot_flip_z_axis)
        self.alignToUpAxisButton.clicked.connect(self.slot_align_to_up_axis)
        self.alignToForwardAxisButton.clicked.connect(
            self.slot_align_to_forward_axis)

        self.guessSelectedButton.clicked.connect(
            self.slot_guess_selected_cos_map)
        self.resetSelectedCosButton.clicked.connect(
            self.slot_reset_selected_cos_map)
        self.guessAllCosButton.clicked.connect(self.slot_guess_cos_map)
        self.resetAllCosButton.clicked.connect(self.slot_reset_cos_map)
        self.loadDefaultPoseButton.clicked.connect(self.slot_load_default_pose)
        self.applyScaleButton.clicked.connect(self.slot_apply_scale)
        self.jointMapComboBox.currentIndexChanged.connect(
            self.slot_update_joint_map)
        self.aligningRootComboBox.currentIndexChanged.connect(
            self.slot_update_aligning_root_joint)

        self.mirrorLeftButton.clicked.connect(self.slot_mirror_left_to_right)
        self.mirrorRightButton.clicked.connect(self.slot_mirror_right_to_left)

        self.is_updating_joint_info = False
        self.success = False
        self.initialized = False
        self.skeleton_data = None
        self.precision = 3
        self.aligning_root_node = self.skeleton.aligning_root_node
        self.fill_root_combobox()
        self.init_aligning_root_node()

    def init_aligning_root_node(self):
        print("init", self.skeleton.root, self.skeleton.aligning_root_node)
        if self.aligning_root_node is None:
            self.aligning_root_node = self.skeleton.root
        if self.aligning_root_node is not None:
            index = self.aligningRootComboBox.findText(self.aligning_root_node,
                                                       Qt.MatchFixedString)
            print("found index", index, self.aligning_root_node)
            if index >= 0:
                self.aligningRootComboBox.setCurrentIndex(index)

    def closeEvent(self, e):
        self.timer.stop()
        self.view.makeCurrent()
        del self.view

    def on_mouse_click(self, event, ray_start, ray_dir, pos, node_id):
        if event.button() == Qt.LeftButton:
            self.scene.select_object(node_id)
            joint_knob = self.get_selected_joint()
            self.update_joint_info(joint_knob)

    def update_joint_info(self, joint_knob):
        self.is_updating_joint_info = True
        self.scene.scene_edit_widget.reset_rotation()
        self.jointMapComboBox.setCurrentIndex(0)
        label = "Selected Joint: "
        if joint_knob is None:
            label += "None"
            self.jointLabel.setText(label)
            self.is_updating_joint_info = False
            return

        label += joint_knob.joint_name
        joint_name = joint_knob.joint_name
        if "joints" in self.skeleton_model:
            key = find_key(self.skeleton_model["joints"], joint_name)
            print("key", joint_name, key)
            if key is not None:
                index = self.jointMapComboBox.findText(key,
                                                       Qt.MatchFixedString)
                if index >= 0:
                    self.jointMapComboBox.setCurrentIndex(index)

        if "cos_map" in self.skeleton_model and joint_name in self.skeleton_model[
                "cos_map"]:
            x_vector = self.skeleton_model["cos_map"][joint_name]["x"]
            if x_vector is None:
                x_vector = [1, 0, 0]
                self.skeleton_model["cos_map"][joint_name]["x"] = x_vector
            y_vector = self.skeleton_model["cos_map"][joint_name]["y"]
            if y_vector is None:
                y_vector = [0, 1, 0]
                self.skeleton_model["cos_map"][joint_name]["y"] = y_vector
            swing = np.round(x_vector, self.precision)
            twist = np.round(y_vector, self.precision)
            self.set_swing_text(swing)
            self.set_twist_text(twist)
            m = self.skeleton.nodes[joint_name].get_global_matrix(
                self.reference_frame)[:3, :3]
            g_swing = np.dot(m, swing)
            g_swing = normalize(g_swing)
            g_twist = np.dot(m, twist)
            g_twist = normalize(g_twist)
            q = axes_to_q(g_twist, g_swing)
            m = quaternion_matrix(q)
            #print("g_twist", g_twist, twist)
            #print("g_swing", g_swing, swing)
            self.scene.scene_edit_widget.rotation = m[:3, :3].T
        else:
            print("no cos map", self.skeleton_model.keys())

        self.jointLabel.setText(label)
        self.is_updating_joint_info = False

    def set_swing_text(self, swing):
        self.swingXLineEdit.setText(str(swing[0]))
        self.swingYLineEdit.setText(str(swing[1]))
        self.swingZLineEdit.setText(str(swing[2]))

    def set_twist_text(self, twist):
        self.twistXLineEdit.setText(str(twist[0]))
        self.twistYLineEdit.setText(str(twist[1]))
        self.twistZLineEdit.setText(str(twist[2]))

    def fill_joint_map(self):
        self.jointMapComboBox.clear()
        for idx, joint in enumerate(STANDARD_JOINTS):
            print("add", joint)
            self.jointMapComboBox.addItem(joint, idx)

    def fill_root_combobox(self):
        self.aligningRootComboBox.clear()
        for idx, joint in enumerate(self.controller.get_animated_joints()):
            self.aligningRootComboBox.addItem(joint, idx)

    def init_joints(self, controller):
        for joint_name in controller.get_animated_joints():
            if len(self.skeleton.nodes[joint_name].children
                   ) > 0:  # filter out end site joints
                node = self.skeleton.nodes[joint_name]
                if joint_name == self.skeleton.root or np.linalg.norm(
                        node.offset) > 0:
                    self.scene.object_builder.create_object(
                        "joint_control_knob", controller, joint_name,
                        self.radius * self.skeleton_vis.box_scale)

    def draw(self):
        """ draw current scene on the given view
        (note before calling this function the context of the view has to be set as current using makeCurrent() and afterwards the doubble buffer has to swapped to display the current frame swapBuffers())
        """
        if not self.initialized:
            if self.view.graphics_context is not None:
                self.view.resize(400, 400)
                self.initialized = True
        self.scene.update(self.dt)
        self.view.makeCurrent()
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
        self.view.graphics_context.render(self.scene)
        self.view.swapBuffers()

    def left_display_changed(self, frame_idx):
        if self.controller is not None:
            self.controller.setCurrentFrameNumber(frame_idx)
            self.leftDisplayFrameSpinBox.setValue(frame_idx)

    def left_spinbox_frame_changed(self, frame):
        self.leftStartFrameSlider.setValue(self.leftStartFrameSpinBox.value())
        self.leftEndFrameSlider.setValue(self.leftEndFrameSpinBox.value())

    def left_slider_frame_changed(self, frame):
        self.leftStartFrameSpinBox.setValue(self.leftStartFrameSlider.value())
        self.leftEndFrameSpinBox.setValue(self.leftEndFrameSlider.value())

    def slot_accept(self):
        self.name = str(self.nameLineEdit.text())
        if self.name != "":
            print("accept")
            self.success = True
            self.skeleton = self.controller.get_skeleton()
            self.skeleton.set_reference_frame(self.reference_frame)
            if self.aligning_root_node is not None:
                self.skeleton.aligning_root_node = self.aligning_root_node
            self.skeleton_data = self.skeleton.to_unity_format()
            if "cos_map" in self.skeleton_model:
                for k in self.skeleton_model["cos_map"]:
                    for l in self.skeleton_model["cos_map"][k]:
                        if type(self.skeleton_model["cos_map"][k]
                                [l]) == np.ndarray:
                            self.skeleton_model["cos_map"][k][
                                l] = self.skeleton_model["cos_map"][k][
                                    l].tolist()
                        else:
                            self.skeleton_model["cos_map"][k][
                                l] = self.skeleton_model["cos_map"][k][l]
            self.close()
        else:
            print("Please provide a name")

    def slot_reject(self):
        self.close()

    def get_selected_joint(self):
        joint_knob = None
        o = self.scene.selected_scene_object
        if o is not None and "joint_control_knob" in o._components:
            joint_knob = o._components["joint_control_knob"]
        return joint_knob

    def set_twist(self, joint_name):
        x = round(float(self.twistXLineEdit.text()), self.precision)
        y = round(float(self.twistYLineEdit.text()), self.precision)
        z = round(float(self.twistZLineEdit.text()), self.precision)
        #set twist axis
        twist = np.array([x, y, z])
        magnitude = np.linalg.norm(twist)
        if magnitude > 0:
            twist /= magnitude
        self.skeleton_model["cos_map"][joint_name]["y"] = twist

    def set_swing(self, joint_name):
        x = round(float(self.swingXLineEdit.text()), self.precision)
        y = round(float(self.swingYLineEdit.text()), self.precision)
        z = round(float(self.swingZLineEdit.text()), self.precision)
        #set swing axis
        swing = np.array([x, y, z])
        magnitude = np.linalg.norm(swing)
        if magnitude > 0:
            swing /= magnitude
        self.skeleton_model["cos_map"][joint_name]["x"] = swing.tolist()

    def slot_set_twist(self):
        plot = False
        joint_knob = self.get_selected_joint()
        if joint_knob is None:
            return
        self.set_swing(joint_knob.joint_name)
        self.set_twist(joint_knob.joint_name)
        self.update_joint_info(joint_knob)

    def slot_set_swing(self):
        joint_knob = self.get_selected_joint()
        if joint_knob is None:
            return
        self.set_swing(joint_knob.joint_name)
        self.set_twist(joint_knob.joint_name)
        self.update_joint_info(joint_knob)

    def slot_set_orthogonal_twist(self):
        """ https://stackoverflow.com/questions/33658620/generating-two-orthogonal-vectors-that-are-orthogonal-to-a-particular-direction """
        joint_knob = self.get_selected_joint()
        if joint_knob is None:
            return
        joint_name = joint_knob.joint_name
        #get swing axis
        swing = np.array(self.skeleton_model["cos_map"][joint_name]["x"])
        # find orthogonal vector
        y = np.array(self.skeleton_model["cos_map"][joint_name]["y"])

        #y = np.random.randn(3)  # take a random vector
        y -= y.dot(swing) * swing  # make it orthogonal to twist
        y /= np.linalg.norm(y)  # normalize it

        #replace twist axis
        self.set_twist_text(y)
        self.skeleton_model["cos_map"][joint_name]["y"] = y
        self.update_joint_info(joint_knob)

    def slot_set_orthogonal_swing(self):
        """ https://stackoverflow.com/questions/33658620/generating-two-orthogonal-vectors-that-are-orthogonal-to-a-particular-direction """
        joint_knob = self.get_selected_joint()
        if joint_knob is None:
            return
        joint_name = joint_knob.joint_name
        #get twist axis
        twist = np.array(self.skeleton_model["cos_map"][joint_name]["y"])
        x = np.array(self.skeleton_model["cos_map"][joint_name]["x"])
        x -= x.dot(twist) * twist  # make it orthogonal to twist
        x /= np.linalg.norm(x)  # normalize it
        #replace twist axis
        self.set_swing_text(x)
        self.skeleton_model["cos_map"][joint_name]["x"] = x
        self.update_joint_info(joint_knob)

    def slot_flip_twist(self):
        joint_knob = self.get_selected_joint()
        if joint_knob is None:
            return
        joint_name = joint_knob.joint_name
        #get twist axis
        twist = np.array(self.skeleton_model["cos_map"][joint_name]["y"])
        twist *= -1
        self.skeleton_model["cos_map"][joint_name]["y"] = twist
        self.set_twist_text(twist)
        self.update_joint_info(joint_knob)

    def slot_flip_swing(self):
        joint_knob = self.get_selected_joint()
        if joint_knob is None:
            return
        joint_name = joint_knob.joint_name
        swing = np.array(self.skeleton_model["cos_map"][joint_name]["x"])
        swing *= -1
        self.skeleton_model["cos_map"][joint_name]["x"] = swing
        self.set_swing_text(swing)
        self.update_joint_info(joint_knob)

    def slot_rotate_twist(self):
        joint_knob = self.get_selected_joint()
        if joint_knob is None:
            return
        joint_name = joint_knob.joint_name
        #get twist axis
        angle = round(float(self.twistRotationLineEdit.text()), self.precision)
        rotation_axis = np.array(
            self.skeleton_model["cos_map"][joint_name]["x"])
        q = quaternion_about_axis(np.deg2rad(angle), rotation_axis)
        q = normalize(q)
        twist = np.array(self.skeleton_model["cos_map"][joint_name]["y"])
        twist = rotate_vector(q, twist)
        twist = normalize(twist)
        self.skeleton_model["cos_map"][joint_name]["y"] = twist
        self.set_twist_text(twist)
        self.update_joint_info(joint_knob)

    def slot_rotate_swing(self):
        joint_knob = self.get_selected_joint()
        if joint_knob is None:
            return
        joint_name = joint_knob.joint_name

        angle = round(float(self.swingRotationLineEdit.text()), self.precision)
        rotation_axis = np.array(
            self.skeleton_model["cos_map"][joint_name]["y"])
        q = quaternion_about_axis(np.deg2rad(angle), rotation_axis)
        q = normalize(q)
        swing = np.array(self.skeleton_model["cos_map"][joint_name]["x"])
        swing = rotate_vector(q, swing)
        swing = normalize(swing)

        self.skeleton_model["cos_map"][joint_name]["x"] = swing
        self.set_swing_text(swing)
        self.update_joint_info(joint_knob)

    def slot_flip_z_axis(self):
        joint_knob = self.get_selected_joint()
        if joint_knob is None:
            return
        joint_name = joint_knob.joint_name
        twist = np.array(self.skeleton_model["cos_map"][joint_name]["y"])
        swing = np.array(self.skeleton_model["cos_map"][joint_name]["x"])
        new_swing = twist
        new_twist = swing
        #print("new swing", new_swing, swing)
        self.skeleton_model["cos_map"][joint_name]["y"] = new_twist
        self.skeleton_model["cos_map"][joint_name]["x"] = new_swing
        self.set_swing_text(new_swing)
        self.set_twist_text(new_twist)
        self.update_joint_info(joint_knob)

    def slot_guess_cos_map(self):
        """ creates a guess for the coordinate system for all joints"""
        temp_skeleton = copy(self.skeleton)
        temp_skeleton.skeleton_model = self.skeleton_model
        cos_map = create_local_cos_map_from_skeleton_axes_with_map(
            temp_skeleton)
        self.skeleton_model["cos_map"] = cos_map
        joint_knob = self.get_selected_joint()
        if joint_knob is not None:
            self.update_joint_info(joint_knob)

    def slot_reset_cos_map(self):
        """ resets the coordinate systems for all joints"""
        for joint_name in self.skeleton_model["cos_map"]:
            up_vector = self.skeleton_model["cos_map"][joint_name]["y"]
            x_vector = self.skeleton_model["cos_map"][joint_name]["x"]
            if up_vector is not None and x_vector is not None:
                new_up_vector, new_x_vector = self.reset_joint_cos(
                    joint_name, up_vector, x_vector)
                self.skeleton_model["cos_map"][joint_name]["y"] = new_up_vector
                self.skeleton_model["cos_map"][joint_name]["x"] = new_x_vector
        joint_knob = self.get_selected_joint()
        if joint_knob is not None:
            self.update_joint_info(joint_knob)

    def slot_guess_selected_cos_map(self):
        """ creates a guess for the for the selected joint"""
        joint_knob = self.get_selected_joint()
        if joint_knob is None:
            return
        joint_name = joint_knob.joint_name
        temp_skeleton = copy(self.skeleton)
        temp_skeleton.skeleton_model = self.skeleton_model
        cos_map = create_local_cos_map_from_skeleton_axes_with_map(
            temp_skeleton)
        self.skeleton_model["cos_map"][joint_name] = cos_map[joint_name]
        self.update_joint_info(joint_knob)

    def slot_reset_selected_cos_map(self):
        """ creates resetrs the coordinate system for the selected joint"""
        joint_knob = self.get_selected_joint()
        if joint_knob is None:
            return
        joint_name = joint_knob.joint_name
        if joint_name in self.skeleton_model["cos_map"]:
            up_vector = self.skeleton_model["cos_map"][joint_name]["y"]
            x_vector = self.skeleton_model["cos_map"][joint_name]["x"]
            new_up_vector, new_x_vector = self.reset_joint_cos(
                joint_name, up_vector, x_vector)
            self.skeleton_model["cos_map"][joint_name]["y"] = new_up_vector
            self.skeleton_model["cos_map"][joint_name]["x"] = new_x_vector
            self.update_joint_info(joint_knob)

    def slot_update_joint_map(self):
        if not self.is_updating_joint_info and "joints" in self.skeleton_model:
            joint_knob = self.get_selected_joint()
            if joint_knob is not None:
                new_joint_key = str(self.jointMapComboBox.currentText())
                old_joint_key = find_key(self.skeleton_model["joints"],
                                         joint_knob.joint_name)
                if old_joint_key in self.skeleton_model["joints"]:
                    self.skeleton_model["joints"][old_joint_key] = None
                self.skeleton_model["joints"][
                    new_joint_key] = joint_knob.joint_name
                print("update joint mapping", joint_knob.joint_name,
                      new_joint_key)
            else:
                print("is updating joint info")

    def reset_joint_cos(self,
                        joint_name,
                        up_vector,
                        x_vector,
                        target_up_vector=DEFAULT_TARGET_CS_UP):
        """ rotates the up_vector to look towards target_up_vector and rotates the x_vector with the same rotation """
        m = self.skeleton.nodes[joint_name].get_global_matrix(
            self.skeleton.reference_frame)[:3, :3]
        m_inv = np.linalg.inv(m)
        target_up_vector = normalize(target_up_vector)
        local_target = np.dot(m_inv, target_up_vector)
        local_target = normalize(local_target)
        q = quaternion_from_vector_to_vector(up_vector, local_target)
        x_vector = rotate_vector(q, x_vector)

        x_vector -= x_vector.dot(
            local_target) * local_target  # make it orthogonal to twist
        x_vector /= np.linalg.norm(x_vector)  # normalize it
        x_vector = normalize(x_vector)
        return local_target, x_vector

    def slot_update_aligning_root_joint(self):
        if not self.is_updating_joint_info and "joints" in self.skeleton_model:
            self.aligning_root_node = str(
                self.aligningRootComboBox.currentText())

    def slot_load_default_pose(self):
        filename = QFileDialog.getOpenFileName(self, 'Load From File', '.')[0]
        filename = str(filename)
        if os.path.isfile(filename):
            motion = load_motion_from_bvh(filename)
            if len(motion.frames):
                self.reference_frame = motion.frames[0]
                frames = [self.reference_frame]
                self.controller.replace_frames(frames)
                self.controller.set_reference_frame(0)
                self.controller.updateTransformation()
                print("replaced frames")

    def slot_apply_scale(self):
        scale = float(self.scaleLineEdit.text())
        if scale > 0:
            self.controller.set_scale(scale)
            frames = [self.reference_frame]
            self.controller.replace_frames(frames)
            self.controller.currentFrameNumber = 0
            self.controller.updateTransformation()

    def slot_align_to_up_axis(self):
        joint_knob = self.get_selected_joint()
        if joint_knob is None:
            return
        joint_name = joint_knob.joint_name
        if joint_name in self.skeleton_model["cos_map"]:
            up_vector = self.skeleton_model["cos_map"][joint_name]["y"]
            x_vector = self.skeleton_model["cos_map"][joint_name]["x"]
            q_offset = get_axis_correction(self.skeleton, joint_name,
                                           up_vector, OPENGL_UP_AXIS)
            up_vector = rotate_vector(q_offset, up_vector)
            x_vector = rotate_vector(q_offset, x_vector)
            self.skeleton_model["cos_map"][joint_name]["x"] = normalize(
                x_vector)
            self.skeleton_model["cos_map"][joint_name]["y"] = normalize(
                up_vector)
            self.update_joint_info(joint_knob)

    def slot_align_to_forward_axis(self):
        joint_knob = self.get_selected_joint()
        if joint_knob is None:
            return
        joint_name = joint_knob.joint_name
        if joint_name in self.skeleton_model["cos_map"]:
            up_vector = self.skeleton_model["cos_map"][joint_name]["y"]

            m = self.skeleton.nodes[joint_name].get_global_matrix(
                self.skeleton.reference_frame)[:3, :3]
            m_inv = np.linalg.inv(m)
            target_vector = np.dot(m, up_vector)
            target_vector[1] = 0
            target_vector = normalize(target_vector)
            local_up = np.dot(m_inv, target_vector)
            local_up = normalize(local_up)
            self.skeleton_model["cos_map"][joint_name]["y"] = local_up

            x_vector = self.skeleton_model["cos_map"][joint_name]["x"]
            q = quaternion_from_vector_to_vector(up_vector, local_up)
            x_vector = rotate_vector(q, x_vector)

            x_vector -= x_vector.dot(
                local_up) * local_up  # make it orthogonal to twist
            x_vector /= np.linalg.norm(x_vector)  # normalize it
            self.skeleton_model["cos_map"][joint_name]["x"] = normalize(
                x_vector)
            self.update_joint_info(joint_knob)

    def slot_mirror_left_to_right(self):
        self.skeleton_model = mirror_join_map(self.skeleton,
                                              self.skeleton_model,
                                              STANDARD_MIRROR_MAP_LEFT)
        print("mirrored left to right")
        print(self.skeleton_model["joints"])

    def slot_mirror_right_to_left(self):
        self.skeleton_model = mirror_join_map(self.skeleton,
                                              self.skeleton_model,
                                              STANDARD_MIRROR_MAP_RIGHT)
        print("mirrored right to left")
Пример #40
0
    def __init__(self):
        super(MainWindow, self).__init__()
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)
        self.popup = QMessageBox()
        self.inputPopup = QInputDialog()
        self.tasks = []
        self.timer = False
        self.study_time = 0
        self.break_time = 0
        self.second_count = 60
        self.status = "Work"
        self.timer_status = "Play"

        #Setting up the Table
        self.ui.tasksTable.setColumnCount(5)
        self.ui.tasksTable.setHorizontalHeaderLabels([
            "Task Name", "Subject", "Estimated Duration", "Actual Time Taken",
            "Completed"
        ])
        self.ui.tasksTable.resizeColumnsToContents()

        if not os.path.exists("tasks.npy"):
            tasks_to_save = np.array(self.tasks)
            np.save("tasks", tasks_to_save)
        else:
            self.tasks = list(np.load("tasks.npy"))
            self.loadTasks()

        study_methods = [
            "Traditional Pomodoro", "Extended Pomodoro", "Animedoro"
        ]
        self.study_intervals = ["25 minutes", "50 minutes", "40-60 minutes"]
        self.break_intervals = [
            "5 minutes", "10 minutes",
            "approx. 20 minutes - one anime (or other with a similar duration) episode"
        ]
        self.cycles = [
            "4, then take 15-30 minutes as a break",
            "2, then take 15-30 minutes as a break",
            "2, then take an extra 10 minutes"
        ]

        self.ui.productivityChoose.addItems(study_methods)

        self.updateMethods()

        self.ui.productivityChoose.currentIndexChanged.connect(
            self.updateMethods)

        self.ui.chooseButton.clicked.connect(self.selectTask)

        self.ui.addTaskButton.clicked.connect(self.addTask)

        self.ui.completeButton.clicked.connect(self.completeTask)

        self.ui.deleteButton.clicked.connect(self.deleteTask)

        self.ui.actionNewDay.triggered.connect(self.clear)

        self.ui.timerButton.clicked.connect(self.startCycle)

        self.ui.endButton.clicked.connect(self.endEarly)

        self.ui.resetButton.clicked.connect(self.newCycle)

        self.ui.playPauseButton.clicked.connect(self.playPause)

        timer = QTimer(self)
        timer.timeout.connect(self.cycleTimer)
        timer.start(1000)  #change back to 1000
Пример #41
0
class DownloadTask(QObject):
    download_ready = Signal(QObject)
    download_not_ready = Signal(QObject)
    download_complete = Signal(QObject)
    download_failed = Signal(QObject)
    download_error = Signal(str)
    download_ok = Signal()

    download_finishing = Signal()
    copy_added = Signal(str)
    chunk_downloaded = Signal(
        str,  # obj_id
        str,  # str(offset) to fix offset >= 2**31
        int)  # length
    chunk_aborted = Signal()
    request_data = Signal(
        str,  # node_id
        str,  # obj_id
        str,  # str(offset) to fix offset >= 2**31
        int)  # length
    abort_data = Signal(
        str,  # node_id
        str,  # obj_id
        str)  # str(offset) to fix offset >= 2**31
    possibly_sync_folder_is_removed = Signal()
    no_disk_space = Signal(
        QObject,  # task
        str,  # display_name
        bool)  # is error
    wrong_hash = Signal(QObject)  # task)
    signal_info_rx = Signal(tuple)

    default_part_size = DOWNLOAD_PART_SIZE
    receive_timeout = 20  # seconds
    retry_limit = 2
    timeouts_limit = 2
    max_node_chunk_requests = 128
    end_race_timeout = 5.  # seconds

    def __init__(self,
                 tracker,
                 connectivity_service,
                 priority,
                 obj_id,
                 obj_size,
                 file_path,
                 display_name,
                 file_hash=None,
                 parent=None,
                 files_info=None):
        QObject.__init__(self, parent=parent)
        self._tracker = tracker
        self._connectivity_service = connectivity_service

        self.priority = priority
        self.size = obj_size
        self.id = obj_id
        self.file_path = file_path
        self.file_hash = file_hash
        self.download_path = file_path + '.download'
        self._info_path = file_path + '.info'
        self.display_name = display_name
        self.received = 0
        self.files_info = files_info

        self.hash_is_wrong = False
        self._ready = False
        self._started = False
        self._paused = False
        self._finished = False
        self._no_disk_space_error = False

        self._wanted_chunks = SortedDict()
        self._downloaded_chunks = SortedDict()
        self._nodes_available_chunks = dict()
        self._nodes_requested_chunks = dict()
        self._nodes_last_receive_time = dict()
        self._nodes_downloaded_chunks_count = dict()
        self._nodes_timeouts_count = dict()
        self._total_chunks_count = 0

        self._file = None
        self._info_file = None

        self._started_time = time()

        self._took_from_turn = 0
        self._received_via_turn = 0
        self._received_via_p2p = 0

        self._retry = 0

        self._limiter = None

        self._init_wanted_chunks()

        self._on_downloaded_cb = None
        self._on_failed_cb = None
        self.download_complete.connect(self._on_downloaded)
        self.download_failed.connect(self._on_failed)

        self._timeout_timer = QTimer(self)
        self._timeout_timer.setInterval(15 * 1000)
        self._timeout_timer.setSingleShot(False)
        self._timeout_timer.timeout.connect(self._on_check_timeouts)

        self._leaky_timer = QTimer(self)
        self._leaky_timer.setInterval(1000)
        self._leaky_timer.setSingleShot(True)
        self._leaky_timer.timeout.connect(self._download_chunks)

        self._network_limited_error_set = False

    def __lt__(self, other):
        if not isinstance(other, DownloadTask):
            return object.__lt__(self, other)

        if self == other:
            return False

        if self.priority == other.priority:
            if self.size - self.received == other.size - other.received:
                return self.id < other.id

            return self.size - self.received < other.size - other.received

        return self.priority > other.priority

    def __le__(self, other):
        if not isinstance(other, DownloadTask):
            return object.__le__(self, other)

        if self == other:
            return True

        if self.priority == other.priority:
            if self.size - self.received == other.size - other.received:
                return self.id < other.id

            return self.size - self.received < other.size - other.received

        return self.priority >= other.priority

    def __gt__(self, other):
        if not isinstance(other, DownloadTask):
            return object.__gt__(self, other)

        if self == other:
            return False

        if self.priority == other.priority:
            if self.size - self.received == other.size - other.received:
                return self.id > other.id

            return self.size - self.received > other.size - other.received

        return self.priority <= other.priority

    def __ge__(self, other):
        if not isinstance(other, DownloadTask):
            return object.__ge__(self, other)

        if self == other:
            return True

        if self.priority == other.priority:
            if self.size - self.received == other.size - other.received:
                return self.id > other.id

            return self.size - self.received > other.size - other.received

        return self.priority <= other.priority

    def __eq__(self, other):
        if not isinstance(other, DownloadTask):
            return object.__eq__(self, other)

        return self.id == other.id

    def on_availability_info_received(self, node_id, obj_id, info):
        if obj_id != self.id or self._finished or not info:
            return

        logger.info(
            "availability info received, "
            "node_id: %s, obj_id: %s, info: %s", node_id, obj_id, info)

        new_chunks_stored = self._store_availability_info(node_id, info)
        if not self._ready and new_chunks_stored:
            if self._check_can_receive(node_id):
                self._ready = True
                self.download_ready.emit(self)
            else:
                self.download_error.emit('Turn limit reached')

        if self._started and not self._paused \
                and not self._nodes_requested_chunks.get(node_id, None):
            logger.debug("Downloading next chunk")
            self._download_next_chunks(node_id)
            self._clean_nodes_last_receive_time()
            self._check_download_not_ready(self._nodes_requested_chunks)

    def on_availability_info_failure(self, node_id, obj_id, error):
        if obj_id != self.id or self._finished:
            return

        logger.info(
            "availability info failure, "
            "node_id: %s, obj_id: %s, error: %s", node_id, obj_id, error)
        try:
            if error["err_code"] == "FILE_CHANGED":
                self.download_failed.emit(self)
        except Exception as e:
            logger.warning("Can't parse error message. Reson: %s", e)

    def start(self, limiter):
        if exists(self.file_path):
            logger.info("download task file already downloaded %s",
                        self.file_path)
            self.received = self.size
            self.download_finishing.emit()
            self.download_complete.emit(self)
            return

        self._limiter = limiter

        if self._started:
            # if we swapped task earlier
            self.resume()
            return

        self._no_disk_space_error = False
        if not self.check_disk_space():
            return

        logger.info("starting download task, obj_id: %s", self.id)
        self._started = True
        self._paused = False
        self.hash_is_wrong = False
        self._started_time = time()
        self._send_start_statistic()
        if not self._open_file():
            return

        self._read_info_file()

        for downloaded_chunk in self._downloaded_chunks.items():
            self._remove_from_chunks(downloaded_chunk[0], downloaded_chunk[1],
                                     self._wanted_chunks)

        self.received = sum(self._downloaded_chunks.values())
        if self._complete_download():
            return

        self._download_chunks()
        if not self._timeout_timer.isActive():
            self._timeout_timer.start()

    def check_disk_space(self):
        if self.size * 2 + get_signature_file_size(self.size) > \
                get_free_space_by_filepath(self.file_path):
            self._emit_no_disk_space()
            return False

        return True

    def pause(self, disconnect_cb=True):
        self._paused = True
        if disconnect_cb:
            self.disconnect_callbacks()
        self.stop_download_chunks()

    def resume(self, start_download=True):
        self._started_time = time()
        self._paused = False
        self.hash_is_wrong = False
        if start_download:
            self._started = True
            self._download_chunks()
            if not self._timeout_timer.isActive():
                self._timeout_timer.start()

    def cancel(self):
        self._close_file()
        self._close_info_file()
        self.stop_download_chunks()

        self._finished = True

    def clean(self):
        logger.debug("Cleaning download files %s", self.download_path)
        try:
            remove_file(self.download_path)
        except:
            pass
        try:
            remove_file(self._info_path)
        except:
            pass

    def connect_callbacks(self, on_downloaded, on_failed):
        self._on_downloaded_cb = on_downloaded
        self._on_failed_cb = on_failed

    def disconnect_callbacks(self):
        self._on_downloaded_cb = None
        self._on_failed_cb = None

    @property
    def ready(self):
        return self._ready

    @property
    def paused(self):
        return self._paused

    @property
    def no_disk_space_error(self):
        return self._no_disk_space_error

    def _init_wanted_chunks(self):
        self._total_chunks_count = math.ceil(
            float(self.size) / float(DOWNLOAD_CHUNK_SIZE))

        self._wanted_chunks[0] = self.size

    def _on_downloaded(self, task):
        if callable(self._on_downloaded_cb):
            self._on_downloaded_cb(task)
            self._on_downloaded_cb = None

    def _on_failed(self, task):
        if callable(self._on_failed_cb):
            self._on_failed_cb(task)
            self._on_failed_cb = None

    def on_data_received(self, node_id, obj_id, offset, length, data):
        if obj_id != self.id or self._finished:
            return

        logger.debug(
            "on_data_received for objId: %s, offset: %s, from node_id: %s",
            self.id, offset, node_id)

        now = time()
        last_received_time = self._nodes_last_receive_time.get(node_id, 0.)
        if node_id in self._nodes_last_receive_time:
            self._nodes_last_receive_time[node_id] = now

        self._nodes_timeouts_count.pop(node_id, 0)

        downloaded_count = \
            self._nodes_downloaded_chunks_count.get(node_id, 0) + 1
        self._nodes_downloaded_chunks_count[node_id] = downloaded_count

        # to collect traffic info
        node_type = self._connectivity_service.get_self_node_type()
        is_share = node_type == "webshare"
        # tuple -> (obj_id, rx_wd, rx_wr, is_share)
        if self._connectivity_service.is_relayed(node_id):
            # relayed traffic
            info_rx = (obj_id, 0, length, is_share)
        else:
            # p2p traffic
            info_rx = (obj_id, length, 0, is_share)
        self.signal_info_rx.emit(info_rx)

        if not self._is_chunk_already_downloaded(offset):
            if not self._on_new_chunk_downloaded(node_id, offset, length,
                                                 data):
                return

        else:
            logger.debug("chunk %s already downloaded", offset)

        requested_chunks = self._nodes_requested_chunks.get(
            node_id, SortedDict())
        if not requested_chunks:
            return

        self._remove_from_chunks(offset, length, requested_chunks)

        if not requested_chunks:
            self._nodes_requested_chunks.pop(node_id, None)

        requested_count = sum(requested_chunks.values()) // DOWNLOAD_CHUNK_SIZE
        if downloaded_count * 4 >= requested_count \
                and requested_count < self.max_node_chunk_requests:
            self._download_next_chunks(node_id, now - last_received_time)
            self._clean_nodes_last_receive_time()
            self._check_download_not_ready(self._nodes_requested_chunks)

    def _is_chunk_already_downloaded(self, offset):
        if self._downloaded_chunks:
            chunk_index = self._downloaded_chunks.bisect_right(offset)
            if chunk_index > 0:
                chunk_index -= 1

                chunk = self._downloaded_chunks.peekitem(chunk_index)
                if offset < chunk[0] + chunk[1]:
                    return True

        return False

    def _on_new_chunk_downloaded(self, node_id, offset, length, data):
        if not self._write_to_file(offset, data):
            return False

        self.received += length
        if self._connectivity_service.is_relayed(node_id):
            self._received_via_turn += length
        else:
            self._received_via_p2p += length

        new_offset = offset
        new_length = length

        left_index = self._downloaded_chunks.bisect_right(new_offset)
        if left_index > 0:
            left_chunk = self._downloaded_chunks.peekitem(left_index - 1)
            if left_chunk[0] + left_chunk[1] == new_offset:
                new_offset = left_chunk[0]
                new_length += left_chunk[1]
                self._downloaded_chunks.popitem(left_index - 1)

        right_index = self._downloaded_chunks.bisect_right(new_offset +
                                                           new_length)
        if right_index > 0:
            right_chunk = self._downloaded_chunks.peekitem(right_index - 1)
            if right_chunk[0] == new_offset + new_length:
                new_length += right_chunk[1]
                self._downloaded_chunks.popitem(right_index - 1)

        self._downloaded_chunks[new_offset] = new_length

        assert self._remove_from_chunks(offset, length, self._wanted_chunks)

        logger.debug("new chunk downloaded from node: %s, wanted size: %s",
                     node_id, sum(self._wanted_chunks.values()))

        part_offset = (offset / DOWNLOAD_PART_SIZE) * DOWNLOAD_PART_SIZE
        part_size = min([DOWNLOAD_PART_SIZE, self.size - part_offset])
        if new_offset <= part_offset \
                and new_offset + new_length >= part_offset + part_size:
            if self._file:
                self._file.flush()
            self._write_info_file()

            self.chunk_downloaded.emit(self.id, str(part_offset), part_size)

        if self._complete_download():
            return False

        return True

    def _remove_from_chunks(self, offset, length, chunks):
        if not chunks:
            return False

        chunk_left_index = chunks.bisect_right(offset)
        if chunk_left_index > 0:
            left_chunk = chunks.peekitem(chunk_left_index - 1)
            if offset >= left_chunk[0] + left_chunk[1] \
                    and len(chunks) > chunk_left_index:
                left_chunk = chunks.peekitem(chunk_left_index)
            else:
                chunk_left_index -= 1
        else:
            left_chunk = chunks.peekitem(chunk_left_index)

        if offset >= left_chunk[0] + left_chunk[1] or \
                offset + length <= left_chunk[0]:
            return False

        chunk_right_index = chunks.bisect_right(offset + length)
        right_chunk = chunks.peekitem(chunk_right_index - 1)

        if chunk_right_index == chunk_left_index:
            to_del = [right_chunk[0]]
        else:
            to_del = list(chunks.islice(chunk_left_index, chunk_right_index))

        for chunk in to_del:
            chunks.pop(chunk)

        if left_chunk[0] < offset:
            if left_chunk[0] + left_chunk[1] >= offset:
                chunks[left_chunk[0]] = offset - left_chunk[0]

        if right_chunk[0] + right_chunk[1] > offset + length:
            chunks[offset + length] = \
                right_chunk[0] + right_chunk[1] - offset - length
        return True

    def on_data_failed(self, node_id, obj_id, offset, error):
        if obj_id != self.id or self._finished:
            return

        logger.info(
            "data request failure, "
            "node_id: %s, obj_id: %s, offset: %s, error: %s", node_id, obj_id,
            offset, error)

        self.on_node_disconnected(node_id)

    def get_downloaded_chunks(self):
        if not self._downloaded_chunks:
            return None

        return self._downloaded_chunks

    def on_node_disconnected(self,
                             node_id,
                             connection_alive=False,
                             timeout_limit_exceed=True):
        requested_chunks = self._nodes_requested_chunks.pop(node_id, None)
        logger.info("node disconnected %s, chunks removed from requested: %s",
                    node_id, requested_chunks)
        if timeout_limit_exceed:
            self._nodes_available_chunks.pop(node_id, None)
            self._nodes_timeouts_count.pop(node_id, None)
            if connection_alive:
                self._connectivity_service.reconnect(node_id)
        self._nodes_last_receive_time.pop(node_id, None)
        self._nodes_downloaded_chunks_count.pop(node_id, None)

        if connection_alive:
            self.abort_data.emit(node_id, self.id, None)

        if self._nodes_available_chunks:
            self._download_chunks(check_node_busy=True)
        else:
            chunks_to_test = self._nodes_requested_chunks \
                if self._started and not self._paused \
                else self._nodes_available_chunks
            self._check_download_not_ready(chunks_to_test)

    def complete(self):
        if self._started and not self._finished:
            self._complete_download(force_complete=True)
        elif not self._finished:
            self._finished = True
            self.clean()
            self.download_complete.emit(self)

    def _download_chunks(self, check_node_busy=False):
        if not self._started or self._paused or self._finished:
            return

        logger.debug("download_chunks for %s", self.id)

        node_ids = list(self._nodes_available_chunks.keys())
        random.shuffle(node_ids)
        for node_id in node_ids:
            node_free = not check_node_busy or \
                        not self._nodes_requested_chunks.get(node_id, None)
            if node_free:
                self._download_next_chunks(node_id)
        self._clean_nodes_last_receive_time()
        self._check_download_not_ready(self._nodes_requested_chunks)

    def _check_can_receive(self, node_id):
        return True

    def _write_to_file(self, offset, data):
        self._file.seek(offset)
        try:
            self._file.write(data)
        except EnvironmentError as e:
            logger.error("Download task %s can't write to file. Reason: %s",
                         self.id, e)
            self._send_error_statistic()
            if e.errno == errno.ENOSPC:
                self._emit_no_disk_space(error=True)
            else:
                self.download_failed.emit(self)
                self.possibly_sync_folder_is_removed.emit()
            return False

        return True

    def _open_file(self, clean=False):
        if not self._file or self._file.closed:
            try:
                if clean:
                    self._file = open(self.download_path, 'wb')
                else:
                    self._file = open(self.download_path, 'r+b')
            except IOError:
                try:
                    self._file = open(self.download_path, 'wb')
                except IOError as e:
                    logger.error(
                        "Can't open file for download for task %s. "
                        "Reason: %s", self.id, e)
                    self.download_failed.emit(self)
                    return False

        return True

    def _close_file(self):
        if not self._file:
            return True

        try:
            self._file.close()
        except EnvironmentError as e:
            logger.error("Download task %s can't close file. Reason: %s",
                         self.id, e)
            self._send_error_statistic()
            if e.errno == errno.ENOSPC:
                self._emit_no_disk_space(error=True)
            else:
                self.download_failed.emit(self)
                self.possibly_sync_folder_is_removed.emit()
            self._file = None
            return False

        self._file = None
        return True

    def _write_info_file(self):
        try:
            self._info_file.seek(0)
            self._info_file.truncate()
            pickle.dump(self._downloaded_chunks, self._info_file,
                        pickle.HIGHEST_PROTOCOL)
            self._info_file.flush()
        except EnvironmentError as e:
            logger.debug("Can't write to info file for task id %s. Reason: %s",
                         self.id, e)

    def _read_info_file(self):
        try:
            if not self._info_file or self._info_file.closed:
                self._info_file = open(self._info_path, 'a+b')
                self._info_file.seek(0)
            try:
                self._downloaded_chunks = pickle.load(self._info_file)
            except:
                pass
        except EnvironmentError as e:
            logger.debug("Can't open info file for task id %s. Reason: %s",
                         self.id, e)

    def _close_info_file(self, to_remove=False):
        if not self._info_file:
            return

        try:
            self._info_file.close()
            if to_remove:
                remove_file(self._info_path)
        except Exception as e:
            logger.debug(
                "Can't close or remove info file "
                "for task id %s. Reason: %s", self.id, e)
        self._info_file = None

    def _complete_download(self, force_complete=False):
        if (not self._wanted_chunks or force_complete) and \
                not self._finished:
            logger.debug("download %s completed", self.id)
            self._nodes_requested_chunks.clear()
            for node_id in self._nodes_last_receive_time.keys():
                self.abort_data.emit(node_id, self.id, None)

            if not force_complete:
                self.download_finishing.emit()

            if not force_complete and self.file_hash:
                hash_check_result = self._check_file_hash()
                if hash_check_result is not None:
                    return hash_check_result

            self._started = False
            self._finished = True
            self.stop_download_chunks()
            self._close_info_file(to_remove=True)
            if not self._close_file():
                return False

            try:
                if force_complete:
                    remove_file(self.download_path)
                    self.download_complete.emit(self)
                else:
                    shutil.move(self.download_path, self.file_path)
                    self._send_end_statistic()
                    self.download_complete.emit(self)
                    if self.file_hash:
                        self.copy_added.emit(self.file_hash)
            except EnvironmentError as e:
                logger.error(
                    "Download task %s can't (re)move file. "
                    "Reason: %s", self.id, e)
                self._send_error_statistic()
                self.download_failed.emit(self)
                self.possibly_sync_folder_is_removed.emit()
                return False

            result = True
        else:
            result = not self._wanted_chunks
        return result

    def _check_file_hash(self):
        self._file.flush()
        try:
            hash = Rsync.hash_from_block_checksum(
                Rsync.block_checksum(self.download_path))
        except IOError as e:
            logger.error("download %s error: %s", self.id, e)
            hash = None
        if hash != self.file_hash:
            logger.error(
                "download hash check failed objId: %s, "
                "expected hash: %s, actual hash: %s", self.id, self.file_hash,
                hash)
            if not self._close_file() or not self._open_file(clean=True):
                return False

            self._downloaded_chunks.clear()
            self._nodes_downloaded_chunks_count.clear()
            self._nodes_last_receive_time.clear()
            self._nodes_timeouts_count.clear()
            self._write_info_file()
            self._init_wanted_chunks()

            self.received = 0
            if self._retry < self.retry_limit:
                self._retry += 1
                self.resume()
            else:
                self._retry = 0
                self._nodes_available_chunks.clear()
                self.hash_is_wrong = True
                self.wrong_hash.emit(self)
            return True

        return None

    def _download_next_chunks(self, node_id, time_from_last_received_chunk=0.):
        if (self._paused or not self._started or not self._ready
                or self._finished or not self._wanted_chunks
                or self._leaky_timer.isActive()):
            return

        total_requested = sum(
            map(lambda x: sum(x.values()),
                self._nodes_requested_chunks.values()))

        if total_requested + self.received >= self.size:
            if self._nodes_requested_chunks.get(node_id, None) and \
                    time_from_last_received_chunk <= self.end_race_timeout:
                return

            available_chunks = \
                self._get_end_race_chunks_to_download_from_node(node_id)
        else:
            available_chunks = \
                self._get_available_chunks_to_download_from_node(node_id)

        if not available_chunks:
            logger.debug("no chunks available for download %s", self.id)
            logger.debug("downloading from: %s nodes, length: %s, wanted: %s",
                         len(self._nodes_requested_chunks), total_requested,
                         self.size - self.received)
            return

        available_offset = random.sample(available_chunks.keys(), 1)[0]
        available_length = available_chunks[available_offset]
        logger.debug("selected random offset: %s", available_offset)

        parts_count = math.ceil(
            float(available_length) / float(DOWNLOAD_PART_SIZE)) - 1
        logger.debug("parts count: %s", parts_count)

        part_to_download_number = random.randint(0, parts_count)
        offset = available_offset + \
                 part_to_download_number * DOWNLOAD_PART_SIZE
        length = min(DOWNLOAD_PART_SIZE,
                     available_offset + available_length - offset)
        logger.debug("selected random part: %s, offset: %s, length: %s",
                     part_to_download_number, offset, length)

        self._request_data(node_id, offset, length)

    def _get_end_race_chunks_to_download_from_node(self, node_id):
        available_chunks = self._nodes_available_chunks.get(node_id, None)
        if not available_chunks:
            return []

        available_chunks = available_chunks.copy()
        logger.debug("end race downloaded_chunks: %s", self._downloaded_chunks)
        logger.debug("end race requested_chunks: %s",
                     self._nodes_requested_chunks)
        logger.debug("end race available_chunks before excludes: %s",
                     available_chunks)
        if self._downloaded_chunks:
            for downloaded_chunk in self._downloaded_chunks.items():
                self._remove_from_chunks(downloaded_chunk[0],
                                         downloaded_chunk[1], available_chunks)
        if not available_chunks:
            return []

        available_from_other_nodes = available_chunks.copy()
        for requested_offset, requested_length in \
                self._nodes_requested_chunks.get(node_id, dict()).items():
            self._remove_from_chunks(requested_offset, requested_length,
                                     available_from_other_nodes)

        result = available_from_other_nodes if available_from_other_nodes \
            else available_chunks

        if result:
            logger.debug("end race available_chunks after excludes: %s",
                         available_chunks)
        return result

    def _get_available_chunks_to_download_from_node(self, node_id):
        available_chunks = self._nodes_available_chunks.get(node_id, None)
        if not available_chunks:
            return []

        available_chunks = available_chunks.copy()
        logger.debug("downloaded_chunks: %s", self._downloaded_chunks)
        logger.debug("requested_chunks: %s", self._nodes_requested_chunks)
        logger.debug("available_chunks before excludes: %s", available_chunks)
        for _, requested_chunks in self._nodes_requested_chunks.items():
            for requested_offset, requested_length in requested_chunks.items():
                self._remove_from_chunks(requested_offset, requested_length,
                                         available_chunks)
        if not available_chunks:
            return []

        for downloaded_chunk in self._downloaded_chunks.items():
            self._remove_from_chunks(downloaded_chunk[0], downloaded_chunk[1],
                                     available_chunks)
        logger.debug("available_chunks after excludes: %s", available_chunks)
        return available_chunks

    def _request_data(self, node_id, offset, length):
        logger.debug("Requesting date from node %s, request_chunk (%s, %s)",
                     node_id, offset, length)
        if self._limiter:
            try:
                self._limiter.leak(length)
            except LeakyBucketException:
                if node_id not in self._nodes_requested_chunks:
                    self._nodes_last_receive_time.pop(node_id, None)
                    if not self._network_limited_error_set:
                        self.download_error.emit('Network limited.')
                        self._network_limited_error_set = True
                if not self._leaky_timer.isActive():
                    self._leaky_timer.start()
                return

        if self._network_limited_error_set:
            self._network_limited_error_set = False
            self.download_ok.emit()

        requested_chunks = self._nodes_requested_chunks.get(node_id, None)
        if not requested_chunks:
            requested_chunks = SortedDict()
            self._nodes_requested_chunks[node_id] = requested_chunks
        requested_chunks[offset] = length
        logger.debug("Requested chunks %s", requested_chunks)
        self._nodes_last_receive_time[node_id] = time()
        self.request_data.emit(node_id, self.id, str(offset), length)

    def _clean_nodes_last_receive_time(self):
        for node_id in list(self._nodes_last_receive_time.keys()):
            if node_id not in self._nodes_requested_chunks:
                self._nodes_last_receive_time.pop(node_id, None)

    def _on_check_timeouts(self):
        if self._paused or not self._started \
                or self._finished or self._leaky_timer.isActive():
            return

        timed_out_nodes = set()
        cur_time = time()
        logger.debug("Chunk requests check %s",
                     len(self._nodes_requested_chunks))
        if self._check_download_not_ready(self._nodes_requested_chunks):
            return

        for node_id in self._nodes_last_receive_time:
            last_receive_time = self._nodes_last_receive_time.get(node_id)
            if cur_time - last_receive_time > self.receive_timeout:
                timed_out_nodes.add(node_id)

        logger.debug("Timed out nodes %s, nodes last receive time %s",
                     timed_out_nodes, self._nodes_last_receive_time)
        for node_id in timed_out_nodes:
            timeout_count = self._nodes_timeouts_count.pop(node_id, 0)
            timeout_count += 1
            if timeout_count >= self.timeouts_limit:
                retry = False
            else:
                retry = True
                self._nodes_timeouts_count[node_id] = timeout_count
            logger.debug("Node if %s, timeout_count %s, retry %s", node_id,
                         timeout_count, retry)
            self.on_node_disconnected(node_id,
                                      connection_alive=True,
                                      timeout_limit_exceed=not retry)

    def _get_chunks_from_info(self, chunks, info):
        new_added = False
        for part_info in info:
            logger.debug("get_chunks_from_info part_info %s", part_info)
            if part_info.length == 0:
                continue

            if not chunks:
                chunks[part_info.offset] = part_info.length
                new_added = True
                continue

            result_offset = part_info.offset
            result_length = part_info.length
            left_index = chunks.bisect_right(part_info.offset)
            if left_index > 0:
                left_chunk = chunks.peekitem(left_index - 1)
                if (left_chunk[0] <= part_info.offset
                        and left_chunk[0] + left_chunk[1] >=
                        part_info.offset + part_info.length):
                    continue

                if part_info.offset <= left_chunk[0] + left_chunk[1]:
                    result_offset = left_chunk[0]
                    result_length = part_info.offset + \
                                    part_info.length - result_offset
                    left_index -= 1

            right_index = chunks.bisect_right(part_info.offset +
                                              part_info.length)
            if right_index > 0:
                right_chunk = chunks.peekitem(right_index - 1)
                if part_info.offset + part_info.length <= \
                        right_chunk[0] + right_chunk[1]:
                    result_length = right_chunk[0] + \
                                    right_chunk[1] - result_offset

            to_delete = list(chunks.islice(left_index, right_index))

            for to_del in to_delete:
                chunks.pop(to_del)

            new_added = True
            chunks[result_offset] = result_length

        return new_added

    def _store_availability_info(self, node_id, info):
        known_chunks = self._nodes_available_chunks.get(node_id, None)
        if not known_chunks:
            known_chunks = SortedDict()
            self._nodes_available_chunks[node_id] = known_chunks
        return self._get_chunks_from_info(known_chunks, info)

    def _check_download_not_ready(self, checkable):
        if not self._wanted_chunks and self._started:
            self._complete_download(force_complete=False)
            return False

        if self._leaky_timer.isActive():
            if not self._nodes_available_chunks:
                self._make_not_ready()
                return True

        elif not checkable:
            self._make_not_ready()
            return True

        return False

    def _make_not_ready(self):
        if not self._ready:
            return

        logger.info("download %s not ready now", self.id)
        self._ready = False
        self._started = False
        if self._timeout_timer.isActive():
            self._timeout_timer.stop()
        if self._leaky_timer.isActive():
            self._leaky_timer.stop()
        self.download_not_ready.emit(self)

    def _clear_globals(self):
        self._wanted_chunks.clear()
        self._downloaded_chunks.clear()
        self._nodes_available_chunks.clear()
        self._nodes_requested_chunks.clear()
        self._nodes_last_receive_time.clear()
        self._nodes_downloaded_chunks_count.clear()
        self._nodes_timeouts_count.clear()
        self._total_chunks_count = 0

    def stop_download_chunks(self):
        if self._leaky_timer.isActive():
            self._leaky_timer.stop()
        if self._timeout_timer.isActive():
            self._timeout_timer.stop()

        for node_id in self._nodes_requested_chunks:
            self.abort_data.emit(node_id, self.id, None)

        self._nodes_requested_chunks.clear()
        self._nodes_last_receive_time.clear()

    def _emit_no_disk_space(self, error=False):
        self._no_disk_space_error = True
        self._nodes_available_chunks.clear()
        self._clear_globals()
        self._make_not_ready()
        file_name = self.display_name.split()[-1] \
            if self.display_name else ""
        self.no_disk_space.emit(self, file_name, error)

    def _send_start_statistic(self):
        if self._tracker:
            self._tracker.download_start(self.id, self.size)

    def _send_end_statistic(self):
        if self._tracker:
            time_diff = time() - self._started_time
            if time_diff < 1e-3:
                time_diff = 1e-3

            self._tracker.download_end(
                self.id,
                time_diff,
                websockets_bytes=0,
                webrtc_direct_bytes=self._received_via_p2p,
                webrtc_relay_bytes=self._received_via_turn,
                chunks=len(self._downloaded_chunks),
                chunks_reloaded=0,
                nodes=len(self._nodes_available_chunks))

    def _send_error_statistic(self):
        if self._tracker:
            time_diff = time() - self._started_time
            if time_diff < 1e-3:
                time_diff = 1e-3

            self._tracker.download_error(
                self.id,
                time_diff,
                websockets_bytes=0,
                webrtc_direct_bytes=self._received_via_p2p,
                webrtc_relay_bytes=self._received_via_turn,
                chunks=len(self._downloaded_chunks),
                chunks_reloaded=0,
                nodes=len(self._nodes_available_chunks))
Пример #42
0
class MainWindow(QObject):
    def __init__(self):
        QObject.__init__(self)

        # Creating a timer
        self.timer = QTimer()
        self.timer.timeout.connect(lambda: self.setTime())
        self.timer.start(1000)

    # Signal Set name
    setName = Signal(str)

    # print time
    printTime = Signal(str)

    # Recieve switch status signal
    isVisible = Signal(bool)

    # Signal for read text
    readTextSignal = Signal(str)

    # Text string
    textField = ""

    # Set time slot
    def setTime(self):
        now = datetime.datetime.now()
        formattedTime = now.strftime("The time is %I:%M:%S %p in %Y-%m-%d")
        # print(formattedTime)
        self.printTime.emit(formattedTime)

    # set the slot
    @Slot(str)
    def welcomeText(self, name):
        self.setName.emit("Welcome, " + name)

    # Act on the switch status signal
    @Slot(bool)
    def showHideRectangle(self, isChecked):
        print("Rectangle visible ", isChecked)
        self.isVisible.emit(isChecked)

    # function to open file
    @Slot(str)
    def openFile(self, filePath):
        file = open(QUrl(filePath).toLocalFile(), encoding="utf-8")
        textRead = file.read()
        file.close()
        print(textRead)
        self.readTextSignal.emit(str(textRead))

    # function to receive text file that has been modified
    @Slot(str)
    def getTextFile(self, text):
        self.textField = text

    @Slot(str)
    def writeFile(self, filePath):
        file = open(QUrl(filePath).toLocalFile(), "w")
        file.write(self.textField)
        file.close()
        print(self.textField)
Пример #43
0
class DatapoolDialog(QDialog):
    finished = Signal(KnechtModel, Path)
    check_column = Kg.NAME

    # Close connection after 10 minutes of user inactivity
    timeout = 600000

    def __init__(self, ui):
        """ Dialog to import Datapool items

        :param modules.gui.main_ui.KnechtWindow ui: Main Window
        """
        super(DatapoolDialog, self).__init__(ui)
        SetupWidget.from_ui_file(self, Resource.ui_paths['knecht_datapool'])
        self.setWindowTitle('Datapool Import')

        self._asked_for_close = False
        self._current_project_name = ''
        self.ui = ui

        # Avoid db/thread polling within timeout
        self.action_timeout = QTimer()
        self.action_timeout.setSingleShot(True)
        self.action_timeout.setInterval(300)

        # --- Translations n Style ---
        self.project_icon: QLabel
        self.project_icon.setPixmap(IconRsc.get_pixmap('storage'))
        self.project_title: QLabel
        self.project_title.setText(_('Datapool Projekte'))
        self.image_icon: QLabel
        self.image_icon.setPixmap(IconRsc.get_pixmap('img'))
        self.image_title: QLabel
        self.image_title.setText(_('Bildeinträge'))
        self.details_btn: QPushButton
        self.details_btn.setText(_('Detailspalten anzeigen'))
        self.details_btn.toggled.connect(self.toggle_view_columns)
        self.filter_box: QLineEdit
        self.filter_box.setPlaceholderText(
            _('Im Baum tippen um zu filtern...'))

        # -- Trigger filter update for all views ---
        self.update_filter_timer = QTimer()
        self.update_filter_timer.setInterval(5)
        self.update_filter_timer.setSingleShot(True)
        self.update_filter_timer.timeout.connect(self.update_filter_all_views)

        self.filter_box: QLineEdit
        self.filter_box.textChanged.connect(self.update_filter_timer.start)

        # --- Init Tree Views ---
        self.project_view = KnechtTreeViewCheckable(
            self,
            None,
            filter_widget=self.filter_box,
            replace=self.project_view)
        self.image_view = KnechtTreeViewCheckable(
            self, None, filter_widget=self.filter_box, replace=self.image_view)

        # --- Database Connector ---
        self.dp = DatapoolController(self)
        self.dp.add_projects.connect(self.update_project_view)
        self.dp.add_images.connect(self.update_image_view)
        self.dp.error.connect(self.error)

        # Connection timeout
        self.connection_timeout = QTimer()
        self.connection_timeout.setInterval(self.timeout)
        self.connection_timeout.setSingleShot(True)
        self.connection_timeout.timeout.connect(self.connection_timed_out)

        # Make sure to end thread on App close
        self.ui.is_about_to_quit.connect(self.close)

        # Intercept mouse press events from project view
        self.org_view_mouse_press_event = self.project_view.mousePressEvent
        self.project_view.mousePressEvent = self.view_mouse_press_event

        # Start thread
        QTimer.singleShot(100, self.start_datapool_connection)

    def view_mouse_press_event(self, event: QMouseEvent):
        if event.buttons(
        ) == Qt.LeftButton and not self.action_timeout.isActive():
            idx = self.project_view.indexAt(event.pos())
            name = idx.siblingAtColumn(Kg.NAME).data(Qt.DisplayRole)
            self._current_project_name = name
            _id = idx.siblingAtColumn(Kg.ID).data(Qt.DisplayRole)
            LOGGER.debug('Project %s Id %s selected', name, _id)
            self.action_timeout.start()

            if _id:
                self.request_project(_id)

        self.org_view_mouse_press_event(event)

    def update_filter_all_views(self):
        # Do not filter project view
        self.project_view.filter_timer.stop()

        # Update image view filter
        if not self.filter_box.text():
            self.image_view.clear_filter()
        else:
            self.image_view.filter_timer.start()

    def start_datapool_connection(self):
        self.show_progress(_('Verbinde mit Datenbank'))
        self.dp.start()
        self.connection_timeout.start()

    @Slot(dict)
    def update_project_view(self, projects: dict):
        """ (Name, ModelYear, 'JobNo) """
        if not projects:
            return

        root_item = KnechtItem(
            None,
            ('', _('Bezeichnung'), _('Modelljahr'), _('Job'), '', _('Id')))

        for num_idx, (_id, project_data) in enumerate(projects.items()):
            data = (f'{num_idx:03d}', *project_data, '', str(_id))
            p_item = KnechtItem(root_item, data)
            KnechtItemStyle.style_column(p_item,
                                         'render_preset',
                                         column=Kg.NAME)
            root_item.append_item_child(p_item)

        update_model = UpdateModel(self.project_view)
        update_model.update(KnechtModel(root_item))

        self.toggle_view_columns(self.details_btn.isChecked())
        self.project_view.setHeaderHidden(False)

    def request_project(self, _id: str):
        self.image_view.clear_filter()
        self.image_view.progress_msg.msg(_('Daten werden angefordert'))
        self.image_view.progress_msg.show_progress()

        self.dp.request_project(_id)
        self.connection_timeout.start()

    @Slot(dict)
    def update_image_view(self, images: dict):
        if not images:
            return

        root_item = KnechtItem(None, ('', _('Name'), _('Priorität'),
                                      _('Erstellt'), '', _('wagenbauteil Id')))

        for num_idx, (img_id, image_data) in enumerate(images.items()):
            """ (name, priority, created, pr_string, opt_id, produced_image_id) """
            name, priority, created, pr_string, opt_id, produced_image_id = image_data
            img_item = KnechtItem(
                root_item,
                (f'{num_idx:03d}', name, priority, created, '', str(opt_id)))
            KnechtItemStyle.style_column(img_item, 'preset', Kg.NAME)
            root_item.append_item_child(img_item)

        update_model = UpdateModel(self.image_view)
        update_model.update(
            KnechtModel(root_item, checkable_columns=[self.check_column]))

        self.toggle_view_columns(self.details_btn.isChecked())
        self.image_view.setHeaderHidden(False)
        self.image_view.check_items([], Kg.NAME, check_all=True)

    def create_presets(self):
        root_item = KnechtItem()

        for (src_index,
             item) in self.image_view.editor.iterator.iterate_view():
            if item.data(self.check_column, Qt.CheckStateRole) == Qt.Unchecked:
                continue

            name = item.data(Kg.NAME)
            data = (f'{root_item.childCount():03d}', name, '', 'preset', '',
                    Kid.convert_id(f'{root_item.childCount()}'))
            root_item.insertChildren(root_item.childCount(), 1, data)

        date = datetime.datetime.now().strftime('%Y%m%d')
        project = self._current_project_name.replace(' ', '_')
        self.finished.emit(KnechtModel(root_item),
                           Path(f'{date}_{project}.xml'))

    def toggle_view_columns(self, checked: bool):
        columns = {
            Kg.ORDER, Kg.NAME, Kg.VALUE, Kg.TYPE, Kg.REF, Kg.ID, Kg.DESC
        }

        if checked:
            show_columns = {Kg.NAME, Kg.VALUE, Kg.TYPE, Kg.ID}
        else:
            show_columns = {Kg.NAME}

        for col in columns:
            self.project_view.setColumnHidden(col, col not in show_columns)
            self.image_view.setColumnHidden(col, col not in show_columns)

        self._setup_view_headers()

    def _setup_view_headers(self):
        setup_header_layout(self.project_view)
        setup_header_layout(self.image_view)

    @Slot(str)
    def error(self, error_msg):
        self.project_view.progress_msg.hide_progress()
        self.image_view.progress_msg.hide_progress()

        self.ui.msg(error_msg, 9000)

    @Slot(str)
    def show_progress(self, msg: str):
        self.project_view.progress_msg.msg(msg)
        self.project_view.progress_msg.show_progress()

        self.image_view.progress_msg.msg(_('Projekt auswählen'))
        self.image_view.progress_msg.show_progress()
        self.image_view.progress_msg.progressBar.setValue(0)

    def connection_timed_out(self):
        self.show_progress(_('Zeitüberschreitung'))
        self.ui.msg(
            _('Zeitüberschreitung bei Datenbankverbindung. Die Verbindung wurde automatisch getrennt.'
              ), 12000)
        self.dp.close()

    def reject(self):
        self.close()

    def accept(self):
        self.create_presets()

        self._asked_for_close = True
        self.close()

    def closeEvent(self, close_event):
        LOGGER.debug('Datapool close event called. %s', close_event.type())
        if self._ask_abort_close():
            close_event.ignore()
            return False

        LOGGER.info(
            'Datapool window close event triggered. Aborting database connection'
        )
        # End thread
        if not self._finalize_dialog():
            close_event.ignore()
            return False

        close_event.accept()
        return True

    def _ask_abort_close(self):
        if self._asked_for_close:
            return False

        msg_box = AskToContinue(self)

        if not msg_box.ask(
                title=_('Importvorgang'),
                txt=_('Soll der Vorgang wirklich abgebrochen werden?'),
                ok_btn_txt=_('Ja'),
                abort_btn_txt=_('Nein'),
        ):
            # Cancel close
            return True

        # Close confirmed
        return False

    def _finalize_dialog(self, self_destruct: bool = True) -> bool:
        LOGGER.debug('Datapool dialog is finishing tasks.')
        if not self.dp.close():
            return False

        if self_destruct:
            self.deleteLater()
        return True
Пример #44
0
## $QT_END_LICENSE$
##
#############################################################################

from __future__ import print_function

import os
import sys
from PySide2.QtCore import QTimer, QUrl
from PySide2.QtGui import QGuiApplication
from PySide2.QtQuick import QQuickView

if __name__ == '__main__':
    app = QGuiApplication(sys.argv)

    timer = QTimer()
    timer.start(2000)

    view = QQuickView()
    qmlFile = os.path.join(os.path.dirname(__file__), 'view.qml')
    view.setSource(QUrl.fromLocalFile(os.path.abspath(qmlFile)))
    if view.status() == QQuickView.Error:
        sys.exit(-1)
    root = view.rootObject()

    timer.timeout.connect(root.updateRotater)

    view.show()
    res = app.exec_()
    # Deleting the view before it goes out of scope is required to make sure all child QML instances
    # are destroyed in the correct order.
Пример #45
0
class MainController:
    PING_RATE_MS = 3000

    def __init__(self):
        self.window = MainWindow()
        self.network_access_manager = QNetworkAccessManager(self.window)
        self.window.controllers_mdi.tileSubWindows()
        self.ping_timer = QTimer(self.window)
        self.detected_devices = {}
        self.open_devices = {}
        self.ssdp_interface = SSDPInterface(parent=self.window)
        self.ssdp_interface.new_device.connect(self.device_info_received)
        self.window.refresh_button.clicked.connect(self.refresh_clicked)
        self.ping_timer.timeout.connect(self.ssdp_interface.ping_devices)
        self.window.auto_ping.stateChanged.connect(self.auto_ping_changed)
        self.window.camera_list.itemDoubleClicked.connect(
            self.item_double_clicked)
        if self.window.auto_ping.isChecked():
            print("Starting auto ping")
            self.ping_timer.start(MainController.PING_RATE_MS)

    def refresh_clicked(self):
        self.window.camera_list.clear()
        self.detected_devices.clear()
        self.ssdp_interface.ping_devices()

    def auto_ping_changed(self, state):
        if state == Qt.Checked:
            if not self.ping_timer.isActive():
                print("Starting auto ping")
                self.ping_timer.start(MainController.PING_RATE_MS)
        else:
            print("Stopping auto ping")
            self.ping_timer.stop()

    def show(self):
        self.window.show()

    def device_info_received(self, cam_desc: CamDesc):
        add_string = cam_desc.host.toString()
        if add_string not in self.detected_devices:
            self.detected_devices[add_string] = cam_desc
            self.window.camera_list.addItem(add_string)
        if self.window.open_automatically.isChecked():
            self.open_controller(add_string)

    def item_double_clicked(self, item: QtWidgets.QListWidgetItem):
        self.open_controller(item.text())

    def open_controller(self, add_string):
        if add_string in self.open_devices:
            return

        print(f"Opening controller for {add_string}")
        cam_controller = CameraController(self,
                                          self.detected_devices[add_string],
                                          self.window.controllers_mdi)
        self.open_devices[add_string] = cam_controller

    def controller_closed(self, add_string):
        self.open_devices.pop(add_string)
Пример #46
0
    global sec
    item2.setTransformOriginPoint(300, 100)
    item2.setRotation(sec)
    sec += 1


if __name__ == '__main__':
    app = QApplication(sys.argv)
    scene = QGraphicsScene(QRectF(0, 0, 1000, 200))
    scene.addItem(QGraphicsRectItem(scene.sceneRect()))

    item1 = createItem(0, scene)
    sec = 0
    item2 = createItem(200, scene)

    timer = QTimer()
    timer.setInterval(1000)
    timer.timeout.connect(timerHandler)
    # item2.mapToScene(300, 100)
    # # item2.translate(300, 100)
    item2.setTransformOriginPoint(300, 100)
    item2.setRotation(30)
    # # item2.translate(-300, -100)
    # item2.mapToScene(-300, -100)

    item3 = createItem(400, scene)
    # item3.translate(500, 100)
    item3.setTransformOriginPoint(500, 100)
    item3.setScale(0.5)
    # item3.translate(-500, -100)
Пример #47
0
 def testSingleShotSignal(self):
     emitter = SigEmitter()
     emitter.sig1.connect(self.callback)
     QTimer.singleShot(100, emitter.sig1)
     self.app.exec_()
     self.assert_(self.called)
Пример #48
0
class Station:
    """Station class containing the necessary data to control
    a single station

    Paramaters:
        stationID(int):
            ID identifier for the station and suffix for
            all widget names
    """
    DEFAULT_DEVICE = None
    DEFAULT_CUSTOMERNAME = ''
    DEFAULT_ACTIVATION = False
    DEFAULT_SESSIONS = []

    def __init__(self, windows, stationID: int, tracked_sessions: List[Session] = []):
        self.windows = windows
        # -Main Variables-
        # Static Paramaters
        self.stationID = stationID
        self.device: Union[Device, None] = self.DEFAULT_DEVICE
        self.sessionTracker = SessionTracker(self,
                                             tracked_sessions)
        # Dynamic Paramaters
        self._customerName: str = self.DEFAULT_CUSTOMERNAME
        self._is_activated: bool = self.DEFAULT_ACTIVATION
        self.sessions: List[Session] = self.DEFAULT_SESSIONS
        # Helper Variables
        self._editWindow_shownSessions: List[Session] = []
        self.rowTranslator: Dict[int, int] = {}  # Connect edit row to sessionID

        # -Setup-
        self._initialize_timers()
        self._initialize_binds()
        self.hide()
        self.refresh()

    @property
    def is_activated(self):
        return self._is_activated

    @is_activated.setter
    def is_activated(self, value: bool):
        assert isinstance(value, bool), "is_activated has to be bool"
        self._is_activated = value

        if not self.is_activated:
            # Deactivate station
            if self.device is not None:
                # Device is registered
                self.device.turn_off()
        self.refresh()

    @property
    def customerName(self):
        return self._customerName

    @customerName.setter
    def customerName(self, value: str):
        assert isinstance(value, str), "customerName has to be str"
        self._customerName = value
        self._update_texts()

    # -Initialize methods-
    def _initialize_timers(self):
        """
        Set up timers here
        """
        # Refresh timer
        self.timer = QTimer()
        self.timer.timeout.connect(self.refresh)
        self.timer.start(2500)

    def _initialize_binds(self):
        """
        Bind the buttons of this station to
        """
        reconnect(self.windows['main'].findChild(QPushButton, f"pushButton_newSession_{self.stationID}").clicked, self.clicked_newSession)  # nopep8
        reconnect(self.windows['main'].findChild(QPushButton, f"pushButton_edit_{self.stationID}").clicked, self.clicked_editSession)  # nopep8
        reconnect(self.windows['main'].findChild(QPushButton, f"pushButton_toggleState_{self.stationID}").clicked, self.clicked_onOff)  # nopep8

    # -Station methods-
    def refresh(self):
        """
        Refresh this station:
            - Update sessions
            - Update texts
            - Update colors
            - Update Edit Window
            - Update Statistics Tracker
        """
        self._update_sessions()
        self._update_texts()
        self._update_colors()
        self._editWindow_refresh()
        self.sessionTracker.refresh()

    def update(self, **kwargs):
        """Update the data of this station

        Paramaters:
            **kwargs:
                Data to update the station on
        """
        if 'device' in kwargs:
            assert (isinstance(kwargs['device'], Device) or kwargs['device'] is None)
            self.device = kwargs['device']
        if 'customerName' in kwargs:
            assert isinstance(kwargs['customerName'], str)
            self._customerName = kwargs['customerName']
        if 'is_activated' in kwargs:
            assert isinstance(kwargs['is_activated'], bool)
            self._is_activated = kwargs['is_activated']
        if 'sessions' in kwargs:
            assert isinstance(kwargs['sessions'], list)
            self.sessions = kwargs['sessions'].copy()
        self.refresh()

    def extract_data(self) -> dict:
        """
        Extract the data of this station

        Returns(dict):
            Full data of the station
        """
        data = {
            'device': self.device,
            'customerName': self.customerName,
            'is_activated': self.is_activated,
            'sessions': self.sessions,
        }
        return data

    def show(self, **kwargs):
        """Display the station

        Paramaters:
            **kwargs:
                Data to update the station on
        """
        self.windows['main'].findChild(QWidget, f"frame_station_{self.stationID}").setHidden(False)
        if kwargs:
            self.update(**kwargs)

        self.refresh()

    def hide(self):
        """Hide the station"""
        self.windows['main'].findChild(QWidget, f"frame_station_{self.stationID}").setHidden(True)

    def reset(self, hide: bool = False):
        """Reset the data of this station

        Paramaters:
            hide(bool):
                Hide the station
        """
        if hide:
            self.hide()
        self.update(**{
            'device': self.DEFAULT_DEVICE,
            'customerName': self.DEFAULT_CUSTOMERNAME,
            'is_activated': self.DEFAULT_ACTIVATION,
            'sessions': self.DEFAULT_SESSIONS,
        })

    # -Session methods-
    def add_session(self, customerName: str, start_date: Union[dt.datetime, None], duration: dt.time, sessionID: Union[int, None] = None) -> bool:
        """
        Add a new session

        Paramaters:
            customerName(str):
                Name of customer for this session
            start_date(dt.datetime or None):
                If None, the end_date of the last queue item will be taken
            duration(dt.time):
                Time length of the session
            sessionID(int or None):
                ID of session, if None a new session is created, otherwise
                it may replace a session if a session already has that sessionID (no warning)
        Returns(bool):
            Succesfully added session
        """
        # -Determine session paramaters-
        if start_date is None:
            if self.sessions:
                start_date = self.sessions[-1].end_date
            else:
                start_date = dt.datetime.now()
        new_session = Session(customerName=customerName,
                              start_date=start_date,
                              duration=duration,
                              sessionID=sessionID)

        # -Check for conflicting sessions-
        conflicting_sessions = []
        for queued_session in self.sessions:
            if new_session.range_conflicts(queued_session.start_date, queued_session.end_date):
                # Ranges conflict
                if queued_session.sessionID == new_session.sessionID:
                    # Ignore the session as it is the one being replaced
                    continue
                conflicting_sessions.append(queued_session)
        # Ask for confirmation on deletion of overlapping sessions
        if conflicting_sessions:
            # Create messagebox
            msg = QMessageBox()
            msg.setWindowTitle("Conflicting Sessions")
            msg.setIcon(QMessageBox.Warning)
            msg.setText(f"Your session is conflicting with {len(conflicting_sessions)} already registered session(s).\nDo you wish to delete the overlapping sessions?")  # nopep8
            detailedText = f"Your sessions start: {new_session.start_date.strftime('%H:%M')}\nYour sessions end: {new_session.end_date.strftime('%H:%M')}"
            detailedText += '\n\nConflicting session(s):\n\n'
            for conflicting_data in conflicting_sessions:
                conflicting_session = conflicting_data
                detailedText += f"{conflicting_session.customerName}´s session start: {conflicting_session.start_date.strftime('%H:%M')}"
                detailedText += f"\n{conflicting_session.customerName}´s session end: {conflicting_session.end_date.strftime('%H:%M')}"
                detailedText += '\n\n'
            msg.setDetailedText(detailedText)
            msg.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
            msg.setWindowFlag(Qt.WindowStaysOnTopHint)
            val = msg.exec_()

            # User pressed continue
            if val == QMessageBox.Yes:
                # Remove all conflicting sessions
                for conflicting_session in conflicting_sessions:
                    self.delete_session(session=conflicting_session,
                                        track=None)
                # Add the session again
                session_data = new_session.extract_data()
                del session_data['sessionID']
                return self.add_session(**session_data)
            else:
                # User pressed cancel -> unsuccessful
                return False

        if sessionID is not None:
            # Delete old session
            self.delete_session(session=sessionID,
                                track=False)
        self.sessions.append(new_session)
        self.refresh()
        return True

    def delete_session(self, session: Union[int, Session] = None, track: Union[bool, None] = None):
        """
        Delete the session

        Paramaters:
            session(int or Session):
                ID of the session to delete OR
                Session class to delete
            track(bool):
                Whether to track the session
                If track is None, the session is only tracked if its range is in the current date.
                If that condition is true, the session is tracked up until the current date
        """
        deleted_session: Session
        if isinstance(session, int):
            deleted_session = self._find_session(session)
        elif isinstance(session, Session):
            deleted_session = session

        if track is None:
            datetime_now = dt.datetime.now()
            if deleted_session.range_contains(datetime_now):
                deleted_session.end_date = datetime_now
                track = True
            else:
                track = False
        if track:
            self.sessionTracker.add_session_to_history(deleted_session)
        self.sessions.remove(deleted_session)

    def replace_session(self, sessionID: int, new_customerName: str = None, new_start_date: dt.datetime = None,
                        new_end_date: dt.datetime = None, new_duration: dt.time = None):
        """
        Update the session with the given session id with the
        newly given session data

        Paramaters:
            sessionID(int):
                ID of the session to delete
            new_customerName(str):
                The new customer name
            new_start_date(dt.datetime):
                New start date (end date will change)
            new_end_date(dt.datetime):
                New end date (duration will change)
            new_duration(dt.time):
                New duration (end date will change)
        """
        session = self._find_session(sessionID).copy()
        if new_customerName is not None:
            session.customerName = new_customerName
        if new_start_date is not None:
            session.start_date = new_start_date
        if new_end_date is not None:
            session.end_date = new_end_date
        if new_duration is not None:
            session.duration = new_duration

        self.add_session(**session.extract_data())

    def running_session(self) -> bool:
        """
        Check if a session is currently running
        Returns(bool):

            Currently running a session
        """
        if (not self.sessions or
                not self.is_activated):
            return False
        # Sort session by start date
        self.sessions = sorted(self.sessions, key=lambda s: s.start_date)
        first_session = self.sessions[0]

        datetime_now = dt.datetime.now()
        # Current time inside the sessions range
        return first_session.range_contains(datetime_now)

    def _update_sessions(self):
        """
        Update the session queue
        """
        datetime_now = dt.datetime.now()
        # Sort session by start date
        self.sessions = sorted(self.sessions, key=lambda s: s.start_date)
        # Clear out expired sessions (already past sessions; includes the most recent active session)
        for queue_session in self.sessions:
            if queue_session.end_date <= datetime_now:
                # Session is done
                self.delete_session(session=queue_session,
                                    track=True)

        # -Determine Text shown and states-
        if not self.running_session():
            # No session running
            self.customerName = self.DEFAULT_CUSTOMERNAME
            # Check for an upcoming session
            if self.sessions:
                upcoming_session = self.sessions[0]
                if upcoming_session.start_date > datetime_now:
                    customerName = f"{upcoming_session.customerName}"
                    self.customerName = customerName + f" <span style=\" font-size:8pt; font-style:italic; color:#333;\" >starts at {upcoming_session.start_date.strftime('%H:%M')}</span>"  # nopep8
            if self.is_activated:
                # Station is activated
                if self.device is not None:
                    # Device is registered
                    self.device.turn_off()
        else:
            # Session running
            self.customerName = self.sessions[0].customerName
            if self.device is not None:
                # Device is registered
                self.device.turn_on()

    def _find_session(self, sessionID: int) -> Session:
        """
        Find a session by its id

        Paramaters:
            sessionID(int):
                ID of the session to delete

        Returns(Session):
            Session instance with that id
        """
        for session in self.sessions:
            if session.sessionID == sessionID:
                return session
        else:
            raise KeyError('No session found with id', sessionID)

    # -Button clicks-
    def clicked_onOff(self):
        """
        Toggle between activated and deactivated
        """
        if self.is_activated:
            if self.sessions:
                msg = QMessageBox()
                msg.setWindowTitle("Confirmation")
                msg.setIcon(QMessageBox.Warning)
                msg.setText("Deactivating this station will close the current customer session and all sessions queued.\nDo you wish to proceed?")  # nopep8
                msg.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
                msg.setWindowFlag(Qt.WindowStaysOnTopHint)
                val = msg.exec_()

                if val != QMessageBox.Yes:  # Not Continue
                    return

            for session in self.sessions:
                self.delete_session(session=session,
                                    track=None)
            self.is_activated = False
        else:
            self.is_activated = True

    def clicked_newSession(self):
        """
        Open the session window
        """
        # -Window Setup-
        if self.sessions:
            time = self.sessions[-1].end_date
        else:
            time = dt.datetime.now()
        time = self.ceil_dt(time,
                            dt.timedelta(minutes=30))

        # Time is over the current day
        if time.day > dt.date.today().day:
            time = dt.time(23, 59)
        else:
            time = time.time()

        # Reset variable
        self.windows['session'].lineEdit_customerName.setText('')
        self.windows['session'].timeEdit_startAt.setTime(time)
        # Set dynamic properties
        self.windows['session'].setProperty('stationID', self.stationID)
        # Reshow window
        self.windows['session'].setWindowFlag(Qt.WindowStaysOnTopHint)
        self.windows['session'].show()
        # Focus window
        self.windows['session'].activateWindow()
        self.windows['session'].raise_()

    def clicked_editSession(self):
        """
        Open the edit sessions window
        """
        # -Window Setup-
        # Reset variable
        # Set stationID
        self.windows['edit'].setProperty('stationID', self.stationID)
        # Reshow window
        self.windows['edit'].setWindowFlag(Qt.WindowStaysOnTopHint)
        self.windows['edit'].show()
        # Focus window
        self.windows['edit'].activateWindow()
        self.windows['edit'].raise_()
        # -Fill List-
        self._editWindow_updateTable()
        self.refresh()

    # -Edit Window-
    def delete_selection(self):
        """
        Delete all session selected
        """
        assert self.windows['edit'].property('stationID') == self.stationID, "stationID in edit window does not equal stationID deletion is being performed on"  # nopep8

        # Get selection
        widget = self.windows['edit'].tableWidget_queue
        selection = widget.selectionModel().selectedRows()
        # Perform deletion
        datetime_now = dt.datetime.now()
        for item in selection:
            session = self._find_session(self.rowTranslator[item.row()])
            if session.range_contains(datetime_now):
                # Current time inside the sessions range
                msg = QMessageBox()
                msg.setWindowTitle('Confirmation')
                msg.setIcon(QMessageBox.Icon.Information)
                msg.setText('You are deleting a session that is currently active. Do you wish to proceed?')
                msg.setStandardButtons(QMessageBox.Yes | QMessageBox.Cancel)
                msg.setWindowFlag(Qt.WindowStaysOnTopHint)
                val = msg.exec_()
                if val == QMessageBox.Cancel:
                    # Skip this session deletion
                    continue
            self.delete_session(session=session,
                                track=None)
        self.refresh()

    @staticmethod
    def ceil_dt(date, delta) -> dt.datetime:
        """
        Round up datetime
        """
        return date + (dt.datetime.min - date) % delta

    def _editWindow_refresh(self):
        """
        Check if the table in the edit window needs an update
        Is being checked as a refill of the table results in an
        unselection
        """
        if self.windows['edit'].property('stationID') == self.stationID:
            if self.sessions != self._editWindow_shownSessions:
                self._editWindow_updateTable()

    def _editWindow_updateTable(self):
        """
        Fill the table in the edit sessions window
        """
        headers = ['Customer Name', 'Start Time', 'End Time', 'Total Time']
        headerTranslator = {
            'Customer Name': 'customerName',
            'Start Time': 'start_date',
            'End Time': 'end_date',
            'Total Time': 'duration',
        }
        self.rowTranslator = {}

        tableWidget = self.windows['edit'].tableWidget_queue
        tableWidget.setRowCount(0)
        # Base widget settings
        tableWidget.setRowCount(len(self.sessions))
        tableWidget.setColumnCount(len(headers))
        tableWidget.setHorizontalHeaderLabels(headers)
        # -Set column widths-
        tableWidget.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeToContents)
        column_width = max(tableWidget.columnWidth(1), tableWidget.columnWidth(2), tableWidget.columnWidth(3))
        tableWidget.horizontalHeader().setSectionResizeMode(QHeaderView.Fixed)
        tableWidget.setColumnWidth(1, column_width)
        tableWidget.setColumnWidth(2, column_width)
        tableWidget.horizontalHeader().setSectionResizeMode(0, QHeaderView.Stretch)
        # -Fill table-
        for row in range(tableWidget.rowCount()):
            self.rowTranslator[row] = self.sessions[row].sessionID
            datas = self.sessions[row].extract_data()
            datas['end_date'] = self.sessions[row].end_date
            for col, header_key in enumerate(headers):
                data = datas[headerTranslator[header_key]]
                if type(data) in (dt, dt.date, dt.datetime, dt.time):
                    data = data.strftime('%H:%M')
                item = QTableWidgetItem(str(data))
                item.setTextAlignment(Qt.AlignCenter)

                tableWidget.setItem(row, col, item)
        tableWidget.setItemDelegate(QWidgetDelegate(self.windows['edit'], station=self))
        self._editWindow_shownSessions = self.sessions.copy()

    def _update_texts(self):
        """
        Update the text elements of the station
        """
        def strfdelta(tdelta, fmt):
            """
            Stringify timedelta
            """
            class DeltaTemplate(Template):
                delimiter = "%"
            d = {"D": tdelta.days}
            d["H"], rem = divmod(tdelta.seconds, 3600)
            d["M"], d["S"] = divmod(rem, 60)
            for key in d.keys():
                d[key] = str(d[key]).zfill(2)

            t = DeltaTemplate(fmt)
            return t.substitute(**d)

        # -Update start date, end date and time left-
        if not self.running_session():
            # Station is either deactivated, or has no session running
            start_date = ''
            end_date = ''
            time_left = ''
        else:
            # Stringify the time formats
            datetime_now = dt.datetime.now()
            start_date = self.sessions[0].start_date.strftime('%H:%M')
            end_date = self.sessions[0].end_date.strftime('%H:%M')
            time_left = strfdelta(self.sessions[0].end_date - datetime_now, '%H:%M')

        # -Push Buttons-
        if not self.is_activated:
            toggleState_text = 'Take Control'
            newSession_enabled = False
            edit_enabled = False
        else:
            toggleState_text = 'Release Control'
            newSession_enabled = True
            edit_enabled = True

        # -Update texts-
        self.windows['main'].findChild(QLabel, f"label_customerName_{self.stationID}").setText(self.customerName)
        self.windows['main'].findChild(QLabel, f"label_startTimeValue_{self.stationID}").setText(start_date)
        self.windows['main'].findChild(QLabel, f"label_endTimeValue_{self.stationID}").setText(end_date)
        self.windows['main'].findChild(QLabel, f"label_timeLeftValue_{self.stationID}").setText(time_left)
        self.windows['main'].findChild(QPushButton, f"pushButton_toggleState_{self.stationID}").setText(toggleState_text)  # nopep8
        self.windows['main'].findChild(QPushButton, f"pushButton_newSession_{self.stationID}").setEnabled(newSession_enabled)  # nopep8
        self.windows['main'].findChild(QPushButton, f"pushButton_edit_{self.stationID}").setEnabled(edit_enabled)
        if self.device is not None:
            # Device is registered
            self.windows['main'].findChild(QLabel, f"label_deviceName_{self.stationID}").setText(self.device.deviceName)
        else:
            self.windows['main'].findChild(QLabel, f"label_deviceName_{self.stationID}").setText('N/A')

    def _update_colors(self):
        """
        Update the indicator colors of the station
        """
        if self.running_session():
            frame_labels_color = const.NOT_AVAILABLE_COLOR
        elif self.is_activated:
            frame_labels_color = const.AVAILABLE_COLOR
        else:
            frame_labels_color = const.DEACTIVATED_COLOR
        frame_labels_stylesheet = """QFrame { background-color: rgb(%d, %d, %d);}""" % (frame_labels_color[0],
                                                                                        frame_labels_color[1],
                                                                                        frame_labels_color[2])

        # -Update color-
        self.windows['main'].findChild(QFrame, f"frame_labels_{self.stationID}").setStyleSheet(frame_labels_stylesheet)
Пример #49
0
class SessionTracker:
    """Class containing the necessary data to track
    a stations sessions

    Paramaters:
        station(Station):
            Station to track the times on
    """

    def __init__(self, station: Station, tracked_sessions: List[Session] = []):
        self.station = station
        self.windows = self.station.windows
        self.stationID = self.station.stationID
        self.tracked_sessions = tracked_sessions
        # -Setup-
        self._initialize_timers()
        self._initialize_binds()
        self.refresh()

    # -Initialize methods-
    def _initialize_timers(self):
        """
        Set up timers here
        """
        # Refresh timer
        self.timer = QTimer()
        self.timer.timeout.connect(self.refresh)
        self.timer.start(2500)

    def _initialize_binds(self):
        """
        Bind the buttons of this station to
        """
        reconnect(self.windows['main'].findChild(QPushButton, f"pushButton_statistics_showSessions_{self.stationID}").clicked, self.clicked_showSessions)  # nopep8

    def refresh(self):
        """
        Refresh this statistics tracker:
            - Sort tracked sessions
            - Update visibility
            - Update texts
            - Update table
        """
        self.tracked_sessions = sorted(self.tracked_sessions, key=lambda s: s.start_date)
        self._update_visibility()
        self._update_texts(sessions=self.tracked_sessions)
        if self.windows['history'].property('stationID') == self.stationID:
            self._historyWindow_updateTable(sessions=self.tracked_sessions)

    def update(self, tracked_sessions: list, override: bool):
        """Update the data of this sessionTracker"""
        new_tracked_sessions = tracked_sessions
        if not override:
            # Do not override existing data
            new_tracked_sessions.append(self.tracked_sessions)

        self.tracked_sessions = new_tracked_sessions
        self.refresh()

    def extract_data(self) -> Union[dict, None]:
        """
        Extract the data of this tracker

        Returns(dict or None):
            Full data of the session history or None if the
            parent station does not have a registered device
        """
        if self.station.device is None:
            # No device registered for this station
            return None
        data = {
            'deviceID': self.station.device.deviceID,
            'tracked_sessions': self.tracked_sessions
        }
        return data

    # -Session tracking-
    def add_session_to_history(self, session: Session):
        """
        Add a session to the session history of this station
        """
        # Search for conflicting sessions
        for tracked_session in self.tracked_sessions:
            if tracked_session.range_conflicts(start_date=session.start_date,
                                               end_date=session.end_date):
                # Two tracked sessions might conflict if the user
                # finished one session and then set up a session that
                # happened during that previous sessions time range
                # Action: Override old session
                self.tracked_sessions.remove(tracked_session)
        self.tracked_sessions.append(session)
        self.refresh()

    def calculate_stats(self, sessions: list) -> dict:
        """
        Return the stats displayed on the application
        """
        if not len(sessions):
            # No tracked sessions
            session_history = {
                'total_time': dt.time(hour=0, minute=0),
                'average_time': dt.time(hour=0, minute=0),
                'total_sessions': 0,
                'average_sessions': 0,
            }
            return session_history
        session_history = {}
        total_minutes = 0

        # -Total time-
        for session in sessions:
            total_minutes += session.duration.hour * 60
            total_minutes += session.duration.minute

        hours, minutes = divmod(total_minutes, 60)
        session_history['total_time'] = dt.time(hour=hours,
                                                minute=minutes)
        # -Average session time-
        average_minutes = int(total_minutes / len(sessions))
        hours, minutes = divmod(average_minutes, 60)
        session_history['average_time'] = dt.time(hour=hours,
                                                  minute=minutes)
        # -Total sessions-
        session_history['total_sessions'] = len(sessions)
        # -Average sessions/day-
        dates = []
        for session in sessions:
            start_date = session.start_date.date()
            if start_date in dates:
                continue
            else:
                dates.append(start_date)
        average_sessions = round(len(sessions) / len(dates), 1)
        if average_sessions.is_integer():
            average_sessions = int(average_sessions)
        session_history['average_sessions'] = average_sessions

        return session_history

    # -Button clicks-
    def clicked_showSessions(self):
        """
        Show a table of the sessions history
        """
        # -Window Setup-
        # Set variable
        if self.station.device is not None:
            # Device is registered
            self.windows['history'].label.setText(f'{self.station.device.deviceName} Session History')
        else:
            self.windows['history'].label.setText(f'N/A Session History')
        # Set stationID
        self.windows['history'].setProperty('stationID', self.stationID)
        # Reshow window
        self.windows['history'].setWindowFlag(Qt.WindowStaysOnTopHint)
        self.windows['history'].show()
        # Focus window
        self.windows['history'].activateWindow()
        self.windows['history'].raise_()
        # -Fill List-

        self.refresh()

    def _historyWindow_updateTable(self, sessions: list):
        """
        Update the history table
        """
        headers = ['Date', 'Customer Name', 'Start Time', 'End Time']
        headerTranslator = {
            'Date': 'date',
            'Customer Name': 'customerName',
            'Start Time': 'start_date',
            'End Time': 'end_date',
            'Total Time': 'duration',
        }
        tableWidget = self.windows['history'].tableWidget_history
        tableWidget.setRowCount(0)
        # Base widget settings
        tableWidget.setRowCount(len(sessions))
        tableWidget.setColumnCount(len(headers))
        tableWidget.setHorizontalHeaderLabels(headers)
        # -Set column widths-
        column_width = 90
        tableWidget.horizontalHeader().setSectionResizeMode(QHeaderView.Fixed)
        tableWidget.setColumnWidth(0, column_width + 20)
        tableWidget.horizontalHeader().setSectionResizeMode(1, QHeaderView.Stretch)
        tableWidget.setColumnWidth(2, column_width)
        tableWidget.setColumnWidth(3, column_width)
        # -Fill table-
        for row in range(tableWidget.rowCount()):
            session = sessions[row]
            session_data = session.extract_data()
            session_data['end_date'] = session.end_date
            for col, header_key in enumerate(headers):
                key = headerTranslator[header_key]
                if key == 'date':
                    data = session_data['start_date'].strftime(r'%d.%m.%Y')
                else:
                    data = session_data[key]
                    if type(data) in (dt, dt.date, dt.datetime, dt.time):
                        data = data.strftime('%H:%M')
                item = QTableWidgetItem(str(data))
                item.setTextAlignment(Qt.AlignCenter)
                tableWidget.setItem(row, col, item)

    def _update_visibility(self):
        """
        Check whether to hide or show this station
        """
        station_frame = self.windows['main'].findChild(QWidget, f"frame_station_{self.stationID}")
        statistic_frame = self.windows['main'].findChild(QWidget, f"frame_statistics_{self.stationID}")
        if station_frame.isHidden():
            # Parent station is hidden
            # Hide the tracker as well
            statistic_frame.setHidden(True)
        else:
            statistic_frame.setHidden(False)

    def _update_texts(self, sessions: list):
        """
        Update the text elements of the statistics
        """
        # -Get Values-
        session_history = self.calculate_stats(sessions)
        total_time = session_history['total_time'].strftime('%H:%M')
        average_time = session_history['average_time'].strftime('%H:%M')
        total_sessions = str(session_history['total_sessions'])
        average_sessions = str(session_history['average_sessions'])
        # -Update texts-
        self.windows['main'].findChild(QLabel, f"label_statistics_totalTimeValue_{self.stationID}").setText(total_time)
        self.windows['main'].findChild(QLabel, f"label_statistics_avgSessionTimeValue_{self.stationID}").setText(average_time)  # nopep8
        self.windows['main'].findChild(QLabel, f"label_statistics_totalSessionsValue_{self.stationID}").setText(total_sessions)  # nopep8
        self.windows['main'].findChild(QLabel, f"label_statistics_avgSessionDayValue_{self.stationID}").setText(average_sessions)  # nopep8
        if self.station.device is not None:
            # Device is registered
            self.windows['main'].findChild(QLabel, f"label_statistics_deviceName_{self.stationID}").setText(self.station.device.deviceName)  # nopep8
        else:
            self.windows['main'].findChild(QLabel, f"label_statistics_deviceName_{self.stationID}").setText('N/A')
Пример #50
0
 def __init__(self, parent):
     super().__init__()
     self.parent = parent
     self.setupUi()
     self.timer = QTimer()
     self.timer.timeout.connect(self.progress_flash)
Пример #51
0
 def __init__(self):
     super().__init__()
     self.imit_peop = threading.Thread(target=imitation_people, args=())
     self._timer = QTimer()
     self.loadFinished.connect(self.startTimer)
     self._timer.timeout.connect(self.start_thread_imitation)
Пример #52
0
 def run(self):
     self.timer = QTimer()
     self.timer.timeout.connect(self.frame)
     self.timer.start(1000 / config.FPS)
Пример #53
0
class DebugView(QWidget, View):
    class DebugViewHistoryEntry(HistoryEntry):
        def __init__(self, memory_addr, address, is_raw):
            HistoryEntry.__init__(self)

            self.memory_addr = memory_addr
            self.address = address
            self.is_raw = is_raw

        def __repr__(self):
            if self.is_raw:
                return "<raw history: {}+{:0x} (memory: {:0x})>".format(
                    self.address['module'], self.address['offset'],
                    self.memory_addr)
            return "<code history: {:0x} (memory: {:0x})>".format(
                self.address, self.memory_addr)

    def __init__(self, parent, data):
        if not type(data) == BinaryView:
            raise Exception('expected widget data to be a BinaryView')

        self.bv = data

        self.debug_state = binjaplug.get_state(data)
        memory_view = self.debug_state.memory_view
        self.debug_state.ui.debug_view = self

        QWidget.__init__(self, parent)
        self.controls = ControlsWidget.DebugControlsWidget(
            self, "Controls", data, self.debug_state)
        View.__init__(self)

        self.setupView(self)

        self.current_offset = 0

        self.splitter = QSplitter(Qt.Orientation.Horizontal, self)

        frame = ViewFrame.viewFrameForWidget(self)
        self.memory_editor = LinearView(memory_view, frame)
        self.binary_editor = DisassemblyContainer(frame, data, frame)

        self.binary_text = TokenizedTextView(self, memory_view)
        self.is_raw_disassembly = False
        self.raw_address = 0

        self.is_navigating_history = False
        self.memory_history_addr = 0

        # TODO: Handle these and change views accordingly
        # Currently they are just disabled as the DisassemblyContainer gets confused
        # about where to go and just shows a bad view
        self.binary_editor.getDisassembly().actionHandler().bindAction(
            "View in Hex Editor", UIAction())
        self.binary_editor.getDisassembly().actionHandler().bindAction(
            "View in Linear Disassembly", UIAction())
        self.binary_editor.getDisassembly().actionHandler().bindAction(
            "View in Types View", UIAction())

        self.memory_editor.actionHandler().bindAction("View in Hex Editor",
                                                      UIAction())
        self.memory_editor.actionHandler().bindAction(
            "View in Disassembly Graph", UIAction())
        self.memory_editor.actionHandler().bindAction("View in Types View",
                                                      UIAction())

        small_font = QApplication.font()
        small_font.setPointSize(11)

        bv_layout = QVBoxLayout()
        bv_layout.setSpacing(0)
        bv_layout.setContentsMargins(0, 0, 0, 0)

        bv_label = QLabel("Loaded File")
        bv_label.setFont(small_font)
        bv_layout.addWidget(bv_label)
        bv_layout.addWidget(self.binary_editor)

        self.bv_widget = QWidget()
        self.bv_widget.setLayout(bv_layout)

        disasm_layout = QVBoxLayout()
        disasm_layout.setSpacing(0)
        disasm_layout.setContentsMargins(0, 0, 0, 0)

        disasm_label = QLabel("Raw Disassembly at PC")
        disasm_label.setFont(small_font)
        disasm_layout.addWidget(disasm_label)
        disasm_layout.addWidget(self.binary_text)

        self.disasm_widget = QWidget()
        self.disasm_widget.setLayout(disasm_layout)

        memory_layout = QVBoxLayout()
        memory_layout.setSpacing(0)
        memory_layout.setContentsMargins(0, 0, 0, 0)

        memory_label = QLabel("Debugged Process")
        memory_label.setFont(small_font)
        memory_layout.addWidget(memory_label)
        memory_layout.addWidget(self.memory_editor)

        self.memory_widget = QWidget()
        self.memory_widget.setLayout(memory_layout)

        self.splitter.addWidget(self.bv_widget)
        self.splitter.addWidget(self.memory_widget)

        # Equally sized
        self.splitter.setSizes([0x7fffffff, 0x7fffffff])

        layout = QVBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(0)
        layout.addWidget(self.controls)
        layout.addWidget(self.splitter, 100)
        self.setLayout(layout)

        self.needs_update = True
        self.update_timer = QTimer(self)
        self.update_timer.setInterval(200)
        self.update_timer.setSingleShot(False)
        self.update_timer.timeout.connect(lambda: self.updateTimerEvent())

        self.add_scripting_ref()

    def add_scripting_ref(self):
        # Hack: The interpreter is just a thread, so look through all threads
        # and assign our state to the interpreter's locals
        for thread in threading.enumerate():
            if type(thread) == PythonScriptingInstance.InterpreterThread:
                thread.locals["dbg"] = self.debug_state

    def getData(self):
        return self.bv

    def getFont(self):
        return binaryninjaui.getMonospaceFont(self)

    def getCurrentOffset(self):
        if not self.is_raw_disassembly:
            return self.binary_editor.getDisassembly().getCurrentOffset()
        return self.raw_address

    def getSelectionOffsets(self):
        if not self.is_raw_disassembly:
            return self.binary_editor.getDisassembly().getSelectionOffsets()
        return (self.raw_address, self.raw_address)

    def getCurrentFunction(self):
        if not self.is_raw_disassembly:
            return self.binary_editor.getDisassembly().getCurrentFunction()
        return None

    def getCurrentBasicBlock(self):
        if not self.is_raw_disassembly:
            return self.binary_editor.getDisassembly().getCurrentBasicBlock()
        return None

    def getCurrentArchitecture(self):
        if not self.is_raw_disassembly:
            return self.binary_editor.getDisassembly().getCurrentArchitecture()
        return None

    def getCurrentLowLevelILFunction(self):
        if not self.is_raw_disassembly:
            return self.binary_editor.getDisassembly(
            ).getCurrentLowLevelILFunction()
        return None

    def getCurrentMediumLevelILFunction(self):
        if not self.is_raw_disassembly:
            return self.binary_editor.getDisassembly(
            ).getCurrentMediumLevelILFunction()
        return None

    def getHistoryEntry(self):
        if self.is_navigating_history:
            return None
        memory_addr = self.memory_editor.getCurrentOffset()
        if memory_addr != self.memory_history_addr:
            self.memory_history_addr = memory_addr
        if self.is_raw_disassembly and self.debug_state.connected:
            rel_addr = self.debug_state.modules.absolute_addr_to_relative(
                self.raw_address)
            return DebugView.DebugViewHistoryEntry(memory_addr, rel_addr, True)
        else:
            address = self.binary_editor.getDisassembly().getCurrentOffset()
            return DebugView.DebugViewHistoryEntry(memory_addr, address, False)

    def navigateToHistoryEntry(self, entry):
        self.is_navigating_history = True
        if hasattr(entry, 'is_raw'):
            self.memory_editor.navigate(entry.memory_addr)
            if entry.is_raw:
                if self.debug_state.connected:
                    address = self.debug_state.modules.relative_addr_to_absolute(
                        entry.address)
                    self.navigate_raw(address)
            else:
                self.navigate_live(entry.address)

        View.navigateToHistoryEntry(self, entry)
        self.is_navigating_history = False

    def navigate(self, addr):
        if self.debug_state.memory_view.is_local_addr(addr):
            local_addr = self.debug_state.memory_view.remote_addr_to_local(
                addr)
            if self.debug_state.bv.read(local_addr, 1) and len(
                    self.debug_state.bv.get_functions_containing(
                        local_addr)) > 0:
                return self.navigate_live(local_addr)

        # This runs into conflicts if some other address space is mapped over
        # where the local BV is currently loaded, but this is was less likely
        # than the user navigating to a function from the UI
        if self.debug_state.bv.read(addr, 1) and len(
                self.debug_state.bv.get_functions_containing(addr)) > 0:
            return self.navigate_live(addr)

        return self.navigate_raw(addr)

    def navigate_live(self, addr):
        self.show_raw_disassembly(False)
        return self.binary_editor.getDisassembly().navigate(addr)

    def navigate_raw(self, addr):
        if not self.debug_state.connected:
            # Can't navigate to remote addr when disconnected
            return False
        self.raw_address = addr
        self.show_raw_disassembly(True)
        self.load_raw_disassembly(addr)
        return True

    def notifyMemoryChanged(self):
        self.needs_update = True

    def updateTimerEvent(self):
        if self.needs_update:
            self.needs_update = False

            # Refresh the editor
            if not self.debug_state.connected:
                self.memory_editor.navigate(0)
                return

            # self.memory_editor.navigate(self.debug_state.stack_pointer)

    def showEvent(self, event):
        if not event.spontaneous():
            self.update_timer.start()
            self.add_scripting_ref()

    def hideEvent(self, event):
        if not event.spontaneous():
            self.update_timer.stop()

    def shouldBeVisible(self, view_frame):
        if view_frame is None:
            return False
        else:
            return True

    def load_raw_disassembly(self, start_ip):
        # Read a few instructions from rip and disassemble them
        inst_count = 50

        arch_dis = self.debug_state.remote_arch
        rip = self.debug_state.ip

        # Assume the worst, just in case
        read_length = arch_dis.max_instr_length * inst_count
        data = self.debug_state.memory_view.read(start_ip, read_length)

        lines = []

        # Append header line
        tokens = [
            InstructionTextToken(
                InstructionTextTokenType.TextToken,
                "(Code not backed by loaded file, showing only raw disassembly)"
            )
        ]
        contents = DisassemblyTextLine(tokens, start_ip)
        line = LinearDisassemblyLine(LinearDisassemblyLineType.BasicLineType,
                                     None, None, 0, contents)
        lines.append(line)

        total_read = 0
        for i in range(inst_count):
            line_addr = start_ip + total_read
            (insn_tokens,
             length) = arch_dis.get_instruction_text(data[total_read:],
                                                     line_addr)

            if insn_tokens is None:
                insn_tokens = [
                    InstructionTextToken(InstructionTextTokenType.TextToken,
                                         "??")
                ]
                length = arch_dis.instr_alignment
                if length == 0:
                    length = 1

            tokens = []
            color = HighlightStandardColor.NoHighlightColor
            if line_addr == rip:
                if self.debug_state.breakpoints.contains_absolute(start_ip +
                                                                  total_read):
                    # Breakpoint & pc
                    tokens.append(
                        InstructionTextToken(
                            InstructionTextTokenType.TagToken,
                            self.debug_state.ui.get_breakpoint_tag_type().icon
                            + ">",
                            width=5))
                    color = HighlightStandardColor.RedHighlightColor
                else:
                    # PC
                    tokens.append(
                        InstructionTextToken(
                            InstructionTextTokenType.TextToken, " ==> "))
                    color = HighlightStandardColor.BlueHighlightColor
            else:
                if self.debug_state.breakpoints.contains_absolute(start_ip +
                                                                  total_read):
                    # Breakpoint
                    tokens.append(
                        InstructionTextToken(
                            InstructionTextTokenType.TagToken,
                            self.debug_state.ui.get_breakpoint_tag_type().icon,
                            width=5))
                    color = HighlightStandardColor.RedHighlightColor
                else:
                    # Regular line
                    tokens.append(
                        InstructionTextToken(
                            InstructionTextTokenType.TextToken, "     "))
            # Address
            tokens.append(
                InstructionTextToken(
                    InstructionTextTokenType.AddressDisplayToken,
                    hex(line_addr)[2:], line_addr))
            tokens.append(
                InstructionTextToken(InstructionTextTokenType.TextToken, "  "))
            tokens.extend(insn_tokens)

            # Convert to linear disassembly line
            contents = DisassemblyTextLine(tokens, line_addr, color=color)
            line = LinearDisassemblyLine(
                LinearDisassemblyLineType.CodeDisassemblyLineType, None, None,
                0, contents)
            lines.append(line)

            total_read += length

        # terrible workaround for libshiboken conversion issue
        for line in lines:
            # line is LinearDisassemblyLine
            last_tok = line.contents.tokens[-1]
            #if last_tok.type != InstructionTextTokenType.PossibleAddressToken: continue
            #if last_tok.width != 18: continue # strlen("0xFFFFFFFFFFFFFFF0")
            if last_tok.size != 8: continue
            #print('fixing: %s' % line)
            last_tok.value &= 0x7FFFFFFFFFFFFFFF

        self.binary_text.setLines(lines)

    def show_raw_disassembly(self, raw):
        if raw != self.is_raw_disassembly:
            self.splitter.replaceWidget(
                0, self.disasm_widget if raw else self.bv_widget)
            self.is_raw_disassembly = raw

    def refresh_raw_disassembly(self):
        if not self.debug_state.connected:
            # Can't navigate to remote addr when disconnected
            return

        if self.is_raw_disassembly:
            self.load_raw_disassembly(self.getCurrentOffset())
Пример #54
0
    def __init__(self, parent, data):
        if not type(data) == BinaryView:
            raise Exception('expected widget data to be a BinaryView')

        self.bv = data

        self.debug_state = binjaplug.get_state(data)
        memory_view = self.debug_state.memory_view
        self.debug_state.ui.debug_view = self

        QWidget.__init__(self, parent)
        self.controls = ControlsWidget.DebugControlsWidget(
            self, "Controls", data, self.debug_state)
        View.__init__(self)

        self.setupView(self)

        self.current_offset = 0

        self.splitter = QSplitter(Qt.Orientation.Horizontal, self)

        frame = ViewFrame.viewFrameForWidget(self)
        self.memory_editor = LinearView(memory_view, frame)
        self.binary_editor = DisassemblyContainer(frame, data, frame)

        self.binary_text = TokenizedTextView(self, memory_view)
        self.is_raw_disassembly = False
        self.raw_address = 0

        self.is_navigating_history = False
        self.memory_history_addr = 0

        # TODO: Handle these and change views accordingly
        # Currently they are just disabled as the DisassemblyContainer gets confused
        # about where to go and just shows a bad view
        self.binary_editor.getDisassembly().actionHandler().bindAction(
            "View in Hex Editor", UIAction())
        self.binary_editor.getDisassembly().actionHandler().bindAction(
            "View in Linear Disassembly", UIAction())
        self.binary_editor.getDisassembly().actionHandler().bindAction(
            "View in Types View", UIAction())

        self.memory_editor.actionHandler().bindAction("View in Hex Editor",
                                                      UIAction())
        self.memory_editor.actionHandler().bindAction(
            "View in Disassembly Graph", UIAction())
        self.memory_editor.actionHandler().bindAction("View in Types View",
                                                      UIAction())

        small_font = QApplication.font()
        small_font.setPointSize(11)

        bv_layout = QVBoxLayout()
        bv_layout.setSpacing(0)
        bv_layout.setContentsMargins(0, 0, 0, 0)

        bv_label = QLabel("Loaded File")
        bv_label.setFont(small_font)
        bv_layout.addWidget(bv_label)
        bv_layout.addWidget(self.binary_editor)

        self.bv_widget = QWidget()
        self.bv_widget.setLayout(bv_layout)

        disasm_layout = QVBoxLayout()
        disasm_layout.setSpacing(0)
        disasm_layout.setContentsMargins(0, 0, 0, 0)

        disasm_label = QLabel("Raw Disassembly at PC")
        disasm_label.setFont(small_font)
        disasm_layout.addWidget(disasm_label)
        disasm_layout.addWidget(self.binary_text)

        self.disasm_widget = QWidget()
        self.disasm_widget.setLayout(disasm_layout)

        memory_layout = QVBoxLayout()
        memory_layout.setSpacing(0)
        memory_layout.setContentsMargins(0, 0, 0, 0)

        memory_label = QLabel("Debugged Process")
        memory_label.setFont(small_font)
        memory_layout.addWidget(memory_label)
        memory_layout.addWidget(self.memory_editor)

        self.memory_widget = QWidget()
        self.memory_widget.setLayout(memory_layout)

        self.splitter.addWidget(self.bv_widget)
        self.splitter.addWidget(self.memory_widget)

        # Equally sized
        self.splitter.setSizes([0x7fffffff, 0x7fffffff])

        layout = QVBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(0)
        layout.addWidget(self.controls)
        layout.addWidget(self.splitter, 100)
        self.setLayout(layout)

        self.needs_update = True
        self.update_timer = QTimer(self)
        self.update_timer.setInterval(200)
        self.update_timer.setSingleShot(False)
        self.update_timer.timeout.connect(lambda: self.updateTimerEvent())

        self.add_scripting_ref()
Пример #55
0
 def testDestructor(self):
     w = Test()
     w.show()
     QTimer.singleShot(0, w.close)
Пример #56
0
        try:
            logger.setLevel(conf['log_level'].upper())
        except KeyError:
            logger.setLevel('INFO')

        conf['interval'] = conf['interval'] if 'interval' in conf else 30

        if getenv('GITHUB_TOKEN'):
            conf['token'] = getenv('GITHUB_TOKEN')
        elif 'token' not in conf:
            raise ValueError('Gihub token has to be provided')

    except (IOError, yaml.YAMLError, ValueError) as e:
        logger.error(e)
        print(e)
        sys.exit(1)

    app = QtWidgets.QApplication()
    widget = QtWidgets.QWidget()
    tray_app = GPRMon.TrayIcon(widget, conf=conf)
    tray_app.show()

    timer = QTimer(widget)
    timer.setInterval(conf['interval'] * 1000)
    timer.timeout.connect(tray_app.update_prs)
    timer.start()

    logger.info('Starting gprmon...')
    sys.exit(app.exec_())
 def startup_check(self):
     QTimer.singleShot(200, QApplication.instance().quit)
Пример #58
0
class TaskForm(QFrame):
    def __init__(self, parent):
        super().__init__()
        self.parent = parent
        self.setupUi()
        self.timer = QTimer()
        self.timer.timeout.connect(self.progress_flash)

    def setupUi(self):
        self.setWindowTitle('设置任务详情')
        self.flayout = QFormLayout(self)
        form_dict = dict(关键词=QLineEdit(),
                         休息数量=QLineEdit(),
                         休息间隔=QLineEdit(),
                         持续时间=QLineEdit())
        form_data_dict = dict()

        for i in form_dict:
            if i == '休息数量':
                form_data_dict[i] = '20'
                form_dict[i].setPlaceholderText('单位:个;默认20个')
            elif i == '休息间隔':
                form_data_dict[i] = '10'
                form_dict[i].setPlaceholderText('单位:秒;默认10秒')
            elif i == '持续时间':
                form_data_dict[i] = '45'
                form_dict[i].setPlaceholderText('单位:分钟;默认45分钟')
            else:
                form_data_dict[i] = ''
                form_dict[i].setPlaceholderText('关键词')
            form_dict[i].textChanged.connect(self.on_text_changed(i))
            self.flayout.addRow(i, form_dict[i])

        start_btn = QPushButton('开始任务')
        start_btn.clicked.connect(self.on_start_task)
        self.flayout.addRow(start_btn)
        self.form_data_dict = form_data_dict
        self.resize(260, 180)
        self.show()

    def on_text_changed(self, which):
        def handle_text_changed(value):
            self.form_data_dict[which] = value

        return handle_text_changed

    def on_start_task(self):
        if not self.form_data_dict['关键词']:
            return QMessageBox.warning(self, '错误提示', '请输入关键词', QMessageBox.Ok)
        self.hide()
        self.working = True
        self.run_task()

    def run_task(self):
        self.parent.hub.run(form_data_dict=self.form_data_dict,
                            serial=self.parent.serial)

        self.task = self.parent.hub.task_pool[self.parent.serial]
        self.parent.total_time_left.setRange(
            0,
            int(self.task.form_data_dict['持续时间']) * 60)
        self.parent.qty_left.setRange(0, int(self.task.form_data_dict['休息数量']))
        self.timer.start(1000)
        # self.pthread = PThread(self)
        # self.pthread.start()

    def progress_flash(self):
        self.task = self.parent.hub.task_pool[self.parent.serial]
        self.parent.total_time_left.setValue(
            int(time.time() - self.task.start_at))
        self.parent.qty_left.setValue(int(self.task.qty))
        print(time.time() - self.task.start_at)
Пример #59
0
 def setUp(self):
     #Acquire resources
     UsesQCoreApplication.setUp(self)
     self.watchdog = WatchDog(self)
     self.timer = QTimer()
     self.called = False
Пример #60
0
class BoidsApp(QWidget):
    def __init__(self):
        super(BoidsApp, self).__init__()
        self.setWindowTitle('Boids')

        self._boids_environment = BoidsEnvironment()

        layout = QHBoxLayout()
        self._boids_widget = BoidsWidget()
        self._boids_widget.set_boids_environment(self._boids_environment)
        self._boids_widget.setMinimumWidth(550)
        self._form_layout = QFormLayout()

        self._is_running_checkbox = QCheckBox()
        self._is_running_checkbox.setChecked(True)
        self._is_running_checkbox.stateChanged.connect(
            self.__is_running_changed)

        self._tick_button = QPushButton()
        self._tick_button.setText('Tick')
        self._tick_button.clicked.connect(self.__do_tick)

        self._total_spinbox = _make_spinbox(TOTAL_BOIDS, 0, 9999)
        self._total_spinbox.valueChanged.connect(self.__total_changed)

        self._max_speed_spinbox = _make_double_spinbox(DEFAULT_MAX_SPEED, 0.0,
                                                       9999.0, 0.1)
        self._max_speed_spinbox.valueChanged.connect(self.__max_speed_changed)

        self._eyesight_radius_spinbox = _make_double_spinbox(
            DEFAULT_MAX_DISTANCE, 0.0, 9999.0, 0.5)
        self._eyesight_radius_spinbox.valueChanged.connect(
            self.__eyesight_radius_changed)

        self._eyesight_angle_spinbox = _make_double_spinbox(
            DEFAULT_EYE_SIGHT_ANGLE, 0.0, 360.0, 1.0)
        self._eyesight_angle_spinbox.valueChanged.connect(
            self.__eyesight_angle_changed)

        self._separation_distance_spinbox = _make_double_spinbox(
            SEPARATION_DISTANCE, 0.0, 9999.0, 1.0)
        self._separation_distance_spinbox.valueChanged.connect(
            self.__separation_distance_changed)

        self._separation_value_spinbox = _make_double_spinbox(
            SEPARATION_VALUE, 0.0, 1.0, 0.1)
        self._separation_value_spinbox.valueChanged.connect(
            self.__separation_value_changed)

        self._cohesion_value_spinbox = _make_double_spinbox(
            COHESION_VALUE, 0.0, 1.0, 0.1)
        self._cohesion_value_spinbox.valueChanged.connect(
            self.__cohesion_value_changed)

        self._alignment_value_spinbox = _make_double_spinbox(
            ALIGNMENT_VALUE, 0.0, 1.0, 0.1)
        self._alignment_value_spinbox.valueChanged.connect(
            self.__alignment_value_changed)

        self._generate_button = QPushButton()
        self._generate_button.setText('Generate')

        self._generate_button.clicked.connect(self._generate_environment)

        self._form_layout.addRow('Total Boids', self._total_spinbox)
        self._form_layout.addRow('Separation', self._separation_value_spinbox)
        self._form_layout.addRow('Cohesion', self._cohesion_value_spinbox)
        self._form_layout.addRow('Alignment', self._alignment_value_spinbox)

        self._form_layout.addRow('Max Speed', self._max_speed_spinbox)
        self._form_layout.addRow('Eyesight Radius',
                                 self._eyesight_radius_spinbox)
        self._form_layout.addRow('Eyesight Angle (degrees)',
                                 self._eyesight_angle_spinbox)
        self._form_layout.addRow('Separation Distance',
                                 self._separation_distance_spinbox)

        self._form_layout.addRow('Running', self._is_running_checkbox)
        self._form_layout.addWidget(self._generate_button)
        self._form_layout.addWidget(self._tick_button)

        layout.addWidget(self._boids_widget)
        layout.addLayout(self._form_layout)
        self.setLayout(layout)

        self.tick_timer = QTimer()
        self.tick_timer.setInterval(int(1000 / 24))
        self.tick_timer.timeout.connect(self._tick)
        self.tick_timer.start()

    def showEvent(self, event):
        self._generate_environment()

    def _tick(self):
        tick_environment(self._boids_environment)
        self._boids_widget.update()

    def __do_tick(self):
        is_running = self._boids_environment.is_running
        self._boids_environment.is_running = True
        tick_environment(self._boids_environment)
        self._boids_widget.update()
        self._boids_environment.is_running = is_running

    def __total_changed(self):
        total = self._total_spinbox.value()
        _change_total(self._boids_environment, total)

    def __cohesion_value_changed(self):
        value = self._cohesion_value_spinbox.value()
        for boid in self._boids_environment.boids:
            boid.cohesion = value

    def __alignment_value_changed(self):
        value = self._alignment_value_spinbox.value()
        for boid in self._boids_environment.boids:
            boid.alignment = value

    def __max_speed_changed(self):
        self._boids_environment.max_speed = self._max_speed_spinbox.value()

    def __eyesight_radius_changed(self):
        value = self._eyesight_radius_spinbox.value()
        self._boids_environment.neighbour_max_distance = value
        self._boids_environment.neighbour_max_distance2 = value * value

    def __is_running_changed(self):
        self._boids_environment.is_running = self._is_running_checkbox.isChecked(
        )

    def __separation_distance_changed(self):
        value = self._separation_distance_spinbox.value()
        for boid in self._boids_environment.boids:
            boid.separation_distance = value
            boid.separation_distance2 = value * value

    def __separation_value_changed(self):
        value = self._separation_value_spinbox.value()
        for boid in self._boids_environment.boids:
            boid.separation = value

    def __eyesight_angle_changed(self):
        for boid in self._boids_environment.boids:
            boid.eyesight = math.radians(self._eyesight_angle_spinbox.value())

    def _generate_environment(self):
        max_speed = self._max_speed_spinbox.value()
        separation = self._separation_value_spinbox.value()
        environment = _make_environment(self._total_spinbox.value(),
                                        Rect(0.0, 0.0, 500.0, 500.0),
                                        max_speed, separation)
        environment.is_running = self._is_running_checkbox.isChecked()
        environment.max_speed = max_speed
        self._boids_environment = environment
        self._boids_widget.set_boids_environment(self._boids_environment)

    def sizeHint(self):
        return QSize(800, 550)