class AbstractDockWidget(Plugin, QtWidgets.QDockWidget): closed = QtCore.Signal(object) enabled = QtCore.Signal() def __init__(self, parent, name, area=QtCore.Qt.LeftDockWidgetArea): QtWidgets.QDockWidget.__init__(self, name, parent) Plugin.__init__(self) self.parent = parent self.setAllowedAreas(QtCore.Qt.AllDockWidgetAreas) self.setFeatures(QtWidgets.QDockWidget.DockWidgetClosable | QtWidgets.QDockWidget.DockWidgetMovable) self.setObjectName(name) parent.addDockWidget(area, self) # Setup main vertical layout self.__layout = QtWidgets.QVBoxLayout() self.__layout.setContentsMargins(0, 0, 0, 0) # Required to get layout on DockWidget self.setWidget(QtWidgets.QWidget()) self.widget().setLayout(self.__layout) def closeEvent(self, event): self.closed.emit(self) def showEvent(self, event): self.enabled.emit() def layout(self): return self.__layout
class PreviewProcessorWatchThread(QtCore.QThread): """ Waits for preview files to appear and emits the progress every second. This thread times out after 60 seconds, which should only occur if there are serious filer problems. """ existCountChanged = QtCore.Signal(int, int) def __init__(self, items, parent=None): QtCore.QThread.__init__(self, parent) self.__items = items self.__timeout = 60 + (30 * len(items)) def run(self): """ Just check to see how many files exist and emit that back to our parent. """ start_time = time.time() while 1: count = len( [path for path in self.__items if os.path.exists(path)]) self.emit(QtCore.SIGNAL('existCountChanged(int, int)'), count, len(self.__items)) self.existsCountChanged.emit(count, len(self.__items)) if count == len(self.__items): break time.sleep(1) if time.time() > self.__timeout + start_time: self.timeout.emit() print "Timed out waiting for preview server." break
class WorkerThread(QtCore.QThread): """WorkerThread A therad for parsing job log files. The log file is parsed using SpiCue.cueprofile and emits a "parsingComplete" signal when complete. """ workComplete = QtCore.Signal(object, object) def __init__(self, name, parent): QtCore.QThread.__init__(self, parent) self.__parent = parent self.__name = name def run(self): self.__running = True while self.__running: work = None self.__parent._q_mutex.lock() try: work = self.__parent._q_queue.pop(0) except: self.__parent._q_empty.wait(self.__parent._q_mutex) self.__parent._q_mutex.unlock() if not work: continue try: if work[3]: result = work[0](*work[3]) else: result = work[0]() if work[1]: self.workComplete.emit(work, result) del result except Exception, e: logger.info("Error processing work:' %s ', %s" % (work[2], e)) logger.info("Done:' %s '" % work[2]) logger.debug("Thread Stopping")
class ServiceForm(QtWidgets.QWidget): """ An Widget for displaying and editing a service. """ saved = QtCore.Signal(object) def __init__(self, parent=None): QtWidgets.QWidget.__init__(self, parent) self.__service = None self.gpu_max_mb = 2 * 1024 self.gpu_min_mb = 0 self.gpu_tick_mb = 256 self.name = QtWidgets.QLineEdit(self) self.threadable = QtWidgets.QCheckBox(self) self.min_cores = QtWidgets.QSpinBox(self) self.min_cores.setRange(50, int(self._cfg().get('max_cores', 16)) * 100) self.min_cores.setSingleStep(50) self.min_cores.setValue(100) self.max_cores = QtWidgets.QSpinBox(self) self.max_cores.setRange(0, int(self._cfg().get('max_cores', 16)) * 100) self.max_cores.setSingleStep(100) self.max_cores.setValue(100) self.min_memory = QtWidgets.QSpinBox(self) self.min_memory.setRange(512, int(self._cfg().get('max_memory', 48)) * 1024) self.min_memory.setValue(3276) self.min_gpu = QtWidgets.QSpinBox(self) self.min_gpu.setRange(self.gpu_min_mb, self.gpu_max_mb) self.min_gpu.setValue(0) self.min_gpu.setSingleStep(self.gpu_tick_mb) self.min_gpu.setSuffix(" MB") layout = QtWidgets.QGridLayout(self) layout.addWidget(QtWidgets.QLabel("Name:", self), 0, 0) layout.addWidget(self.name, 0, 1) layout.addWidget(QtWidgets.QLabel("Threadable:", self), 1, 0) layout.addWidget(self.threadable, 1, 1) layout.addWidget( QtWidgets.QLabel("Min Threads (100 = 1 thread):", self), 2, 0) layout.addWidget(self.min_cores, 2, 1) layout.addWidget( QtWidgets.QLabel("Max Threads (100 = 1 thread):", self), 3, 0) layout.addWidget(self.max_cores, 3, 1) layout.addWidget(QtWidgets.QLabel("Min Memory MB:", self), 4, 0) layout.addWidget(self.min_memory, 4, 1) layout.addWidget(QtWidgets.QLabel("Min Gpu Memory MB:", self), 5, 0) layout.addWidget(self.min_gpu, 5, 1) self.__buttons = QtWidgets.QDialogButtonBox( QtWidgets.QDialogButtonBox.Save, QtCore.Qt.Horizontal, self) self.__buttons.setDisabled(True) layout.addWidget(self.__buttons, 8, 1) self.__buttons.accepted.connect(self.save) self._tags_w = TagsWidget(allowed_tags=Constants.ALLOWED_TAGS) layout.addWidget(self._tags_w, 6, 0, 1, 2) def _cfg(self): """ Loads (if necessary) and returns the config values. Warns and returns an empty dict if there's a problem reading the config @return: The keys & values stored in the config file @rtype: dict<str:str> """ if not hasattr(self, '__config'): self.__config = Utils.getResourceConfig() return self.__config def setService(self, service): """ Update the form with data from the given service. """ self.__buttons.setDisabled(False) self.__service = service.data self.name.setText(self.__service.name) self.threadable.setChecked(self.__service.threadable) self.min_cores.setValue(self.__service.min_cores) self.max_cores.setValue(self.__service.max_cores) self.min_memory.setValue(self.__service.min_memory / 1024) self.min_gpu.setValue(self.__service.min_gpu / 1024) self._tags_w.set_tags(self.__service.tags) def new(self): """ Clear the form for a new service. """ self.__buttons.setDisabled(False) self.__service = None self.name.setFocus() self.name.setText("") self.threadable.setChecked(False) self.min_cores.setValue(100) self.max_cores.setValue(100) self.min_memory.setValue(3276) self.min_gpu.setValue(0) self._tags_w.set_tags(['general']) def save(self): """ Create and emit a ServiceData object based on the contents of the form. """ if len(str(self.name.text())) < 3: QtWidgets.QMessageBox.critical( self, "Error", "The service name must be at least 3 characters.") return if not str(self.name.text()).isalnum(): QtWidgets.QMessageBox.critical( self, "Error", "The service name must alphanumeric.") return data = opencue.api.service_pb2.Service() data.name = str(self.name.text()) data.threadable = self.threadable.isChecked() data.min_cores = self.min_cores.value() data.max_cores = self.max_cores.value() data.min_memory = self.min_memory.value() * 1024 data.min_gpu = self.min_gpu.value() * 1024 data.tags.extend(self._tags_w.get_tags()) self.saved.emit(data)
class FrameMonitorTree(AbstractTreeWidget): job_changed = QtCore.Signal() handle_filter_layers_byLayer = QtCore.Signal(list) def __init__(self, parent): self.frameLogDataBuffer = FrameLogDataBuffer() self.frameEtaDataBuffer = FrameEtaDataBuffer() self.startColumnsForType(Constants.TYPE_FRAME) self.addColumn( "Order", 60, id=1, data=lambda job, frame: frame.data.dispatch_order, sort=lambda job, frame: frame.data.dispatch_order, tip= "The order the frame will be rendered in for it's layer if resources " "are available.") self.addColumn("Frame", 70, id=2, data=lambda job, frame: frame.data.number, sort=lambda job, frame: frame.data.number, tip="The number of the frame.") self.addColumn("Layer", 250, id=3, data=lambda job, frame: frame.data.layer_name, tip="The layer that the frame is in.") self.addColumn( "Status", 100, id=4, data=lambda job, frame: job_pb2.FrameState.Name(frame.data.state), tip="The status of the frame:\n" "Succeeded: \t The frame finished without errors.\n" "Running: \t The frame is currently running.\n" "Waiting: \t The frame is ready to be run when resources\n" "\t are available.\n" "Depend: \t The frame depends on another frame or job.\n" "Dead: \t The frame failed with an error.") self.addColumn("Cores", 55, id=5, data=lambda job, frame: (self.getCores(frame, True) or ""), sort=lambda job, frame: (self.getCores(frame)), tip="The number of cores a frame is using") self.addColumn( "Host", 120, id=6, data=lambda job, frame: frame.data.last_resource, tip="The last or current resource that the frame used or is using." ) self.addColumn( "Retries", 55, id=7, data=lambda job, frame: frame.data.retry_count, sort=lambda job, frame: frame.data.retry_count, tip="The number of times that each frame has had to retry.") self.addColumn( "_CheckpointEnabled", 20, id=8, data=lambda job, frame: "", sort=lambda job, frame: (frame.data.checkpoint_state == opencue.api.job_pb2.ENABLED), tip= "A green check mark here indicates the frame has written out at least " "1 checkpoint segment.") self.addColumn( "CheckP", 55, id=9, data=lambda job, frame: frame.data.checkpoint_count, sort=lambda job, frame: frame.data.checkpoint_count, tip="The number of times a frame has been checkpointed.") self.addColumn( "Runtime", 70, id=10, data=lambda job, frame: (Utils.secondsToHMMSS( frame.data.start_time and frame.data.stop_time and frame.data. stop_time - frame.data.start_time or frame.data.start_time and frame.data.stop_time != frame.data.start_time and time.time( ) - frame.data.start_time or 0)), sort=lambda job, frame: (frame.data.start_time and frame.data.stop_time and frame.data. stop_time - frame.data.start_time or frame.data.start_time and frame.data.stop_time != frame.data.start_time and time.time( ) - frame.data.start_time or 0), tip="The amount of HOURS:MINUTES:SECONDS that the frame\n" "has run for or last ran for.\n") self.addColumn( "LLU", 70, id=11, data=lambda job, frame: (frame.data.state == opencue.api. job_pb2.RUNNING and self.frameLogDataBuffer.getLastLineData( job, frame)[FrameLogDataBuffer.LLU] or ""), sort=lambda job, frame: (frame.data.state == opencue.api. job_pb2.RUNNING and self.frameLogDataBuffer.getLastLineData( job, frame)[FrameLogDataBuffer.LLU] or ""), tip="The amount of HOURS:MINUTES:SECONDS since the last\n" "time the log file was written to. A long period of\n" "time without an update is an indication of a stuck\n" "frame for most types of jobs") self.addColumn("Memory", 60, id=12, data=lambda job, frame: (frame.data.state == opencue.api.job_pb2.RUNNING and Utils.memoryToString(frame.data.used_memory) or Utils. memoryToString(frame.data.max_rss)), sort=lambda job, frame: (frame.data.state == opencue.api.job_pb2.RUNNING and frame.data.used_memory or frame.data.max_rss), tip="If a frame is running:\n" "\t The amount of memory currently used by the frame.\n" "If a frame is not running:\n" "\t The most memory this frame has used at one time.") self.addColumn( "Remain", 70, id=13, data=lambda job, frame: (frame.data.state == opencue.api.job_pb2.RUNNING and self. frameEtaDataBuffer.getEtaFormatted(job, frame) or ""), sort=lambda job, frame: (frame.data.state == opencue.api.job_pb2.RUNNING and self. frameEtaDataBuffer.getEta(job, frame) or -1), tip="Hours:Minutes:Seconds remaining.") self.addColumn("Start Time", 100, id=14, data=lambda job, frame: (self.getTimeString(frame.data.start_time) or ""), tip="The time the frame was started or retried.") self.addColumn("Stop Time", 100, id=15, data=lambda job, frame: (self.getTimeString(frame.data.stop_time) or ""), tip="The time that the frame finished or died.") self.addColumn("Last Line", 0, id=16, data=lambda job, frame: (frame.data.state == opencue.api.job_pb2.RUNNING and self.frameLogDataBuffer.getLastLineData( job, frame)[FrameLogDataBuffer.LASTLINE] or ""), tip="The last line of a running frame's log file.") self.frameSearch = opencue.search.FrameSearch() self.__job = None self.__jobState = None AbstractTreeWidget.__init__(self, parent) # Used to build right click context menus self.__menuActions = MenuActions(self, self.updateSoon, self.selectedObjects, self.getJob) self.__sortByColumnCache = {} self.itemClicked.connect(self.__itemSingleClickedCopy) self.itemClicked.connect(self.__itemSingleClickedViewLog) self.itemDoubleClicked.connect(self.__itemDoubleClickedViewLog) self.header().sortIndicatorChanged.connect(self.__sortByColumnSave) self.__load = None self.startTicksUpdate(20) def tick(self): if self.__load: self.__setJob(self.__load) self.__load = None self.ticksWithoutUpdate = 0 self._update() return if self.__job: if self.ticksWithoutUpdate > 9990: logger.info("doing full update") self.ticksWithoutUpdate = 0 self._update() return elif self.ticksWithoutUpdate > self.updateInterval: logger.info("doing changed update") self.ticksWithoutUpdate = 0 self._updateChanged() return if self.ticksWithoutUpdate <= self.updateInterval + 1: self.ticksWithoutUpdate += 1 # Redrawing every even number of seconds to see the current frame # runtime, LLU and last log line changes. Every second was excessive. if not self.ticksWithoutUpdate % 2: self.redraw() def getCores(self, frame, format=False): cores = None m = re.search(".*\/(\d+\.?\d*)", frame.data.last_resource) if m: cores = float(m.group(1)) if format: cores = "{:.2f}".format(cores) return cores def getTimeString(self, timestamp): tstring = None if timestamp and timestamp > 0: tstring = datetime.datetime.fromtimestamp(timestamp).strftime( "%m/%d %H:%M") return tstring def redrawRunning(self): """Forces the running frames to be redrawn with current values""" try: items = self.findItems("Running", QtCore.Qt.MatchExactly, STATUS_COLUMN) if items: self.dataChanged( self.indexFromItem(items[0], RUNTIME_COLUMN), self.indexFromItem(items[-1], LASTLINE_COLUMN)) except Exception, e: map(logger.warning, Utils.exceptionOutput(e))
class LayerMonitorTree(AbstractTreeWidget): handle_filter_layers_byLayer = QtCore.Signal(list) def __init__(self, parent): self.startColumnsForType(Constants.TYPE_LAYER) self.addColumn("dispatchOrder", 0, id=1, data=lambda layer: layer.data.dispatch_order, sort=lambda layer: layer.data.dispatch_order) self.addColumn("Name", 250, id=2, data=lambda layer: layer.data.name, tip="Name of the layer.") self.addColumn( "Services", 100, id=3, data=lambda layer: ",".join(layer.data.services), tip="The underlying application being run within the frames.") self.addColumn("Range", 150, id=4, data=lambda layer: displayRange(layer), tip="The range of frames that the layer should render.") self.addColumn( "Cores", 45, id=5, data=lambda layer: "%.2f" % layer.data.min_cores, sort=lambda layer: layer.data.min_cores, tip="The number of cores that the frames in this layer\n" "will reserve as a minimum.") self.addColumn( "Memory", 60, id=6, data=lambda layer: Utils.memoryToString(layer.data.min_memory), sort=lambda layer: layer.data.min_memory, tip="The amount of memory that each frame in this layer\n" "will reserve for its use. If the frame begins to use\n" "more memory than this, the cuebot will increase this\n" "number.") self.addColumn( "Gpu", 40, id=7, data=lambda layer: Utils.memoryToString(layer.data.min_gpu), sort=lambda layer: layer.data.min_gpu, tip="The amount of gpu memory each frame in this layer\n" "will reserve for its use. Note that we may not have\n" "machines as much gpu memory as you request.") self.addColumn("MaxRss", 60, id=8, data=lambda layer: Utils.memoryToString( layer.data.layer_stats.max_rss), sort=lambda layer: layer.data.layer_stats.max_rss, tip="Maximum amount of memory used by any frame in\n" "this layer at any time since the job was launched.") self.addColumn("Total", 40, id=9, data=lambda layer: layer.data.layer_stats.total_frames, sort=lambda layer: layer.data.layer_stats.total_frames, tip="Total number of frames in this layer.") self.addColumn( "Done", 40, id=10, data=lambda layer: layer.data.layer_stats.succeeded_frames, sort=lambda layer: layer.data.layer_stats.succeeded_frames, tip="Total number of done frames in this layer.") self.addColumn( "Run", 40, id=11, data=lambda layer: layer.data.layer_stats.running_frames, sort=lambda layer: layer.data.layer_stats.running_frames, tip="Total number or running frames in this layer.") self.addColumn("Depend", 53, id=12, data=lambda layer: layer.data.layer_stats.depend_frames, sort=lambda layer: layer.data.layer_stats.depend_frames, tip="Total number of dependent frames in this layer.") self.addColumn( "Wait", 40, id=13, data=lambda layer: layer.data.layer_stats.waiting_frames, sort=lambda layer: layer.data.layer_stats.waiting_frames, tip="Total number of waiting frames in this layer.") self.addColumn("Eaten", 40, id=14, data=lambda layer: layer.data.layer_stats.eaten_frames, sort=lambda layer: layer.data.layer_stats.eaten_frames, tip="Total number of eaten frames in this layer.") self.addColumn("Dead", 40, id=15, data=lambda layer: layer.data.layer_stats.dead_frames, sort=lambda layer: layer.data.layer_stats.dead_frames, tip="Total number of dead frames in this layer.") self.addColumn( "Avg", 65, id=16, data=lambda layer: Utils.secondsToHHMMSS(layer.data.layer_stats. avg_frame_sec), sort=lambda layer: layer.data.layer_stats.avg_frame_sec, tip="Average number of HOURS:MINUTES:SECONDS per frame\n" "in this layer.") self.addColumn("Tags", 100, id=17, data=lambda layer: " | ".join(layer.data.tags), tip="The tags define what resources may be booked on\n" "frames in this layer.") AbstractTreeWidget.__init__(self, parent) self.itemDoubleClicked.connect(self.__itemDoubleClickedFilterLayer) # Used to build right click context menus self.__menuActions = MenuActions(self, self.updateSoon, self.selectedObjects, self.getJob) self.__job = None self.disableUpdate = False self.__load = None self.startTicksUpdate(20, False, 60 * 60 * 24) def tick(self): if self.__load: self.__job = self.__load self.__load = None self.ticksWithoutUpdate = 0 if not self.disableUpdate: self._update() return if self.__job: if self.tickNeedsUpdate() and not self.disableUpdate: self.ticksWithoutUpdate = 0 self._update() return self.ticksWithoutUpdate += 1 def updateRequest(self): """Updates the items in the TreeWidget if sufficient time has passed since last updated""" self.ticksWithoutUpdate = 9999 def setJob(self, job): """Sets the current job @param job: Job can be None, a job object, or a job name. @type job: job, string, None""" if job is None: return self.__setJob(None) job = Utils.findJob(job) if job: self.__load = job def __setJob(self, job): """Sets the current job @param job: Job can be None, a job object, or a job name. @type job: job, string, None""" self.sortByColumn(0, QtCore.Qt.AscendingOrder) self.__job = job self.removeAllItems() def getJob(self): return self.__job def _createItem(self, obj): """Creates and returns the proper item""" return LayerWidgetItem(obj, self) def _getUpdate(self): """Returns the proper data from the cuebot""" if self.__job: try: return self.__job.getLayers() except opencue.exceptions.EntityNotFoundException: self.setJob(None) return [] return [] def contextMenuEvent(self, e): """When right clicking on an item, this raises a context menu""" __selectedObjects = self.selectedObjects() menu = QtWidgets.QMenu() self.__menuActions.layers().addAction(menu, "view") depend_menu = QtWidgets.QMenu("&Dependencies", self) self.__menuActions.layers().addAction(depend_menu, "viewDepends") self.__menuActions.layers().addAction(depend_menu, "dependWizard") depend_menu.addSeparator() self.__menuActions.layers().addAction(depend_menu, "markdone") menu.addMenu(depend_menu) if len(__selectedObjects) == 1: menu.addSeparator() self.__menuActions.layers().addAction(menu, "useLocalCores") if len(set([layer.data.range for layer in __selectedObjects])) == 1: self.__menuActions.layers().addAction(menu, "reorder") self.__menuActions.layers().addAction(menu, "stagger") menu.addSeparator() self.__menuActions.layers().addAction(menu, "setProperties") menu.addSeparator() self.__menuActions.layers().addAction(menu, "kill") self.__menuActions.layers().addAction(menu, "eat") self.__menuActions.layers().addAction(menu, "retry") if [ layer for layer in __selectedObjects if layer.data.layer_stats.dead_frames ]: menu.addSeparator() self.__menuActions.layers().addAction(menu, "retryDead") menu.exec_(e.globalPos()) def __itemDoubleClickedFilterLayer(self, item, col): self.handle_filter_layers_byLayer.emit([item.rpcObject.data.name])
class FrameRangeSelectionWidget(QtWidgets.QWidget): """The Timeline Widget is a rectangular area with ticks to represent units of frames. A frame range may be selected. This widget emits the following signals: * startFrameChanged(int) - user clicks new time or program changes time * endFrameChanged(int) - user clicks new time or program changes time * frameRangeChanged(int,int) - program changes viewed frame range * selectionChanged(int,int) - user releases a drag that selects a frame range """ endFrameChanged = QtCore.Signal(int) startFrameChanged = QtCore.Signal(int) frameRangeChanged = QtCore.Signal(int, int) selectionChanged = QtCore.Signal(int, int) def __init__(self, parent, *args): QtWidgets.QWidget.__init__(self, parent, *args) self.setMinimumWidth(100) self.__layout = QtWidgets.QHBoxLayout(self) self.__layout.addStretch(100) self.__right_margin = 25 self.__layout.setContentsMargins(0,0,0,0) toolButton = QtWidgets.QToolButton() toolButton.setArrowType(QtCore.Qt.UpArrow) toolButton.setFocusPolicy(QtCore.Qt.NoFocus) toolButton.setFixedSize(20,20) #toolButton.setContentsMargins(0,0,0,0) self.__layout.addWidget(toolButton) self.setLayout(self.__layout) self.setFixedHeight(25) self.__endFrame = 5000 self.__endFrameFinal = True self.__startFrame = 1 self.__startFrameFinal = True # The range we are displaying self.__frameRange = (self.__startFrame, self.__endFrame) # The range selected, or None if nothing is selected. self.__selectionRange = None # floatTime is the time the mouse is floating over. self.__floatTime = None # Request mouseover events from QT. self.setMouseTracking(True) # Don't erase the background for us; we redraw everything # with offscreen buffers. self.setAutoFillBackground(False) self.__labelFont = QtGui.QFont('Helvetica', 8) self.__double = False self.default_select_size = 1000 def endFrame(self): return self.__endFrame def setEndTime(self, t, final = True): if (self.__endFrame == t and self.__endFrameFinal == final): return self.__endFrame = int(t) self.__endFrameFinal = final #mine self.__selectionRange = (self.__startFrame, self.__endFrame) self.update() self.endFrameChanged.emit(int(t)) # The current time displayed in the timeline. def startFrame(self): return self.__startFrame def setStartTime(self, t, final = True): if (self.__startFrame == t and self.__startFrameFinal == final): return self.__startFrame = int(t) self.__startFrameFinal = final if self.__endFrame == self.__startFrame: self.setEndTime(min(self.__startFrame + self.default_select_size - 1, self.__frameRange[1]), True) self.__selectionRange = (self.__startFrame, self.__endFrame) self.update() self.startFrameChanged.emit(int(t)) # The viewed range of time in the timeline. def frameRange(self): return self.__frameRange def setFrameRange(self, frameRange): frameRange = tuple(sorted(map(int, frameRange))) self.__floatTime = None self.__frameRange = frameRange self.setStartTime(self.__frameRange[0], False) self.setEndTime(self.__frameRange[1], False) self.frameRangeChanged.emit(self.__frameRange[0], self.__frameRange[1]) self.update() # The selected (highlighted) range of time in the timeline. def selectedFrameRange(self): return self.__selectionRange def setSelectedFrameRange(self, selectedRange): if selectedRange != self.__selectionRange: self.__selectionRange = selectedRange self.selectionChanged.emit(self.__selectionRange[0], self.__selectionRange[1]) # QT event handlers and implementation details below this line. def mousePressEvent(self, mouseEvent): hitTime = self.__getTimeFromLocalPoint(mouseEvent.x()) if mouseEvent.buttons() & QtCore.Qt.LeftButton: self.setStartTime(hitTime, False) self.__selectionRange = (hitTime, hitTime) def mouseMoveEvent(self, mouseEvent): self.__floatTime = None hitTime = self.__getTimeFromLocalPoint(mouseEvent.x()) if mouseEvent.buttons() & QtCore.Qt.LeftButton \ or mouseEvent.buttons() & QtCore.Qt.RightButton: # Update the selection range, but don't send signals. if self.__selectionRange: self.__selectionRange = (self.__selectionRange[0], hitTime) self.setEndTime(hitTime, True) else: self.__selectionRange = (hitTime, hitTime) self.__floatTime = hitTime self.update() def mouseReleaseEvent(self, mouseEvent): if self.__double: self.__double = False self.setStartTime(self.__frameRange[0], True) self.setEndTime(self.__frameRange[1], True) else: hitTime = self.__getTimeFromLocalPoint(mouseEvent.x()) self.setEndTime(max(hitTime, self.startFrame()), True) self.setStartTime(min(hitTime, self.startFrame()), True) self.__selectionRange = (min(self.__selectionRange[0], self.__selectionRange[1]), max(self.__selectionRange[0], self.__selectionRange[1])) self.setSelectedFrameRange(self.__selectionRange) self.update() def mouseDoubleClickEvent(self, mouseEvent): self.__double = True def paintEvent(self, paintEvent): painter = QtGui.QPainter(self) self.__paintBackground(painter) # Put the baseline at 0, 0 painter.translate(0, self.height() / 2) painter.setFont(self.__labelFont) self.__paintSelection(painter) self.__paintTickmarks(painter) self.__paintLabels(painter) self.__paintFloatTime(painter) self.__paintStartTime(painter) self.__paintEndTime(painter) def leaveEvent(self, event): self.__floatTime = None self.update() def __getTickAreaExtent(self): return QtCore.QRect(10, -self.height()/2, self.width() - self.__right_margin - 20, self.height()) def __getTickArea(self, time): tickArea = self.__getTickAreaExtent() tickSpacing = float(self.__getTickAreaExtent().width()) / max(1,(self.__frameRange[1] - self.__frameRange[0])) return QtCore.QRect(tickArea.left() + tickSpacing * (time - self.__frameRange[0]), tickArea.top(), tickSpacing, tickArea.height()) def __getTimeFromLocalPoint(self, x): tickSpacing = float(self.__getTickAreaExtent().width()) / max(1,(self.__frameRange[1] - self.__frameRange[0])) deltaX = x - self.__getTickAreaExtent().left() hitTime = int(deltaX / tickSpacing + 0.5) + self.__frameRange[0] hitTime = int(max(self.__frameRange[0], min(hitTime, self.__frameRange[1]))) return hitTime def __getLabelPeriod(self): delta = self.__frameRange[1] - self.__frameRange[0] if (delta < 20): return 2 if (delta < 10000): base = 5 offset = 2 else: base = 10 offset = 1 scale = math.ceil(math.log(self.__frameRange[1] - self.__frameRange[0]) / math.log(base)) return base ** max(1, scale - offset) def __paintBackground(self, painter): bgBrush = self.palette().window() painter.fillRect(0, 0, self.width() - self.__right_margin, self.height(), bgBrush) highlightBrush = QtGui.QBrush(QtGui.QColor(75, 75, 75)) painter.fillRect(0, self.height()/2, self.width() - self.__right_margin+5, self.height()/2, highlightBrush) def __paintTickmarks(self, painter): tickExtent = self.__getTickAreaExtent() tickHeight = tickExtent.height() / 8 pen = QtGui.QPen(self.palette().window().color()) painter.setPen(pen) # Draw the baseline painter.drawLine(tickExtent.left(), 0, tickExtent.right(), 0) tickArea = self.__getTickArea(self.__frameRange[0]) if tickArea.width() >= 5: for frame in range(self.__frameRange[0], self.__frameRange[1] + 1, 1): xPos = self.__getTickArea(frame).left() painter.drawLine(xPos, -tickHeight, xPos, 0) def __paintLabels(self, painter): tickExtent = self.__getTickAreaExtent() labelHeight = tickExtent.height() / 3 labelPeriod = self.__getLabelPeriod() if labelPeriod == 0: return firstLabel = self.__frameRange[0] + labelPeriod - 1 firstLabel = firstLabel - (firstLabel % labelPeriod) frames = [] for frame in range(int(firstLabel), int(self.__frameRange[1])+1, int(labelPeriod)): frames.append(frame) if frames[0] != self.__frameRange[0]: frames.insert(0, self.__frameRange[0]) if frames[-1] != self.__frameRange[1]: frames.append(self.__frameRange[1]) oldPen = painter.pen() # draw hatches for labelled frames painter.setPen(self.palette().color(QtGui.QPalette.Foreground)) for frame in frames: xPos = self.__getTickArea(frame).left() painter.drawLine(xPos, -labelHeight, xPos, 0) painter.setPen(QtGui.QColor(10, 10, 10)) metric = QtGui.QFontMetrics(painter.font()) yPos = metric.ascent() + 1 rightEdge = -10000 width = metric.width(str(frames[-1])) farEdge = self.__getTickArea(frames[-1]).right() - width / 2 farEdge -= 4 for frame in frames: xPos = self.__getTickArea(frame).left() frameString = str(frame) width = metric.width(frameString) xPos = xPos - width / 2 if (xPos > rightEdge and xPos + width < farEdge) or frame is frames[-1]: painter.drawText(xPos, yPos, frameString) rightEdge = xPos + width + 4 painter.setPen(oldPen) def __paintStartTime(self, painter): startFrame = self.startFrame() timeExtent = self.__getTickArea(startFrame) oldPen = painter.pen() painter.setPen(QtGui.QColor(0, 255, 0)) painter.drawLine(timeExtent.left(), timeExtent.top(), timeExtent.left(), 0) metric = QtGui.QFontMetrics(painter.font()) frameString = str(int(startFrame)) xPos = timeExtent.left() - metric.width(frameString) / 2 yPos = metric.ascent() + 1 painter.drawText(xPos, yPos, frameString) painter.setPen(oldPen) def __paintEndTime(self, painter): endFrame = self.endFrame() timeExtent = self.__getTickArea(endFrame) oldPen = painter.pen() painter.setPen(QtGui.QColor(255, 0, 0)) painter.drawLine(timeExtent.left(), timeExtent.top(), timeExtent.left(), 0) metric = QtGui.QFontMetrics(painter.font()) frameString = str(int(endFrame)) xPos = timeExtent.left() - metric.width(frameString) / 2 yPos = metric.ascent() + 1 painter.drawText(xPos, yPos, frameString) painter.setPen(oldPen) def __paintFloatTime(self, painter): if self.__floatTime == None: return timeExtent = self.__getTickArea(self.__floatTime) oldPen = painter.pen() painter.setPen(QtGui.QColor(90, 90, 90)) painter.drawLine(timeExtent.left(), timeExtent.top(), timeExtent.left(), timeExtent.bottom()) if self.__selectionRange: painter.setPen(QtGui.QColor(255,255,255)) else: painter.setPen(QtGui.QColor(128, 128, 128)) metric = QtGui.QFontMetrics(painter.font()) frameString = str(self.__floatTime) xPos = timeExtent.left() - metric.width(frameString) / 2 yPos = timeExtent.top() + metric.ascent() painter.drawText(xPos, yPos, frameString) painter.setPen(oldPen) def __paintSelection(self, painter): if self.__selectionRange == None: return selection = (min(self.__selectionRange[0], self.__selectionRange[1]), max(self.__selectionRange[0], self.__selectionRange[1])) leftExtent = self.__getTickArea(selection[0]) rightExtent = self.__getTickArea(selection[1] - 1) selectionExtent = QtCore.QRect(leftExtent.left(), leftExtent.top(), rightExtent.right() - leftExtent.left() + 2, leftExtent.height()/2) painter.fillRect(selectionExtent, QtGui.QBrush(QtGui.QColor(75, 75, 75)))
class AbstractTreeWidget(QtWidgets.QTreeWidget): """Forms the basis for all TreeWidgets""" itemDoubleClicked = QtCore.Signal(QtWidgets.QTreeWidgetItem, int) itemSingleClicked = QtCore.Signal(QtWidgets.QTreeWidgetItem, int) updated = QtCore.Signal() def __init__(self, parent): """Standard method to display a list or tree using QTreeWidget columnInfoByType is a dictionary of lists keyed to opencue.Constants.TYPE_* Each value is a list of lists that each define a column. [<column name>, <width>, <lambda function>, <function name for sorting>, <column delegate class>] Only supported on the primary column: <column name>, <column delegate class>, <width> @type parent: QWidget @param parent: The widget to set as the parent""" QtWidgets.QTreeWidget.__init__(self, parent) self._items = {} self._lastUpdate = 0 self._itemsLock = QtCore.QReadWriteLock() self._timer = QtCore.QTimer(self) self.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection) self.setUniformRowHeights(True) self.setAutoScroll(False) self.setFocusPolicy(QtCore.Qt.NoFocus) self.setAlternatingRowColors(True) self.setSortingEnabled(True) self.header().setSectionsMovable(False) self.header().setStretchLastSection(True) self.sortByColumn(0, QtCore.Qt.AscendingOrder) self.setItemDelegate(ItemDelegate(self)) self.__setupColumns() self.__setupColumnMenu() self.itemClicked.connect(self.__itemSingleClickedEmitToApp) self.itemDoubleClicked.connect(self.__itemDoubleClickedEmitToApp) self._timer.timeout.connect(self.updateRequest) QtGui.qApp.request_update.connect(self.updateRequest) self.updateRequest() self.setUpdateInterval(10) def startColumnsForType(self, itemType): """Start column definitions for the given item type. The first call to this defines the primary column type used to populate the column headers. @type itemType: int @param itemType: The id for the item type""" # First call defines the primary type if not hasattr(self, "columnPrimaryType"): self.columnPrimaryType = itemType self.__columnPrimaryType = itemType self.__columnInfoByType = {} self.__columnInfoByType[itemType] = [] self.__columnCurrent = itemType def addColumn(self, name, width, id=0, default=True, data=DEFAULT_LAMBDA, sort=None, delegate=None, tip=""): """Define a new column for the current item type. @type name: str @param name: The name of the column. @type width: int @param width: The width of the column. @type id: int @param id: A unique id @type default: bool @param default: Will determine if the column is displayed by default. @type data: callable @param data: The callable to use when displaying. @type sort: callable @param sort: The callable to use when sorting. @type delegate: delegate @param delegate: The delegate that should draw the cells. @type tip: str @param tip: A tooltip for the column.""" assert isinstance(name, str), "Column name must be string" assert isinstance(width, int), "Column width must be int" assert hasattr(data, '__call__'), "Column data function must be callable" assert isinstance(tip, str), "Column tooltip must be string" columnsInfo = self.__columnInfoByType[self.__columnCurrent] columnsInfo.append( [name, width, data, sort, delegate, tip, default, id]) def __setupColumns(self): """Setup the QTreeWidget based on the column information""" primaryColumnInfo = self.__columnInfoByType[self.__columnPrimaryType] self.setColumnCount(len(primaryColumnInfo)) columnNames = [] for col, columnInfo in enumerate(primaryColumnInfo): # Set up column widths self.setColumnWidth(col, primaryColumnInfo[col][COLUMN_WIDTH]) # Setup the column tooltips if columnInfo[COLUMN_TOOLTIP]: self.model().setHeaderData(col, QtCore.Qt.Horizontal, columnInfo[COLUMN_TOOLTIP], QtCore.Qt.ToolTipRole) # Setup column delegates if primaryColumnInfo[col][COLUMN_DELEGATE]: self.setItemDelegateForColumn( col, primaryColumnInfo[col][COLUMN_DELEGATE](self)) # Setup column name list if columnInfo[COLUMN_NAME].startswith("_"): columnNames.append("") else: columnNames.append(columnInfo[COLUMN_NAME]) self.setHeaderLabels(columnNames) def startTicksUpdate(self, updateInterval, updateWhenMinimized=False, maxUpdateInterval=None): """A method of updating the display on a one second timer to avoid multiple update requests and reduce excess cuebot calls. You will need to implement self.tick, You do not need to provide locking or unhandled error logging. You will need to implement tick. self.ticksWithoutUpdate = number of seconds since the last update. self.ticksLock = QMutex""" self.updateInterval = updateInterval self.__updateWhenMinimized = updateWhenMinimized self.__maxUpdateInterval = maxUpdateInterval # Stop the default update method if hasattr(self, "_timer"): self._timer.stop() self.ticksLock = QtCore.QMutex() self.__ticksTimer = QtCore.QTimer(self) self.__ticksTimer.timeout.connect(self.__tick) self.__ticksTimer.start(1000) self.ticksWithoutUpdate = 999 def tickNeedsUpdate(self): if self.ticksWithoutUpdate >= self.updateInterval: if self.window().isMinimized(): if self.__maxUpdateInterval is not None and \ self.ticksWithoutUpdate >= self.__maxUpdateInterval: # Sufficient maximum interval return True elif not self.__updateWhenMinimized: # Sufficient interval, except minimized return False # Sufficient interval, set to update when minimized return True # Sufficient interval, not minimized return True # Insufficient interval return False def __tick(self): """Provides locking and logging for the implementation of the tick function""" if not self.ticksLock.tryLock(): return try: try: self.tick() except Exception, e: map(logger.warning, Utils.exceptionOutput(e)) finally: self.ticksLock.unlock() def tick(self): raise NotImplementedError def getColumnInfo(self, columnType=None): """Returns the list that defines the column. @type columnType: Constants.TYPE_* @param columnType: If given, the column information for that type will be returned. Otherwise the primary column information will be returned @rtype: list @return: The list that defines the column, (see AbstractTreeWidget.__init__() documentation)""" if columnType: return self.__columnInfoByType[columnType] return self.__columnInfoByType[self.__columnPrimaryType] def __itemSingleClickedEmitToApp(self, item, col): """When an item is single clicked on: emits "single_click(PyQt_PyObject)" to the app @type item: QTreeWidgetItem @param item: The item single clicked on @type col: int @param col: Column number single clicked on""" QtGui.qApp.single_click.emit(item.rpcObject) def __itemDoubleClickedEmitToApp(self, item, col): """Handles when an item is double clicked on. emits "double_click(PyQt_PyObject)" to the app emits "view_object(PyQt_PyObject)" to the app @type item: QTreeWidgetItem @param item: The item double clicked on @type col: int @param col: Column number double clicked on""" QtGui.qApp.view_object.emit(item.rpcObject) QtGui.qApp.double_click.emit(item.rpcObject) def addObject(self, rpcObject): """Adds or updates an rpcObject in the list using the _createItem function and object.proxy as the key. Used when user is adding an item but will not want to wait for an update. @type paramA: opencue object @param paramA: Object that provides .proxy""" self._itemsLock.lockForWrite() try: # If id already exists, update it objectKey = Utils.getObjectKey(rpcObject) if objectKey in self._items: self._items[objectKey].update(rpcObject) # If id does not exist, create it else: self._items[objectKey] = self._createItem(rpcObject) finally: self._itemsLock.unlock() def removeItem(self, item): """Removes an item from the TreeWidget @param item: A tree widget item @type item: AbstractTreeWidgetItem""" self._itemsLock.lockForWrite() try: self._removeItem(item) finally: self._itemsLock.unlock() def _removeItem(self, item): """Removes an item from the TreeWidget without locking @type item: AbstractTreeWidgetItem or String @param item: A tree widget item or the string with the id of the item""" if item in self._items: item = self._items[item] elif not isinstance(item, AbstractWidgetItem): # if the parent was already deleted, then this one was too return # If it has children, they must be deleted first if item.childCount() > 0: for child in item.takeChildren(): self._removeItem(child) if item.isSelected(): item.setSelected(False) if item.parent(): #This allowed more segfaults #item.parent().removeChild(item) self.invisibleRootItem().removeChild(item) self.takeTopLevelItem(self.indexOfTopLevelItem(item)) objectClass = item.rpcObject.__class__.__name__ objectId = item.rpcObject.id() del self._items['{}.{}'.format(objectClass, objectId)] def removeAllItems(self): self._itemsLock.lockForWrite() try: self._items = {} self.clear() finally: self._itemsLock.unlock() def selectedObjects(self): """Provides a list of all objects from selected items @return: A list of objects from selected items @rtype: list<object>""" return [item.rpcObject for item in self.selectedItems()] def setUpdateInterval(self, seconds): """Changes the update interval @param seconds: Update interval in seconds @type seconds: int""" self._timer.start(seconds * 1000) def updateRequest(self): """Updates the items in the TreeWidget if sufficient time has passed since last updated and the user has not scrolled recently if self._limitUpdatesDuringScrollSetup() was called in the TreeWidget object init""" if time.time() - self._lastUpdate > Constants.MINIMUM_UPDATE_INTERVAL: if self.__limitUpdatesDuringScrollAllowUpdate(): self._update() def _update(self): """Updates the items in the TreeWidget without checking when it was last updated""" self._lastUpdate = time.time() if hasattr(QtGui.qApp, "threadpool"): QtGui.qApp.threadpool.queue(self._getUpdate, self._processUpdate, "getting data for %s" % self.__class__) else: logger.warning("threadpool not found, doing work in gui thread") self._processUpdate(None, self._getUpdate()) def _processUpdate(self, work, rpcObjects): """A generic function that Will: Create new TreeWidgetItems if an item does not exist for the object. Update existing TreeWidgetItems if an item already exists for the object. Remove items that were not updated with rpcObjects. @param work: @type work: from ThreadPool @param rpcObjects: A list of rpc objects @type rpcObjects: list<rpc object> """ self._itemsLock.lockForWrite() try: updated = [] for rpcObject in rpcObjects: objectId = "{}.{}".format(rpcObject.__class__.__name__, rpcObject.id()) updated.append(objectId) # If id already exists, update it if objectId in self._items: self._items[objectId].update(rpcObject) # If id does not exist, create it else: self._items[objectId] = self._createItem(rpcObject) # Remove any items that were not updated for proxy in list(set(self._items.keys()) - set(updated)): self._removeItem(proxy) self.redraw() finally: self._itemsLock.unlock() def updateSoon(self): """Returns immediately. Causes an update to happen Constants.AFTER_ACTION_UPDATE_DELAY after calling this function.""" if hasattr(self, "ticksWithoutUpdate"): self.ticksWithoutUpdate = self.updateInterval - \ Constants.AFTER_ACTION_UPDATE_DELAY / 1000 else: QtCore.QTimer.singleShot(Constants.AFTER_ACTION_UPDATE_DELAY, self.updateRequest) def redraw(self): """Forces the displayed items to be redrawn""" if not self.window().isMinimized(): try: self.scheduleDelayedItemsLayout() # This setDirtyRegion works but can give this error sometimes: # "underlying C/C++ object has been deleted" #self.setDirtyRegion(QtGui.QRegion(self.viewport().rect())) except Exception, e: map(logger.warning, Utils.exceptionOutput(e))
class CueGuiApplication(QtWidgets.QApplication): # Global signals display_log_file_content = QtCore.Signal(object) double_click = QtCore.Signal(object) facility_changed = QtCore.Signal() single_click = QtCore.Signal(object) unmonitor = QtCore.Signal(object) view_hosts = QtCore.Signal(object) view_object = QtCore.Signal(object) view_procs = QtCore.Signal(object) request_update = QtCore.Signal() status = QtCore.Signal() quit = QtCore.Signal() def __init__(self, *args, **kwargs): super(CueGuiApplication, self).__init__(*args, **kwargs)
class CueJobMonitorTree(AbstractTreeWidget): view_object = QtCore.Signal(object) single_click = QtCore.Signal(object) def __init__(self, parent): self.__shows = {} self.startColumnsForType(Constants.TYPE_JOB) self.addColumn( "Job", 550, id=1, data=lambda job: (job.data.name), tip="The name of the job: show-shot-user_uniqueName\n\n" "The color behind the job will change to:\n" "Blue \t if it is paused\n" "Red \t if it has dead frames\n" "Green \t if it has no running frames with frames waiting\n" "Purple \t if all remaining frames depend on something\n" "Yellow \t if the maxRss is over %sKb" % Constants.MEMORY_WARNING_LEVEL) self.addColumn( "_Comment", 20, id=2, sort=lambda job: (job.data.has_comment), tip="A comment icon will appear if a job has a comment. You\n" "may click on it to view the comments.") self.addColumn( "_Autoeat", 20, id=3, sort=lambda job: (job.data.auto_eat), tip="If the job has auto eating enabled, a pac-man icon\n" "will appear here and all frames that become dead will\n" "automatically be eaten.") self.addColumn("Run", 38, id=3, data=lambda job: (job.stats.running_frames), sort=lambda job: (job.stats.running_frames), tip="The number of running frames.") self.addColumn("Cores", 55, id=4, data=lambda job: ("%.02f" % job.stats.reserved_cores), sort=lambda job: (job.stats.reserved_cores), tip="The number of reserved cores.") self.addColumn("Wait", 45, id=5, data=lambda job: (job.stats.waiting_frames), sort=lambda job: (job.stats.waiting_frames), tip="The number of waiting frames.") self.addColumn("Depend", 55, id=6, data=lambda job: (job.stats.depend_frames), sort=lambda job: (job.stats.depend_frames), tip="The number of dependent frames.") self.addColumn("Total", 50, id=7, data=lambda job: (job.stats.total_frames), sort=lambda job: (job.stats.total_frames), tip="The total number of frames.") # self.addColumn("_Booking Bar", 150, id=8, default=False, # delegate=JobBookingBarDelegate) self.addColumn( "Min", 38, id=9, data=lambda job: ("%.0f" % job.data.min_cores), sort=lambda job: (job.data.min_cores), tip="The minimum number of running cores that the cuebot\n" "will try to maintain.") self.addColumn( "Max", 38, id=10, data=lambda job: ("%.0f" % job.data.max_cores), sort=lambda job: (job.data.max_cores), tip="The maximum number of running cores that the cuebot\n" "will allow.") self.addColumn( "Age", 50, id=11, data=lambda job: (Utils.secondsToHHHMM(time.time() - job.data.start_time)), sort=lambda job: (time.time() - job.data.start_time), tip="The HOURS:MINUTES since the job was launched.") self.addColumn( "Pri", 30, id=12, data=lambda job: (job.data.priority), sort=lambda job: (job.data.priority), tip="The job priority. The cuebot uses this as a suggestion\n" "to determine what job needs the next available matching\n" "resource.") self.addColumn( "ETA", 65, id=13, data=lambda job: (""), tip="(Inacurate and disabled until a better solution exists)\n" "A very rough estimate of the number of HOURS:MINUTES\n" "it will be before the entire job is done.") self.addColumn( "MaxRss", 60, id=14, data=lambda job: (Utils.memoryToString(job.stats.maxRss)), sort=lambda job: (job.stats.maxRss), tip="The most memory used at one time by any single frame.") self.addColumn("_Blank", 20, id=15, tip="Spacer") self.addColumn("Progress", 0, id=16, delegate=JobThinProgressBarDelegate, tip="A visual overview of the job progress.\n" "Green \t is succeeded\n" "Yellow \t is running\n" "Red \t is dead\n" "Purple \t is waiting on a dependency\n" "Light Blue \t is waiting to be booked") for itemType in [Constants.TYPE_GROUP, Constants.TYPE_ROOTGROUP]: self.startColumnsForType(itemType) self.addColumn("", 0, id=1, data=lambda group: group.name) self.addColumn("", 0, id=2) self.addColumn("", 0, id=3) self.addColumn("", 0, id=4, data=lambda group: group.stats.running_frames) self.addColumn( "", 0, id=5, data=lambda group: "%.2f" % group.stats.reserved_cores) self.addColumn("", 0, id=6, data=lambda group: group.stats.waiting_frames) self.addColumn("", 0, id=7) self.addColumn("", 0, id=8) self.addColumn("", 0, id=9, data=lambda group: (group.min_cores or "")) self.addColumn("", 0, id=10, data=lambda group: (group.max_cores > 0 and group.max_cores or "")) self.addColumn("", 0, id=11) self.addColumn("", 0, id=12) self.addColumn("", 0, id=13) self.addColumn("", 0, id=14) self.addColumn("", 0, id=15) self.addColumn( "", 0, id=16, data=lambda group: (group.department != "Unknown" and group.department or "")) AbstractTreeWidget.__init__(self, parent) self.setAnimated(False) self.setAcceptDrops(True) self.setDropIndicatorShown(True) self.setDragEnabled(True) self.setDragDropMode(QtWidgets.QAbstractItemView.DragDrop) # Used to build right click context menus self.__menuActions = MenuActions(self, self.updateSoon, self.selectedObjects) QtGui.qApp.facility_changed.connect(self.removeAllShows) self.itemClicked.connect(self.__itemSingleClickedCopy) self.itemClicked.connect(self.__itemSingleClickedComment) # Skip updates if the user is scrolling self._limitUpdatesDuringScrollSetup() self.setUpdateInterval(22) def __itemSingleClickedCopy(self, item, col): """Called when an item is clicked on. Copies selected object names to the middle click selection clip board. @type item: QTreeWidgetItem @param item: The item clicked on @type col: int @param col: The column clicked on""" selected = [ job.data.name for job in self.selectedObjects() if Utils.isJob(job) ] if selected: QtWidgets.QApplication.clipboard().setText(" ".join(selected)) def __itemSingleClickedComment(self, item, col): """If the comment column is clicked on, and there is a comment on the job, this pops up the comments dialog @type item: QTreeWidgetItem @param item: The item clicked on @type col: int @param col: The column clicked on""" job = item.rpcObject if col == COLUMN_COMMENT and Utils.isJob(job) and job.data.has_comment: self.__menuActions.jobs().viewComments([job]) def startDrag(self, dropActions): """Called when a drag begins""" Utils.startDrag(self, dropActions, self.selectedObjects()) def dragEnterEvent(self, event): Utils.dragEnterEvent(event) def dragMoveEvent(self, event): Utils.dragMoveEvent(event) # Causes the list to scroll when dragging is over the top or bottom 20% ypos = event.answerRect().y() height = self.viewport().height() move = 0 if ypos < height * .2: if ypos < height * .1: move = -5 else: move = -2 elif ypos > height * .8: if ypos > height * .9: move = 2 else: move = 5 if move: self.verticalScrollBar().setValue( self.verticalScrollBar().value() + move) def dropEvent(self, event): item = self.itemAt(event.pos()) if item and item.type() in (Constants.TYPE_ROOTGROUP, Constants.TYPE_GROUP): job_ids = Utils.dropEvent(event, "application/x-job-ids") group_ids = Utils.dropEvent(event, "application/x-group-ids") job_names = Utils.dropEvent(event, "application/x-job-names") group_names = Utils.dropEvent(event, "application/x-group-names") if job_ids or group_ids: body = "" if group_ids: body += "Groups:\n" + "\n".join( Utils.dropEvent(event, "application/x-group-names")) if group_ids and job_ids: body += "\n\n" if job_ids: body += "Jobs:\n" + "\n".join( Utils.dropEvent(event, "application/x-job-names")) result = QtWidgets.QMessageBox.question( self, "Move groups/jobs?", "Move the following into the group: " + "\"%s\"?\n\n%s" % (item.rpcObject.data.name, body), QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No) if result == QtWidgets.QMessageBox.Yes: if job_ids: item.rpcObject.reparentJobs(job_ids) # If no exception, then move was allowed, so do it locally: for id_ in job_ids: proxy = Utils.getObjectKey( opencue.util.proxy(id_, "Job")) self._items[proxy].update( self._items[proxy].rpcObject, item) if group_ids: item.rpcObject.reparentGroups(group_ids) # If no exception, then move was allowed, so do it locally: for id_ in group_ids: proxy = Utils.getObjectKey( opencue.util.proxy(id_, "Group")) self._items[proxy].update( self._items[proxy].rpcObject, item) self.updateSoon() def addShow(self, show, update=True): """Adds a show to the list of monitored shows @type show: Show name @param show: string @type update: boolean @param update: True if the display should update the displayed shows/jobs""" show = str(show) if show not in self.__shows: try: self.__shows[show] = opencue.api.findShow(show) except: logger.warning("This show does not exist: %s" % show) if update: self._update() def removeShow(self, show): """Unmonitors a show @type show: str @param show: The show to unmonitor""" show = str(show) self._itemsLock.lockForWrite() try: if show in self.__shows: del self.__shows[show] finally: self._itemsLock.unlock() self._update() def removeAllShows(self): """Unmonitors all shows""" if self.__shows: self.removeAllItems() self.__shows = {} self._update() def getShows(self): """Returns a list of monitored show objects @rtype: list<show> @return: List of monitored show objects""" return self.__shows.values() def getShowNames(self): """Returns a list of monitored shows @rtype: list<str> @return: List of monitored shows""" return self.__shows.keys() def __getCollapsed(self): return [ item.rpcObject for item in self._items.values() if not item.isExpanded() ] def __setCollapsed(self, collapsed): self.expandAll() for id in collapsed: if id in self._items: self._items[id].setExpanded(False) def _getUpdate(self): """Returns a list of NestedGroup from the cuebot for the monitored shows @rtype: [list<NestedGroup>, set(str)] @return: List that contains updated nested groups and a set of all updated item ideas""" try: nestedShows = [ opencue.wrappers.show.Show(show.data).getJobWhiteboard() for show in self.getShows() ] allIds = set(self.__getNestedIds(nestedShows)) except Exception, e: map(logger.warning, Utils.exceptionOutput(e)) return None return [nestedShows, allIds]
class EmailWidget(QtWidgets.QWidget): send = QtCore.Signal() cancel = QtCore.Signal() def __init__(self, job, format, parent=None): QtWidgets.QWidget.__init__(self) self.__job = job __default_from = "%s-pst@%s" % (job.show(), Constants.EMAIL_DOMAIN) __default_to = "%s@%s" % (job.username(), Constants.EMAIL_DOMAIN) __default_cc = "%s-pst@%s" % (job.show(), Constants.EMAIL_DOMAIN) __default_bcc = "" __default_subject = "%s%s" % (Constants.EMAIL_SUBJECT_PREFIX, job.data.name) __default_body = "%s%s%s" % (Constants.EMAIL_BODY_PREFIX, job.data.name, Constants.EMAIL_BODY_SUFFIX) __default_body += "Hi %s,\n\n" % pwd.getpwnam(job.username()).pw_gecos self.__btnSend = QtWidgets.QPushButton("Send", self) self.__btnCancel = QtWidgets.QPushButton("Cancel", self) # Body Widgets self.__email_body = QtWidgets.QTextEdit(self) self.appendToBody(__default_body) spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) self.__email_from = QtWidgets.QLineEdit(__default_from, self) self.__email_to = QtWidgets.QLineEdit(__default_to, self) self.__email_cc = QtWidgets.QLineEdit(__default_cc, self) self.__email_bcc = QtWidgets.QLineEdit(__default_bcc, self) self.__email_subject = QtWidgets.QLineEdit(__default_subject, self) # Main Virtical Layout vlayout = QtWidgets.QVBoxLayout(self) # Top Grid Layout glayout = QtWidgets.QGridLayout() glayout.setContentsMargins(0, 0, 0, 0) glayout.addWidget(QtWidgets.QLabel("From:", self), 0, 0) glayout.addWidget(self.__email_from, 0, 1) glayout.addWidget(QtWidgets.QLabel("To:", self), 1, 0) glayout.addWidget(self.__email_to, 1, 1) glayout.addWidget(QtWidgets.QLabel("CC:", self), 2, 0) glayout.addWidget(self.__email_cc, 2, 1) glayout.addWidget(QtWidgets.QLabel("BCC:", self), 3, 0) glayout.addWidget(self.__email_bcc, 3, 1) glayout.addWidget(QtWidgets.QLabel("Subject:", self), 4, 0) glayout.addWidget(self.__email_subject, 4, 1) vlayout.addLayout(glayout) vlayout.addWidget(self.__email_body) # Bottom Horizontal Layout hlayout = QtWidgets.QHBoxLayout() hlayout.addItem(spacerItem) hlayout.addWidget(self.__btnSend) hlayout.addWidget(self.__btnCancel) vlayout.addLayout(hlayout) self.__btnSend.clicked.connect(self.sendEmail) self.__btnCancel.clicked.connect(self.cancel.emit) def giveFocus(self): self.__email_body.setFocus(QtCore.Qt.OtherFocusReason) self.__email_body.moveCursor(QtGui.QTextCursor.Start) self.__email_body.moveCursor(QtGui.QTextCursor.Down) self.__email_body.moveCursor(QtGui.QTextCursor.Down) self.__email_body.moveCursor(QtGui.QTextCursor.Down) self.__email_body.moveCursor(QtGui.QTextCursor.Down) def email_from(self): return "%s" % self.__email_from.text() def email_to(self): return "%s" % self.__email_to.text() def email_cc(self): return "%s" % self.__email_cc.text() def email_bcc(self): return "%s" % self.__email_bcc.text() def email_subject(self): return "%s" % self.__email_subject.text() def email_body(self): return "%s" % self.__email_body.toPlainText().toAscii() def appendToBody(self, txt): self.__email_body.append(txt) def setBody(self, txt): self.__email_body.setText(txt) def sendEmail(self): self.send.emit() msg = MIMEText(self.email_body()) msg["Subject"] = Header(self.email_subject(), continuation_ws=' ') msg["To"] = self.email_to() msg["From"] = self.email_from() msg["Cc"] = self.email_cc() recipient_list = [] if self.email_to(): recipient_list.extend(self.email_to().split(",")) if self.email_cc(): recipient_list.extend(self.email_cc().split(",")) if self.email_bcc(): recipient_list.extend(self.email_bcc().split(",")) server = smtplib.SMTP('smtp') server.sendmail(self.email_from(), recipient_list, msg.as_string()) server.quit()
class FrameMonitor(QtWidgets.QWidget): """This contains the frame list table with controls at the top""" handle_filter_layers_byLayer = QtCore.Signal(list) def __init__(self, parent): QtWidgets.QWidget.__init__(self, parent) self.frameMonitorTree = FrameMonitorTree(self) self.page = self.frameMonitorTree.frameSearch.page # Setup main vertical layout layout = QtWidgets.QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) self.setLayout(layout) self.layout().setContentsMargins(0, 0, 0, 0) self.layout().setSpacing(4) # This hlayout would contain any filter/control buttons hlayout = QtWidgets.QHBoxLayout() self._refreshButtonSetup(hlayout) # Button to refresh self._clearButtonSetup(hlayout) # Button to clear all filters self._pageButtonSetup(hlayout) # Button to flip page self._selectStatusSetup(hlayout) # Menu to select frames by status self._filterLayersSetup(hlayout) # Menu to filter layers self._filterStatusSetup(hlayout) # Menu to filter frames by status #For the filter range setup: self._filterRangeSetup(hlayout) hlayout.addStretch() hlayout.addWidget(QtWidgets.QLabel("(Limited to 1000 frames)")) hlayout.addStretch() self._displayJobNameSetup(hlayout) self.layout().addLayout(hlayout) self.layout().addWidget(self.frameMonitorTree) self._frameRangeSelectionFilterSetup(self.layout()) def updateRequest(self): self.frameMonitorTree.updateRequest() def updateChangedRequest(self): self.frameMonitorTree.updateChangedRequest() def setJob(self, job): self.frameMonitorTree.setJob(job) def getColumnWidths(self): return self.frameMonitorTree.getColumnWidths() def setColumnWidths(self, widths): self.frameMonitorTree.setColumnWidths(widths) def getColumnVisibility(self): return self.frameMonitorTree.getColumnVisibility() def setColumnVisibility(self, settings): self.frameMonitorTree.setColumnVisibility(settings) # ============================================================================== # Frame range bar to filter by frame range # ============================================================================== def _frameRangeSelectionFilterSetup(self, layout): from FrameRangeSelection import FrameRangeSelectionWidget widget = FrameRangeSelectionWidget(self) layout.addWidget(widget) widget.selectionChanged.connect(self._frameRangeSelectionFilterHandle) self.frameRangeSelection = widget self.frameMonitorTree.job_changed.connect(self._frameRangeSelectionFilterUpdate) def _frameRangeSelectionFilterUpdate(self): if not self.frameMonitorTree.getJob(): self.frameRangeSelection.setFrameRange(["1", "10000"]) else: layers = self.frameMonitorTree.getJob().getLayers() _min = None _max = None for layer in layers: seq = FileSequence.FrameSet(layer.range()) seq.normalize() frameList = seq.getAll() if _min is not None: _min = min(_min, int(frameList[0])) else: _min = int(frameList[0]) if _max is not None: _max = max(_max, int(frameList[-1])) else: _max = int(frameList[-1]) if _min == _max: _max += 1 self.frameRangeSelection.default_select_size = 1000/len(layers) self.frameRangeSelection.setFrameRange(["%s" % _min,"%s" % _max]) def _frameRangeSelectionFilterHandle(self, start, end): self.frameMonitorTree.frameSearch = opencue.search.FrameSearch.criteriaFromOptions( range="%s-%s" % (start, end)) self.frameMonitorTree.updateRequest() # ============================================================================== # Widgets to filter by frame range # ============================================================================== def _filterRangeSetup(self, layout): btn = QtWidgets.QSpinBox(self) btn.setValue(1) layout.addWidget(btn) self.filter_range_start_box = btn btn = QtWidgets.QSpinBox(self) btn.setValue(1000) layout.addWidget(btn) self.filter_range_end_box = btn btn = QtWidgets.QPushButton("Set Frame Range") btn.setFocusPolicy(QtCore.Qt.NoFocus) layout.addWidget(btn) self.filter_range_btn = btn btn.clicked.connect(self._filterRangeHandle) def _filterRangeHandle(self): value = "%s-%s" % (self.filter_range_start_box.value(), self.filter_range_end_box.value()) self.frameMonitorTree.frameSearch.setOptions(range=value) # ============================================================================== # Button to refresh # ============================================================================== def _refreshButtonSetup(self, layout): """Sets up the refresh button, adds it to the given layout @param layout: The layout to add the button to @type layout: QLayout""" self.btn_refresh = QtWidgets.QPushButton("Refresh") self.btn_refresh.setFocusPolicy(QtCore.Qt.NoFocus) layout.addWidget(self.btn_refresh) self.btn_refresh.clicked.connect(self.frameMonitorTree.updateRequest) self.frameMonitorTree.updated.connect(self._refreshButtonDisableHandle) def _refreshButtonEnableHandle(self): """Called when the refresh button should be enabled""" self.btn_refresh.setEnabled(True) def _refreshButtonDisableHandle(self): """Called when the refresh button should be disabled""" self.btn_refresh.setEnabled(False) QtCore.QTimer.singleShot(5000, self._refreshButtonEnableHandle) # ============================================================================== # Button to clear all filters # ============================================================================== def _clearButtonSetup(self, layout): """Sets up the clear button, adds it to the given layout @param layout: The layout to add the button to @type layout: QLayout""" btn = QtWidgets.QPushButton("Clear") btn.setFocusPolicy(QtCore.Qt.NoFocus) btn.setContentsMargins(0,0,0,0) layout.addWidget(btn) btn.clicked.connect(self._clearButtonHandle) def _clearButtonHandle(self): """Called when the clear button is clicked""" self._filterStatusClear() self._filterLayersUpdate() self._frameRangeSelectionFilterUpdate() self.frameMonitorTree.clearFilters() # ============================================================================== # Widgets to Load previous/next page # ============================================================================== def _pageButtonSetup(self, layout): '''Sets up the page flipping buttons and the page # label @param layout: The layout to add the buttons & label to @type layout: QLayout''' # Previous page button self.prev_page_btn = QtWidgets.QPushButton("<") self.prev_page_btn.setFocusPolicy(QtCore.Qt.NoFocus) self.prev_page_btn.setContentsMargins(0, 0, 0, 0) layout.addWidget(self.prev_page_btn) self.prev_page_btn.clicked.connect(lambda: self._pageButtonsHandle(-1)) # Next page button self.next_page_btn = QtWidgets.QPushButton(">") self.next_page_btn.setFocusPolicy(QtCore.Qt.NoFocus) self.next_page_btn.setContentsMargins(0, 0, 0, 0) layout.addWidget(self.next_page_btn) self.next_page_btn.clicked.connect(lambda: self._pageButtonsHandle(1)) self.frameMonitorTree.job_changed.connect(self._updatePageButtonState) # Page number label self.page_label = QtWidgets.QLabel('') layout.addWidget(self.page_label) # Update Status self._updatePageButtonState() def _pageButtonsHandle(self, offset): '''Called When the "next page" or the "previous page" button is pressed. Updates the FrameSearch by adding the given "offset" value to the page attribute and updating the frameSearch result @param offset: The offset value to add to the page attribute in the frameSearch @type offset: int''' self.page += offset self.frameMonitorTree.frameSearch.page = self.page self.frameMonitorTree.updateRequest() self._updatePageButtonState() def _updatePageButtonState(self): '''Called when a new job is selected, or when any of the frame filters is changed. Updates the "next page" & the "previous page" button state, as well as the page # label. ''' self.prev_page_btn.setEnabled(not self.page == 1) self.next_page_btn.setEnabled(False) job = self.frameMonitorTree.getJob() if not job: self.page_label.setText('') return total_frames = job.totalFrames() if total_frames <= 1000: self.page_label.setText('<font color="gray">{0}</font>' .format('Page 1 of 1')) return has_filters = False for menu in [self._filterLayersButton.menu(), self._filterStatusButton.menu()]: if menu and not has_filters: for item in menu.actions(): if item.isChecked(): has_filters = True break total_pages = int(math.ceil(total_frames / 1000.0)) page_label_text = 'Page {0}'.format(self.page) if has_filters: temp_search = deepcopy(self.frameMonitorTree.frameSearch) temp_search.page = self.page + 1 temp_frames = job.getFrames(temp_search) self.next_page_btn.setEnabled(len(temp_frames) > 0) else: page_label_text += ' of {0}'.format(total_pages) self.next_page_btn.setEnabled(self.page < total_pages) self.page_label.setText('<font color="gray">{0}</font>' .format(page_label_text)) # ============================================================================== # Menu to select frames by status # ============================================================================== def _selectStatusSetup(self, layout): """Sets up the select status menu, adds it to the given layout @param layout: The layout to add the menu to @type layout: QLayout""" btn = QtWidgets.QPushButton("Select Status") btn.setFocusPolicy(QtCore.Qt.NoFocus) btn.setContentsMargins(0,0,0,0) btn.setFlat(True) layout.addWidget(btn) self.select_status_btn = btn menu = QtWidgets.QMenu(self) btn.setMenu(menu) menu.triggered.connect(self._selectStatusHandle) for item in ["Clear", None, "Succeeded", "Running", "Waiting", "Depend", "Dead", "Eaten"]: if item: menu.addAction(item) else: menu.addSeparator() def _selectStatusHandle(self, action): """Called when an option in the select status menu is triggered @param action: Defines the menu option that was selected @type action: QAction""" if action.text() == "Clear": self.frameMonitorTree.clearSelection() else: self.frameMonitorTree.selectByStatus(action.text()) # ============================================================================== # Menu to filter frames by layers # ============================================================================== def _filterLayersSetup(self, layout): """Sets up the filter layers menu, adds it to the given layout @param layout: The layout to add the menu to @type layout: QLayout""" btn = QtWidgets.QPushButton("Filter Layers") btn.setFocusPolicy(QtCore.Qt.NoFocus) btn.setContentsMargins(0,0,0,0) btn.setFlat(True) layout.addWidget(btn) self._filterLayersButton = btn self.frameMonitorTree.job_changed.connect(self._filterLayersUpdate) # For "Filter Selected Layers" on frame selection right click and double click self.frameMonitorTree.handle_filter_layers_byLayer.connect( self._filterLayersHandleByLayer) def _filterLayersUpdate(self): """Updates the filter layers menu with the layers in the current job""" btn = self._filterLayersButton menu = btn.menu() if menu: for action in menu.actions(): menu.removeAction(action) else: menu = QtWidgets.QMenu(self) btn.setMenu(menu) menu.triggered[QtWidgets.QAction].connect(self._filterLayersHandle) if self.frameMonitorTree.getJob(): layers = [x.data.name for x in self.frameMonitorTree.getJob().getLayers()] else: layers = [] for item in ["Clear", None ] + sorted(layers): if item: a = QtWidgets.QAction(menu) a.setText(item) if item != "Clear": a.setCheckable(True) menu.addAction(a) else: menu.addSeparator() def _filterLayersHandle(self, action): """Called when an option in the filter layers menu is triggered. Tells the FrameMonitorTree widget what layers to filter by. @param action: Defines the menu item selected @type action: QAction""" if action.text() == "Clear": for item in self._filterLayersButton.menu().actions(): if item.isChecked(): self.frameMonitorTree.frameSearch.layers.remove("%s" % item.text()) item.setChecked(False) else: if action.isChecked(): self.frameMonitorTree.frameSearch.layers.append("%s" % action.text()) self.page = 1 self.frameMonitorTree.frameSearch.page = self.page else: self.frameMonitorTree.frameSearch.layers.remove("%s" % action.text()) self.frameMonitorTree.updateRequest() self._updatePageButtonState() def _filterLayersHandleByLayer(self, layer_list): """When the FrameMonitorTree widget emits a: "handle_filter_layers_byLayer(PyQt_PyObject)" This function will be called and filter the display according to the layers provided by the signal. @param layer_list: A list of layers to filter by. @type layer_list: list<string>""" for item in self._filterLayersButton.menu().actions(): # If item is checked and not in list: remove if item.isChecked() and not item.text() in layer_list: self.frameMonitorTree.frameSearch.layers.remove(str(item.text())) item.setChecked(False) # if item is not checked, and item is in list: add elif not item.isChecked() and item.text() in layer_list: self.frameMonitorTree.frameSearch.layers.append(str(item.text())) item.setChecked(True) self.page = 1 self.frameMonitorTree.frameSearch.page = self.page self.frameMonitorTree.updateRequest() self._updatePageButtonState() # ============================================================================== # Menu to filter frames by status # ============================================================================== def _filterStatusSetup(self, layout): """Sets up the filter status menu, adds it to the given layout @param layout: The layout to add the menu to @type layout: QLayout""" btn = QtWidgets.QPushButton("Filter Status") btn.setFocusPolicy(QtCore.Qt.NoFocus) btn.setContentsMargins(0,0,0,0) btn.setFlat(True) menu = QtWidgets.QMenu(self) btn.setMenu(menu) menu.triggered.connect(self._filterStatusHandle) for item in [("Clear", QtCore.Qt.ALT + QtCore.Qt.Key_QuoteLeft), None, ("Succeeded", QtCore.Qt.ALT + QtCore.Qt.Key_1), ("Running", QtCore.Qt.ALT + QtCore.Qt.Key_2), ("Waiting", QtCore.Qt.ALT + QtCore.Qt.Key_3), ("Depend", QtCore.Qt.ALT + QtCore.Qt.Key_4), ("Dead", QtCore.Qt.ALT + QtCore.Qt.Key_5), ("Eaten", QtCore.Qt.ALT + QtCore.Qt.Key_6)]: if item: a = QtWidgets.QAction(item[0], menu) if item[0] != "Clear": a.setCheckable(True) if item[1]: a.setShortcut(item[1]) menu.addAction(a) else: menu.addSeparator() layout.addWidget(btn) self._filterStatusButton = btn self.frameMonitorTree.job_changed.connect(self._filterStatusClear) def _filterStatusClear(self): """Clears the currently selected status menu items""" btn = self._filterStatusButton menu = btn.menu() for action in menu.actions(): action.setChecked(False) def _filterStatusHandle(self, action): """Called when an option in the filter status menu is triggered. Tells the FrameMonitorTree widget what status to filter by. @param action: Defines the menu item selected @type action: QAction""" __frameSearch = self.frameMonitorTree.frameSearch if action.text() == "Clear": for item in self._filterStatusButton.menu().actions(): if item.isChecked(): if item.text() != "Clear": __state = getattr(opencue.job_pb2.FrameState, str(item.text())) __frameSearch.states.remove(__state) item.setChecked(False) else: self.page = 1 self.frameMonitorTree.frameSearch.page = self.page __state = getattr(opencue.job_pb2.FrameState, str(action.text())) if action.isChecked(): __frameSearch.states.append(__state) else: __frameSearch.states.remove(__state) self._updatePageButtonState() self.frameMonitorTree.updateRequest() # ============================================================================== # QLabel that displays the job name # ============================================================================== def _displayJobNameSetup(self, layout): """Sets up the displaying the name of the currently job. @param layout: The layout to add the label to @type layout: QLayout""" self._displayJobNameLabel = QtWidgets.QLabel() self._displayJobNameLabel.setMinimumWidth(1) layout.addWidget(self._displayJobNameLabel) self.frameMonitorTree.job_changed.connect(self._displayJobNameUpdate) def _displayJobNameUpdate(self): """Updates the display job name label with the name of the current job.""" if self.frameMonitorTree.getJob(): self._displayJobNameLabel.setText(" <font color=\"green\">%s</font> " % self.frameMonitorTree.getJob().data.name) else: self._displayJobNameLabel.clear()
class LocalBookingWidget(QtWidgets.QWidget): """ A widget for creating opencue RenderParitions, otherwise know as local core booking. """ hosts_changed = QtCore.Signal() def __init__(self, target, parent=None): QtWidgets.QWidget.__init__(self, parent) # Can either be a opencue job, layer, or frame. self.__target = target self.__parent = parent self.jobName = self.getTargetJobName() QtWidgets.QVBoxLayout(self) layout = QtWidgets.QGridLayout() self.__select_host = QtWidgets.QComboBox(self) self.__lba_group = QtWidgets.QGroupBox("Settings", self) try: owner = opencue.api.getOwner(os.environ["USER"]) for host in owner.getHosts(): if host.data.lockState != opencue.api.host_pb2.OPEN: self.__select_host.addItem(host.data.name) except Exception, e: pass self.__deed_button = None self.__msg_widget = None if self.__select_host.count() == 0: self.__deed_button = QtWidgets.QPushButton("Deed This Machine", self) msg = "You have not deeded any hosts or they are not NIMBY locked." self.__msg_widget = QtWidgets.QLabel(msg, self) self.layout().addWidget(self.__msg_widget) self.layout().addWidget(self.__deed_button) self.__deed_button.pressed.connect(self.deedLocalhost) self.__lba_group.setDisabled(True) self.__text_target = QtWidgets.QLabel(self.__target.data.name, self) self.__num_threads = QtWidgets.QSpinBox(self) self.__num_threads.setValue(1) self.__num_cores = QtWidgets.QLineEdit(self) self.__num_cores.setText("1") self.__num_cores.setReadOnly(True) self.__num_frames = QtWidgets.QSpinBox(self) self.__num_frames.setValue(1) self.__frame_warn = QtWidgets.QLabel(self) self.__num_mem = QtWidgets.QSlider(self) self.__num_mem.setValue(4) self.__num_mem.setOrientation(QtCore.Qt.Horizontal) self.__num_mem.setTickPosition(QtWidgets.QSlider.TicksBelow) self.__num_mem.setTickInterval(1) self.__text_num_mem = QtWidgets.QSpinBox(self) self.__text_num_mem.setValue(4) self.__text_num_mem.setSuffix("GB") # # Next layout is if the deed is in use. # layout2 = QtWidgets.QGridLayout() self.__run_group = QtWidgets.QGroupBox("Deed Currently in Use", self) self.__run_cores = QtWidgets.QSpinBox(self) self.__run_mem = QtWidgets.QSlider(self) self.__run_mem.setValue(4) self.__run_mem.setOrientation(QtCore.Qt.Horizontal) self.__run_mem.setTickPosition(QtWidgets.QSlider.TicksBelow) self.__run_mem.setTickInterval(1) self.__text_run_mem = QtWidgets.QSpinBox(self) self.__text_run_mem.setValue(4) self.__text_run_mem.setSuffix("GB") self.__btn_clear = QtWidgets.QPushButton("Clear", self) # # Setup the signals. # self.__btn_clear.pressed.connect(self.clearCurrentHost) self.__select_host.activated.connect(self.__host_changed) self.__num_mem.valueChanged.connect(self.__text_num_mem.setValue) self.__text_num_mem.valueChanged.connect(self.__num_mem.setValue) self.__num_threads.valueChanged.connect(self.__calculateCores) self.__num_frames.valueChanged.connect(self.__calculateCores) self.__run_mem.valueChanged.connect(self.__text_run_mem.setValue) self.__text_run_mem.valueChanged.connect(self.__run_mem.setValue) self.layout().addWidget(QtWidgets.QLabel("Target Host:")) self.layout().addWidget(self.__select_host) layout.addWidget(QtWidgets.QLabel("Target:"), 1, 0) layout.addWidget(self.__text_target, 1, 1, 1, 3) layout.addWidget(QtWidgets.QLabel("Parallel Frames:"), 2, 0) layout.addWidget(self.__num_frames, 2, 1) layout.addWidget(QtWidgets.QLabel("Threads: "), 2, 2) layout.addWidget(self.__num_threads, 2, 3) layout.addWidget(QtWidgets.QLabel("Cores: "), 3, 0) layout.addWidget(self.__num_cores, 3, 1) layout.addWidget(self.__frame_warn, 3, 2, 1, 2) layout.addWidget(QtWidgets.QLabel("Memory (GB): "), 4, 0) layout.addWidget(self.__num_mem, 4, 1, 1, 2) layout.addWidget(self.__text_num_mem, 4, 3) # # Layout 2 # layout2.addWidget(QtWidgets.QLabel("Running Cores:"), 1, 0) layout2.addWidget(self.__run_cores, 1, 1) layout2.addWidget(QtWidgets.QLabel("Memory (GB): "), 3, 0) layout2.addWidget(self.__run_mem, 3, 1, 1, 2) layout2.addWidget(self.__text_run_mem, 3, 3) layout2.addWidget(self.__btn_clear, 4, 0) # # Set up overall layouts # self.__run_group.setLayout(layout2) self.__lba_group.setLayout(layout) self.__stack = QtWidgets.QStackedLayout() self.__stack.addWidget(self.__lba_group) self.__stack.addWidget(self.__run_group) self.layout().addLayout(self.__stack) ## Set initial values. self.__host_changed(self.__select_host.currentText()) self.resize(400, 400)