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
def dropEvent(event, format="application/x-job-names"):
    if event.mimeData().hasFormat(format):
        item = event.mimeData().data(format)
        stream = QtCore.QDataStream(item, QtCore.QIODevice.ReadOnly)
        names = QtCore.QString()
        stream >> names
        event.accept()
        return [name for name in str(names).split(":") if name]
Esempio n. 3
0
    def __init__(self, num_threads, max_queue=20):
        QtCore.QObject.__init__(self)
        self.__threads = []
        self.__started = False
        self.__max_queue = max_queue
        self.__num_threads = num_threads

        self._q_mutex = QtCore.QMutex()
        self._q_empty = QtCore.QWaitCondition()
        self._q_queue = []
Esempio n. 4
0
    def __restoreSettings(self):
        """Restores the windows settings"""
        self.__plugins.restoreState()

        self.setWindowTitle(
            self.settings.value("%s/Title" % self.name, self.app_name))
        self.restoreState(
            self.settings.value("%s/State" % self.name, QtCore.QByteArray()))
        self.resize(
            self.settings.value("%s/Size" % self.name,
                                QtCore.QSize(1280, 1024)))
        self.move(
            self.settings.value("%s/Position" % self.name, QtCore.QPoint(0,
                                                                         0)))
Esempio n. 5
0
def startDrag(dragSource, dropActions, objects):
    mimeData = QtCore.QMimeData()
    mimeData.setText("\n".join(["%s" % job.data.name for job in objects]))

    mimeDataAdd(mimeData, "application/x-job-names",
                [object.data.name for object in objects if isJob(object)])

    mimeDataAdd(mimeData, "application/x-job-ids",
                [opencue.id(object) for object in objects if isJob(object)])

    mimeDataAdd(mimeData, "application/x-group-names",
                [object.data.name for object in objects if isGroup(object)])

    mimeDataAdd(mimeData, "application/x-group-ids",
                [opencue.id(object) for object in objects if isGroup(object)])

    mimeDataAdd(mimeData, "application/x-host-names",
                [object.data.name for object in objects if isHost(object)])

    mimeDataAdd(mimeData, "application/x-host-ids",
                [opencue.id(object) for object in objects if isHost(object)])

    drag = QtGui.QDrag(dragSource)
    drag.setMimeData(mimeData)
    drag.exec_(QtCore.Qt.MoveAction)
Esempio n. 6
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. 7
0
    def __filterHardwareStateSetup(self, layout):
        self.__filterHardwareStateList = sorted(
            [state for state in dir(
                opencue.api.host_pb2.HardwareState) if not state.startswith("_")])

        btn = QtWidgets.QPushButton("Filter HardwareState")
        btn.setMaximumHeight(FILTER_HEIGHT)
        btn.setFocusPolicy(QtCore.Qt.NoFocus)
        btn.setContentsMargins(0,0,0,0)
        btn.setFlat(True)

        menu = QtWidgets.QMenu(self)
        btn.setMenu(menu)
        QtCore.QObject.connect(menu,
                               QtCore.SIGNAL("triggered(QAction*)"),
                               self.__filterHardwareStateHandle)

        for item in ["Clear", None] + self.__filterHardwareStateList:
            if item:
                a = QtWidgets.QAction(menu)
                a.setText(str(item))
                if item != "Clear":
                    a.setCheckable(True)
                menu.addAction(a)
            else:
                menu.addSeparator()

        layout.addWidget(btn)
        self.__filterHardwareStateButton = btn
Esempio n. 8
0
    def __filterAllocationSetup(self, layout):
        self.__filterAllocationList = sorted(
            [alloc.name() for alloc in opencue.api.getAllocations()])

        btn = QtWidgets.QPushButton("Filter Allocation")
        btn.setMaximumHeight(FILTER_HEIGHT)
        btn.setFocusPolicy(QtCore.Qt.NoFocus)
        btn.setContentsMargins(0,0,0,0)
        btn.setFlat(True)

        menu = QtWidgets.QMenu(self)
        btn.setMenu(menu)
        QtCore.QObject.connect(menu,
                               QtCore.SIGNAL("triggered(QAction*)"),
                               self.__filterAllocationHandle)

        for item in ["Clear", None] + self.__filterAllocationList:
            if item:
                a = QtWidgets.QAction(menu)
                a.setText(str(item))
                if item != "Clear":
                    a.setCheckable(True)
                menu.addAction(a)
            else:
                menu.addSeparator()

        layout.addWidget(btn)
        self.__filterAllocationButton = btn
Esempio n. 9
0
    def __init__(self,
                 title,
                 function,
                 work,
                 concurrent,
                 cancelTitle,
                 cancelText,
                 parent=None):
        """Creates, displays and starts the progress bar.
        @type  title: str
        @param title: The title for the progress bar
        @type  function: callable
        @param function: The function that the work units should be passed to
        @type  work: list<sequence>
        @param work: A list of sequences to pass to the function
        @type  concurrent: int
        @param concurrent: The number of units to submit to threadpool at once
        @type  cancelTitle: string
        @param cancelTitle: This is displayed as the title of the confirmation
                            dialog box if the user attempts to cancel
        @type  cancelText: string
        @param cancelText: This is displayed as the text of the confirmation
                           dialog box if the user attempts to cancel
        @type  parent: QObject
        @param parent: The parent for this object"""
        QtWidgets.QDialog.__init__(self, parent)

        self.__work = work
        self.__function = function

        self.__workLock = QtCore.QReadWriteLock()
        self.__count = 0

        self.__bar = QtWidgets.QProgressBar(self)
        self.__bar.setRange(0, len(self.__work))
        self.__bar.setValue(0)
        self.__btn_cancel = QtWidgets.QPushButton("Cancel", self)
        self.__cancelConfirmation = None
        self.__cancelTitle = cancelTitle
        self.__cancelText = cancelText

        vlayout = QtWidgets.QVBoxLayout(self)
        vlayout.addWidget(self.__bar)
        vlayout.addWidget(self.__btn_cancel)
        self.setLayout(vlayout)

        self.setWindowFlags(self.windowFlags()
                            | QtCore.Qt.WindowStaysOnTopHint)
        self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
        self.setSizeGripEnabled(True)
        self.setFixedSize(300, 100)
        self.setWindowTitle(title)

        self.__btn_cancel.clicked.connect(self.cancel)

        self.show()

        for thread in range(max(concurrent, 1)):
            self._submitWork()
    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)))
    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)
Esempio n. 12
0
 def __refreshToggleCheckBoxSetup(self, layout):
     checkBox = QtWidgets.QCheckBox("Auto-refresh", self)
     layout.addWidget(checkBox)
     if self.hostMonitorTree.enableRefresh:
         checkBox.setCheckState(QtCore.Qt.Checked)
     QtCore.QObject.connect(checkBox,
                            QtCore.SIGNAL('stateChanged(int)'),
                            self.__refreshToggleCheckBoxHandle)
     __refreshToggleCheckBoxCheckBox = checkBox
Esempio n. 13
0
    def contextMenuEvent(self, e):
        """When right clicking on an item, this raises a context menu"""

        menu = QtWidgets.QMenu()
        self.__menuActions.subscriptions().addAction(menu, "editSize")
        self.__menuActions.subscriptions().addAction(menu, "editBurst")
        menu.addSeparator()
        self.__menuActions.subscriptions().addAction(menu, "delete")
        menu.exec_(QtCore.QPoint(e.globalX(),e.globalY()))
Esempio n. 14
0
    def __init__(self, sourceTree, parent = None):
        """CueStateBar init
        @type  sourceTree: QTreeWidget
        @param sourceTree: The tree to get the jobs from
        @type  parent: QWidget
        @param parent: The parent widget"""
        QtWidgets.QWidget.__init__(self, parent)
        self.setContentsMargins(8, 1, 1, 1)
        self.setFixedWidth(22)

        self.__sourceTree = weakref.proxy(sourceTree)
        self.__colors = []
        self.__baseColor = QtGui.qApp.palette().color(QtGui.QPalette.Base)
        self.__colorsLock = QtCore.QReadWriteLock()
        self.__timer = QtCore.QTimer(self)
        self.__lastUpdate = 0

        self.__timer.timeout.connect(self.updateColors)
        self.__sourceTree.verticalScrollBar().valueChanged.connect(self.update)
        self.__sourceTree.verticalScrollBar().rangeChanged.connect(self.__updateColors)

        self.__timer.start(10000)
Esempio n. 15
0
    def contextMenuEvent(self, e):
        """When right clicking on an item, this raises a context menu"""

        count = len(self.selectedObjects())
        menu = QtWidgets.QMenu()

        if count:
            self.__menuActions.shows().addAction(menu, "properties")
            if count == 1:
                menu.addSeparator()
                self.__menuActions.shows().addAction(menu,
                                                     "createSubscription")

            menu.exec_(QtCore.QPoint(e.globalX(), e.globalY()))
Esempio n. 16
0
    def __filterByHostNameSetup(self, layout):
        btn = QtWidgets.QLineEdit(self)
        btn.setMaximumHeight(FILTER_HEIGHT)
        btn.setFixedWidth(155)
        btn.setFocusPolicy(QtCore.Qt.StrongFocus)
        layout.addWidget(btn)
        self.__filterByHostName = btn

        self.__filterByHostNameLastInput = None

        QtCore.QObject.connect(self.__filterByHostName,
                               QtCore.SIGNAL('editingFinished()'),
                               self.__filterByHostNameHandle)

        btn = QtWidgets.QPushButton("Clr")
        btn.setMaximumHeight(FILTER_HEIGHT)
        btn.setFocusPolicy(QtCore.Qt.NoFocus)
        btn.setFixedWidth(24)
        layout.addWidget(btn)
        QtCore.QObject.connect(btn,
                               QtCore.SIGNAL('clicked()'),
                               self.__filterByHostNameClear)

        self.__filterByHostNameClearBtn = btn
    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
Esempio n. 18
0
 def refreshComments(self):
     """Clears and populates the comment list from the cuebot"""
     comments = self.__source.getComments()
     self.__treeSubjects.clear()
     for comment in comments:
         item = Comment(comment)
         item.setSizeHint(0, QtCore.QSize(300, 1))
         self.__treeSubjects.addTopLevelItem(item)
     self.__treeSubjects.resizeColumnToContents(0)
     last_item = self.__treeSubjects.topLevelItem(len(comments) - 1)
     if last_item:
         self.__btnSave.setText(SAVE_EDIT)
         self.__btnSave.setEnabled(False)
         last_item.setSelected(True)
     else:
         self.__createNewComment()
    def __displayColumnMenu(self):
        point = self.__dropdown.mapToGlobal(
            QtCore.QPoint(self.__dropdown.width(), self.__dropdown.height()))

        menu = QtWidgets.QMenu(self)
        menu.triggered.connect(self.__handleColumnMenu)
        for col in range(self.columnCount()):
            if self.columnWidth(col) or self.isColumnHidden(col):
                name = self.__columnInfoByType[
                    self.__columnPrimaryType][col][COLUMN_NAME]
                a = QtWidgets.QAction(menu)
                a.setText("%s. %s" % (col, name))
                a.setCheckable(True)
                a.setChecked(not self.isColumnHidden(col))
                menu.addAction(a)
        menu.exec_(point)
Esempio n. 20
0
    def __init__(self, parent):
        QtWidgets.QWidget.__init__(self, parent)

        self.__color = QtGui.QColor(55, 200, 55)
        self.__brush = QtGui.QBrush()
        self.__brush.setColor(self.__color)
        self.__brush.setStyle(QtCore.Qt.SolidPattern)

        self.__show = opencue.api.findShow("clo")
        self.__history = [0] * 100
        self.__line = 575
        self.__max = max(self.__line * 1.2, 80)

        self.__timer = QtCore.QTimer(self)
        self.__timer.timeout.connect(self.addNumber)

        self.__timer.start(10000)
Esempio n. 21
0
 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. 22
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. 23
0
def startup(app_name, app_version, argv):
    app = CueGuiApplication(argv)

    # Start splash screen
    from SplashWindow import SplashWindow
    splash = SplashWindow(app, app_name, app_version, Constants.RESOURCE_PATH)
    # Display a splash message with: splash.msg("Message")

    # Allow ctrl-c to kill the application
    import signal
    signal.signal(signal.SIGINT, signal.SIG_DFL)

    # Load window icon
    app.setWindowIcon(QtGui.QIcon('%s/images/windowIcon.png' % Constants.RESOURCE_PATH))

    app.setApplicationName(app_name)
    app.lastWindowClosed.connect(app.quit)

    QtGui.qApp.threadpool = ThreadPool(3)

    config_path = "/.%s/config" % app_name.lower()
    settings = QtCore.QSettings(QtCore.QSettings.IniFormat, QtCore.QSettings.UserScope, config_path)
    QtGui.qApp.settings = settings

    Style.init()

    # If the config file does not exist, copy over the default
    local = "%s%s.ini" % (os.getenv("HOME"), config_path)
    if not os.path.exists(local):
        default = os.path.join(Constants.DEFAULT_INI_PATH, "%s.ini" % app_name.lower())
        print "Not found: %s\nCopying:   %s" % (local, default)
        try:
            os.mkdir(os.path.dirname(local))
        except Exception, e:
            logger.debug(e)
        try:
            import shutil
            shutil.copy2(default, local)
        except Exception, e:
            logger.debug(e)
Esempio n. 24
0
def mimeDataAdd(mimeData, format, objects):
    data = QtCore.QByteArray()
    stream = QtCore.QDataStream(data, QtCore.QIODevice.WriteOnly)
    text = QtCore.QString(":".join(objects))
    stream << text
    mimeData.setData(format, data)
Esempio n. 25
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. 26
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))
Esempio n. 27
0
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)))
 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 __getTickAreaExtent(self):
     return QtCore.QRect(10, -self.height()/2, self.width() - self.__right_margin - 20, self.height())