+node_status = { "in": "Already Selected",
+ "out": "Not Selected",
+ "add": "To be Added",
+ "remove": "To be Removed"}
+
+tag_status = { "in": "Already Set",
+ "out": "Not Set",
+ "add": "To be Added",
+ "remove": "To be Removed"}
+
+default_tags = "Default tags"
+settable_tags = ['delegations', 'initscript']
+
+NAME_COLUMN = 0
+NODE_STATUS_COLUMN = 1
+MEMBERSHIP_STATUS_COLUMN = 2
+KIND_COLUMN = 3
+
+# maximum length of a name to display before clipping
+NAME_MAX_LEN = 48
+
+def itemType(index):
+ if index.parent().parent().isValid():
+ return "tag"
+ else:
+ return "node"
+
+
+class NodeView(QTreeView):
+ def __init__(self, parent):
+ QTreeView.__init__(self, parent)
+
+ self.setAnimated(True)
+ self.setItemsExpandable(True)
+ self.setRootIsDecorated(True)
+ self.setAlternatingRowColors(True)
+# self.setSelectionMode(self.MultiSelection)
+ self.setAttribute(Qt.WA_MacShowFocusRect, 0)
+ self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
+ self.setToolTip("Double click on a row to change its status. Right click on a host to add a tag.")
+
+ def mouseDoubleClickEvent(self, event):
+ index = self.currentIndex()
+ model = index.model()
+ status_index = model.index(index.row(), MEMBERSHIP_STATUS_COLUMN, index.parent())
+ status_data = status_index.data().toString()
+ node_index = model.index(index.row(), NAME_COLUMN, index.parent())
+ node_data = node_index.data().toString()
+
+ if itemType(node_index) == "tag":
+ data = node_index.data().toString()
+ tagname, value = data.split(": ")
+ if tagname not in settable_tags:
+ # Pop up error msg
+ QMessageBox.warning(self, "Not settable", "Insufficient permission to change '%s' tag" % tagname)
+ return
+ if status_data == tag_status['in']:
+ model.setData(status_index, QString(tag_status['remove']))
+ elif status_data == tag_status['add']:
+ model.setData(status_index, QString(tag_status['out']))
+ elif status_data == tag_status['remove']:
+ model.setData(status_index, QString(tag_status['in']))
+ else: model.setData(status_index, QString(node_status['out']))
+ else:
+ # This is a hostname
+ if status_data == node_status['in']:
+ model.setData(status_index, QString(node_status['remove']))
+ elif status_data == node_status['out']:
+ model.setData(status_index, QString(node_status['add']))
+ elif status_data in (node_status['add'], node_status['remove']):
+ if node_data in already_in_nodes: model.setData(status_index, QString(node_status['in']))
+ else: model.setData(status_index, QString(node_status['out']))
+
+ model.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"), node_index, node_index)
+
+ def mousePressEvent(self, event):
+ QTreeView.mousePressEvent(self, event)
+ if event.button() == Qt.LeftButton:
+ return
+
+ # Right click
+ index = self.currentIndex()
+ model = index.model()
+ status_index = model.index(index.row(), 1, index.parent())
+ status_data = status_index.data().toString()
+ node_index = model.index(index.row(), 0, index.parent())
+ node_data = node_index.data().toString()
+
+ if itemType(node_index) == "node":
+ # This is a hostname
+ if status_data in (node_status['in'], node_status['add'], ""):
+ # Pop up a dialog box for adding a new attribute
+ tagname, ok = QInputDialog.getItem(self, "Add tag",
+ "Tag name:", settable_tags)
+ if ok:
+ value, ok = QInputDialog.getText(self, "Add tag",
+ "Value for tag '%s'" % tagname)
+ if ok:
+ # Add a new row to the model for the tag
+
+ # For testing with the QStandardItemModel
+ #nodeItem = model.itemFromIndex(index)
+ #tagstring = QString("%s: %s" % (tagname, value))
+ #tagItem = QStandardItem(tagstring)
+ #status = QStandardItem(QString(tag_status['add']))
+ #nodeItem.appendRow([tagItem, status])
+
+ # We're using the QSortFilterProxyModel here
+ src_index = model.mapToSource(index)
+ src_model = src_index.model()
+ nodeItem = src_model.itemFromIndex(src_index)
+ tagstring = QString("%s: %s" % (tagname, value))
+ tagItem = QStandardItem(tagstring)
+ status = QStandardItem(QString(tag_status['add']))
+ nodeItem.appendRow([tagItem, QStandardItem(QString("")), status])
+
+ elif status_data in (node_status['out'], node_status['remove']):
+ QMessageBox.warning(self, "Not selected", "Can only add tags to selected nodes")
+ return
+
+ model.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"), node_index, node_index)
+
+ def currentChanged(self, current, previous):
+ model = current.model()
+ node_index = model.index(current.row(), 0, current.parent())
+ node_data = node_index.data().toString()
+ self.emit(SIGNAL('hostnameClicked(QString)'), node_data)
+
+
+
+class NodeNameDelegate(QStyledItemDelegate):
+ def __init__(self, parent):
+ QStyledItemDelegate.__init__(self, parent)
+
+ def displayText(self, value, locale):
+ data = str(QStyledItemDelegate.displayText(self, value, locale))
+ if (len(data)>NAME_MAX_LEN):
+ data = data[:(NAME_MAX_LEN-3)] + "..."
+ return QString(data)
+
+ def paint(self, painter, option, index):
+ model = index.model()
+ data = str(self.displayText(index.data(), QLocale()))
+ status_index = model.index(index.row(), MEMBERSHIP_STATUS_COLUMN, index.parent())
+ status_data = status_index.data().toString()
+
+ fm = QFontMetrics(option.font)
+ rect = QRect(option.rect)
+
+ rect.setHeight(rect.height() - 2)
+ rect.setWidth(fm.width(QString(data)) + 6)
+ rect.setX(rect.x() + 5)
+ rect.setY(rect.y() - 1)
+
+ x, y, h, w = rect.x(), rect.y(), rect.height(), rect.width()
+
+ path = QPainterPath()
+ path.addRoundedRect(x - 1, y + 1, w, h, 4, 4)
+
+ painter.save()
+ painter.setRenderHint(QPainter.Antialiasing)
+
+ if option.state & QStyle.State_Selected:
+ painter.fillRect(option.rect, option.palette.color(QPalette.Active, QPalette.Highlight))
+
+ if itemType(index) == "node":
+ if status_data == node_status['in']: # already in the slice
+ painter.fillPath(path, QColor.fromRgb(0, 250, 250))
+ painter.setPen(QColor.fromRgb(0, 0, 0))
+ painter.drawText(rect, 0, QString(data))
+
+ elif status_data == node_status['add']: # newly added to the slice
+ painter.fillPath(path, QColor.fromRgb(0, 250, 0))
+ painter.setPen(QColor.fromRgb(0, 0, 0))
+ painter.drawText(rect, 0, QString(data))
+
+ elif status_data == node_status['remove']: # removed from the slice
+ painter.fillPath(path, QColor.fromRgb(250, 0, 0))
+ painter.setPen(QColor.fromRgb(0, 0, 0))
+ painter.drawText(rect, 0, QString(data))
+
+ else:
+ painter.setPen(QColor.fromRgb(0, 0, 0))
+ painter.drawText(rect, 0, QString(data))
+
+ else:
+ if status_data == tag_status['in']: # already in the slice
+ painter.fillPath(path, QColor.fromRgb(0, 250, 250))
+ painter.setPen(QColor.fromRgb(0, 0, 0))
+ painter.drawText(rect, 0, QString(data))
+
+ elif status_data == tag_status['add']: # newly added to the slice
+ painter.fillPath(path, QColor.fromRgb(0, 250, 0))
+ painter.setPen(QColor.fromRgb(0, 0, 0))
+ painter.drawText(rect, 0, QString(data))
+
+ elif status_data == tag_status['remove']: # removed from the slice
+ painter.fillPath(path, QColor.fromRgb(250, 0, 0))
+ painter.setPen(QColor.fromRgb(0, 0, 0))
+ painter.drawText(rect, 0, QString(data))
+
+ else:
+ painter.setPen(QColor.fromRgb(0, 0, 0))
+ painter.drawText(rect, 0, QString(data))
+
+ painter.restore()
+
+class NodeStatusDelegate(QStyledItemDelegate):
+ def __init__(self, parent):
+ QStyledItemDelegate.__init__(self, parent)
+
+ def paint(self, painter, option, index):
+ model = index.model()
+ nodestatus_index = model.index(index.row(), NODE_STATUS_COLUMN, index.parent())
+ nodestatus_data = nodestatus_index.data().toString()
+
+ fm = QFontMetrics(option.font)
+ rect = QRect(option.rect)
+
+ data = index.data().toString()
+ rect.setHeight(rect.height() - 2)
+ rect.setWidth(fm.width(QString(data)) + 6)
+ rect.setX(rect.x() + 5)
+ rect.setY(rect.y() - 1)
+
+ x, y, h, w = rect.x(), rect.y(), rect.height(), rect.width()
+
+ path = QPainterPath()
+ path.addRoundedRect(x - 1, y + 1, w, h, 4, 4)
+
+ painter.save()
+ painter.setRenderHint(QPainter.Antialiasing)
+
+ if option.state & QStyle.State_Selected:
+ painter.fillRect(option.rect, option.palette.color(QPalette.Active, QPalette.Highlight))
+
+ if (nodestatus_data == ""):
+ painter.setPen(QColor.fromRgb(0, 0, 0))
+ painter.drawText(rect, 0, QString(data))
+ elif (nodestatus_data == "boot"):
+ painter.fillPath(path, QColor.fromRgb(0, 250, 0))
+ painter.setPen(QColor.fromRgb(0, 0, 0))
+ painter.drawText(rect, 0, QString(data))
+ else:
+ painter.fillPath(path, QColor.fromRgb(250, 0, 0))
+ painter.setPen(QColor.fromRgb(0, 0, 0))
+ painter.drawText(rect, 0, QString(data))
+
+ painter.restore()
+
+class NodeFilterProxyModel(QSortFilterProxyModel):