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()
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_()
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