Sandboxie/SandboxiePlus/MiscHelpers/Common/TreeItemModel.cpp

551 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()
{
Q_ASSERT(m_Root == NULL);
}
CSimpleTreeModel::CSimpleTreeModel(QObject *parent)
: CTreeItemModel(parent)
{
m_bTree = true;
m_Root = MkNode(QVariant());
}
CSimpleTreeModel::~CSimpleTreeModel()
{
FreeNode(m_Root);
m_Root = NULL;
}
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<QModelIndex>* pNewBranches)
{
if(!Old.isEmpty())
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(), pNewBranches);
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);
FreeNode(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, QList<QModelIndex>* pNewBranches)
{
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
STreeNode* &pNode = m_Map[CurPath];
if(!pNode)
{
//i = 0;
pNode = MkVirtualNode(CurPath, pParent);
//if (pNewNode) pNewNode->append(createIndex(pParent->Children.size(), FIRST_COLUMN, pNode));
if (pNewBranches && pParent->Children.size() == 0 && pParent != m_Root) pNewBranches->append(createIndex(pParent->Row, FIRST_COLUMN, 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();
}
Fill(pNode, /*index(i, 0, parent),*/ Paths, PathsIndex + 1, New, pNewBranches);
}
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;
//if (pNewNode) pNewNode->append(createIndex(pParent->Children.size(), FIRST_COLUMN, pNode));
if (pNewBranches && pParent->Children.size() == 0 && pParent != m_Root) pNewBranches->append(createIndex(pParent->Row, FIRST_COLUMN, pParent));
//int Count = pParent->Children.count();
//beginInsertRows(parent, Count, Count);
//if(!m_LeafsOnly) // when all non virtual entries are always leafs, don't fill the aux map
// 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);
return NodeData(pNode, role, section);
}
QVariant CTreeItemModel::NodeData(STreeNode* pNode, int role, int section) const
{
if (pNode->Values.size() <= section)
return QVariant();
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();
}