/* * Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de. * Copyright 2012-2013, Rene Gollent, rene@gollent.com. * Distributed under the terms of the MIT License. */ #include "table/TreeTable.h" #include // #pragma mark - TreeTableField class TreeTableField : public BField { public: TreeTableField(void* object) : fObject(object) { } void* Object() const { return fObject; } private: void* fObject; }; // #pragma mark - TreeTablePath TreeTablePath::TreeTablePath() { } TreeTablePath::TreeTablePath(const TreeTablePath& other) { *this = other; } TreeTablePath::TreeTablePath(const TreeTablePath& other, int32 childIndex) { *this = other; AddComponent(childIndex); } TreeTablePath::~TreeTablePath() { } bool TreeTablePath::AddComponent(int32 childIndex) { try { fComponents.push_back(childIndex); return true; } catch (...) { return false; } } int32 TreeTablePath::RemoveLastComponent() { if (fComponents.empty()) return -1; int32 index = fComponents.back(); fComponents.pop_back(); return index; } void TreeTablePath::Clear() { fComponents.clear(); } int32 TreeTablePath::CountComponents() const { return fComponents.size(); } int32 TreeTablePath::ComponentAt(int32 index) const { if (index < 0 || (size_t)index >= fComponents.size()) return -1; return fComponents[index]; } TreeTablePath& TreeTablePath::operator=(const TreeTablePath& other) { try { fComponents = other.fComponents; } catch (...) { } return *this; } bool TreeTablePath::operator==(const TreeTablePath& other) const { return fComponents == other.fComponents; } bool TreeTablePath::operator!=(const TreeTablePath& other) const { return fComponents != other.fComponents; } // #pragma mark - TreeTableModelListener TreeTableModelListener::~TreeTableModelListener() { } void TreeTableModelListener::TableNodesAdded(TreeTableModel* model, const TreeTablePath& path, int32 childIndex, int32 count) { } void TreeTableModelListener::TableNodesRemoved(TreeTableModel* model, const TreeTablePath& path, int32 childIndex, int32 count) { } void TreeTableModelListener::TableNodesChanged(TreeTableModel* model, const TreeTablePath& path, int32 childIndex, int32 count) { } void TreeTableModelListener::TableModelReset(TreeTableModel* model) { } // #pragma mark - TreeTableModel TreeTableModel::~TreeTableModel() { } void* TreeTableModel::NodeForPath(const TreeTablePath& path) const { void* node = Root(); int32 count = path.CountComponents(); for (int32 i = 0; node != NULL && i < count; i++) node = ChildAt(node, path.ComponentAt(i)); return node; } bool TreeTableModel::AddListener(TreeTableModelListener* listener) { return fListeners.AddItem(listener); } void TreeTableModel::RemoveListener(TreeTableModelListener* listener) { fListeners.RemoveItem(listener); } void TreeTableModel::NotifyNodesAdded(const TreeTablePath& path, int32 childIndex, int32 count) { int32 listenerCount = fListeners.CountItems(); for (int32 i = listenerCount - 1; i >= 0; i--) { TreeTableModelListener* listener = fListeners.ItemAt(i); listener->TableNodesAdded(this, path, childIndex, count); } } void TreeTableModel::NotifyNodesRemoved(const TreeTablePath& path, int32 childIndex, int32 count) { int32 listenerCount = fListeners.CountItems(); for (int32 i = listenerCount - 1; i >= 0; i--) { TreeTableModelListener* listener = fListeners.ItemAt(i); listener->TableNodesRemoved(this, path, childIndex, count); } } void TreeTableModel::NotifyNodesChanged(const TreeTablePath& path, int32 childIndex, int32 count) { int32 listenerCount = fListeners.CountItems(); for (int32 i = listenerCount - 1; i >= 0; i--) { TreeTableModelListener* listener = fListeners.ItemAt(i); listener->TableNodesChanged(this, path, childIndex, count); } } void TreeTableModel::NotifyTableModelReset() { int32 listenerCount = fListeners.CountItems(); for (int32 i = listenerCount - 1; i >= 0; i--) { TreeTableModelListener* listener = fListeners.ItemAt(i); listener->TableModelReset(this); } } // #pragma mark - TreeTableToolTipProvider TreeTableToolTipProvider::~TreeTableToolTipProvider() { } // #pragma mark - TreeTableListener TreeTableListener::~TreeTableListener() { } void TreeTableListener::TreeTableSelectionChanged(TreeTable* table) { } void TreeTableListener::TreeTableNodeInvoked(TreeTable* table, const TreeTablePath& path) { } void TreeTableListener::TreeTableNodeExpandedChanged(TreeTable* table, const TreeTablePath& path, bool expanded) { } void TreeTableListener::TreeTableCellMouseDown(TreeTable* table, const TreeTablePath& path, int32 columnIndex, BPoint screenWhere, uint32 buttons) { } void TreeTableListener::TreeTableCellMouseUp(TreeTable* table, const TreeTablePath& path, int32 columnIndex, BPoint screenWhere, uint32 buttons) { } // #pragma mark - Column class TreeTable::Column : public AbstractColumn { public: Column(TreeTableModel* model, TableColumn* tableColumn); virtual ~Column(); virtual void SetModel(AbstractTableModelBase* model); protected: virtual void DrawTitle(BRect rect, BView* targetView); virtual void DrawField(BField* field, BRect rect, BView* targetView); virtual int CompareFields(BField* field1, BField* field2); virtual void GetColumnName(BString* into) const; virtual float GetPreferredWidth(BField* field, BView* parent) const; private: TreeTableModel* fModel; }; TreeTable::Column::Column(TreeTableModel* model, TableColumn* tableColumn) : AbstractColumn(tableColumn), fModel(model) { } TreeTable::Column::~Column() { } void TreeTable::Column::SetModel(AbstractTableModelBase* model) { fModel = dynamic_cast(model); } void TreeTable::Column::DrawTitle(BRect rect, BView* targetView) { fTableColumn->DrawTitle(rect, targetView); } void TreeTable::Column::DrawField(BField* _field, BRect rect, BView* targetView) { TreeTableField* field = dynamic_cast(_field); if (field == NULL) return; int32 modelIndex = fTableColumn->ModelIndex(); BVariant value; if (!fModel->GetValueAt(field->Object(), modelIndex, value)) return; fTableColumn->DrawValue(value, rect, targetView); } int TreeTable::Column::CompareFields(BField* _field1, BField* _field2) { TreeTableField* field1 = dynamic_cast(_field1); TreeTableField* field2 = dynamic_cast(_field2); if (field1 == field2) return 0; if (field1 == NULL) return -1; if (field2 == NULL) return 1; int32 modelIndex = fTableColumn->ModelIndex(); BVariant value1; bool valid1 = fModel->GetValueAt(field1->Object(), modelIndex, value1); BVariant value2; bool valid2 = fModel->GetValueAt(field2->Object(), modelIndex, value2); if (!valid1) return valid2 ? -1 : 0; if (!valid2) return 1; return fTableColumn->CompareValues(value1, value2); } void TreeTable::Column::GetColumnName(BString* into) const { fTableColumn->GetColumnName(into); } float TreeTable::Column::GetPreferredWidth(BField* _field, BView* parent) const { TreeTableField* field = dynamic_cast(_field); if (field == NULL) return Width(); int32 modelIndex = fTableColumn->ModelIndex(); BVariant value; if (!fModel->GetValueAt(field->Object(), modelIndex, value)) return Width(); return fTableColumn->GetPreferredWidth(value, parent); } // #pragma mark - TreeTableRow class TreeTableRow : public BRow { public: TreeTableRow(TreeTableNode* node) : fNode(node) { } TreeTableNode* Node() { return fNode; } private: TreeTableNode* fNode; }; // #pragma mark - TreeTableNode class TreeTableNode { public: TreeTableNode(TreeTableNode* parent); ~TreeTableNode(); status_t Init(void* modelObject, int32 columnCount); void DetachRow(); TreeTableNode* Parent() const { return fParent; } TreeTableRow* Row() const { return fRow; } void* ModelObject() const; bool AddChild(TreeTableNode* child, int32 index); TreeTableNode* RemoveChild(int32 index); int32 CountChildren() const; TreeTableNode* ChildAt(int32 index); int32 IndexOf(TreeTableNode* child); private: typedef BObjectList NodeList; private: TreeTableNode* fParent; TreeTableRow* fRow; NodeList* fChildren; }; TreeTableNode::TreeTableNode(TreeTableNode* parent) : fParent(parent), fRow(NULL), fChildren(NULL) { } TreeTableNode::~TreeTableNode() { delete fChildren; delete fRow; } status_t TreeTableNode::Init(void* modelObject, int32 columnCount) { // create row fRow = new(std::nothrow) TreeTableRow(this); if (fRow == NULL) return B_NO_MEMORY; // add dummy fields to row for (int32 columnIndex = 0; columnIndex < columnCount; columnIndex++) { // It would be nice to create only a single field and set it for all // columns, but the row takes ultimate ownership, so it have to be // individual objects. TreeTableField* field = new(std::nothrow) TreeTableField(modelObject); if (field == NULL) return B_NO_MEMORY; fRow->SetField(field, columnIndex); } return B_OK; } void TreeTableNode::DetachRow() { fRow = NULL; if (fChildren != NULL) { for (int32 i = 0; TreeTableNode* child = fChildren->ItemAt(i); i++) child->DetachRow(); } } void* TreeTableNode::ModelObject() const { TreeTableField* field = dynamic_cast(fRow->GetField(0)); return field->Object(); } bool TreeTableNode::AddChild(TreeTableNode* child, int32 index) { if (fChildren == NULL) { fChildren = new(std::nothrow) NodeList(10, true); if (fChildren == NULL) return false; } return fChildren->AddItem(child, index); } TreeTableNode* TreeTableNode::RemoveChild(int32 index) { return fChildren != NULL ? fChildren->RemoveItemAt(index) : NULL; } int32 TreeTableNode::CountChildren() const { return fChildren != NULL ? fChildren->CountItems() : 0; } TreeTableNode* TreeTableNode::ChildAt(int32 index) { return fChildren != NULL ? fChildren->ItemAt(index) : NULL; } int32 TreeTableNode::IndexOf(TreeTableNode* child) { return fChildren != NULL ? fChildren->IndexOf(child) : -1; } // #pragma mark - TreeTableSelectionModel TreeTableSelectionModel::TreeTableSelectionModel(TreeTable* table) : fTreeTable(table), fNodes(NULL), fNodeCount(-1) { } TreeTableSelectionModel::~TreeTableSelectionModel() { delete[] fNodes; } int32 TreeTableSelectionModel::CountNodes() { _Update(); return fNodeCount; } void* TreeTableSelectionModel::NodeAt(int32 index) { if (TreeTableNode* node = _NodeAt(index)) return node->ModelObject(); return NULL; } bool TreeTableSelectionModel::GetPathAt(int32 index, TreeTablePath& _path) { if (TreeTableNode* node = _NodeAt(index)) { fTreeTable->_GetPathForNode(node, _path); return true; } return false; } void TreeTableSelectionModel::_SelectionChanged() { if (fNodeCount >= 0) { fNodeCount = -1; delete[] fNodes; fNodes = NULL; } } void TreeTableSelectionModel::_Update() { if (fNodeCount >= 0) return; // count the nodes fNodeCount = 0; BRow* row = NULL; while ((row = fTreeTable->CurrentSelection(row)) != NULL) fNodeCount++; if (fNodeCount == 0) return; // allocate node array fNodes = new(std::nothrow) TreeTableNode*[fNodeCount]; if (fNodes == NULL) { fNodeCount = 0; return; } // get the nodes row = NULL; int32 index = 0; while ((row = fTreeTable->CurrentSelection(row)) != NULL) fNodes[index++] = dynamic_cast(row)->Node(); } TreeTableNode* TreeTableSelectionModel::_NodeAt(int32 index) { _Update(); return index >= 0 && index < fNodeCount ? fNodes[index] : NULL; } // #pragma mark - Table TreeTable::TreeTable(const char* name, uint32 flags, border_style borderStyle, bool showHorizontalScrollbar) : AbstractTable(name, flags, borderStyle, showHorizontalScrollbar), fModel(NULL), fToolTipProvider(NULL), fRootNode(NULL), fSelectionModel(this), fIgnoreSelectionChange(0) { } TreeTable::TreeTable(TreeTableModel* model, const char* name, uint32 flags, border_style borderStyle, bool showHorizontalScrollbar) : AbstractTable(name, flags, borderStyle, showHorizontalScrollbar), fModel(NULL), fToolTipProvider(NULL), fRootNode(NULL), fSelectionModel(this), fIgnoreSelectionChange(0) { SetTreeTableModel(model); } TreeTable::~TreeTable() { SetTreeTableModel(NULL); } bool TreeTable::SetTreeTableModel(TreeTableModel* model) { if (model == fModel) return true; if (fModel != NULL) { fModel->RemoveListener(this); if (fRootNode != NULL) { fRootNode->DetachRow(); delete fRootNode; fRootNode = NULL; } Clear(); for (int32 i = 0; AbstractColumn* column = fColumns.ItemAt(i); i++) column->SetModel(NULL); } fModel = model; if (fModel == NULL) return true; fRootNode = new(std::nothrow) TreeTableNode(NULL); if (fRootNode == NULL) return false; if (fRootNode->Init(fModel->Root(), fModel->CountColumns()) != B_OK) { delete fRootNode; fRootNode = NULL; return false; } fModel->AddListener(this); for (int32 i = 0; AbstractColumn* column = fColumns.ItemAt(i); i++) column->SetModel(fModel); // recursively create the rows if (!_AddChildRows(fRootNode, 0, fModel->CountChildren(fModel->Root()), fModel->CountColumns())) { SetTreeTableModel(NULL); return false; } return true; } void TreeTable::SetToolTipProvider(TreeTableToolTipProvider* toolTipProvider) { fToolTipProvider = toolTipProvider; } TreeTableSelectionModel* TreeTable::SelectionModel() { return &fSelectionModel; } void TreeTable::SelectNode(const TreeTablePath& path, bool extendSelection) { TreeTableNode* node = _NodeForPath(path); if (node == NULL) return; if (!extendSelection) { fIgnoreSelectionChange++; DeselectAll(); fIgnoreSelectionChange--; } AddToSelection(node->Row()); } void TreeTable::DeselectNode(const TreeTablePath& path) { if (TreeTableNode* node = _NodeForPath(path)) Deselect(node->Row()); } void TreeTable::DeselectAllNodes() { DeselectAll(); } bool TreeTable::IsNodeExpanded(const TreeTablePath& path) const { if (TreeTableNode* node = _NodeForPath(path)) return node->Row()->IsExpanded(); return false; } void TreeTable::SetNodeExpanded(const TreeTablePath& path, bool expanded, bool expandAncestors) { if (TreeTableNode* node = _NodeForPath(path)) _SetNodeExpanded(node, expanded, expandAncestors); } void TreeTable::ScrollToNode(const TreeTablePath& path) { if (TreeTableNode* node = _NodeForPath(path)) BColumnListView::ScrollTo(node->Row()); } bool TreeTable::AddTreeTableListener(TreeTableListener* listener) { return fListeners.AddItem(listener); } void TreeTable::RemoveTreeTableListener(TreeTableListener* listener) { fListeners.RemoveItem(listener); } status_t TreeTable::GetCellRectAt(const TreeTablePath& path, int32 colIndex, BRect& _output) const { TreeTableNode* node = _NodeForPath(path); if (node == NULL) return B_ENTRY_NOT_FOUND; AbstractColumn* column = fColumns.ItemAt(colIndex); if (column == NULL) return B_ENTRY_NOT_FOUND; _output = GetFieldRect(node->Row(), column); return B_OK; } bool TreeTable::GetToolTipAt(BPoint point, BToolTip** _tip) { if (fToolTipProvider == NULL) return AbstractTable::GetToolTipAt(point, _tip); // get the table row BRow* row = RowAt(point); if (row == NULL) return AbstractTable::GetToolTipAt(point, _tip); TreeTableRow* treeRow = dynamic_cast(row); // get the table column BColumn* column = ColumnAt(point); int32 columnIndex = column != NULL ? column->LogicalFieldNum() : -1; TreeTablePath path; _GetPathForNode(treeRow->Node(), path); return fToolTipProvider->GetToolTipForTablePath(path, columnIndex, _tip); } void TreeTable::SelectionChanged() { if (fIgnoreSelectionChange > 0) return; fSelectionModel._SelectionChanged(); if (!fListeners.IsEmpty()) { int32 listenerCount = fListeners.CountItems(); for (int32 i = listenerCount - 1; i >= 0; i--) fListeners.ItemAt(i)->TreeTableSelectionChanged(this); } } AbstractTable::AbstractColumn* TreeTable::CreateColumn(TableColumn* column) { return new Column(fModel, column); } void TreeTable::ColumnMouseDown(AbstractColumn* column, BRow* _row, BField* field, BPoint screenWhere, uint32 buttons) { if (!fListeners.IsEmpty()) { // get the table node and the column index TreeTableRow* row = dynamic_cast(_row); int32 columnIndex = column->LogicalFieldNum(); if (row == NULL || columnIndex < 0) return; // get the node path TreeTablePath path; _GetPathForNode(row->Node(), path); // notify listeners int32 listenerCount = fListeners.CountItems(); for (int32 i = listenerCount - 1; i >= 0; i--) { fListeners.ItemAt(i)->TreeTableCellMouseDown(this, path, columnIndex, screenWhere, buttons); } } } void TreeTable::ColumnMouseUp(AbstractColumn* column, BRow* _row, BField* field, BPoint screenWhere, uint32 buttons) { if (!fListeners.IsEmpty()) { // get the table node and the column index TreeTableRow* row = dynamic_cast(_row); int32 columnIndex = column->LogicalFieldNum(); if (row == NULL || columnIndex < 0) return; // get the node path TreeTablePath path; _GetPathForNode(row->Node(), path); // notify listeners int32 listenerCount = fListeners.CountItems(); for (int32 i = listenerCount - 1; i >= 0; i--) { fListeners.ItemAt(i)->TreeTableCellMouseUp(this, path, columnIndex, screenWhere, buttons); } } } void TreeTable::TableNodesAdded(TreeTableModel* model, const TreeTablePath& path, int32 childIndex, int32 count) { TreeTableNode* node = _NodeForPath(path); if (node == NULL) return; _AddChildRows(node, childIndex, count, fModel->CountColumns()); } void TreeTable::TableNodesRemoved(TreeTableModel* model, const TreeTablePath& path, int32 childIndex, int32 count) { TreeTableNode* node = _NodeForPath(path); if (node == NULL) return; _RemoveChildRows(node, childIndex, count); } void TreeTable::TableNodesChanged(TreeTableModel* model, const TreeTablePath& path, int32 childIndex, int32 count) { TreeTableNode* node = _NodeForPath(path); if (node == NULL) return; int32 endIndex = childIndex + count; for (int32 i = childIndex; i < endIndex; i++) { if (TreeTableNode* child = node->ChildAt(i)) UpdateRow(child->Row()); } } void TreeTable::TableModelReset(TreeTableModel* model) { _RemoveChildRows(fRootNode, 0, fRootNode->CountChildren()); _AddChildRows(fRootNode, 0, fModel->CountChildren( fModel->Root()), fModel->CountColumns()); } void TreeTable::ExpandOrCollapse(BRow* _row, bool expand) { TreeTableRow* row = dynamic_cast(_row); if (row == NULL || row->IsExpanded() == expand) return; AbstractTable::ExpandOrCollapse(row, expand); if (row->IsExpanded() != expand) return; TreeTablePath path; _GetPathForNode(row->Node(), path); int32 listenerCount = fListeners.CountItems(); for (int32 i = listenerCount - 1; i >= 0; i--) fListeners.ItemAt(i)->TreeTableNodeExpandedChanged(this, path, expand); } void TreeTable::ItemInvoked() { if (fListeners.IsEmpty()) return; TreeTableRow* row = dynamic_cast(CurrentSelection()); if (row == NULL) return; TreeTablePath path; _GetPathForNode(row->Node(), path); int32 listenerCount = fListeners.CountItems(); for (int32 i = listenerCount - 1; i >= 0; i--) fListeners.ItemAt(i)->TreeTableNodeInvoked(this, path); } bool TreeTable::_AddChildRows(TreeTableNode* parentNode, int32 childIndex, int32 count, int32 columnCount) { int32 childEndIndex = childIndex + count; for (int32 i = childIndex; i < childEndIndex; i++) { void* child = fModel->ChildAt(parentNode->ModelObject(), i); // create node TreeTableNode* node = new(std::nothrow) TreeTableNode(parentNode); if (node == NULL || node->Init(child, columnCount) != B_OK || !parentNode->AddChild(node, i)) { delete node; return false; } // add row AddRow(node->Row(), i, parentNode != fRootNode ? parentNode->Row() : NULL); // recursively create children if (!_AddChildRows(node, 0, fModel->CountChildren(child), columnCount)) return false; } return true; } void TreeTable::_RemoveChildRows(TreeTableNode* parentNode, int32 childIndex, int32 count) { // check if the removal request would in effect remove all // existing nodes. if (parentNode == fRootNode && childIndex == 0 && count == parentNode->CountChildren()) { Clear(); return; } for (int32 i = childIndex + count - 1; i >= childIndex; i--) { if (TreeTableNode* child = parentNode->RemoveChild(i)) { int32 childCount = child->CountChildren(); if (childCount > 0) _RemoveChildRows(child, 0, childCount); RemoveRow(child->Row()); delete child; } } } void TreeTable::_SetNodeExpanded(TreeTableNode* node, bool expanded, bool expandAncestors) { if (expanded && expandAncestors && node != fRootNode) _SetNodeExpanded(node->Parent(), true, true); ExpandOrCollapse(node->Row(), expanded); } TreeTableNode* TreeTable::_NodeForPath(const TreeTablePath& path) const { TreeTableNode* node = fRootNode; int32 count = path.CountComponents(); for (int32 i = 0; node != NULL && i < count; i++) node = node->ChildAt(path.ComponentAt(i)); return node; } void TreeTable::_GetPathForNode(TreeTableNode* node, TreeTablePath& _path) const { if (node == fRootNode) { _path.Clear(); return; } _GetPathForNode(node->Parent(), _path); _path.AddComponent(node->Parent()->IndexOf(node)); }