class SplashScreen(QtWidgets.QSplashScreen):
    """
    Generic splashcreen. Must always be spawned in a new thread, while the original thread
    executes qt_app.processEvents() in a while loop, until it should be stopped.
    """

    def __init__(self, background=True):
        """
        background: set the image as the background.
        The layout changes if background ist set.
        Without: the spinner plays in the middle. A screenshot is taken when the splashcreen
        initializes and set as background, so changing elements in the gui are not seen by the user.
        With: background image is scaled and spinner plays in the bottom middle.
        """
        self._is_background_set = background
        self._label = None
        self._spinner = None

        if background:
            pixmap = QtGui.QPixmap(str(get_asset_file("gui_base", "loading")))
            pixmap = pixmap.scaled(800, 480, transformMode=Qt.SmoothTransformation)
        else:
            if not config.qt_app:
                return # can not really happen...
            screen = config.qt_app.primaryScreen()
            pixmap = screen.grabWindow(voidptr(0))

        QtWidgets.QSplashScreen.__init__(self, pixmap)
        if config.DEBUG_LEVEL > 0: # unlock gui when debugging
            self.setWindowFlags(Qt.FramelessWindowHint)

    def mousePressEvent(self, event):  # pylint: disable=unused-argument, invalid-name, no-self-use
        """ Do nothing on mouse click. Otherwise it disappears. """
        return

    def showEvent(self, event):  # pylint: disable=unused-argument, invalid-name
        """ Start movie, when it is shown. """

        # show version label
        if self._is_background_set:
            self._label = QtWidgets.QLabel(self)
            self._label.setText(WAQD_VERSION + "   ")
            self._label.setAlignment(Qt.AlignmentFlag(Qt.AlignBottom | Qt.AlignRight))
            self._label.setGeometry(0, 0, 800, 480)
            self._label.show()
        self._spinner = WaitingSpinner(self, centerOnParent=False, radius=30, roundness=60,
                                      line_length=20, line_width=6, speed=0.5, color=(15, 67, 116))
        if self._is_background_set:
            self._spinner.setGeometry(int(self.width()/2 - self._spinner.width()/2),
                                     int(self.height() - self._spinner.height() - 20),
                                     self._spinner.width(),
                                     self._spinner.height())
        else:
            self._spinner.setColor("white")
            self._spinner.setInnerRadius(40)
            self._spinner.setLineLength(40)
            self._spinner.setLineWidth(8)
            self._spinner.setTrailFadePercentage(90)
            self._spinner.setGeometry(int(self.width()/2 - self._spinner.width()/2),
                                      int(self.height()/2 - self._spinner.height()/2),
                                     self._spinner.width(),
                                     self._spinner.height())
        self._spinner.start()

    def hideEvent(self, event):  # pylint: disable=unused-argument, invalid-name
        """ Stop movie, when it is hidden. """
        if self._spinner:
            self._spinner.stop()
예제 #2
0
class Demo(QWidget):
    sb_roundness = None
    sb_opacity = None
    sb_fadeperc = None
    sb_lines = None
    sb_line_length = None
    sb_line_width = None
    sb_inner_radius = None
    sb_rev_s = None

    btn_start = None
    btn_stop = None
    btn_pick_color = None

    spinner = None

    def __init__(self):
        super().__init__()
        self.init_ui()

    def init_ui(self):
        grid = QGridLayout()
        groupbox1 = QGroupBox()
        groupbox1_layout = QHBoxLayout()
        groupbox2 = QGroupBox()
        groupbox2_layout = QGridLayout()
        button_hbox = QHBoxLayout()
        self.setLayout(grid)
        self.setWindowTitle("QtWaitingSpinner Demo")
        self.setWindowFlags(Qt.Dialog)

        # SPINNER
        self.spinner = WaitingSpinner(self)

        # Spinboxes
        self.sb_roundness = QDoubleSpinBox()
        self.sb_opacity = QDoubleSpinBox()
        self.sb_fadeperc = QDoubleSpinBox()
        self.sb_lines = QSpinBox()
        self.sb_line_length = QDoubleSpinBox()
        self.sb_line_width = QDoubleSpinBox()
        self.sb_inner_radius = QDoubleSpinBox()
        self.sb_rev_s = QDoubleSpinBox()

        # set spinbox default values
        self.sb_roundness.setValue(70)
        self.sb_roundness.setRange(0, 9999)
        self.sb_opacity.setValue(15)
        self.sb_opacity.setRange(0, 9999)
        self.sb_fadeperc.setValue(70)
        self.sb_fadeperc.setRange(0, 9999)
        self.sb_lines.setValue(12)
        self.sb_lines.setRange(1, 9999)
        self.sb_line_length.setValue(10)
        self.sb_line_length.setRange(0, 9999)
        self.sb_line_width.setValue(5)
        self.sb_line_width.setRange(0, 9999)
        self.sb_inner_radius.setValue(10)
        self.sb_inner_radius.setRange(0, 9999)
        self.sb_rev_s.setValue(1)
        self.sb_rev_s.setRange(0.1, 9999)

        # Buttons
        self.btn_start = QPushButton("Start")
        self.btn_stop = QPushButton("Stop")
        self.btn_pick_color = QPushButton("Pick Color")
        self.btn_show_init = QPushButton("Show init args")

        # Connects
        self.sb_roundness.valueChanged.connect(self.set_roundness)
        self.sb_opacity.valueChanged.connect(self.set_opacity)
        self.sb_fadeperc.valueChanged.connect(self.set_fadeperc)
        self.sb_lines.valueChanged.connect(self.set_lines)
        self.sb_line_length.valueChanged.connect(self.set_line_length)
        self.sb_line_width.valueChanged.connect(self.set_line_width)
        self.sb_inner_radius.valueChanged.connect(self.set_inner_radius)
        self.sb_rev_s.valueChanged.connect(self.set_rev_s)

        self.btn_start.clicked.connect(self.spinner_start)
        self.btn_stop.clicked.connect(self.spinner_stop)
        self.btn_pick_color.clicked.connect(self.show_color_picker)
        self.btn_show_init.clicked.connect(self.show_init_args)

        # Layout adds
        groupbox1_layout.addWidget(self.spinner)
        groupbox1.setLayout(groupbox1_layout)

        groupbox2_layout.addWidget(QLabel("Roundness:"), *(1, 1))
        groupbox2_layout.addWidget(self.sb_roundness, *(1, 2))
        groupbox2_layout.addWidget(QLabel("Opacity:"), *(2, 1))
        groupbox2_layout.addWidget(self.sb_opacity, *(2, 2))
        groupbox2_layout.addWidget(QLabel("Fade Perc:"), *(3, 1))
        groupbox2_layout.addWidget(self.sb_fadeperc, *(3, 2))
        groupbox2_layout.addWidget(QLabel("Lines:"), *(4, 1))
        groupbox2_layout.addWidget(self.sb_lines, *(4, 2))
        groupbox2_layout.addWidget(QLabel("Line Length:"), *(5, 1))
        groupbox2_layout.addWidget(self.sb_line_length, *(5, 2))
        groupbox2_layout.addWidget(QLabel("Line Width:"), *(6, 1))
        groupbox2_layout.addWidget(self.sb_line_width, *(6, 2))
        groupbox2_layout.addWidget(QLabel("Inner Radius:"), *(7, 1))
        groupbox2_layout.addWidget(self.sb_inner_radius, *(7, 2))
        groupbox2_layout.addWidget(QLabel("Rev/s:"), *(8, 1))
        groupbox2_layout.addWidget(self.sb_rev_s, *(8, 2))

        groupbox2.setLayout(groupbox2_layout)

        button_hbox.addWidget(self.btn_start)
        button_hbox.addWidget(self.btn_stop)
        button_hbox.addWidget(self.btn_pick_color)
        button_hbox.addWidget(self.btn_show_init)

        grid.addWidget(groupbox1, *(1, 1))
        grid.addWidget(groupbox2, *(1, 2))
        grid.addLayout(button_hbox, *(2, 1))

        self.spinner.start()
        self.show()

    def set_roundness(self):
        self.spinner.setRoundness(self.sb_roundness.value())

    def set_opacity(self):
        self.spinner.setMinimumTrailOpacity(self.sb_opacity.value())

    def set_fadeperc(self):
        self.spinner.setTrailFadePercentage(self.sb_fadeperc.value())

    def set_lines(self):
        self.spinner.setNumberOfLines(self.sb_lines.value())

    def set_line_length(self):
        self.spinner.setLineLength(self.sb_line_length.value())

    def set_line_width(self):
        self.spinner.setLineWidth(self.sb_line_width.value())

    def set_inner_radius(self):
        self.spinner.setInnerRadius(self.sb_inner_radius.value())

    def set_rev_s(self):
        self.spinner.setRevolutionsPerSecond(self.sb_rev_s.value())

    def spinner_start(self):
        self.spinner.start()

    def spinner_stop(self):
        self.spinner.stop()

    def show_color_picker(self):
        self.spinner.setColor(QColorDialog.getColor())

    def show_init_args(self):
        text = ('WaitingSpinner(\n'
                '    parent,\n'
                '    roundness={}, opacity={},\n'
                '    fade={}, radius={}, lines={},\n'
                '    line_length={}, line_width={},\n'
                '    speed={}, color={}\n'
                ')\n').format(self.sb_roundness.value(),
                              self.sb_opacity.value(),
                              self.sb_fadeperc.value(),
                              self.sb_inner_radius.value(),
                              self.sb_lines.value(),
                              self.sb_line_length.value(),
                              self.sb_line_width.value(),
                              self.sb_rev_s.value(),
                              self.spinner.color.getRgb()[:3])

        msg_box = QMessageBox(text=text)
        msg_box.setWindowTitle('Text was copied to clipboard')
        cb = QApplication.clipboard()
        cb.clear(mode=cb.Clipboard)
        cb.setText(text, mode=cb.Clipboard)
        print(text)
        msg_box.exec_()
예제 #3
0
class JobList(QWidget):
    def __init__(self, connection):
        super(JobList, self).__init__()
        self.connection = connection
        self.jobTableModel = JobTableModel()
        self.jobTableView = JobTableView(self.jobTableModel)
        #self.dbTable = config.DB.table("jobs")
        self.log = Logger(jobList=self)
        self.listJobs()
        self.tunnels = {}
        self.threadpool = QThreadPool()

        # Waiting spinner
        self.spinner = WaitingSpinner(self.jobTableView)
        self.spinner.setNumberOfLines(18)
        self.spinner.setInnerRadius(12)
        self.spinner.setLineLength(20)
        self.spinner.setLineWidth(5)

        # Init, but do not start timers before
        # a connection with the server is established
        self.timer = QTimer(self)
        self.timer.setInterval(1000)
        self.timer.timeout.connect(self.smartSync)

        self.elapsedTimer = QElapsedTimer()
        self.loaded = False
        self.jobsPending = True

    def isConnected(self):
        self.timer.start()
        self.elapsedTimer.restart()
        self.labelJobs.setText("Connecting with %s" %
                               self.connection.connectionName)
        self.labelJobs.setStyleSheet("QLabel { color : black; }")

    def isDisconnected(self):
        self.timer.stop()
        self.labelJobs.setText("Not connected with  %s" %
                               self.connection.connectionName)
        self.labelJobs.setStyleSheet("QLabel { color : red; }")

    def updateConnection(self, connection):
        self.connection = connection

    def smartSync(self):

        if self.loaded:
            minutes = floor(self.elapsedTimer.elapsed() / (60 * 1000))
            seconds = self.elapsedTimer.elapsed() % (60 * 1000) / 1000

            self.labelJobs.setText(
                "Jobs (%d). Last update: %d min and %d sec ago" %
                (len(self.jobTableModel.jobs), minutes, seconds))

            if self.jobsPending and self.elapsedTimer.hasExpired(
                    config.SYNC_JOBS_WITH_SERVER_INTERVAL_SECONDS_SHORT *
                    1000):
                self.syncJobs()
            elif self.elapsedTimer.hasExpired(
                    config.SYNC_JOBS_WITH_SERVER_INTERVAL_SECONDS_LONG * 1000):
                self.syncJobs()

    def updateJobsPending(self):
        self.jobsPending = False
        for jobID, job in self.jobTableModel.jobs.items():
            if job.status == 0:
                self.jobsPending = True
                return

    def listJobs(self):

        basedir = "icons"
        if hasattr(sys, 'frozen') and hasattr(sys, '_MEIPASS'):
            basedir = os.path.join(sys._MEIPASS, "icons")

        self.vLayout = QVBoxLayout()

        ### Add label
        font = QFont()
        font.setBold(True)
        font.setWeight(75)

        if self.connection.isConnected():
            self.labelJobs = QLabel("Jobs (%d)" % len(self.jobTableModel.jobs))
            self.labelJobs.setStyleSheet("QLabel { color : black; }")
        else:
            self.labelJobs = QLabel("Not connected with %s" %
                                    self.connection.connectionName)
            self.labelJobs.setStyleSheet("QLabel { color : red; }")
        self.labelJobs.setFont(font)
        self.vLayout.addWidget(self.labelJobs)

        ### Add jobView
        self.jobTableView.setView()
        self.vLayout.addWidget(self.jobTableView)

        ### Buttons
        hLayoutWidget = QWidget()
        hLayoutWidget.setGeometry(QRect(0, 0, 200, 200))
        hLayout = QHBoxLayout(hLayoutWidget)

        # Add job
        icon = QIcon()
        icon.addPixmap(QPixmap(os.path.join(basedir, "add_job_normal.png")),
                       QIcon.Normal)
        icon.addPixmap(QPixmap(os.path.join(basedir, "add_job_hot.png")),
                       QIcon.Active)
        icon.addPixmap(QPixmap(os.path.join(basedir, "add_job_disabled.png")),
                       QIcon.Disabled)

        self.addButton = QPushButton("Add Job")
        self.addButton.setObjectName("add_job")
        self.addButton.setIcon(icon)
        self.addButton.setIconSize(QSize(24, 24))
        self.addButton.installEventFilter(self)
        self.addButton.setEnabled(False)
        hLayout.addWidget(self.addButton)

        # Delete job
        icon1 = QIcon()
        icon1.addPixmap(
            QPixmap(os.path.join(basedir, "delete_jobs_normal.png")),
            QIcon.Normal)
        icon1.addPixmap(QPixmap(os.path.join(basedir, "delete_jobs_hot.png")),
                        QIcon.Active)
        icon1.addPixmap(
            QPixmap(os.path.join(basedir, "delete_jobs_disabled.png")),
            QIcon.Disabled)

        self.deleteAllButton = QPushButton("Delete all jobs")
        self.deleteAllButton.setObjectName("delete_jobs")
        self.deleteAllButton.setIcon(icon1)
        self.deleteAllButton.setIconSize(QSize(24, 24))
        self.deleteAllButton.installEventFilter(self)
        self.deleteAllButton.setEnabled(False)
        hLayout.addWidget(self.deleteAllButton)

        # syncButton
        icon2 = QIcon()
        icon2.addPixmap(QPixmap(os.path.join(basedir, "sync_jobs_normal.png")),
                        QIcon.Normal)
        icon2.addPixmap(QPixmap(os.path.join(basedir, "sync_job_hot.png")),
                        QIcon.Active)
        icon2.addPixmap(
            QPixmap(os.path.join(basedir, "sync_jobs_disabled.png")),
            QIcon.Disabled)

        self.syncButton = QPushButton("Sync jobs")
        self.syncButton.setObjectName("sync_jobs")
        self.syncButton.setIcon(icon2)
        self.syncButton.setIconSize(QSize(24, 24))
        self.syncButton.installEventFilter(self)
        self.syncButton.setEnabled(False)
        hLayout.addWidget(self.syncButton)

        # clearLogButton
        icon3 = QIcon()
        icon3.addPixmap(QPixmap(os.path.join(basedir, "clear_log_normal.png")),
                        QIcon.Normal)
        icon3.addPixmap(QPixmap(os.path.join(basedir, "clear_log_hot.png")),
                        QIcon.Active)
        icon3.addPixmap(
            QPixmap(os.path.join(basedir, "clear_log_disabled.png")),
            QIcon.Disabled)

        self.clearLogButton = QPushButton("Clear log")
        self.clearLogButton.setObjectName("clear_log")
        self.clearLogButton.setIcon(icon3)
        self.clearLogButton.setIconSize(QSize(24, 24))
        self.clearLogButton.installEventFilter(self)
        self.clearLogButton.setEnabled(False)
        hLayout.addWidget(self.clearLogButton)

        self.vLayout.addWidget(hLayoutWidget)

        # Log
        labelLogOutput = QLabel("Log output:")
        labelLogOutput.setFont(font)
        self.vLayout.addWidget(labelLogOutput)

        # Log edit box
        self.logEdit = QTextEdit()
        self.logEdit.setGeometry(QRect(0, 0, 800, 400))
        self.logEdit.setReadOnly(True)
        self.vLayout.addWidget(self.logEdit)

        # Button handling
        self.addButton.clicked.connect(self.addJob)
        self.deleteAllButton.pressed.connect(self.confirmDeleteAllJobs)
        self.syncButton.pressed.connect(self.syncJobs)
        self.clearLogButton.pressed.connect(self.clearLog)

        self.setLayout(self.vLayout)

    def clearLog(self):
        self.logEdit.clear()

    def syncJobs(self):
        # Start a worker that calls the __connect function and
        # synchronizes the jobs running on the server

        Q = JobQueue.getQueueObject(self.connection)

        # connect to host
        worker = Worker(Q.syncJobs, task="sync")
        worker.signals.updateStarted.connect(self.updateStarted)
        worker.signals.progress.connect(self.writeLog)
        worker.signals.jobsSynced.connect(self.jobTableModel.setJobs)
        worker.signals.updateFinished.connect(self.updateFinished)

        self.threadpool.start(worker)
        self.updateJobsPending()

    def addJob(self):
        frm = JobForm(Job(connection=self.connection))
        frm.setModal(True)
        if frm.exec():

            port = randint(8787, 10000)

            # Submit
            job = Job(
                connection=self.connection,
                jobID=0,
                jobName=frm.edtJobName.text(),
                projectFolder=frm.cmbProjectFolder.currentText().strip(),
                startTime=datetime.now().strftime("%m/%d/%Y %H:%M:%S"),
                runTime=frm.spinRuntime.value(),
                nCPU=frm.spinNrOfCPU.value(),
                nGPU=frm.spinNrOfGPU.value(),
                memory=frm.spinMemory.value(),
                singularityImg=frm.cmbSingularityImg.currentText().strip(),
                workerNode=None,
                status=0,
                editor=frm.cmbEditor.currentText().strip(),
                port=port)

            self.submitJob(job)

    def viewJob(self, jobID):
        frm = JobForm(self.jobTableModel.jobs[jobID], edit=False)
        frm.setModal(True)
        frm.exec()

    def submitJob(self, job):
        Q = JobQueue.getQueueObject(self.connection)

        worker = Worker(Q.submitJob, job=job, task="submit")

        # make
        # worker = Worker(self.__submit, job)
        worker.signals.updateStarted.connect(self.updateStarted)
        worker.signals.progress.connect(self.writeLog)
        worker.signals.jobSubmitted.connect(self.jobTableModel.addJob)
        worker.signals.updateFinished.connect(self.updateFinished)

        self.jobsPending = True
        self.threadpool.start(worker)

    def confirmDeleteJob(self, index):
        jobIDs = list(self.connection.jobs.keys())
        job = self.connection.jobs[jobIDs[index.row()]]

        msg = QMessageBox(self)
        msg.setIcon(QMessageBox.Information)

        msg.setText("Delete job %s (%d)?" % (job.jobName, job.jobID))
        msg.setInformativeText("Unsaved work will be lost!")
        msg.setWindowTitle("Delete Job")
        msg.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel)
        msg.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel)

        if msg.exec() == QMessageBox.Ok:
            job.closeSSHTunnel(self)
            self.connection.deleteJob(job.jobID)

    def confirmDeleteAllJobs(self):
        msg = QMessageBox(self)
        msg.setIcon(QMessageBox.Information)

        msg.setText("Delete all jobs?")
        msg.setInformativeText("Unsaved work will be lost!")
        msg.setWindowTitle("Delete jobs")
        msg.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel)
        if msg.exec() == QMessageBox.Ok:
            self.deleteAllJobs()

    def deleteAllJobs(self):
        for jobID in self.jobTableModel.jobs:
            self.deleteJob(jobID)

    def deleteJob(self, jobID):
        try:
            #print("joblist -> DELETE JOB")

            # part of the queue => this should use Factory design
            Q = JobQueue.getQueueObject(self.connection)

            worker = Worker(Q.deleteJob, jobID=jobID, task="delete")
            worker.signals.updateStarted.connect(self.updateStarted)
            worker.signals.progress.connect(self.writeLog)
            worker.signals.jobDeleted.connect(self.jobTableModel.deleteJob)
            worker.signals.updateFinished.connect(self.updateFinished)

            # Execute job
            self.threadpool.start(worker)
        except:
            self.msgSignal.emit({
                'connectionID': self.connectionID,
                'jobID': jobID,
                'message': str(sys.exc_info()[1]),
                'messageType': 'ERROR'
            })

    def updateStarted(self):
        self.addButton.setEnabled(False)
        self.deleteAllButton.setEnabled(False)
        self.syncButton.setEnabled(False)
        self.clearLogButton.setEnabled(False)

        self.spinner.start()

    def updateFinished(self):
        self.spinner.stop()

        # emit layout changed to reload all jobs
        self.jobTableModel.layoutChanged.emit()

        # enable buttons
        self.addButton.setEnabled(True)
        self.deleteAllButton.setEnabled(True)
        self.syncButton.setEnabled(True)
        self.clearLogButton.setEnabled(True)

        # restart timer
        self.loaded = True
        self.elapsedTimer.restart()

    def writeLog(self, msg):
        self.log.writeLine(connectionID=msg['connectionID'],
                           jobID=msg['jobID'],
                           message=msg['message'],
                           messageType=msg['messageType'],
                           show=True)

    def browse(self, jobID):
        job = self.jobTableModel.jobs[jobID]
        res = job.openSSHTunnel(self)

        if res is True:
            webbrowser.open_new_tab("http://localhost:%d" % int(job.port))
        else:
            self.log.writeLine(connectionID=self.connection.connectionID,
                               jobID=jobID,
                               message=res,
                               messageType="ERROR",
                               show=True)

    # Change the image from "normal" to "hot" on mouseover
    def eventFilter(self, object, event):

        basedir = "icons"
        if hasattr(sys, 'frozen') and hasattr(sys, '_MEIPASS'):
            basedir = os.path.join(sys._MEIPASS, "icons")

        if event.type() == QEvent.HoverEnter:
            object.setIcon(
                QIcon(os.path.join(basedir,
                                   object.objectName() + "_hot.png")))
        elif event.type() == QEvent.HoverLeave:
            object.setIcon(
                QIcon(
                    os.path.join(basedir,
                                 object.objectName() + "_normal.png")))

        return False