537 lines
13 KiB
C++
537 lines
13 KiB
C++
#include "stdafx.h"
|
|
#include "TreeItemModel.h"
|
|
|
|
QString QTreeViewEx::m_ResetColumns = "Reset Columns";
|
|
|
|
#define FIRST_COLUMN 0
|
|
|
|
bool CTreeItemModel::m_DarkMode = false;
|
|
|
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
|
bool operator < (const QVariant& l, const QVariant& r)
|
|
{
|
|
auto ret = QVariant::compare(l, r);
|
|
Q_ASSERT(ret != QPartialOrdering::Unordered);
|
|
return ret == QPartialOrdering::Less;
|
|
}
|
|
#endif
|
|
|
|
CTreeItemModel::CTreeItemModel(QObject *parent)
|
|
: QAbstractItemModelEx(parent)
|
|
{
|
|
m_bUseIcons = false;
|
|
m_Root = NULL;
|
|
}
|
|
|
|
CTreeItemModel::~CTreeItemModel()
|
|
{
|
|
delete m_Root;
|
|
}
|
|
|
|
CSimpleTreeModel::CSimpleTreeModel(QObject *parent)
|
|
: CTreeItemModel(parent)
|
|
{
|
|
m_bTree = true;
|
|
m_Root = MkNode(QVariant());
|
|
}
|
|
|
|
QList<QVariant> CSimpleTreeModel::MakePath(const QVariantMap& Cur, const QMap<QVariant, QVariantMap>& List)
|
|
{
|
|
QVariant ParentID = Cur["ParentID"];
|
|
QVariantMap Parent = List.value(ParentID);
|
|
|
|
QList<QVariant> Path;
|
|
if (!Parent.isEmpty() && ParentID != Cur["ID"])
|
|
{
|
|
Path = MakePath(Parent, List);
|
|
Path.append(ParentID);
|
|
}
|
|
return Path;
|
|
}
|
|
|
|
bool CSimpleTreeModel::TestPath(const QList<QVariant>& Path, const QVariantMap& Cur, const QMap<QVariant, QVariantMap>& List, int Index)
|
|
{
|
|
QVariant ParentID = Cur["ParentID"];
|
|
QVariantMap Parent = List.value(ParentID);
|
|
|
|
if (!Parent.isEmpty())
|
|
{
|
|
if(Index >= Path.size() || Path[Path.size() - Index - 1] != ParentID)
|
|
return false;
|
|
|
|
return TestPath(Path, Parent, List, Index + 1);
|
|
}
|
|
|
|
return Path.size() == Index;
|
|
}
|
|
|
|
void CSimpleTreeModel::Sync(const QMap<QVariant, QVariantMap>& List)
|
|
{
|
|
QMap<QList<QVariant>, QList<STreeNode*> > New;
|
|
QHash<QVariant, STreeNode*> Old = m_Map;
|
|
|
|
foreach (const QVariantMap& Cur, List)
|
|
{
|
|
QVariant ID = Cur["ID"];
|
|
|
|
QModelIndex Index;
|
|
|
|
STreeNode* pNode = static_cast<STreeNode*>(Old.value(ID));
|
|
if(!pNode || (m_bTree ? !TestPath(pNode->Path, Cur, List) : !pNode->Path.isEmpty()))
|
|
{
|
|
pNode = static_cast<STreeNode*>(MkNode(ID));
|
|
pNode->Values.resize(columnCount());
|
|
if(m_bTree)
|
|
pNode->Path = MakePath(Cur, List);
|
|
pNode->Icon = Cur["Icon"];
|
|
New[pNode->Path].append(pNode);
|
|
}
|
|
else
|
|
{
|
|
Old[ID] = NULL;
|
|
Index = Find(m_Root, pNode);
|
|
}
|
|
|
|
//if(Index.isValid()) // this is to slow, be more precise
|
|
// emit dataChanged(createIndex(Index.row(), 0, pNode), createIndex(Index.row(), columnCount()-1, pNode));
|
|
|
|
int Col = 0;
|
|
bool State = false;
|
|
bool Changed = false;
|
|
|
|
if (pNode->IsBold != Cur["IsBold"].toBool()) {
|
|
pNode->IsBold = Cur["IsBold"].toBool();
|
|
Changed = true;
|
|
}
|
|
|
|
QVariantMap Values = Cur["Values"].toMap();
|
|
for(int section = FIRST_COLUMN; section < columnCount(); section++)
|
|
{
|
|
if (!m_Columns.contains(section))
|
|
continue; // ignore columns which are hidden
|
|
|
|
QVariant Value = Cur[m_ColumnKeys.at(section).second];
|
|
|
|
STreeNode::SValue& ColValue = pNode->Values[section];
|
|
|
|
if (ColValue.Raw != Value)
|
|
{
|
|
Changed = true;
|
|
ColValue.Raw = Value;
|
|
|
|
//ColValue.Formatted =
|
|
}
|
|
|
|
if(State != Changed)
|
|
{
|
|
if(State && Index.isValid())
|
|
emit dataChanged(createIndex(Index.row(), Col, pNode), createIndex(Index.row(), section-1, pNode));
|
|
State = Changed;
|
|
Col = section;
|
|
}
|
|
Changed = false;
|
|
}
|
|
if(State && Index.isValid())
|
|
emit dataChanged(createIndex(Index.row(), Col, pNode), createIndex(Index.row(), columnCount()-1, pNode));
|
|
|
|
}
|
|
|
|
CTreeItemModel::Sync(New, Old);
|
|
}
|
|
|
|
void CTreeItemModel::Sync(QMap<QList<QVariant>, QList<STreeNode*> >& New, QHash<QVariant, STreeNode*>& Old, QList<QVariant>* pAdded)
|
|
{
|
|
Purge(m_Root, QModelIndex(), Old);
|
|
|
|
if(!New.isEmpty())
|
|
{
|
|
emit layoutAboutToBeChanged();
|
|
|
|
//foreach(const QString& Path, New.keys())
|
|
for(QMap<QList<QVariant>, QList<STreeNode*> >::const_iterator I = New.begin(); I != New.end(); I++)
|
|
Fill(m_Root, QModelIndex(), I.key(), 0, I.value(), I.key(), pAdded);
|
|
|
|
emit layoutChanged();
|
|
}
|
|
|
|
emit Updated();
|
|
}
|
|
|
|
/*void CTreeItemModel::CountItems()
|
|
{
|
|
CountItems(m_Root);
|
|
}
|
|
|
|
int CTreeItemModel::CountItems(STreeNode* pRoot)
|
|
{
|
|
if(pRoot->Children.isEmpty())
|
|
return 1;
|
|
|
|
int Counter = 0;
|
|
foreach(STreeNode* pChild, pRoot->Children)
|
|
Counter += CountItems(pChild);
|
|
//pRoot->AllChildren = Counter;
|
|
return Counter;
|
|
}*/
|
|
|
|
CTreeItemModel::STreeNode* CTreeItemModel::MkVirtualNode(const QVariant& Id, STreeNode* pParent)
|
|
{
|
|
STreeNode* pNode = MkNode(Id);
|
|
pNode->Parent = pParent;
|
|
pNode->Virtual = true;
|
|
pNode->Values.resize(columnCount());
|
|
return pNode;
|
|
}
|
|
|
|
void CTreeItemModel::Purge(STreeNode* pParent, const QModelIndex &parent, QHash<QVariant, STreeNode*> &Old)
|
|
{
|
|
int Removed = 0;
|
|
|
|
int Begin = -1;
|
|
int End = -1;
|
|
for(int i = pParent->Children.count()-1; i >= -1; i--)
|
|
{
|
|
STreeNode* pNode = i >= 0 ? pNode = pParent->Children[i] : NULL;
|
|
if(pNode)
|
|
Purge(pNode, index(i, 0, parent), Old);
|
|
|
|
bool bRemove = false;
|
|
if(pNode && (pNode->Virtual || pNode->ID.isNull() || (bRemove = Old.value(pNode->ID) != NULL)) && pNode->Children.isEmpty()) // remove it
|
|
{
|
|
//m_Map.remove(pNode->ID, pNode);
|
|
m_Map.remove(pNode->ID);
|
|
if(End == -1)
|
|
End = i;
|
|
}
|
|
else // keep it
|
|
{
|
|
if(bRemove)
|
|
{
|
|
ASSERT(!pNode->Children.isEmpty()); // we wanted to remove it but we have to keep it
|
|
//m_Map.remove(pNode->ID, pNode);
|
|
m_Map.remove(pNode->ID);
|
|
pNode->ID = QVariant();
|
|
pNode->Icon.clear();
|
|
}
|
|
|
|
if(End != -1) // remove what's to be removed at once
|
|
{
|
|
Begin = i + 1;
|
|
|
|
beginRemoveRows(parent, Begin, End);
|
|
//ASSERT(pParent->Children.count() > End);
|
|
for(int j = End; j >= Begin; j--)
|
|
{
|
|
pNode = pParent->Children.takeAt(j);
|
|
delete pNode;
|
|
Removed++;
|
|
}
|
|
endRemoveRows();
|
|
|
|
End = -1;
|
|
Begin = -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(Removed > 0)
|
|
{
|
|
pParent->Aux.clear();
|
|
for (int i = pParent->Children.count() - 1; i >= 0; i--)
|
|
{
|
|
pParent->Children[i]->Row = i;
|
|
pParent->Aux.insert(pParent->Children[i]->ID, i);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CTreeItemModel::Fill(STreeNode* pParent, const QModelIndex &parent, const QList<QVariant>& Paths, int PathsIndex, const QList<STreeNode*>& New, const QList<QVariant>& Path, QList<QVariant>* pAdded)
|
|
{
|
|
if(Paths.size() > PathsIndex)
|
|
{
|
|
QVariant CurPath = Paths.at(PathsIndex);
|
|
STreeNode* pNode;
|
|
int i = pParent->Aux.value(CurPath, -1);
|
|
if(i != -1)
|
|
pNode = pParent->Children[i];
|
|
else
|
|
{
|
|
i = 0;
|
|
pNode = MkVirtualNode(CurPath, pParent);
|
|
if (pAdded) pAdded->append(CurPath);
|
|
m_Map.insert(CurPath, pNode);
|
|
|
|
//int Count = pParent->Children.count();
|
|
//beginInsertRows(parent, Count, Count);
|
|
pParent->Aux.insert(pNode->ID, pParent->Children.size());
|
|
pNode->Row = pParent->Children.size();
|
|
pParent->Children.append(pNode);
|
|
//endInsertRows();
|
|
}
|
|
Fill(pNode, index(i, 0, parent), Paths, PathsIndex + 1, New, Path, pAdded);
|
|
}
|
|
else
|
|
{
|
|
for(QList<STreeNode*>::const_iterator I = New.begin(); I != New.end(); I++)
|
|
{
|
|
STreeNode* pNode = *I;
|
|
ASSERT(pNode);
|
|
//ASSERT(!m_Map.contains(pNode->ID));
|
|
m_Map.insert(pNode->ID, pNode);
|
|
pNode->Parent = pParent;
|
|
|
|
//int Count = pParent->Children.count();
|
|
//beginInsertRows(parent, Count, Count);
|
|
pParent->Aux.insert(pNode->ID, pParent->Children.size());
|
|
pNode->Row = pParent->Children.size();
|
|
pParent->Children.append(pNode);
|
|
//endInsertRows();
|
|
}
|
|
}
|
|
}
|
|
|
|
QModelIndex CTreeItemModel::FindIndex(const QVariant& ID)
|
|
{
|
|
if(STreeNode* pNode = m_Map.value(ID))
|
|
return Find(m_Root, pNode);
|
|
return QModelIndex();
|
|
}
|
|
|
|
QModelIndex CTreeItemModel::Find(STreeNode* pParent, STreeNode* pNode)
|
|
{
|
|
// ''find''
|
|
ASSERT(pNode->Parent->Children[pNode->Row] == pNode);
|
|
return createIndex(pNode->Row, FIRST_COLUMN, pNode);
|
|
/*
|
|
int count = pParent->Children.count();
|
|
for(int i=0; i < count; i++)
|
|
{
|
|
if (pParent->Children[i] == pNode)
|
|
{
|
|
ASSERT(i == pNode->Row);
|
|
return createIndex(i, FIRST_COLUMN, pNode);
|
|
}
|
|
|
|
QModelIndex Index = Find(pParent->Children[i], pNode);
|
|
if(Index.isValid())
|
|
return Index;
|
|
}
|
|
return QModelIndex();
|
|
*/
|
|
}
|
|
|
|
void CTreeItemModel::Clear()
|
|
{
|
|
QHash<QVariant, STreeNode*> Old = m_Map;
|
|
//beginResetModel();
|
|
Purge(m_Root, QModelIndex(), Old);
|
|
//endResetModel();
|
|
ASSERT(m_Map.isEmpty());
|
|
}
|
|
|
|
void CTreeItemModel::RemoveIndex(const QModelIndex &index)
|
|
{
|
|
if (!index.isValid())
|
|
return;
|
|
|
|
STreeNode* pNode = static_cast<STreeNode*>(index.internalPointer());
|
|
ASSERT(pNode);
|
|
|
|
QHash<QVariant, STreeNode*> Old;
|
|
Old[pNode->ID] = pNode;
|
|
|
|
Purge(m_Root, QModelIndex(), Old);
|
|
}
|
|
|
|
QVariant CTreeItemModel::data(const QModelIndex &index, int role) const
|
|
{
|
|
return Data(index, role, index.column());
|
|
}
|
|
|
|
bool CTreeItemModel::setData(const QModelIndex &index, const QVariant &value, int role)
|
|
{
|
|
if(index.column() == FIRST_COLUMN && role == Qt::CheckStateRole)
|
|
{
|
|
STreeNode* pNode = static_cast<STreeNode*>(index.internalPointer());
|
|
ASSERT(pNode);
|
|
emit CheckChanged(pNode->ID, value.toInt() != Qt::Unchecked);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
QVariant CTreeItemModel::GetItemID(const QModelIndex& index) const
|
|
{
|
|
if (!index.isValid())
|
|
return QVariant();
|
|
|
|
STreeNode* pNode = static_cast<STreeNode*>(index.internalPointer());
|
|
|
|
return pNode->ID;
|
|
}
|
|
|
|
QVariant CTreeItemModel::Data(const QModelIndex &index, int role, int section) const
|
|
{
|
|
if (!index.isValid())
|
|
return QVariant();
|
|
|
|
//if(role == Qt::SizeHintRole)
|
|
// return QSize(64,16); // for fixing height
|
|
|
|
STreeNode* pNode = static_cast<STreeNode*>(index.internalPointer());
|
|
ASSERT(pNode);
|
|
|
|
if (pNode->Values.size() <= section)
|
|
return QVariant();
|
|
|
|
return NodeData(pNode, role, section);
|
|
}
|
|
|
|
QVariant CTreeItemModel::NodeData(STreeNode* pNode, int role, int section) const
|
|
{
|
|
switch(role)
|
|
{
|
|
case Qt::DisplayRole:
|
|
{
|
|
STreeNode::SValue& Value = pNode->Values[section];
|
|
return Value.Formatted.isValid() ? Value.Formatted : Value.Raw;
|
|
}
|
|
case Qt::EditRole: // sort role
|
|
{
|
|
STreeNode::SValue& value = pNode->Values[section];
|
|
return value.SortKey.isValid() ? value.SortKey : value.Raw;
|
|
}
|
|
case Qt::ToolTipRole:
|
|
{
|
|
QString ToolTip;
|
|
emit ToolTipCallback(pNode->ID, ToolTip);
|
|
if(!ToolTip.isNull())
|
|
return ToolTip;
|
|
break;
|
|
}
|
|
case Qt::DecorationRole:
|
|
{
|
|
if (m_bUseIcons && section == FIRST_COLUMN)
|
|
return pNode->Icon.isValid() ? pNode->Icon : GetDefaultIcon();
|
|
break;
|
|
}
|
|
case Qt::FontRole:
|
|
{
|
|
if (section == FIRST_COLUMN && pNode->IsBold)
|
|
{
|
|
QFont fnt;
|
|
fnt.setBold(true);
|
|
return fnt;
|
|
}
|
|
break;
|
|
}
|
|
case Qt::BackgroundRole:
|
|
{
|
|
if(!m_DarkMode)
|
|
return pNode->Color.isValid() ? pNode->Color : QVariant();
|
|
break;
|
|
}
|
|
case Qt::ForegroundRole:
|
|
{
|
|
if(m_DarkMode)
|
|
return pNode->Color.isValid() ? pNode->Color : QVariant();
|
|
else if (pNode->IsGray)
|
|
{
|
|
QColor Color = Qt::darkGray;
|
|
return QBrush(Color);
|
|
}
|
|
break;
|
|
}
|
|
case Qt::CheckStateRole:
|
|
{
|
|
/*if(section == eModule)
|
|
{
|
|
if(pNode->...)
|
|
return Qt::Unchecked;
|
|
else
|
|
return Qt::Checked;
|
|
}*/
|
|
break;
|
|
}
|
|
case Qt::UserRole:
|
|
{
|
|
switch(section)
|
|
{
|
|
case FIRST_COLUMN: return pNode->ID;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
return QVariant();
|
|
}
|
|
|
|
Qt::ItemFlags CTreeItemModel::flags(const QModelIndex &index) const
|
|
{
|
|
if (!index.isValid())
|
|
return Qt::NoItemFlags;
|
|
if(index.column() == 0)
|
|
return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable;
|
|
return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
|
|
}
|
|
|
|
QModelIndex CTreeItemModel::index(int row, int column, const QModelIndex &parent) const
|
|
{
|
|
if (!hasIndex(row, column, parent))
|
|
return QModelIndex();
|
|
|
|
STreeNode* pParent;
|
|
if (!parent.isValid())
|
|
pParent = m_Root;
|
|
else
|
|
pParent = static_cast<STreeNode*>(parent.internalPointer());
|
|
|
|
if(STreeNode* pNode = pParent->Children.count() > row ? pParent->Children[row] : NULL)
|
|
return createIndex(row, column, pNode);
|
|
return QModelIndex();
|
|
}
|
|
|
|
QModelIndex CTreeItemModel::parent(const QModelIndex &index) const
|
|
{
|
|
if (!index.isValid())
|
|
return QModelIndex();
|
|
|
|
STreeNode* pNode = static_cast<STreeNode*>(index.internalPointer());
|
|
ASSERT(pNode->Parent);
|
|
STreeNode* pParent = pNode->Parent;
|
|
if (pParent == m_Root)
|
|
return QModelIndex();
|
|
|
|
int row = 0;
|
|
if(pParent->Parent)
|
|
row = pParent->Parent->Children.indexOf(pParent);
|
|
return createIndex(row, 0, pParent);
|
|
}
|
|
|
|
int CTreeItemModel::rowCount(const QModelIndex &parent) const
|
|
{
|
|
if (parent.column() > 0)
|
|
return 0;
|
|
|
|
STreeNode* pNode;
|
|
if (!parent.isValid())
|
|
pNode = m_Root;
|
|
else
|
|
pNode = static_cast<STreeNode*>(parent.internalPointer());
|
|
return pNode->Children.count();
|
|
}
|
|
|
|
int CSimpleTreeModel::columnCount(const QModelIndex &parent) const
|
|
{
|
|
return m_ColumnKeys.count();
|
|
}
|
|
|
|
QVariant CSimpleTreeModel::headerData(int section, Qt::Orientation orientation, int role) const
|
|
{
|
|
if (orientation == Qt::Horizontal && role == Qt::DisplayRole)
|
|
{
|
|
if (section < m_ColumnKeys.size())
|
|
return m_ColumnKeys.at(section).first;
|
|
}
|
|
return QVariant();
|
|
}
|