/************************************************************************** ** ** This file is part of Qt Creator ** ** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). ** ** Contact: Nokia Corporation (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 http://qt.nokia.com/contact. ** **************************************************************************/ #include "watchhandler.h" #include "breakhandler.h" #include "breakpoint.h" #include "debuggeractions.h" #include "debuggeragents.h" #include "debuggerengine.h" #include "debuggerplugin.h" #include "watchutils.h" #if USE_MODEL_TEST #include "modeltest.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // creates debug output for accesses to the model //#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 namespace Debugger { namespace Internal { static const QString strNotInScope = QCoreApplication::translate("Debugger::Internal::WatchData", ""); static int watcherCounter = 0; static int generationCounter = 0; static DebuggerPlugin *plugin() { return DebuggerPlugin::instance(); } //////////////////////////////////////////////////////////////////// // // WatchItem // //////////////////////////////////////////////////////////////////// class WatchItem : public WatchData { public: WatchItem() { parent = 0; } ~WatchItem() { if (parent != 0) parent->children.removeOne(this); qDeleteAll(children); } WatchItem(const WatchData &data) : WatchData(data) { parent = 0; } void setData(const WatchData &data) { static_cast(*this) = data; } WatchItem *parent; QList children; // fetched children }; /////////////////////////////////////////////////////////////////////// // // WatchModel // /////////////////////////////////////////////////////////////////////// WatchModel::WatchModel(WatchHandler *handler, WatchType type) : QAbstractItemModel(handler), m_handler(handler), m_type(type) { m_root = new WatchItem; m_root->hasChildren = 1; m_root->state = 0; m_root->name = WatchHandler::tr("Root"); m_root->parent = 0; switch (m_type) { case ReturnWatch: m_root->iname = "return"; m_root->name = WatchHandler::tr("Return Value"); break; case LocalsWatch: m_root->iname = "local"; m_root->name = WatchHandler::tr("Locals"); break; case WatchersWatch: m_root->iname = "watch"; m_root->name = WatchHandler::tr("Watchers"); break; case TooltipsWatch: m_root->iname = "tooltip"; m_root->name = WatchHandler::tr("Tooltip"); break; } } WatchModel::~WatchModel() { delete m_root; } WatchItem *WatchModel::rootItem() const { return m_root; } void WatchModel::reinitialize() { int n = m_root->children.size(); if (n == 0) return; //MODEL_DEBUG("REMOVING " << n << " CHILDREN OF " << m_root->iname); QModelIndex index = watchIndex(m_root); beginRemoveRows(index, 0, n - 1); qDeleteAll(m_root->children); m_root->children.clear(); endRemoveRows(); } void WatchModel::emitAllChanged() { emit layoutChanged(); } void WatchModel::beginCycle() { emit enableUpdates(false); } void WatchModel::endCycle() { removeOutdated(); m_fetchTriggered.clear(); emit enableUpdates(true); } DebuggerEngine *WatchModel::engine() const { return m_handler->m_engine; } void WatchModel::dump() { qDebug() << "\n"; foreach (WatchItem *child, m_root->children) dumpHelper(child); } void WatchModel::dumpHelper(WatchItem *item) { qDebug() << "ITEM: " << item->iname << (item->parent ? item->parent->iname : "") << item->generation; foreach (WatchItem *child, item->children) dumpHelper(child); } void WatchModel::removeOutdated() { foreach (WatchItem *child, m_root->children) removeOutdatedHelper(child); #if DEBUG_MODEL #if USE_MODEL_TEST //(void) new ModelTest(this, this); #endif #endif } void WatchModel::removeOutdatedHelper(WatchItem *item) { if (item->generation < generationCounter) { destroyItem(item); } else { foreach (WatchItem *child, item->children) removeOutdatedHelper(child); } } void WatchModel::destroyItem(WatchItem *item) { WatchItem *parent = item->parent; QModelIndex index = watchIndex(parent); int n = parent->children.indexOf(item); //MODEL_DEBUG("NEED TO REMOVE: " << item->iname << "AT" << n); beginRemoveRows(index, n, n); parent->children.removeAt(n); endRemoveRows(); delete item; } static QByteArray parentName(const QByteArray &iname) { int pos = iname.lastIndexOf('.'); if (pos == -1) return QByteArray(); return iname.left(pos); } static QString chopConst(QString type) { while (1) { if (type.startsWith("const")) type = type.mid(5); else if (type.startsWith(' ')) type = type.mid(1); else if (type.endsWith("const")) type.chop(5); else if (type.endsWith(' ')) type.chop(1); else break; } return type; } static inline QRegExp stdStringRegExp(const QString &charType) { QString rc = QLatin1String("basic_string<"); rc += charType; rc += QLatin1String(",[ ]?std::char_traits<"); rc += charType; rc += QLatin1String(">,[ ]?std::allocator<"); rc += charType; rc += QLatin1String("> >"); const QRegExp re(rc); Q_ASSERT(re.isValid()); return re; } static QString niceTypeHelper(const QString typeIn) { static QMap cache; const QMap::const_iterator it = cache.constFind(typeIn); if (it != cache.constEnd()) return it.value(); QString type = typeIn; type.replace(QLatin1Char('*'), QLatin1Char('@')); for (int i = 0; i < 10; ++i) { int start = type.indexOf("std::allocator<"); if (start == -1) break; // search for matching '>' int pos; int level = 0; for (pos = start + 12; pos < type.size(); ++pos) { int c = type.at(pos).unicode(); if (c == '<') { ++level; } else if (c == '>') { --level; if (level == 0) break; } } QString alloc = type.mid(start, pos + 1 - start).trimmed(); QString inner = alloc.mid(15, alloc.size() - 16).trimmed(); if (inner == QLatin1String("char")) { // std::string const QRegExp stringRegexp = stdStringRegExp(inner); type.replace(stringRegexp, QLatin1String("string")); } else if (inner == QLatin1String("wchar_t")) { // std::wstring const QRegExp wchartStringRegexp = stdStringRegExp(inner); type.replace(wchartStringRegexp, QLatin1String("wstring")); } else if (inner == QLatin1String("unsigned short")) { // std::wstring/MSVC const QRegExp usStringRegexp = stdStringRegExp(inner); type.replace(usStringRegexp, QLatin1String("wstring")); } // std::vector, std::deque, std::list const QRegExp re1(QString::fromLatin1("(vector|list|deque)<%1, ?%2\\s*>").arg(inner, alloc)); Q_ASSERT(re1.isValid()); if (re1.indexIn(type) != -1) type.replace(re1.cap(0), QString::fromLatin1("%1<%2>").arg(re1.cap(1), inner)); // std::stack QRegExp re6(QString::fromLatin1("stack<%1, ?std::deque<%2> >").arg(inner, inner)); if (!re6.isMinimal()) re6.setMinimal(true); Q_ASSERT(re6.isValid()); if (re6.indexIn(type) != -1) type.replace(re6.cap(0), QString::fromLatin1("stack<%1>").arg(inner)); // std::set QRegExp re4(QString::fromLatin1("set<%1, ?std::less<%2>, ?%3\\s*>").arg(inner, inner, alloc)); if (!re4.isMinimal()) re4.setMinimal(true); Q_ASSERT(re4.isValid()); if (re4.indexIn(type) != -1) type.replace(re4.cap(0), QString::fromLatin1("set<%1>").arg(inner)); // std::map if (inner.startsWith("std::pair<")) { // search for outermost ',' int pos; int level = 0; for (pos = 10; pos < inner.size(); ++pos) { int c = inner.at(pos).unicode(); if (c == '<') ++level; else if (c == '>') --level; else if (c == ',' && level == 0) break; } QString ckey = inner.mid(10, pos - 10); QString key = chopConst(ckey); QString value = inner.mid(pos + 2, inner.size() - 3 - pos).trimmed(); QRegExp re5(QString("map<%1, ?%2, ?std::less<%3 ?>, ?%4\\s*>") .arg(key, value, key, alloc)); if (!re5.isMinimal()) re5.setMinimal(true); Q_ASSERT(re5.isValid()); if (re5.indexIn(type) != -1) { type.replace(re5.cap(0), QString("map<%1, %2>").arg(key, value)); } else { QRegExp re7(QString("map, ?%4\\s*>") .arg(key, value, key, alloc)); if (!re7.isMinimal()) re7.setMinimal(true); if (re7.indexIn(type) != -1) type.replace(re7.cap(0), QString("map").arg(key, value)); } } } type.replace(QLatin1Char('@'), QLatin1Char('*')); type.replace(QLatin1String(" >"), QString(QLatin1Char('>'))); cache.insert(typeIn, type); // For simplicity, also cache unmodified types return type; } QString WatchModel::niceType(const QString &typeIn) const { QString type = niceTypeHelper(typeIn); if (!theDebuggerBoolSetting(ShowStdNamespace)) type = type.remove("std::"); if (!theDebuggerBoolSetting(ShowQtNamespace)) type = type.remove(engine()->qtNamespace()); return type; } template QString reformatInteger(IntType value, int format) { switch (format) { case HexadecimalFormat: return ("(hex) ") + QString::number(value, 16); case BinaryFormat: return ("(bin) ") + QString::number(value, 2); case OctalFormat: return ("(oct) ") + QString::number(value, 8); } return QString::number(value); // not reached } // Format printable (char-type) characters static inline QString reformatCharacter(int code, int format) { const QString codeS = reformatInteger(code, format); if (code < 0) // Append unsigned value. return codeS + QLatin1String(" / ") + reformatInteger(256 + code, format); if (code >= 32 && code < 128) return codeS + QLatin1String(" '") + QChar(code) + QLatin1Char('\''); switch (code) { case 0: return codeS + QLatin1String(" '\\0'"); case '\r': return codeS + QLatin1String(" '\\r'"); case '\t': return codeS + QLatin1String(" '\\t'"); case '\n': return codeS + QLatin1String(" '\\n'"); } return codeS; } static inline QString formattedValue(const WatchData &data, int format) { if (isIntType(data.type)) { if (data.value.isEmpty()) return data.value; // Do not reformat booleans (reported as 'true, false'). const QChar firstChar = data.value.at(0); if (!firstChar.isDigit() && firstChar != QLatin1Char('-')) return data.value; // Append quoted, printable character also for decimal. if (data.type.endsWith(QLatin1String("char"))) { bool ok; const int code = data.value.toInt(&ok); return ok ? reformatCharacter(code, format) : data.value; } // Rest: Leave decimal as is if (format <= 0) return data.value; // Evil hack, covers 'unsigned' as well as quint64. if (data.type.contains(QLatin1Char('u'))) return reformatInteger(data.value.toULongLong(), format); return reformatInteger(data.value.toLongLong(), format); } return data.value; } bool WatchModel::canFetchMore(const QModelIndex &index) const { WatchItem *item = watchItem(index); QTC_ASSERT(item, return false); return index.isValid() && !m_fetchTriggered.contains(item->iname); } void WatchModel::fetchMore(const QModelIndex &index) { QTC_ASSERT(index.isValid(), return); WatchItem *item = watchItem(index); QTC_ASSERT(item, return); QTC_ASSERT(!m_fetchTriggered.contains(item->iname), return); m_handler->m_expandedINames.insert(item->iname); m_fetchTriggered.insert(item->iname); if (item->children.isEmpty()) { WatchData data = *item; data.setChildrenNeeded(); engine()->updateWatchData(data); } } QModelIndex WatchModel::index(int row, int column, const QModelIndex &parent) const { if (!hasIndex(row, column, parent)) return QModelIndex(); const WatchItem *item = watchItem(parent); QTC_ASSERT(item, return QModelIndex()); if (row >= item->children.size()) return QModelIndex(); return createIndex(row, column, (void*)(item->children.at(row))); } QModelIndex WatchModel::parent(const QModelIndex &idx) const { if (!idx.isValid()) return QModelIndex(); const WatchItem *item = watchItem(idx); if (!item->parent || item->parent == m_root) return QModelIndex(); const WatchItem *grandparent = item->parent->parent; if (!grandparent) return QModelIndex(); for (int i = 0; i < grandparent->children.size(); ++i) if (grandparent->children.at(i) == item->parent) return createIndex(i, 0, (void*) item->parent); return QModelIndex(); } int WatchModel::rowCount(const QModelIndex &idx) const { if (idx.column() > 0) return 0; return watchItem(idx)->children.size(); } int WatchModel::columnCount(const QModelIndex &idx) const { Q_UNUSED(idx) return 3; } bool WatchModel::hasChildren(const QModelIndex &parent) const { WatchItem *item = watchItem(parent); return !item || item->hasChildren; } WatchItem *WatchModel::watchItem(const QModelIndex &idx) const { return idx.isValid() ? static_cast(idx.internalPointer()) : m_root; } QModelIndex WatchModel::watchIndex(const WatchItem *item) const { return watchIndexHelper(item, m_root, QModelIndex()); } QModelIndex WatchModel::watchIndexHelper(const WatchItem *needle, const WatchItem *parentItem, const QModelIndex &parentIndex) const { if (needle == parentItem) return parentIndex; for (int i = parentItem->children.size(); --i >= 0; ) { const WatchItem *childItem = parentItem->children.at(i); QModelIndex childIndex = index(i, 0, parentIndex); QModelIndex idx = watchIndexHelper(needle, childItem, childIndex); if (idx.isValid()) return idx; } return QModelIndex(); } void WatchModel::emitDataChanged(int column, const QModelIndex &parentIndex) { QModelIndex idx1 = index(0, column, parentIndex); QModelIndex idx2 = index(rowCount(parentIndex) - 1, column, parentIndex); if (idx1.isValid() && idx2.isValid()) emit dataChanged(idx1, idx2); //qDebug() << "CHANGING:\n" << idx1 << "\n" << idx2 << "\n" // << data(parentIndex, INameRole).toString(); for (int i = rowCount(parentIndex); --i >= 0; ) emitDataChanged(column, index(i, 0, parentIndex)); } // Truncate value for item view, maintaining quotes static inline QString truncateValue(QString v) { enum { maxLength = 512 }; if (v.size() < maxLength) return v; const bool isQuoted = v.endsWith(QLatin1Char('"')); // check for 'char* "Hallo"' v.truncate(maxLength); v += isQuoted ? QLatin1String("...\"") : QLatin1String("..."); return v; } // Get a pointer address from pointer values reported by the debugger. // Fix CDB formatting of pointers "0x00000000`000003fd class foo *", // check gdb formatting of characters. static inline quint64 pointerValue(QString data) { if (data.isEmpty() || !data.startsWith(QLatin1String("0x"))) return 0; data.remove(0, 2); const int blankPos = data.indexOf(QLatin1Char(' ')); if (blankPos != -1) data.truncate(blankPos); data.remove(QLatin1Char('`')); bool ok; const quint64 address = data.toULongLong(&ok, 16); return ok ? address : quint64(0); } QVariant WatchModel::data(const QModelIndex &idx, int role) const { switch (role) { case EngineCapabilitiesRole: return engine()->debuggerCapabilities(); case EngineActionsEnabledRole: return engine()->debuggerActionsEnabled(); } const WatchItem *item = watchItem(idx); const WatchItem &data = *item; switch (role) { case Qt::EditRole: case Qt::DisplayRole: { switch (idx.column()) { case 0: if (data.name == QLatin1String("*") && item->parent) return QVariant(QLatin1Char('*') + item->parent->name); return data.name; case 1: { int format = m_handler->m_individualFormats.value(data.addr, -1); if (format == -1) format = m_handler->m_typeFormats.value(data.type, -1); return truncateValue(formattedValue(data, format)); } case 2: { if (!data.displayedType.isEmpty()) return data.displayedType; return niceType(data.type); } default: break; } break; } case Qt::ToolTipRole: return theDebuggerBoolSetting(UseToolTipsInLocalsView) ? data.toToolTip() : QVariant(); case Qt::ForegroundRole: { static const QVariant red(QColor(200, 0, 0)); static const QVariant gray(QColor(140, 140, 140)); switch (idx.column()) { case 1: return !data.valueEnabled ? gray : data.changed ? red : QVariant(); } break; } case LocalsExpressionRole: return data.exp; case LocalsINameRole: return data.iname; case LocalsExpandedRole: return m_handler->m_expandedINames.contains(data.iname); case LocalsTypeFormatListRole: if (!data.typeFormats.isEmpty()) return data.typeFormats.split(','); if (isIntType(data.type)) return QStringList() << tr("decimal") << tr("hexadecimal") << tr("binary") << tr("octal"); if (data.type.endsWith(QLatin1Char('*'))) return QStringList() << tr("Bald pointer") << tr("Latin1 string") << tr("UTF8 string") << tr("UTF16 string") << tr("UCS4 string"); break; case LocalsTypeFormatRole: return m_handler->m_typeFormats.value(data.type, -1); case LocalsIndividualFormatRole: return m_handler->m_individualFormats.value(data.addr, -1); case LocalsRawValueRole: return data.value; case LocalsPointerValueRole: if (isPointerType(data.type)) return pointerValue(data.value); return QVariant(quint64(0)); case LocalsIsWatchpointAtAddressRole: return engine()->breakHandler() ->watchPointAt(data.coreAddress()); case LocalsAddressRole: return data.coreAddress(); case LocalsIsWatchpointAtPointerValueRole: if (isPointerType(data.type)) return engine()->breakHandler() ->watchPointAt(pointerValue(data.addr)); return false; default: break; } return QVariant(); } bool WatchModel::setData(const QModelIndex &index, const QVariant &value, int role) { switch (role) { case RequestAssignValueRole: { QString str = value.toString(); int i = str.indexOf('='); if (i != -1) engine()->assignValueInDebugger(str.left(i), str.mid(i + 1)); return true; } case RequestAssignTypeRole: { QString str = value.toString(); int i = str.indexOf('='); if (i != -1) engine()->assignValueInDebugger(str.left(i), str.mid(i + 1)); return true; } case RequestToggleWatchRole: { BreakHandler *handler = engine()->breakHandler(); const quint64 address = value.toULongLong(); const QByteArray addressBA = QByteArray("0x") + QByteArray::number(address, 16); const int index = handler->findWatchPointIndexByAddress(addressBA); if (index == -1) { BreakpointData *data = new BreakpointData; data->type = BreakpointData::WatchpointType; data->address = addressBA; handler->appendBreakpoint(data); } else { handler->removeBreakpoint(index); } engine()->attemptBreakpointSynchronization(); return true; } case RequestShowMemoryRole: { (void) new MemoryViewAgent(engine(), value.toULongLong()); return true; } case RequestClearCppCodeModelSnapshotRole: { plugin()->clearCppCodeModelSnapshot(); return true; } } WatchItem &data = *watchItem(index); if (role == LocalsExpandedRole) { if (value.toBool()) { // Should already have been triggered by fetchMore() //QTC_ASSERT(m_handler->m_expandedINames.contains(data.iname), /**/); m_handler->m_expandedINames.insert(data.iname); } else { m_handler->m_expandedINames.remove(data.iname); } } else if (role == LocalsTypeFormatRole) { m_handler->setFormat(data.type, value.toInt()); engine()->updateWatchData(data); } else if (role == LocalsIndividualFormatRole) { const int format = value.toInt(); if (format == -1) { m_handler->m_individualFormats.remove(data.addr); } else { m_handler->m_individualFormats[data.addr] = format; } engine()->updateWatchData(data); } emit dataChanged(index, index); return true; } Qt::ItemFlags WatchModel::flags(const QModelIndex &idx) const { using namespace Qt; if (!idx.isValid()) 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 = *watchItem(idx); 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 && data.valueEditable) return editable; // locals and watcher values are sometimes editable return notEditable; } QVariant WatchModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation == Qt::Vertical) return QVariant(); if (role == Qt::DisplayRole) { switch (section) { case 0: return QString(tr("Name") + QLatin1String(" ")); case 1: return QString(tr("Value") + QLatin1String(" ")); case 2: return QString(tr("Type") + QLatin1String(" ")); } } return QVariant(); } struct IName : public QByteArray { IName(const QByteArray &iname) : QByteArray(iname) {} }; bool iNameLess(const QString &iname1, const QString &iname2) { QString name1 = iname1.section('.', -1); QString name2 = iname2.section('.', -1); if (!name1.isEmpty() && !name2.isEmpty()) { if (name1.at(0).isDigit() && name2.at(0).isDigit()) { bool ok1 = false, ok2 = false; int i1 = name1.toInt(&ok1), i2 = name2.toInt(&ok2); if (ok1 && ok2) return i1 < i2; } } return name1 < name2; } bool operator<(const IName &iname1, const IName &iname2) { return iNameLess(iname1, iname2); } static bool iNameSorter(const WatchItem *item1, const WatchItem *item2) { return iNameLess(item1->iname, item2->iname); } static int findInsertPosition(const QList &list, const WatchItem *item) { QList::const_iterator it = qLowerBound(list.begin(), list.end(), item, iNameSorter); return it - list.begin(); } void WatchModel::insertData(const WatchData &data) { //qDebug() << "WMI:" << data.toString(); //static int bulk = 0; //qDebug() << "SINGLE: " << ++bulk << data.toString(); if (data.iname.isEmpty()) { int x; x = 1; } QTC_ASSERT(!data.iname.isEmpty(), qDebug() << data.toString(); return); WatchItem *parent = findItem(parentName(data.iname), m_root); if (!parent) { WatchData parent; parent.iname = parentName(data.iname); MODEL_DEBUG("\nFIXING MISSING PARENT FOR\n" << data.iname); if (!parent.iname.isEmpty()) insertData(parent); return; } QModelIndex index = watchIndex(parent); if (WatchItem *oldItem = findItem(data.iname, parent)) { bool hadChildren = oldItem->hasChildren; // Overwrite old entry. bool changed = !data.value.isEmpty() && data.value != oldItem->value && data.value != strNotInScope; oldItem->setData(data); oldItem->changed = changed; oldItem->generation = generationCounter; QModelIndex idx = watchIndex(oldItem); emit dataChanged(idx, idx.sibling(idx.row(), 2)); // This works around http://bugreports.qt.nokia.com/browse/QTBUG-7115 // by creating and destroying a dummy child item. if (!hadChildren && oldItem->hasChildren) { WatchData dummy = data; dummy.iname = data.iname + ".x"; dummy.hasChildren = false; dummy.setAllUnneeded(); insertData(dummy); destroyItem(findItem(dummy.iname, m_root)); } } else { // Add new entry. WatchItem *item = new WatchItem(data); item->parent = parent; item->generation = generationCounter; item->changed = true; int n = findInsertPosition(parent->children, item); beginInsertRows(index, n, n); parent->children.insert(n, item); endInsertRows(); } } void WatchModel::insertBulkData(const QList &list) { #if 0 for (int i = 0; i != list.size(); ++i) insertData(list.at(i)); return; #endif // This method does not properly insert items in proper "iname sort // order", leading to random removal of items in removeOutDated(); //qDebug() << "WMI:" << list.toString(); //static int bulk = 0; //foreach (const WatchItem &data, list) // qDebug() << "BULK: " << ++bulk << data.toString(); QTC_ASSERT(!list.isEmpty(), return); QByteArray parentIName = parentName(list.at(0).iname); WatchItem *parent = findItem(parentIName, m_root); if (!parent) { WatchData parent; parent.iname = parentIName; insertData(parent); MODEL_DEBUG("\nFIXING MISSING PARENT FOR\n" << list.at(0).iname); return; } QModelIndex index = watchIndex(parent); QMap newList; typedef QMap::iterator Iterator; foreach (const WatchItem &data, list) newList[data.iname] = data; if (newList.size() != list.size()) { qDebug() << "LIST: "; foreach (const WatchItem &data, list) qDebug() << data.toString(); qDebug() << "NEW LIST: "; foreach (const WatchItem &data, newList) qDebug() << data.toString(); qDebug() << "P->CHILDREN: "; foreach (const WatchItem *item, parent->children) qDebug() << item->toString(); qDebug() << "P->CHILDREN.SIZE: " << parent->children.size() << "NEWLIST SIZE: " << newList.size() << "LIST SIZE: " << list.size(); } QTC_ASSERT(newList.size() == list.size(), return); foreach (WatchItem *oldItem, parent->children) { Iterator it = newList.find(oldItem->iname); if (it == newList.end()) { WatchData data = *oldItem; data.generation = generationCounter; newList[oldItem->iname] = data; } else { bool changed = !it->value.isEmpty() && it->value != oldItem->value && it->value != strNotInScope; it->changed = changed; if (it->generation == -1) it->generation = generationCounter; } } for (Iterator it = newList.begin(); it != newList.end(); ++it) { qDebug() << " NEW: " << it->iname; } // overwrite existing items Iterator it = newList.begin(); QModelIndex idx = watchIndex(parent); const int oldCount = parent->children.size(); for (int i = 0; i < oldCount; ++i, ++it) { if (!parent->children[i]->isEqual(*it)) { qDebug() << "REPLACING" << parent->children.at(i)->iname << " WITH " << it->iname << it->generation; parent->children[i]->setData(*it); if (parent->children[i]->generation == -1) parent->children[i]->generation = generationCounter; //emit dataChanged(idx.sibling(i, 0), idx.sibling(i, 2)); } else { //qDebug() << "SKIPPING REPLACEMENT" << parent->children.at(i)->iname; } } emit dataChanged(idx.sibling(0, 0), idx.sibling(oldCount - 1, 2)); // add new items if (oldCount < newList.size()) { beginInsertRows(index, oldCount, newList.size() - 1); //MODEL_DEBUG("INSERT : " << data.iname << data.value); for (int i = oldCount; i < newList.size(); ++i, ++it) { WatchItem *item = new WatchItem(*it); qDebug() << "ADDING" << it->iname; item->parent = parent; if (item->generation == -1) item->generation = generationCounter; item->changed = true; parent->children.append(item); } endInsertRows(); } //qDebug() << "ITEMS: " << parent->children.size(); dump(); } WatchItem *WatchModel::findItem(const QByteArray &iname, WatchItem *root) const { if (root->iname == iname) return root; for (int i = root->children.size(); --i >= 0; ) if (WatchItem *item = findItem(iname, root->children.at(i))) return item; return 0; } static void debugRecursion(QDebug &d, const WatchItem *item, int depth) { d << QString(2 * depth, QLatin1Char(' ')) << item->toString() << '\n'; foreach (const WatchItem *child, item->children) debugRecursion(d, child, depth + 1); } QDebug operator<<(QDebug d, const WatchModel &m) { QDebug nospace = d.nospace(); if (m.m_root) debugRecursion(nospace, m.m_root, 0); return d; } void WatchModel::formatRequests(QByteArray *out, const WatchItem *item) const { int format = m_handler->m_individualFormats.value(item->iname, -1); if (format == -1) format = m_handler->m_typeFormats.value(item->type, -1); if (format != -1) *out += item->iname + ":format=" + QByteArray::number(format) + ','; foreach (const WatchItem *child, item->children) formatRequests(out, child); } /////////////////////////////////////////////////////////////////////// // // WatchHandler // /////////////////////////////////////////////////////////////////////// WatchHandler::WatchHandler(DebuggerEngine *engine) { m_engine = engine; m_expandPointers = true; m_inChange = false; m_return = new WatchModel(this, ReturnWatch); m_locals = new WatchModel(this, LocalsWatch); m_watchers = new WatchModel(this, WatchersWatch); m_tooltips = new WatchModel(this, TooltipsWatch); connect(theDebuggerAction(WatchExpression), SIGNAL(triggered()), this, SLOT(watchExpression())); connect(theDebuggerAction(RemoveWatchExpression), SIGNAL(triggered()), this, SLOT(removeWatchExpression())); connect(theDebuggerAction(ShowStdNamespace), SIGNAL(triggered()), this, SLOT(emitAllChanged())); connect(theDebuggerAction(ShowQtNamespace), SIGNAL(triggered()), this, SLOT(emitAllChanged())); } void WatchHandler::beginCycle() { ++generationCounter; m_return->beginCycle(); m_locals->beginCycle(); m_watchers->beginCycle(); m_tooltips->beginCycle(); } void WatchHandler::endCycle() { m_return->endCycle(); m_locals->endCycle(); m_watchers->endCycle(); m_tooltips->endCycle(); updateWatchersWindow(); } void WatchHandler::cleanup() { m_expandedINames.clear(); m_return->reinitialize(); m_locals->reinitialize(); m_tooltips->reinitialize(); m_return->m_fetchTriggered.clear(); m_locals->m_fetchTriggered.clear(); m_watchers->m_fetchTriggered.clear(); m_tooltips->m_fetchTriggered.clear(); #if 1 for (EditHandlers::ConstIterator it = m_editHandlers.begin(); it != m_editHandlers.end(); ++it) { if (!it.value().isNull()) delete it.value(); } m_editHandlers.clear(); #endif } void WatchHandler::emitAllChanged() { m_return->emitAllChanged(); m_locals->emitAllChanged(); m_watchers->emitAllChanged(); m_tooltips->emitAllChanged(); } void WatchHandler::insertData(const WatchData &data) { MODEL_DEBUG("INSERTDATA: " << data.toString()); if (!data.isValid()) { qWarning("%s:%d: Attempt to insert invalid watch item: %s", __FILE__, __LINE__, qPrintable(data.toString())); return; } if (data.isSomethingNeeded() && data.iname.contains('.')) { MODEL_DEBUG("SOMETHING NEEDED: " << data.toString()); if (!m_engine->isSynchroneous()) { m_engine->updateWatchData(data); } else { qDebug() << "ENDLESS LOOP: SOMETHING NEEDED: " << data.toString(); WatchData data1 = data; data1.setAllUnneeded(); data1.setValue(QLatin1String("")); data1.setHasChildren(false); WatchModel *model = modelForIName(data.iname); QTC_ASSERT(model, return); model->insertData(data1); } } else { WatchModel *model = modelForIName(data.iname); QTC_ASSERT(model, return); MODEL_DEBUG("NOTHING NEEDED: " << data.toString()); model->insertData(data); showEditValue(data); } } // Bulk-insertion void WatchHandler::insertBulkData(const QList &list) { #if 1 foreach (const WatchItem &data, list) insertData(data); return; #endif if (list.isEmpty()) return; QMap > hash; foreach (const WatchData &data, list) { // we insert everything, including incomplete stuff // to reduce the number of row add operations in the model. if (data.isValid()) { hash[parentName(data.iname)].append(data); } else { qWarning("%s:%d: Attempt to bulk-insert invalid watch item: %s", __FILE__, __LINE__, qPrintable(data.toString())); } } foreach (const QByteArray &parentIName, hash.keys()) { WatchModel *model = modelForIName(parentIName); QTC_ASSERT(model, return); model->insertBulkData(hash[parentIName]); } foreach (const WatchData &data, list) { if (data.isSomethingNeeded()) m_engine->updateWatchData(data); } } void WatchHandler::removeData(const QByteArray &iname) { WatchModel *model = modelForIName(iname); if (!model) return; WatchItem *item = model->findItem(iname, model->m_root); if (item) model->destroyItem(item); } void WatchHandler::watchExpression() { if (QAction *action = qobject_cast(sender())) watchExpression(action->data().toString()); } QByteArray WatchHandler::watcherName(const QByteArray &exp) { return "watch." + QByteArray::number(m_watcherNames[exp]); } void WatchHandler::watchExpression(const QString &exp) { // FIXME: 'exp' can contain illegal characters WatchData data; data.exp = exp.toLatin1(); data.name = exp; m_watcherNames[data.exp] = watcherCounter++; if (exp.isEmpty() || exp == watcherEditPlaceHolder()) data.setAllUnneeded(); data.iname = watcherName(data.exp); if (m_engine && m_engine->isSynchroneous()) m_engine->updateWatchData(data); else insertData(data); m_engine->updateWatchData(data); updateWatchersWindow(); saveWatchers(); } static void swapEndian(char *d, int nchar) { QTC_ASSERT(nchar % 4 == 0, return); for (int i = 0; i < nchar; i += 4) { char c = d[i]; d[i] = d[i + 3]; d[i + 3] = c; c = d[i + 1]; d[i + 1] = d[i + 2]; d[i + 2] = c; } } void WatchHandler::showEditValue(const WatchData &data) { const QByteArray key = data.addr.isEmpty() ? data.iname : data.addr; QObject *w = m_editHandlers.value(key); if (data.editformat == 0x0) { m_editHandlers.remove(data.iname); delete w; } else if (data.editformat == 1 || data.editformat == 3) { // QImage QLabel *l = qobject_cast(w); if (!l) { delete w; l = new QLabel; QString addr = tr("unknown address"); if (!data.addr.isEmpty()) addr = QString::fromLatin1(data.addr); l->setWindowTitle(tr("%1 object at %2").arg(data.type, addr)); m_editHandlers[key] = l; } int width, height, format; QByteArray ba; uchar *bits; if (data.editformat == 1) { ba = QByteArray::fromHex(data.editvalue); const int *header = (int *)(ba.data()); swapEndian(ba.data(), ba.size()); bits = 12 + (uchar *)(ba.data()); width = header[0]; height = header[1]; format = header[2]; } else { // data.editformat == 3 QTextStream ts(data.editvalue); QString fileName; ts >> width >> height >> format >> fileName; QFile f(fileName); f.open(QIODevice::ReadOnly); ba = f.readAll(); bits = (uchar*)ba.data(); } QImage im(bits, width, height, QImage::Format(format)); #if 1 // enforcing copy of image data QImage im2(im); im.detach(); #endif l->setPixmap(QPixmap::fromImage(im)); l->resize(width, height); l->show(); } else if (data.editformat == 2) { // QString QTextEdit *t = qobject_cast(w); if (!t) { delete w; t = new QTextEdit; m_editHandlers[key] = t; } QByteArray ba = QByteArray::fromHex(data.editvalue); QString str = QString::fromUtf16((ushort *)ba.constData(), ba.size()/2); t->setText(str); t->resize(400, 200); t->show(); } else if (data.editformat == 4) { // Generic Process. int pos = data.editvalue.indexOf('|'); QByteArray cmd = data.editvalue.left(pos); QByteArray input = data.editvalue.mid(pos + 1); QProcess *p = qobject_cast(w); if (!p) { p = new QProcess; p->start(cmd); p->waitForStarted(); m_editHandlers[key] = p; } p->write(input + "\n"); } else { QTC_ASSERT(false, qDebug() << "Display format: " << data.editformat); } } void WatchHandler::removeWatchExpression() { if (QAction *action = qobject_cast(sender())) removeWatchExpression(action->data().toString()); } void WatchHandler::removeWatchExpression(const QString &exp0) { QByteArray exp = exp0.toLatin1(); MODEL_DEBUG("REMOVE WATCH: " << exp); m_watcherNames.remove(exp); foreach (WatchItem *item, m_watchers->rootItem()->children) { if (item->exp == exp) { m_watchers->destroyItem(item); saveWatchers(); break; } } } void WatchHandler::updateWatchersWindow() { const bool showWatchers = m_watchers->rowCount(QModelIndex()) > 0; const bool showReturn = m_return->rowCount(QModelIndex()) > 0; plugin()->updateWatchersWindow(showWatchers, showReturn); } void WatchHandler::updateWatchers() { //qDebug() << "UPDATE WATCHERS"; // copy over all watchers and mark all watchers as incomplete foreach (const QByteArray &exp, m_watcherNames.keys()) { WatchData data; data.iname = watcherName(exp); data.setAllNeeded(); data.name = exp; data.exp = exp; insertData(data); } } void WatchHandler::loadWatchers() { QVariant value = plugin()->sessionValue("Watchers"); foreach (const QString &exp, value.toStringList()) m_watcherNames[exp.toLatin1()] = watcherCounter++; //qDebug() << "LOAD WATCHERS: " << m_watchers; //reinitializeWatchersHelper(); } QStringList WatchHandler::watchedExpressions() const { // Filter out invalid watchers. QStringList watcherNames; QHashIterator it(m_watcherNames); while (it.hasNext()) { it.next(); const QString &watcherName = it.key(); if (!watcherName.isEmpty() && watcherName != watcherEditPlaceHolder()) watcherNames.push_back(watcherName); } return watcherNames; } void WatchHandler::saveWatchers() { //qDebug() << "SAVE WATCHERS: " << m_watchers; plugin()->setSessionValue("Watchers", QVariant(watchedExpressions())); } void WatchHandler::loadTypeFormats() { QVariant value = plugin()->sessionValue("DefaultFormats"); QMap typeFormats = value.toMap(); QMapIterator it(typeFormats); while (it.hasNext()) { it.next(); if (!it.key().isEmpty()) m_typeFormats.insert(it.key(), it.value().toInt()); } } void WatchHandler::saveTypeFormats() { QMap typeFormats; QHashIterator it(m_typeFormats); while (it.hasNext()) { it.next(); const int format = it.value(); if (format != DecimalFormat) { const QString key = it.key().trimmed(); if (!key.isEmpty()) typeFormats.insert(key, format); } } plugin()->setSessionValue("DefaultFormats", QVariant(typeFormats)); } void WatchHandler::saveSessionData() { saveWatchers(); saveTypeFormats(); } void WatchHandler::loadSessionData() { loadWatchers(); loadTypeFormats(); foreach (const QByteArray &exp, m_watcherNames.keys()) { WatchData data; data.iname = watcherName(exp); data.setAllUnneeded(); data.name = exp; data.exp = exp; insertData(data); } } WatchModel *WatchHandler::model(WatchType type) const { switch (type) { case ReturnWatch: return m_return; case LocalsWatch: return m_locals; case WatchersWatch: return m_watchers; case TooltipsWatch: return m_tooltips; } QTC_ASSERT(false, /**/); return 0; } WatchModel *WatchHandler::modelForIName(const QByteArray &iname) const { if (iname.startsWith("return")) return m_return; if (iname.startsWith("local")) return m_locals; if (iname.startsWith("tooltip")) return m_tooltips; if (iname.startsWith("watch")) return m_watchers; QTC_ASSERT(false, qDebug() << "INAME: " << iname); return 0; } WatchData *WatchHandler::findItem(const QByteArray &iname) const { const WatchModel *model = modelForIName(iname); QTC_ASSERT(model, return 0); return model->findItem(iname, model->m_root); } QString WatchHandler::watcherEditPlaceHolder() { static const QString rc = tr(""); return rc; } void WatchHandler::setFormat(const QString &type, int format) { if (format == -1) m_typeFormats.remove(type); else m_typeFormats[type] = format; saveTypeFormats(); m_return->emitDataChanged(1); m_locals->emitDataChanged(1); m_watchers->emitDataChanged(1); m_tooltips->emitDataChanged(1); } int WatchHandler::format(const QByteArray &iname) const { int result = -1; if (const WatchData *item = findItem(iname)) { int result = m_individualFormats.value(iname, -1); if (result == -1) result = m_typeFormats.value(item->type, -1); } return result; } QByteArray WatchHandler::expansionRequests() const { QByteArray ba; //m_locals->formatRequests(&ba, m_locals->m_root); //m_watchers->formatRequests(&ba, m_watchers->m_root); if (!m_expandedINames.isEmpty()) { QSetIterator jt(m_expandedINames); while (jt.hasNext()) { QByteArray iname = jt.next(); ba.append(iname); ba.append(','); } ba.chop(1); } return ba; } QByteArray WatchHandler::typeFormatRequests() const { QByteArray ba; if (!m_typeFormats.isEmpty()) { QHashIterator it(m_typeFormats); while (it.hasNext()) { it.next(); ba.append(it.key().toLatin1().toHex()); ba.append('='); ba.append(QByteArray::number(it.value())); ba.append(','); } ba.chop(1); } return ba; } QByteArray WatchHandler::individualFormatRequests() const { QByteArray ba; if (!m_individualFormats.isEmpty()) { QHashIterator it(m_individualFormats); while (it.hasNext()) { it.next(); ba.append(it.key()); ba.append('='); ba.append(QByteArray::number(it.value())); ba.append(','); } ba.chop(1); } return ba; } } // namespace Internal } // namespace Debugger