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
Esempio n. 2
0
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
Esempio n. 3
0
    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")
Esempio n. 4
0
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)
Esempio n. 5
0
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))
Esempio n. 9
0
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)
Esempio n. 10
0
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]
Esempio n. 11
0
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()
Esempio n. 12
0
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()
Esempio n. 13
0
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)