/************************************************************************** ** ** This file is part of Qt Creator ** ** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). ** ** Contact: Qt Software Information (qt-info@nokia.com) ** ** Commercial Usage ** ** Licensees holding valid Qt Commercial licenses may use this file in ** accordance with the Qt Commercial License Agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and Nokia. ** ** GNU Lesser General Public License Usage ** ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** If you are unsure which license is appropriate for your use, please ** contact the sales department at qt-sales@nokia.com. ** **************************************************************************/ #include "watchhandler.h" #include "watchutils.h" #include "debuggeractions.h" #if USE_MODEL_TEST #include "modeltest.h" #endif #include #include #include #include #include #include #include #include #include #include // creates debug output regarding pending watch data results #define DEBUG_PENDING 1 // creates debug output for accesses to the itemmodel //#define DEBUG_MODEL 1 #if DEBUG_MODEL # define MODEL_DEBUG(s) qDebug() << s #else # define MODEL_DEBUG(s) #endif #define MODEL_DEBUGX(s) qDebug() << s using namespace Debugger::Internal; static const QString strNotInScope = QLatin1String(""); //////////////////////////////////////////////////////////////////// // // WatchData // //////////////////////////////////////////////////////////////////// WatchData::WatchData() : childCount(-1), valuedisabled(false), state(InitialState), parentIndex(-1), row(-1), level(-1), changed(false) { } void WatchData::setError(const QString &msg) { setAllUnneeded(); value = msg; setChildCount(0); valuedisabled = true; } void WatchData::setValue(const QString &value0) { value = value0; if (value == "{...}") { value.clear(); childCount = 1; // at least one... } // avoid duplicated information if (value.startsWith("(") && value.contains(") 0x")) value = value.mid(value.lastIndexOf(") 0x") + 2); // doubles are sometimes displayed as "@0x6141378: 1.2". // I don't want that. if (/*isIntOrFloatType(type) && */ value.startsWith("@0x") && value.contains(':')) { value = value.mid(value.indexOf(':') + 2); setChildCount(0); } // "numchild" is sometimes lying //MODEL_DEBUG("\n\n\nPOINTER: " << type << value); if (isPointerType(type)) setChildCount(value != "0x0" && value != ""); // pointer type information is available in the 'type' // column. No need to duplicate it here. if (value.startsWith("(" + type + ") 0x")) value = value.section(" ", -1, -1); setValueUnneeded(); } void WatchData::setValueToolTip(const QString &tooltip) { valuetooltip = tooltip; } void WatchData::setType(const QString &str) { type = str.trimmed(); bool changed = true; while (changed) { if (type.endsWith(QLatin1String("const"))) type.chop(5); else if (type.endsWith(QLatin1Char(' '))) type.chop(1); else if (type.endsWith(QLatin1Char('&'))) type.chop(1); else if (type.startsWith(QLatin1String("const "))) type = type.mid(6); else if (type.startsWith(QLatin1String("volatile "))) type = type.mid(9); else if (type.startsWith(QLatin1String("class "))) type = type.mid(6); else if (type.startsWith(QLatin1String("struct "))) type = type.mid(6); else if (type.startsWith(QLatin1Char(' '))) type = type.mid(1); else changed = false; } setTypeUnneeded(); if (isIntOrFloatType(type)) setChildCount(0); } void WatchData::setAddress(const QString & str) { addr = str; } WatchData WatchData::pointerChildPlaceHolder() const { WatchData data1; data1.iname = iname + QLatin1String(".*"); data1.name = QLatin1Char('*') + name; data1.exp = QLatin1String("(*(") + exp + QLatin1String("))"); data1.type = stripPointerType(type); data1.setValueNeeded(); return data1; } QString WatchData::toString() const { const char *doubleQuoteComma = "\","; QString res; QTextStream str(&res); str <<"{state=\"0x" << QString::number(state, 16) << doubleQuoteComma; if (level != -1) str << "level=\"" << level << doubleQuoteComma; if (parentIndex != -1) str << "parent=\"" << parentIndex << doubleQuoteComma; if (row != -1) str << "row=\"" << row << doubleQuoteComma; if (childCount) str << "childCount=\"" << childCount << doubleQuoteComma; if (const int childCount = childIndex.size()) { str << "child=\""; for (int i = 0; i < childCount; i++) { if (i) str << ','; str << childIndex.at(i); } str << doubleQuoteComma; } if (!iname.isEmpty()) str << "iname=\"" << iname << doubleQuoteComma; if (!exp.isEmpty()) str << "exp=\"" << exp << doubleQuoteComma; if (!variable.isEmpty()) str << "variable=\"" << variable << doubleQuoteComma; if (isValueNeeded()) str << "value=,"; if (isValueKnown() && !value.isEmpty()) str << "value=\"" << value << doubleQuoteComma; if (!editvalue.isEmpty()) str << "editvalue=\"" << editvalue << doubleQuoteComma; if (isTypeNeeded()) str << "type=,"; if (isTypeKnown() && !type.isEmpty()) str << "type=\"" << type << doubleQuoteComma; if (isChildCountNeeded()) str << "numchild=,"; if (isChildCountKnown() && childCount == -1) str << "numchild=\"" << childCount << doubleQuoteComma; if (isChildrenNeeded()) str << "children=,"; str.flush(); if (res.endsWith(QLatin1Char(','))) res.truncate(res.size() - 1); return res + QLatin1Char('}'); } static bool iNameSorter(const WatchData &d1, const WatchData &d2) { if (d1.level != d2.level) return d1.level < d2.level; for (int level = 0; level != d1.level; ++level) { QString name1 = d1.iname.section('.', level, level); QString name2 = d2.iname.section('.', level, level); //MODEL_DEBUG(" SORT: " << name1 << name2 << (name1 < name2)); if (name1 != name2 && !name1.isEmpty() && !name2.isEmpty()) { if (name1.at(0).isDigit() && name2.at(0).isDigit()) return name1.toInt() < name2.toInt(); return name1 < name2; } } return false; } static QString parentName(const QString &iname) { int pos = iname.lastIndexOf(QLatin1Char('.')); if (pos == -1) return QString(); return iname.left(pos); } static void insertDataHelper(QList &list, const WatchData &data) { // FIXME: Quadratic algorithm for (int i = list.size(); --i >= 0; ) { if (list.at(i).iname == data.iname) { list[i] = data; return; } } list.append(data); } static WatchData take(const QString &iname, QList *list) { for (int i = list->size(); --i >= 0;) { if (list->at(i).iname == iname) { WatchData res = list->at(i); (*list)[i] = list->back(); (void) list->takeLast(); return res; //return list->takeAt(i); } } return WatchData(); } static QList initialSet() { QList result; WatchData root; root.state = 0; root.level = 0; root.row = 0; root.name = QLatin1String("Root"); root.parentIndex = -1; root.childIndex.append(1); root.childIndex.append(2); root.childIndex.append(3); result.append(root); WatchData local; local.iname = QLatin1String("local"); local.name = QLatin1String("Locals"); local.state = 0; local.level = 1; local.row = 0; local.parentIndex = 0; result.append(local); WatchData tooltip; tooltip.iname = QLatin1String("tooltip"); tooltip.name = QLatin1String("Tooltip"); tooltip.state = 0; tooltip.level = 1; tooltip.row = 1; tooltip.parentIndex = 0; result.append(tooltip); WatchData watch; watch.iname = QLatin1String("watch"); watch.name = QLatin1String("Watchers"); watch.state = 0; watch.level = 1; watch.row = 2; watch.parentIndex = 0; result.append(watch); return result; } /////////////////////////////////////////////////////////////////////// // // WatchHandler // /////////////////////////////////////////////////////////////////////// WatchHandler::WatchHandler() { m_expandPointers = true; m_inFetchMore = false; m_inChange = false; m_completeSet = initialSet(); m_incompleteSet.clear(); m_displaySet = m_completeSet; connect(theDebuggerAction(WatchExpression), SIGNAL(triggered()), this, SLOT(watchExpression())); connect(theDebuggerAction(RemoveWatchExpression), SIGNAL(triggered()), this, SLOT(removeWatchExpression())); connect(theDebuggerAction(ExpandItem), SIGNAL(triggered()), this, SLOT(expandChildren())); connect(theDebuggerAction(CollapseItem), SIGNAL(triggered()), this, SLOT(collapseChildren())); } static QString niceType(QString type) { if (type.contains(QLatin1String("std::"))) { // std::string type.replace(QLatin1String("std::basic_string, " "std::allocator >"), QLatin1String("std::string")); // std::wstring type.replace(QLatin1String("std::basic_string, " "std::allocator >"), QLatin1String("std::wstring")); // std::vector static QRegExp re1(QLatin1String("std::vector<(.*), std::allocator<(.*)>\\s*>")); re1.setMinimal(true); for (int i = 0; i != 10; ++i) { if (re1.indexIn(type) == -1 || re1.cap(1) != re1.cap(2)) break; type.replace(re1.cap(0), QLatin1String("std::vector<") + re1.cap(1) + QLatin1Char('>')); } // std::list static QRegExp re2(QLatin1String("std::list<(.*), std::allocator<(.*)>\\s*>")); re2.setMinimal(true); for (int i = 0; i != 10; ++i) { if (re2.indexIn(type) == -1 || re2.cap(1) != re2.cap(2)) break; type.replace(re2.cap(0), QLatin1String("std::list<") + re2.cap(1) + QLatin1Char('>')); } // std::map static QRegExp re3(QLatin1String("std::map<(.*), (.*), std::less<(.*)\\s*>, " "std::allocator > >")); re3.setMinimal(true); for (int i = 0; i != 10; ++i) { if (re3.indexIn(type) == -1 || re3.cap(1) != re3.cap(3) || re3.cap(1) != re3.cap(4) || re3.cap(2) != re3.cap(5)) break; type.replace(re3.cap(0), QLatin1String("std::map<") + re3.cap(1) + QLatin1String(", ") + re3.cap(2) + QLatin1Char('>')); } // std::set static QRegExp re4(QLatin1String("std::set<(.*), std::less<(.*)>, std::allocator<(.*)>\\s*>")); re1.setMinimal(true); for (int i = 0; i != 10; ++i) { if (re4.indexIn(type) == -1 || re4.cap(1) != re4.cap(2) || re4.cap(1) != re4.cap(3)) break; type.replace(re4.cap(0), QLatin1String("std::set<") + re4.cap(1) + QLatin1Char('>')); } type.replace(QLatin1String(" >"), QString(QLatin1Char('>'))); } return type; } QVariant WatchHandler::data(const QModelIndex &idx, int role) const { int node = idx.internalId(); if (node < 0) return QVariant(); QTC_ASSERT(node < m_displaySet.size(), return QVariant()); const WatchData &data = m_displaySet.at(node); switch (role) { case Qt::DisplayRole: { switch (idx.column()) { case 0: return data.name; case 1: return data.value; case 2: return niceType(data.type); default: break; } break; } case Qt::ToolTipRole: { QString val = data.value; if (val.size() > 1000) val = val.left(1000) + QLatin1String(" ... "); QString tt = QLatin1String(""); //tt += QLatin1String(""); tt += QLatin1String(""); tt += QLatin1String(""); //if (!valuetooltip.isEmpty()) // tt += valuetooltip; //else tt += QLatin1String(""); tt += QLatin1String(""); tt += QLatin1String(""); tt += QLatin1String(""); tt += QLatin1String("
internal name : "); //tt += Qt::escape(iname) + QLatin1String("
expression : "); tt += Qt::escape(data.exp) + QLatin1String("
type : "); tt += Qt::escape(data.type) + QLatin1String("
value : "); tt += Qt::escape(data.value) + QLatin1String("
object addr : "); tt += Qt::escape(data.addr) + QLatin1String("
stored addr : "); tt += Qt::escape(data.saddr) + QLatin1String("
iname : "); tt += Qt::escape(data.iname) + QLatin1String("
"); tt.replace(QLatin1String("@value@"), Qt::escape(data.value)); if (tt.size() > 10000) tt = tt.left(10000) + QLatin1String(" ... "); return tt; } case Qt::ForegroundRole: { static const QVariant red(QColor(200, 0, 0)); static const QVariant black(QColor(0, 0, 0)); static const QVariant gray(QColor(140, 140, 140)); switch (idx.column()) { case 0: return black; case 1: return data.valuedisabled ? gray : data.changed ? red : black; case 2: return black; } break; } case ExpressionRole: return data.exp; case INameRole: return data.iname; case ExpandedRole: //qDebug() << " FETCHING: " << data.iname // << m_expandedINames.contains(data.iname) // << m_expandedINames; // Level 0 and 1 are always expanded return node < 4 || m_expandedINames.contains(data.iname); default: break; } return QVariant(); } Qt::ItemFlags WatchHandler::flags(const QModelIndex &idx) const { using namespace Qt; if (!idx.isValid()) return ItemFlags(); int node = idx.internalId(); if (node < 0) return ItemFlags(); // enabled, editable, selectable, checkable, and can be used both as the // source of a drag and drop operation and as a drop target. static const ItemFlags notEditable = ItemIsSelectable | ItemIsDragEnabled | ItemIsDropEnabled // | ItemIsUserCheckable // | ItemIsTristate | ItemIsEnabled; static const ItemFlags editable = notEditable | ItemIsEditable; const WatchData &data = m_displaySet.at(node); if (data.isWatcher() && idx.column() == 0) return editable; // watcher names are editable if (data.isWatcher() && idx.column() == 2) return editable; // watcher types are if (idx.column() == 1) return editable; // locals and watcher values are editable return notEditable; } QVariant WatchHandler::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation == Qt::Vertical) return QVariant(); if (role == Qt::DisplayRole) { switch (section) { case 0: return tr("Name") + QLatin1String(" "); case 1: return tr("Value") + QLatin1String(" "); case 2: return tr("Type") + QLatin1String(" "); } } return QVariant(); } QString WatchHandler::toString() const { QString res; QTextStream str(&res); str << "\nIncomplete:\n"; for (int i = 0, n = m_incompleteSet.size(); i != n; ++i) str << i << ' ' << m_incompleteSet.at(i).toString() << '\n'; str << "\nComplete:\n"; for (int i = 0, n = m_completeSet.size(); i != n; ++i) str << i << ' ' << m_completeSet.at(i).toString() << '\n'; str << "\nDisplay:\n"; for (int i = 0, n = m_displaySet.size(); i != n; ++i) str << i << ' ' << m_displaySet.at(i).toString() << '\n'; #if 0 str << "\nOld:\n"; for (int i = 0, n = m_oldSet.size(); i != n; ++i) str << m_oldSet.at(i).toString() << '\n'; #endif return res; } WatchData *WatchHandler::findData(const QString &iname) { for (int i = m_completeSet.size(); --i >= 0; ) if (m_completeSet.at(i).iname == iname) return &m_completeSet[i]; return 0; } WatchData WatchHandler::takeData(const QString &iname) { WatchData data = take(iname, &m_incompleteSet); if (data.isValid()) return data; return take(iname, &m_completeSet); } QList WatchHandler::takeCurrentIncompletes() { QList res = m_incompleteSet; //MODEL_DEBUG("TAKING INCOMPLETES" << toString()); m_incompleteSet.clear(); return res; } void WatchHandler::rebuildModel() { if (m_inChange) { MODEL_DEBUG("RECREATE MODEL IGNORED, CURRENT SET:\n" << toString()); return; } #ifdef DEBUG_PENDING MODEL_DEBUG("RECREATE MODEL, CURRENT SET:\n" << toString()); #endif QHash oldTopINames; QHash oldValues; for (int i = 0, n = m_oldSet.size(); i != n; ++i) { WatchData &data = m_oldSet[i]; oldValues[data.iname] = data.value; if (data.level == 2) ++oldTopINames[data.iname]; } #ifdef DEBUG_PENDING MODEL_DEBUG("OLD VALUES: " << oldValues); #endif for (int i = m_completeSet.size(); --i >= 0; ) { WatchData &data = m_completeSet[i]; data.level = data.iname.isEmpty() ? 0 : data.iname.count('.') + 1; data.childIndex.clear(); } qSort(m_completeSet.begin(), m_completeSet.end(), &iNameSorter); // This helps to decide whether the view has completely changed or not. QHash topINames; QHash iname2idx; for (int i = m_completeSet.size(); --i > 0; ) { WatchData &data = m_completeSet[i]; data.parentIndex = 0; data.childIndex.clear(); iname2idx[data.iname] = i; if (data.level == 2) ++topINames[data.iname]; } //qDebug() << "TOPINAMES: " << topINames << "\nOLD: " << oldTopINames; for (int i = 1; i < m_completeSet.size(); ++i) { WatchData &data = m_completeSet[i]; QString parentIName = parentName(data.iname); data.parentIndex = iname2idx.value(parentIName, 0); WatchData &parent = m_completeSet[data.parentIndex]; data.row = parent.childIndex.size(); parent.childIndex.append(i); } m_oldSet = m_completeSet; m_oldSet += m_incompleteSet; for (int i = 0, n = m_completeSet.size(); i != n; ++i) { WatchData &data = m_completeSet[i]; data.changed = !data.value.isEmpty() && data.value != oldValues[data.iname] && data.value != strNotInScope; } emit layoutAboutToBeChanged(); if (0 && oldTopINames != topINames) { m_displaySet = initialSet(); m_expandedINames.clear(); emit reset(); } m_displaySet = m_completeSet; #ifdef DEBUG_PENDING MODEL_DEBUG("SET " << toString()); #endif #if 1 // Append dummy item to get the [+] effect for (int i = 0, n = m_displaySet.size(); i != n; ++i) { WatchData &data = m_displaySet[i]; if (data.childCount > 0 && data.childIndex.size() == 0) { WatchData dummy; dummy.state = 0; dummy.row = 0; dummy.iname = data.iname + QLatin1String(".dummy"); //dummy.name = data.iname + ".dummy"; //dummy.name = ""; dummy.level = data.level + 1; dummy.parentIndex = i; dummy.childCount = 0; data.childIndex.append(m_displaySet.size()); m_displaySet.append(dummy); } } #endif // Possibly append dummy items to prevent empty views bool ok = true; QTC_ASSERT(m_displaySet.size() >= 2, ok = false); QTC_ASSERT(m_displaySet.at(1).iname == QLatin1String("local"), ok = false); QTC_ASSERT(m_displaySet.at(2).iname == QLatin1String("tooltip"), ok = false); QTC_ASSERT(m_displaySet.at(3).iname == QLatin1String("watch"), ok = false); if (ok) { for (int i = 1; i <= 3; ++i) { WatchData &data = m_displaySet[i]; if (data.childIndex.size() == 0) { WatchData dummy; dummy.state = 0; dummy.row = 0; if (i == 1) { dummy.iname = QLatin1String("local.dummy"); dummy.name = QLatin1String(""); } else if (i == 2) { dummy.iname = QLatin1String("tooltip.dummy"); dummy.name = QLatin1String(""); } else { dummy.iname = QLatin1String("watch.dummy"); dummy.name = QLatin1String(""); } dummy.level = 2; dummy.parentIndex = i; dummy.childCount = 0; data.childIndex.append(m_displaySet.size()); m_displaySet.append(dummy); } } } m_inChange = true; //qDebug() << "WATCHHANDLER: RESET ABOUT TO EMIT"; //emit reset(); emit layoutChanged(); //qDebug() << "WATCHHANDLER: RESET EMITTED"; m_inChange = false; #if DEBUG_MODEL #if USE_MODEL_TEST //(void) new ModelTest(this, this); #endif #endif #ifdef DEBUG_PENDING MODEL_DEBUG("SORTED: " << toString()); MODEL_DEBUG("EXPANDED INAMES: " << m_expandedINames); #endif } void WatchHandler::cleanup() { m_oldSet.clear(); m_expandedINames.clear(); m_displayedINames.clear(); m_incompleteSet.clear(); m_completeSet = initialSet(); m_displaySet = m_completeSet; #if 0 for (EditWindows::ConstIterator it = m_editWindows.begin(); it != m_editWindows.end(); ++it) { if (!it.value().isNull()) delete it.value(); } m_editWindows.clear(); #endif emit reset(); } void WatchHandler::collapseChildren() { if (QAction *act = qobject_cast(sender())) collapseChildren(act->data().toString()); } void WatchHandler::collapseChildren(const QString &iname) { if (m_inChange || m_completeSet.isEmpty()) { qDebug() << "WATCHHANDLER: COLLAPSE IGNORED" << iname; return; } MODEL_DEBUG("COLLAPSE NODE" << iname); #if 0 QString iname1 = iname0 + '.'; for (int i = m_completeSet.size(); --i >= 0; ) { QString iname = m_completeSet.at(i).iname; if (iname.startsWith(iname1)) { // Better leave it in in case the user re-enters the branch? (void) m_completeSet.takeAt(i); MODEL_DEBUG(" REMOVING " << iname); m_expandedINames.remove(iname); } } #endif m_expandedINames.remove(iname); //MODEL_DEBUG(toString()); //rebuildModel(); } void WatchHandler::expandChildren() { if (QAction *act = qobject_cast(sender())) expandChildren(act->data().toString()); } void WatchHandler::expandChildren(const QString &iname) { if (m_inChange || m_completeSet.isEmpty()) { //qDebug() << "WATCHHANDLER: EXPAND IGNORED" << iname; return; } int index = -1; for (int i = 0; i != m_displaySet.size(); ++i) { if (m_displaySet.at(i).iname == iname) { index = i; break; } } if (index == -1) return; QTC_ASSERT(index >= 0, qDebug() << toString() << index; return); QTC_ASSERT(index < m_completeSet.size(), qDebug() << toString() << index; return); const WatchData &display = m_displaySet.at(index); QTC_ASSERT(index >= 0, qDebug() << toString() << index; return); QTC_ASSERT(index < m_completeSet.size(), qDebug() << toString() << index; return); const WatchData &complete = m_completeSet.at(index); MODEL_DEBUG("\n\nEXPAND" << display.iname); if (display.iname.isEmpty()) { // This should not happen but the view seems to send spurious // "expand()" signals folr the root item from time to time. // Try to handle that gracfully. //MODEL_DEBUG(toString()); qDebug() << "FIXME: expandChildren, no data " << display.iname << "found"; //rebuildModel(); return; } //qDebug() << " ... NODE: " << display.toString() // << complete.childIndex.size() << complete.childCount; if (m_expandedINames.contains(display.iname)) return; // This is a performance hack and not strictly necessary. // Remove it if there are troubles when expanding nodes. if (0 && complete.childCount > 0 && complete.childIndex.size() > 0) { MODEL_DEBUG("SKIP FETCHING CHILDREN"); return; } WatchData data = takeData(display.iname); // remove previous data m_expandedINames.insert(data.iname); if (data.iname.contains('.')) // not for top-level items data.setChildrenNeeded(); insertData(data); emit watchModelUpdateRequested(); } void WatchHandler::insertData(const WatchData &data) { //MODEL_DEBUG("INSERTDATA: " << data.toString()); QTC_ASSERT(data.isValid(), return); if (data.isSomethingNeeded()) insertDataHelper(m_incompleteSet, data); else insertDataHelper(m_completeSet, data); //MODEL_DEBUG("INSERT RESULT" << toString()); } void WatchHandler::watchExpression() { if (QAction *action = qobject_cast(sender())) watchExpression(action->data().toString()); } void WatchHandler::watchExpression(const QString &exp) { // FIXME: 'exp' can contain illegal characters //MODEL_DEBUG("WATCH: " << exp); WatchData data; data.exp = exp; data.name = exp; data.iname = QLatin1String("watch.") + exp; insertData(data); m_watchers.append(exp); saveWatchers(); emit watchModelUpdateRequested(); } void WatchHandler::setDisplayedIName(const QString &iname, bool on) { WatchData *d = findData(iname); if (!on || !d) { delete m_editWindows.take(iname); m_displayedINames.remove(iname); return; } if (d->exp.isEmpty()) { //emit statusMessageRequested(tr("Sorry. Cannot visualize objects without known address."), 5000); return; } d->setValueNeeded(); m_displayedINames.insert(iname); insertData(*d); } void WatchHandler::showEditValue(const WatchData &data) { // editvalue is always base64 encoded QByteArray ba = QByteArray::fromBase64(data.editvalue); //QByteArray ba = data.editvalue; QWidget *w = m_editWindows.value(data.iname); qDebug() << "SHOW_EDIT_VALUE " << data.toString() << data.type << data.iname << w; if (data.type == QLatin1String("QImage")) { if (!w) { w = new QLabel; m_editWindows[data.iname] = w; } QDataStream ds(&ba, QIODevice::ReadOnly); QVariant v; ds >> v; QString type = QString::fromAscii(v.typeName()); QImage im = v.value(); if (QLabel *l = qobject_cast(w)) l->setPixmap(QPixmap::fromImage(im)); } else if (data.type == QLatin1String("QPixmap")) { if (!w) { w = new QLabel; m_editWindows[data.iname] = w; } QDataStream ds(&ba, QIODevice::ReadOnly); QVariant v; ds >> v; QString type = QString::fromAscii(v.typeName()); QPixmap im = v.value(); if (QLabel *l = qobject_cast(w)) l->setPixmap(im); } else if (data.type == QLatin1String("QString")) { if (!w) { w = new QTextEdit; m_editWindows[data.iname] = w; } #if 0 QDataStream ds(&ba, QIODevice::ReadOnly); QVariant v; ds >> v; QString type = QString::fromAscii(v.typeName()); QString str = v.value(); #else MODEL_DEBUG("DATA: " << ba); QString str = QString::fromUtf16((ushort *)ba.constData(), ba.size()/2); #endif if (QTextEdit *t = qobject_cast(w)) t->setText(str); } if (w) w->show(); } void WatchHandler::removeWatchExpression() { if (QAction *action = qobject_cast(sender())) removeWatchExpression(action->data().toString()); } void WatchHandler::removeWatchExpression(const QString &exp) { MODEL_DEBUG("REMOVE WATCH: " << exp); m_watchers.removeOne(exp); for (int i = m_completeSet.size(); --i >= 0;) { const WatchData & data = m_completeSet.at(i); if (data.iname.startsWith(QLatin1String("watch.")) && data.exp == exp) { m_completeSet.takeAt(i); break; } } saveWatchers(); rebuildModel(); emit watchModelUpdateRequested(); } void WatchHandler::reinitializeWatchers() { m_completeSet = initialSet(); m_incompleteSet.clear(); reinitializeWatchersHelper(); } void WatchHandler::reinitializeWatchersHelper() { // copy over all watchers and mark all watchers as incomplete int i = 0; foreach (const QString &exp, m_watchers) { WatchData data; data.level = -1; data.row = -1; data.parentIndex = -1; data.variable.clear(); data.setAllNeeded(); data.valuedisabled = false; data.iname = QLatin1String("watch.") + QString::number(i); data.name = exp; data.exp = exp; insertData(data); ++i; } } bool WatchHandler::canFetchMore(const QModelIndex &parent) const { MODEL_DEBUG("CAN FETCH MORE: " << parent << "false"); #if 1 Q_UNUSED(parent); return false; #else // FIXME: not robust enough. Problem is that fetchMore // needs to be made synchronous to be useful. Busy loop is no good. if (!parent.isValid()) return false; QTC_ASSERT(checkIndex(parent.internalId()), return false); const WatchData &data = m_displaySet.at(parent.internalId()); MODEL_DEBUG("CAN FETCH MORE: " << parent << " children: " << data.childCount << data.iname); return data.childCount > 0; #endif } void WatchHandler::fetchMore(const QModelIndex &parent) { MODEL_DEBUG("FETCH MORE: " << parent); return; QTC_ASSERT(checkIndex(parent.internalId()), return); QString iname = m_displaySet.at(parent.internalId()).iname; if (m_inFetchMore) { MODEL_DEBUG("LOOP IN FETCH MORE" << iname); return; } m_inFetchMore = true; WatchData data = takeData(iname); MODEL_DEBUG("FETCH MORE: " << parent << ':' << iname << data.name); if (!data.isValid()) { MODEL_DEBUG("FIXME: FETCH MORE, no data " << iname << "found"); return; } m_expandedINames.insert(data.iname); if (data.iname.contains('.')) // not for top-level items data.setChildrenNeeded(); MODEL_DEBUG("FETCH MORE: data:" << data.toString()); insertData(data); //emit watchUpdateRequested(); while (m_inFetchMore) { QApplication::processEvents(); } m_inFetchMore = false; MODEL_DEBUG("BUSY LOOP FINISHED, data:" << data.toString()); } QModelIndex WatchHandler::index(int row, int col, const QModelIndex &parent) const { #ifdef DEBUG_MODEL MODEL_DEBUG("INDEX " << row << col << parent); #endif //if (col != 0) { // MODEL_DEBUG(" -> " << QModelIndex() << " (3) "); // return QModelIndex(); //} if (row < 0) { MODEL_DEBUG(" -> " << QModelIndex() << " (4) "); return QModelIndex(); } if (!parent.isValid()) { if (row == 0 && col >= 0 && col < 3 && parent.row() == -1) { MODEL_DEBUG(" -> " << createIndex(0, 0, 0) << " (B) "); return createIndex(0, col, 0); } MODEL_DEBUG(" -> " << QModelIndex() << " (1) "); return QModelIndex(); } int parentIndex = parent.internalId(); if (parentIndex < 0) { //MODEL_DEBUG("INDEX " << row << col << parentIndex << "INVALID"); MODEL_DEBUG(" -> " << QModelIndex() << " (2) "); return QModelIndex(); } QTC_ASSERT(checkIndex(parentIndex), return QModelIndex()); const WatchData &data = m_displaySet.at(parentIndex); QTC_ASSERT(row >= 0, qDebug() << "ROW: " << row << "PARENT: " << parent << data.toString() << toString(); return QModelIndex()); QTC_ASSERT(row < data.childIndex.size(), MODEL_DEBUG("ROW: " << row << data.toString() << toString()); return QModelIndex()); QModelIndex idx = createIndex(row, col, data.childIndex.at(row)); QTC_ASSERT(idx.row() == m_displaySet.at(idx.internalId()).row, return QModelIndex()); MODEL_DEBUG(" -> " << idx << " (A) "); return idx; } QModelIndex WatchHandler::parent(const QModelIndex &idx) const { if (!idx.isValid()) { MODEL_DEBUG(" -> " << QModelIndex() << " (1) "); return QModelIndex(); } MODEL_DEBUG("PARENT " << idx); int currentIndex = idx.internalId(); QTC_ASSERT(checkIndex(currentIndex), return QModelIndex()); QTC_ASSERT(idx.row() == m_displaySet.at(currentIndex).row, MODEL_DEBUG("IDX: " << idx << toString(); return QModelIndex())); int parentIndex = m_displaySet.at(currentIndex).parentIndex; if (parentIndex < 0) { MODEL_DEBUG(" -> " << QModelIndex() << " (2) "); return QModelIndex(); } QTC_ASSERT(checkIndex(parentIndex), return QModelIndex()); QModelIndex parent = createIndex(m_displaySet.at(parentIndex).row, 0, parentIndex); MODEL_DEBUG(" -> " << parent); return parent; } int WatchHandler::rowCount(const QModelIndex &idx) const { MODEL_DEBUG("ROW COUNT " << idx); if (idx.column() > 0) { MODEL_DEBUG(" -> " << 0 << " (A) "); return 0; } int thisIndex = idx.internalId(); QTC_ASSERT(checkIndex(thisIndex), return 0); if (idx.row() == -1 && idx.column() == -1) { MODEL_DEBUG(" -> " << 3 << " (B) "); return 1; } if (thisIndex < 0) { MODEL_DEBUG(" -> " << 0 << " (C) "); return 0; } if (thisIndex == 0) { MODEL_DEBUG(" -> " << 3 << " (D) "); return 3; } const WatchData &data = m_displaySet.at(thisIndex); int rows = data.childIndex.size(); MODEL_DEBUG(" -> " << rows << " (E) "); return rows; // Try lazy evaluation //if (rows > 0) // return rows; //return data.childCount; } int WatchHandler::columnCount(const QModelIndex &idx) const { MODEL_DEBUG("COLUMN COUNT " << idx); if (idx == QModelIndex()) { MODEL_DEBUG(" -> " << 3 << " (C) "); return 3; } if (idx.column() != 0) { MODEL_DEBUG(" -> " << 0 << " (A) "); return 0; } MODEL_DEBUG(" -> " << 3 << " (B) "); QTC_ASSERT(checkIndex(idx.internalId()), return 3); return 3; } bool WatchHandler::hasChildren(const QModelIndex &idx) const { // that's the base implementation: bool base = rowCount(idx) > 0 && columnCount(idx) > 0; MODEL_DEBUG("HAS CHILDREN: " << idx << base); return base; QTC_ASSERT(checkIndex(idx.internalId()), return false); const WatchData &data = m_displaySet.at(idx.internalId()); MODEL_DEBUG("HAS CHILDREN: " << idx << data.toString()); return data.childCount > 0; // || data.childIndex.size() > 0; } bool WatchHandler::checkIndex(int id) const { if (id < 0) { MODEL_DEBUG("CHECK INDEX FAILED" << id); return false; } if (id >= m_displaySet.size()) { MODEL_DEBUG("CHECK INDEX FAILED" << id << toString()); return false; } return true; } void WatchHandler::loadWatchers() { QVariant value; sessionValueRequested("Watchers", &value); m_watchers = value.toStringList(); //qDebug() << "LOAD WATCHERS: " << m_watchers; reinitializeWatchersHelper(); } void WatchHandler::saveWatchers() { //qDebug() << "SAVE WATCHERS: " << m_watchers; setSessionValueRequested("Watchers", m_watchers); } void WatchHandler::saveSessionData() { saveWatchers(); } void WatchHandler::loadSessionData() { loadWatchers(); rebuildModel(); }