def __init__(self, parent): super(TextHighlighter, self).__init__(parent) self.__highlightingRules = [] fmt = QtGui.QTextCharFormat() fmt.setForeground(QtCore.Qt.white) fmt.setBackground(QtCore.Qt.darkGreen) pattern = QtCore.QRegExp("", QtCore.Qt.CaseInsensitive) self.__foundMatchFormat = (fmt, pattern) fmt = QtGui.QTextCharFormat() fmt.setForeground(QtGui.QColor(QtCore.Qt.red).lighter(115)) errors = ["error", "critical", "failed", "fail", "crashed", "crash"] for pattern in errors: rx = QtCore.QRegExp(r'\b%s\b' % pattern, QtCore.Qt.CaseInsensitive) rule = (fmt, rx) self.__highlightingRules.append(rule) fmt = QtGui.QTextCharFormat() fmt.setForeground(QtGui.QColor(255, 168, 0)) for pattern in ("warning", "warn"): rx = QtCore.QRegExp(r'\b%s\b' % pattern, QtCore.Qt.CaseInsensitive) rule = (fmt, rx) self.__highlightingRules.append(rule)
def parent(self, index): if not index.isValid(): return QtCore.QModelIndex() node = index.internalPointer() parent = node.parent() if parent is None: return QtCore.QModelIndex() else: return self.createIndex(parent.row, 0, parent)
def paintEvent(self, event): total_width = self.width() - self.Margins[2] total_height = self.height() - self.Margins[3] total_tasks = float(self.__totals.total) bar = [] for i, v in enumerate(self.__values): if v == 0: continue bar.append((total_width * (v / total_tasks), constants.COLOR_TASK_STATE[i + 1])) painter = QtGui.QPainter() painter.begin(self) painter.setRenderHints(painter.HighQualityAntialiasing | painter.SmoothPixmapTransform | painter.Antialiasing) painter.setPen(self.__PEN) move = 0 for width, color in bar: painter.setBrush(color) rect = QtCore.QRectF(self.Margins[0], self.Margins[1], total_width, total_height) if move: rect.setLeft(move) move += width painter.drawRoundedRect(rect, 3, 3) painter.end() event.accept()
def paintEvent(self, event): total_width = self.width() - self.Margins[2] total_height = self.height() - self.Margins[3] total_tasks = float(self.__totals.total) vals = self.__values bar = [(total_width * (val / total_tasks), color) for val, color in vals if val != 0] painter = QtGui.QPainter() painter.begin(self) painter.setRenderHints(painter.HighQualityAntialiasing | painter.SmoothPixmapTransform | painter.Antialiasing) painter.setPen(self.__PEN) move = 0 x, y = self.Margins[:2] for width, color in reversed(bar): painter.setBrush(color) rect = QtCore.QRectF(x, y, total_width, total_height) if move: rect.setLeft(move) move += width painter.drawRoundedRect(rect, 3, 3) painter.end() event.accept()
def itemsRect(self): """ Return a QRect of the specific boundary of the items in the layout """ count = self._itemLayout.count() if not count: return QtCore.QRect(0,0,0,0) first = self._itemLayout.itemAt(0).widget() if count == 1: rect = first.geometry() return QtCore.QRect(rect.topLeft(), rect.bottomRight()) last = self._itemLayout.itemAt(count-1).widget() return QtCore.QRect(first.geometry().topLeft(), last.geometry().bottomRight())
def __init__(self, parent=None): super(FileWatcher, self).__init__(parent) self.__files = {} self.__timer = QtCore.QTimer(self) self.__timer.setInterval(5000) self.__timer.timeout.connect(self.checkFiles)
def mouseMoveEvent(self, event): startDrag = QtGui.QApplication.startDragDistance() if (event.pos() - self.__dragStartPos).manhattanLength() < startDrag: return mimeData = QtCore.QMimeData() data = cPickle.dumps(self.mapToParent(self.__dragStartPos)) mimeData.setData("application/x-DragDropList", QtCore.QByteArray(data)) pix = QtGui.QPixmap(self.size()) self.render(pix) drag = QtGui.QDrag(self) drag.setMimeData(mimeData) drag.setPixmap(pix) drag.setHotSpot(event.pos()) drag.exec_(QtCore.Qt.MoveAction)
def setRefreshTime(self, value): value = int(value) if self.__refreshTimer is None: self.__refreshTimer = QtCore.QTimer(self) self.__refreshTimer.timeout.connect(self.__refresh) self.__refreshTimer.stop() self.__refreshTimer.start(max(value, 1) * 1000)
def _lastIndex(self): """Index of the very last item in the tree. """ currentIndex = QtCore.QModelIndex() rowCount = self.rowCount(currentIndex) while rowCount > 0: currentIndex = self.index(rowCount - 1, 0, currentIndex) rowCount = self.rowCount(currentIndex) return currentIndex
class CheckableComboBox(QtGui.QWidget): """ A combo box with selectable items. """ optionSelected = QtCore.Signal(str) def __init__(self, title, options, selected=None, icons=None, parent=None): QtGui.QWidget.__init__(self, parent) layout = QtGui.QVBoxLayout(self) self.__btn = btn = QtGui.QPushButton(title) btn.setFocusPolicy(QtCore.Qt.NoFocus) btn.setMaximumHeight(22) btn.setFlat(True) btn.setContentsMargins(0, 0, 0, 0) self.__menu = menu = QtGui.QMenu(self) btn.setMenu(menu) self.setOptions(options, selected, icons) layout.addWidget(btn) btn.toggled.connect(btn.showMenu) menu.triggered.connect( lambda action: self.optionSelected.emit(action.text())) def options(self): return [a.text() for a in self.__menu.actions()] def setOptions(self, options, selected=None, icons=None): if selected and not isinstance(selected, (set, dict)): selected = set(selected) menu = self.__menu menu.clear() for opt, icon in izip_longest(options, icons or []): a = QtGui.QAction(menu) a.setText(opt) a.setCheckable(True) if selected and opt in selected: a.setChecked(True) if icon: a.setIcon(icons[i]) menu.addAction(a) def setSelected(self, options): opts = set(options) for action in self.__menu.actions(): checked = action.text() in opts action.setChecked(checked) def selectedOptions(self): return [a.text() for a in self.__menu.actions() if a.isChecked()]
def findIndex(self, rowPath): """Returns the QtCore.QModelIndex at `rowPath` `rowPath` is a sequence of node rows. For example, [1, 2, 1] is the 2nd child of the 3rd child of the 2nd child of the root. """ result = QtCore.QModelIndex() for row in rowPath: result = self.index(row, 0, result) return result
def highlightBlock(self, text): for fmt, pattern in self.__highlightingRules: expression = QtCore.QRegExp(pattern) index = expression.indexIn(text) while index >= 0: length = expression.matchedLength() self.setFormat(index, length, fmt) index = expression.indexIn(text, index + length) fmt, pattern = self.__foundMatchFormat if pattern.isEmpty(): return expression = QtCore.QRegExp(pattern) index = expression.indexIn(text) while index >= 0: length = expression.matchedLength() self.setFormat(index, length, fmt) index = expression.indexIn(text, index + length)
def __init__(self, parent=None): QtCore.QAbstractTableModel.__init__(self, parent) self.__tasks = [] self.__index = {} self.__jobId = None self.__lastUpdateTime = 0 # A timer for refreshing duration column. self.__timer = QtCore.QTimer(self) self.__timer.setInterval(1000) self.__timer.timeout.connect(self.__durationRefreshTimer)
def _openPanelSettingsDialog(self): w = self.widget() if not w: return pos = QtGui.QCursor.pos() menu = QtGui.QMenu(w) action = menu.addAction("Multi-Tab Mode") action.setCheckable(True) action.setChecked(int(self.getAttr("multiTabMode"))) action.toggled.connect(self.__multiTabModeChanged) menu.popup(pos + QtCore.QPoint(5,5))
class AlnumSortProxyModel(QtGui.QSortFilterProxyModel): RX_ALNUMS = QtCore.QRegExp('(\d+|\D+)') def __init__(self, *args, **kwargs): super(AlnumSortProxyModel, self).__init__(*args, **kwargs) self.setSortRole(DATA_ROLE) def lessThan(self, left, right): sortRole = self.sortRole() leftData = left.data(sortRole) if isinstance(leftData, (str, unicode)): rightData = right.data(sortRole) return self.lessThanAlphaNumeric(leftData, rightData) return super(AlnumSortProxyModel, self).lessThan(left, right) def lessThanAlphaNumeric(self, left, right): if left == right: return False alnums = self.RX_ALNUMS leftList = [] rightList = [] pos = 0 while True: pos = alnums.indexIn(left, pos) if pos == -1: break leftList.append(alnums.cap(1)) pos += alnums.matchedLength() pos = 0 while True: pos = alnums.indexIn(right, pos) if pos == -1: break rightList.append(alnums.cap(1)) pos += alnums.matchedLength() for leftItem, rightItem in zip(leftList, rightList): if leftItem != rightItem and leftItem.isdigit( ) and rightItem.isdigit(): return int(leftItem) < int(rightItem) if leftItem != rightItem: return leftItem < rightItem return left < right
def refresh(self): updated = set() to_add = set() object_ids = set() rows = self._index columnCount = self.columnCount() parent = QtCore.QModelIndex() objects = self.fetchObjects() # Update existing for obj in objects: object_ids.add(obj.id) try: idx = self._index[obj.id] self._items[idx] = obj updated.add(obj.id) self.dataChanged.emit(self.index(idx, 0), self.index(idx, columnCount - 1)) except (IndexError, KeyError): to_add.add(obj) # Add new if to_add: size = len(to_add) start = len(self._items) end = start + size - 1 self.beginInsertRows(parent, start, end) self._items.extend(to_add) self.endInsertRows() LOGGER.debug("adding %d new objects", size) # Remove missing if self.refreshShouldRemove: to_remove = set(self._index.iterkeys()).difference(object_ids) if to_remove: row_ids = ((rows[old_id], old_id) for old_id in to_remove) for row, old_id in sorted(row_ids, reverse=True): self.beginRemoveRows(parent, row, row) obj = self._items.pop(row) self.endRemoveRows() LOGGER.debug("removing %s %s", old_id, obj.name) # reindex the items self._index = dict( ((item.id, i) for i, item in enumerate(self._items)))
def checkFiles(self): if not self.__files: return info = QtCore.QFileInfo() for path, mtime in self.__files.iteritems(): info.setFile(path) test_mtime = info.lastModified() if mtime != test_mtime: self.__files[path] = test_mtime LOGGER.debug("Log file modified: (%r) '%s'", test_mtime, path) self.fileChanged.emit(path)
def index(self, row, column, parent): if not self.subnodes: return QtCore.QModelIndex() node = parent.internalPointer() if parent.isValid() else self try: return self.createIndex(row, column, node.subnodes[row]) except IndexError: logging.debug( "Wrong tree index called (%r, %r, %r). Returning DummyNode", row, column, node) parentNode = parent.internalPointer() if parent.isValid() else None dummy = self._createDummyNode(parentNode, row) self._dummyNodes.add(dummy) return self.createIndex(row, column, dummy)
def refreshData(self): """Updates the data on all nodes, but without having to perform a full reset. A full reset on a tree makes us lose selection and expansion states. When all we ant to do is to refresh the data on the nodes without adding or removing a node, a call on dataChanged() is better. But of course, Qt makes our life complicated by asking us topLeft and bottomRight indexes. This is a convenience method refreshing the whole tree. """ columnCount = self.columnCount() topLeft = self.index(0, 0, QtCore.QModelIndex()) bottomLeft = self._lastIndex() bottomRight = self.sibling(bottomLeft.row(), columnCount - 1, bottomLeft) self.dataChanged.emit(topLeft, bottomRight)
def __init__(self, parent=None): super(TaskModel, self).__init__(parent) self.__jobId = None self.__lastUpdateTime = 0 # Tasks are updated incrementally, so don't # remove missing ones self.refreshShouldRemove = False # A timer for refreshing duration column. self.__timer = QtCore.QTimer(self) self.__timer.setInterval(1000) self.__timer.timeout.connect(self.__durationRefreshTimer)
def paint(self, painter, opts, index): job = index.data(self._role) if not job: super(JobProgressDelegate, self).paint(painter, opts, index) return state = plow.client.TaskState colors = constants.COLOR_TASK_STATE totals = job.totals values = [ (totals.dead, colors[state.DEAD]), (totals.eaten, colors[state.EATEN]), (totals.waiting, colors[state.WAITING]), (totals.depend, colors[state.DEPEND]), (totals.running, colors[state.RUNNING]), (totals.succeeded, colors[state.SUCCEEDED]), ] rect = opts.rect total_width = rect.width() - self.MARGINS[2] total_height = rect.height() - self.MARGINS[3] total_tasks = float(totals.total) bar = [(total_width * (val / total_tasks), color) for val, color in values if val != 0] painter.setRenderHints(painter.HighQualityAntialiasing | painter.SmoothPixmapTransform | painter.Antialiasing) # self.drawBackground(painter, opt, index) painter.setPen(self.PEN) x, y = rect.x(), rect.y() x += self.MARGINS[0] y += self.MARGINS[1] move = 0 for width, color in reversed(bar): painter.setBrush(color) rect = QtCore.QRectF(x, y, total_width, total_height) if move: rect.setLeft(x + move) move += width painter.drawRoundedRect(rect, 3, 3)
def __init__(self, value, parent=None): QtGui.QWidget.__init__(self, parent) layout = QtGui.QGridLayout(self) layout.setSpacing(0) layout.setContentsMargins(0, 0, 0, 0) self._widget = None self.__status = QtGui.QLabel(self) self.__status.setContentsMargins(5, 0, 0, 0) layout.addWidget(self.__status, 0, 2) if not FormWidget.__LOCKED_PIX: FormWidget.__LOCKED_PIX = QtGui.QPixmap(":/images/locked.png") FormWidget.__LOCKED_PIX = FormWidget.__LOCKED_PIX.scaled( QtCore.QSize(12, 12), QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation)
def __init__(self, name, ptype, parent=None): QtGui.QDockWidget.__init__(self, parent) # Add the standard dock action buttons in. # TODO: hook up signals self.__label = QtGui.QLabel(self) self.__label.setIndent(10) self.__name = None self.__ptype = ptype self.setName(name) self.attrs = {} self.__refreshTimer = None # Note: the widet in the panel adds more buttons # to this toolbar. titleBar = QtGui.QWidget(self) barLayout = QtGui.QHBoxLayout(titleBar) barLayout.setSpacing(0) barLayout.setContentsMargins(0, 0, 0, 0) self.__toolbar = toolbar = QtGui.QToolBar(self) toolbar.setIconSize(QtCore.QSize(18, 18)) toolbar.addAction(QtGui.QIcon(":/images/close.png"), "Close", self.__close) float_action = QtGui.QAction(QtGui.QIcon(":/images/float.png"), "Float", self) float_action.toggled.connect(self.__floatingChanged) float_action.setCheckable(True) toolbar.addAction(float_action) config_action = QtGui.QAction(QtGui.QIcon(":/images/config.png"), "Configure Panel", self) config_action.triggered.connect(self._openPanelSettingsDialog) toolbar.addAction(config_action) toolbar.addSeparator() barLayout.addWidget(toolbar) barLayout.addStretch() barLayout.addWidget(self.__label) barLayout.addSpacing(4) self.setTitleBarWidget(titleBar) self.init()
def refresh(self): if not self.__items: self.reload() return rows = self.__index colCount = self.columnCount() parent = QtCore.QModelIndex() nodes = plow.client.get_nodes() nodes_ids = set() to_add = set() # Update for node in nodes: nodes_ids.add(node.id) if node.id in self.__index: row = rows[node.id] self.__items[row] = node start = self.index(row, 0) end = self.index(row, colCount - 1) self.dataChanged.emit(start, end) LOGGER.debug("updating %s %s", node.id, node.name) else: to_add.add(node) # Add new if to_add: size = len(to_add) start = len(self.__items) end = start + size - 1 self.beginInsertRows(parent, start, end) self.__items.extend(to_add) self.endInsertRows() LOGGER.debug("adding %d new nodes", size) # Remove to_remove = set(self.__index.iterkeys()).difference(nodes_ids) for row, old_id in sorted( ((rows[old_id], old_id) for old_id in to_remove), reverse=True): self.beginRemoveRows(parent, row, row) node = self.__items.pop(row) self.endRemoveRows() LOGGER.debug("removing %s %s", old_id, node.name) self.__index = dict((n.id, row) for row, n in enumerate(self.__items))
def __initDefaultWorkspaces(self): for space in self.DEFAULTS: name = self.__getWorkspaceConfName(space) settings = util.getSettings(name) if not settings.contains("main::windowState"): src_name = os.path.join(DEFAULTS_DIR, '%s.ini' % name) if not os.path.exists(src_name): LOGGER.warn("Could not locate default layout file: %r", src_name) continue src = QtCore.QSettings(src_name, QtCore.QSettings.IniFormat) for key in self._DEFAULT_KEYS: settings.setValue(key, src.value(key)) settings.sync() settings.deleteLater()
def __init__(self, *args, **kwargs): super(DragDropItem, self).__init__(*args, **kwargs) self.__dragStartPos = QtCore.QPoint(0,0) self.setMinimumHeight(24) self.setMaximumHeight(50) self.setCheckable(True) self.setFocusPolicy(QtCore.Qt.NoFocus) self.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Preferred) wrapperLayout = QtGui.QHBoxLayout(self) wrapperLayout.setSpacing(0) wrapperLayout.setContentsMargins(0,0,0,0) self._widgetLayout = layout = QtGui.QHBoxLayout() layout.setSpacing(self.ITEM_SPACING) layout.setContentsMargins(8,1,8,1) wrapperLayout.addLayout(layout) wrapperLayout.addStretch()
def __init__(self, attrs, parent=None): super(JobWranglerWidget, self).__init__(parent) self.__attrs = attrs layout = QtGui.QVBoxLayout(self) layout.setContentsMargins(4,0,4,4) # DEBUG if not "projects" in attrs: attrs['projects'] = plow.client.get_projects() self.__model = model = JobModel(attrs, self) self.__proxy = proxy = models.AlnumSortProxyModel(self) proxy.setSourceModel(model) self.__view = view = TreeWidget(self) view.setModel(proxy) view.sortByColumn(4, QtCore.Qt.DescendingOrder) for i, width in enumerate(JobNode.HEADER_WIDTHS): view.setColumnWidth(i, width) view.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) view.customContextMenuRequested.connect(self.__showContextMenu) layout.addWidget(view) # # Connections # view.doubleClicked.connect(self.__itemDoubleClicked) view.activated.connect(self.__itemClicked) model.modelReset.connect(view.expandAll) self.__refreshTimer = timer = QtCore.QTimer(self) timer.setSingleShot(True) timer.setInterval(1500) timer.timeout.connect(self.refresh)
class AlnumSortProxyModel(QtGui.QSortFilterProxyModel): RX_ALNUMS = QtCore.QRegExp('(\d+|\D+)') def __init__(self, *args, **kwargs): super(AlnumSortProxyModel, self).__init__(*args, **kwargs) self.setSortRole(DATA_ROLE) self.__validAlnum = (str, unicode) def lessThan(self, left, right): sortRole = self.sortRole() leftData = left.data(sortRole) if isinstance(leftData, self.__validAlnum): rightData = right.data(sortRole) if leftData == rightData: return False return alphaNumericKey(leftData) < alphaNumericKey(rightData) return super(AlnumSortProxyModel, self).lessThan(left, right)
def paintEvent(self, event): total_width = self.width() total_height = self.height() painter = QtGui.QPainter() painter.begin(self) painter.setRenderHints(painter.HighQualityAntialiasing | painter.SmoothPixmapTransform | painter.Antialiasing) if self.__hasErrors: painter.setBrush(constants.RED) else: painter.setBrush(constants.COLOR_JOB_STATE[self.__state]) painter.setPen(painter.brush().color().darker()) rect = QtCore.QRect(0, 0, total_width, total_height) painter.drawRoundedRect(rect, 5, 5) painter.setPen(QtCore.Qt.black) painter.drawText(rect, QtCore.Qt.AlignCenter, constants.JOB_STATES[self.__state]) painter.end()
def refresh(self): projects = self.__attrs.get('projects', []) if not projects: self.reset() return if not self.__folders: self.reload() return rows = self.__folder_index colCount = self.columnCount() parent = QtCore.QModelIndex() folder_ids = set() to_add = set() folderNodes = dict((f.ref.id, f) for f in self.subnodes) # pull the job board folders = chain.from_iterable(imap(plow.client.get_job_board, projects)) # Update for folder in folders: folder_ids.add(folder.id) row = rows.get(folder.id) if row is None: to_add.add(folder) else: oldFolder = self.__folders[row] folderNode = folderNodes[folder.id] self.__updateJobs(folderNode, folder) folderNode.ref = folder self.__folders[row] = folder start = self.index(row, 0) end = self.index(row, colCount-1) self.dataChanged.emit(start, end) LOGGER.debug("updating %s %s", folder.id, folder.name) # Add new if to_add: size = len(to_add) start = len(self.__folders) end = start + size - 1 self.beginInsertRows(parent, start, end) self.__folders.extend(to_add) self.invalidate() self.endInsertRows() LOGGER.debug("adding %d new folders", size) # Remove to_remove = ((rows[f_id], f_id) for f_id in set(rows).difference(folder_ids)) for row, f_id in sorted(to_remove, reverse=True): self.beginRemoveRows(parent, row, row) folder = self.__folders.pop(row) self.subnodes.remove(folderNodes[f_id]) self.endRemoveRows() LOGGER.debug("removing %s %s", f_id, folder.name) # re-index the rows self.__folder_index = dict((f.id, row) for row, f in enumerate(self.__folders))