1 /**************************************************************************
3 ** This file is part of Qt Creator
5 ** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
7 ** Contact: Nokia Corporation (info@qt.nokia.com)
10 ** GNU Lesser General Public License Usage
12 ** This file may be used under the terms of the GNU Lesser General Public
13 ** License version 2.1 as published by the Free Software Foundation and
14 ** appearing in the file LICENSE.LGPL included in the packaging of this file.
15 ** Please review the following information to ensure the GNU Lesser General
16 ** Public License version 2.1 requirements will be met:
17 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
19 ** In addition, as a special exception, Nokia gives you certain additional
20 ** rights. These rights are described in the Nokia Qt LGPL Exception
21 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
25 ** Alternatively, this file may be used in accordance with the terms and
26 ** conditions contained in a signed written agreement between you and Nokia.
28 ** If you have questions regarding the use of this file, please contact
29 ** Nokia at info@qt.nokia.com.
31 **************************************************************************/
33 #include "watchhandler.h"
35 #include "breakhandler.h"
36 #include "debuggerinternalconstants.h"
37 #include "debuggeractions.h"
38 #include "debuggercore.h"
39 #include "debuggerengine.h"
40 #include "watchutils.h"
42 #if USE_WATCH_MODEL_TEST
43 #include "modeltest.h"
46 #include <utils/qtcassert.h>
47 #include <utils/savedaction.h>
49 #include <cplusplus/CppRewriter.h>
51 #include <QtCore/QDebug>
52 #include <QtCore/QEvent>
53 #include <QtCore/QFile>
54 #include <QtCore/QProcess>
55 #include <QtCore/QTextStream>
56 #include <QtCore/QtAlgorithms>
58 #include <QtGui/QLabel>
59 #include <QtGui/QTextEdit>
62 #include <utils/qtcassert.h>
68 // Creates debug output for accesses to the model.
69 enum { debugModel = 0 };
71 #define MODEL_DEBUG(s) do { if (debugModel) qDebug() << s; } while (0)
72 #define MODEL_DEBUGX(s) qDebug() << s
74 static const QString strNotInScope =
75 QCoreApplication::translate("Debugger::Internal::WatchData", "<not in scope>");
77 static int watcherCounter = 0;
78 static int generationCounter = 0;
80 QHash<QByteArray, int> WatchHandler::m_watcherNames;
81 QHash<QByteArray, int> WatchHandler::m_typeFormats;
82 int WatchHandler::m_unprintableBase = 0;
84 static QByteArray stripTemplate(const QByteArray &ba)
86 int pos = ba.indexOf('<');
87 return pos == -1 ? ba : ba.left(pos);
90 ////////////////////////////////////////////////////////////////////
94 ////////////////////////////////////////////////////////////////////
96 class WatchItem : public WatchData
99 WatchItem() { parent = 0; }
103 parent->children.removeOne(this);
104 qDeleteAll(children);
107 WatchItem(const WatchData &data) : WatchData(data)
110 void setData(const WatchData &data)
111 { static_cast<WatchData &>(*this) = data; }
114 QList<WatchItem *> children; // fetched children
118 ///////////////////////////////////////////////////////////////////////
122 ///////////////////////////////////////////////////////////////////////
124 WatchModel::WatchModel(WatchHandler *handler, WatchType type)
125 : QAbstractItemModel(handler), m_handler(handler), m_type(type)
127 m_root = new WatchItem;
128 m_root->hasChildren = 1;
130 m_root->name = WatchHandler::tr("Root");
135 m_root->iname = "return";
136 m_root->name = WatchHandler::tr("Return Value");
139 m_root->iname = "local";
140 m_root->name = WatchHandler::tr("Locals");
143 m_root->iname = "watch";
144 m_root->name = WatchHandler::tr("Watchers");
147 m_root->iname = "tooltip";
148 m_root->name = WatchHandler::tr("Tooltip");
153 WatchModel::~WatchModel()
158 WatchItem *WatchModel::rootItem() const
163 void WatchModel::reinitialize()
165 int n = m_root->children.size();
168 //MODEL_DEBUG("REMOVING " << n << " CHILDREN OF " << m_root->iname);
169 QModelIndex index = watchIndex(m_root);
170 beginRemoveRows(index, 0, n - 1);
171 qDeleteAll(m_root->children);
172 m_root->children.clear();
176 void WatchModel::emitAllChanged()
178 emit layoutChanged();
181 void WatchModel::beginCycle()
183 emit enableUpdates(false);
186 void WatchModel::endCycle()
189 m_fetchTriggered.clear();
190 emit enableUpdates(true);
193 DebuggerEngine *WatchModel::engine() const
195 return m_handler->m_engine;
198 void WatchModel::dump()
201 foreach (WatchItem *child, m_root->children)
205 void WatchModel::dumpHelper(WatchItem *item)
207 qDebug() << "ITEM: " << item->iname
208 << (item->parent ? item->parent->iname : "<none>")
210 foreach (WatchItem *child, item->children)
214 void WatchModel::removeOutdated()
216 foreach (WatchItem *child, m_root->children)
217 removeOutdatedHelper(child);
219 #if USE_WATCH_MODEL_TEST
220 (void) new ModelTest(this, this);
225 void WatchModel::removeOutdatedHelper(WatchItem *item)
227 if (item->generation < generationCounter) {
230 foreach (WatchItem *child, item->children)
231 removeOutdatedHelper(child);
235 void WatchModel::destroyItem(WatchItem *item)
237 WatchItem *parent = item->parent;
238 QModelIndex index = watchIndex(parent);
239 int n = parent->children.indexOf(item);
240 //MODEL_DEBUG("NEED TO REMOVE: " << item->iname << "AT" << n);
241 beginRemoveRows(index, n, n);
242 parent->children.removeAt(n);
247 static QByteArray parentName(const QByteArray &iname)
249 int pos = iname.lastIndexOf('.');
252 return iname.left(pos);
255 static QString niceTypeHelper(const QByteArray &typeIn)
257 typedef QMap<QByteArray, QString> Cache;
259 const Cache::const_iterator it = cache.constFind(typeIn);
260 if (it != cache.constEnd())
262 const QString simplified = CPlusPlus::simplifySTLType(typeIn);
263 cache.insert(typeIn, simplified); // For simplicity, also cache unmodified types
267 static QString removeNamespaces(QString str, const QByteArray &ns)
269 if (!debuggerCore()->boolSetting(ShowStdNamespace))
270 str.remove(QLatin1String("std::"));
271 if (!debuggerCore()->boolSetting(ShowQtNamespace)) {
272 const QString qtNamespace = QString::fromLatin1(ns);
273 if (!qtNamespace.isEmpty())
274 str.remove(qtNamespace);
279 static QString removeInitialNamespace(QString str, const QByteArray &ns)
281 if (str.startsWith(QLatin1String("std::"))
282 && debuggerCore()->boolSetting(ShowStdNamespace))
284 if (!debuggerCore()->boolSetting(ShowQtNamespace)) {
285 const QString qtNamespace = QString::fromLatin1(ns);
286 if (!qtNamespace.isEmpty() && str.startsWith(qtNamespace))
287 str = str.mid(qtNamespace.size());
292 QString WatchModel::displayType(const WatchData &data) const
294 QString base = data.displayedType.isEmpty()
295 ? niceTypeHelper(data.type)
296 : data.displayedType;
298 base += QString(":%1").arg(data.bitsize);
303 static int formatToIntegerBase(int format)
306 case HexadecimalFormat:
316 template <class IntType> QString reformatInteger(IntType value, int format)
319 case HexadecimalFormat:
320 return ("(hex) ") + QString::number(value, 16);
322 return ("(bin) ") + QString::number(value, 2);
324 return ("(oct) ") + QString::number(value, 8);
326 return QString::number(value); // not reached
329 // Format printable (char-type) characters
330 static inline QString reformatCharacter(int code, int format)
332 const QString codeS = reformatInteger(code, format);
333 if (code < 0) // Append unsigned value.
334 return codeS + QLatin1String(" / ") + reformatInteger(256 + code, format);
335 if (code >= 32 && code < 128)
336 return codeS + QLatin1String(" '") + QChar(code) + QLatin1Char('\'');
339 return codeS + QLatin1String(" '\\0'");
341 return codeS + QLatin1String(" '\\r'");
343 return codeS + QLatin1String(" '\\t'");
345 return codeS + QLatin1String(" '\\n'");
350 static inline QString formattedValue(const WatchData &data, int format)
352 if (isIntType(data.type)) {
353 if (data.value.isEmpty())
355 // Do not reformat booleans (reported as 'true, false').
356 const QChar firstChar = data.value.at(0);
357 if (!firstChar.isDigit() && firstChar != QLatin1Char('-'))
359 // Append quoted, printable character also for decimal.
360 if (data.type.endsWith("char")) {
362 const int code = data.value.toInt(&ok);
363 return ok ? reformatCharacter(code, format) : data.value;
365 // Rest: Leave decimal as is
368 // Evil hack, covers 'unsigned' as well as quint64.
369 if (data.type.contains('u'))
370 return reformatInteger(data.value.toULongLong(0, 0), format);
371 return reformatInteger(data.value.toLongLong(), format);
374 if (!isPointerType(data.type) && !isVTablePointer(data.type)) {
376 qulonglong integer = data.value.toULongLong(&ok, 0);
378 return reformatInteger(integer, format);
381 QString result = data.value;
382 result.replace(QLatin1Char('\n'), QLatin1String("\\n"));
383 if (result.startsWith(QLatin1Char('<'))) {
384 if (result == QLatin1String("<Edit>"))
385 result = WatchHandler::tr("<Edit>");
386 else if (result == QLatin1String("<empty>"))
387 result = WatchHandler::tr("<empty>");
388 else if (result == QLatin1String("<uninitialized>"))
389 result = WatchHandler::tr("<uninitialized>");
390 else if (result == QLatin1String("<invalid>"))
391 result = WatchHandler::tr("<invalid>");
392 else if (result == QLatin1String("<not accessible>"))
393 result = WatchHandler::tr("<not accessible>");
394 else if (result.endsWith(" items>")) {
395 // '<10 items>' or '<>10 items>' (more than)
397 const bool moreThan = result.at(1) == QLatin1Char('>');
398 const int numberPos = moreThan ? 2 : 1;
399 const int size = result.mid(numberPos, result.indexOf(' ') - numberPos).toInt(&ok);
400 QTC_ASSERT(ok, qWarning("WatchHandler: Invalid item count '%s'",
403 WatchHandler::tr("<more than %n items>", 0, size) :
404 WatchHandler::tr("<%n items>", 0, size);
411 // Get a pointer address from pointer values reported by the debugger.
412 // Fix CDB formatting of pointers "0x00000000`000003fd class foo *", or
413 // "0x00000000`000003fd "Hallo"", or check gdb formatting of characters.
414 static inline quint64 pointerValue(QString data)
416 const int blankPos = data.indexOf(QLatin1Char(' '));
418 data.truncate(blankPos);
419 data.remove(QLatin1Char('`'));
420 return data.toULongLong(0, 0);
423 // Return the type used for editing
424 static inline int editType(const WatchData &d)
426 if (d.type == "bool")
427 return QVariant::Bool;
428 if (isIntType(d.type))
429 return d.type.contains('u') ? QVariant::ULongLong : QVariant::LongLong;
430 if (isFloatType(d.type))
431 return QVariant::Double;
432 // Check for pointers using hex values (0xAD00 "Hallo")
433 if (isPointerType(d.type) && d.value.startsWith(QLatin1String("0x")))
434 return QVariant::ULongLong;
435 return QVariant::String;
438 // Convert to editable (see above)
439 static inline QVariant editValue(const WatchData &d)
441 switch (editType(d)) {
443 return d.value != QLatin1String("0") && d.value != QLatin1String("false");
444 case QVariant::ULongLong:
445 if (isPointerType(d.type)) // Fix pointer values (0xAD00 "Hallo" -> 0xAD00)
446 return QVariant(pointerValue(d.value));
447 return QVariant(d.value.toULongLong());
448 case QVariant::LongLong:
449 return QVariant(d.value.toLongLong());
450 case QVariant::Double:
451 return QVariant(d.value.toDouble());
455 // Some string value: '0x434 "Hallo"':
456 // Remove quotes and replace newlines, which will cause line edit troubles.
457 QString stringValue = d.value;
458 if (stringValue.endsWith(QLatin1Char('"'))) {
459 const int leadingDoubleQuote = stringValue.indexOf(QLatin1Char('"'));
460 if (leadingDoubleQuote != stringValue.size() - 1) {
461 stringValue.truncate(stringValue.size() - 1);
462 stringValue.remove(0, leadingDoubleQuote + 1);
463 stringValue.replace(QLatin1String("\n"), QLatin1String("\\n"));
466 return QVariant(stringValue);
469 bool WatchModel::canFetchMore(const QModelIndex &index) const
471 WatchItem *item = watchItem(index);
472 QTC_ASSERT(item, return false);
473 return index.isValid() && !m_fetchTriggered.contains(item->iname);
476 void WatchModel::fetchMore(const QModelIndex &index)
478 QTC_ASSERT(index.isValid(), return);
479 WatchItem *item = watchItem(index);
480 QTC_ASSERT(item, return);
481 QTC_ASSERT(!m_fetchTriggered.contains(item->iname), return);
482 m_handler->m_expandedINames.insert(item->iname);
483 m_fetchTriggered.insert(item->iname);
484 if (item->children.isEmpty()) {
485 WatchData data = *item;
486 data.setChildrenNeeded();
487 WatchUpdateFlags flags;
488 flags.tryIncremental = true;
489 engine()->updateWatchData(data, flags);
493 QModelIndex WatchModel::index(int row, int column, const QModelIndex &parent) const
495 if (!hasIndex(row, column, parent))
496 return QModelIndex();
498 const WatchItem *item = watchItem(parent);
499 QTC_ASSERT(item, return QModelIndex());
500 if (row >= item->children.size())
501 return QModelIndex();
502 return createIndex(row, column, (void*)(item->children.at(row)));
505 QModelIndex WatchModel::parent(const QModelIndex &idx) const
508 return QModelIndex();
510 const WatchItem *item = watchItem(idx);
511 if (!item->parent || item->parent == m_root)
512 return QModelIndex();
514 const WatchItem *grandparent = item->parent->parent;
516 return QModelIndex();
518 for (int i = 0; i < grandparent->children.size(); ++i)
519 if (grandparent->children.at(i) == item->parent)
520 return createIndex(i, 0, (void*) item->parent);
522 return QModelIndex();
525 int WatchModel::rowCount(const QModelIndex &idx) const
527 if (idx.column() > 0)
529 return watchItem(idx)->children.size();
532 int WatchModel::columnCount(const QModelIndex &idx) const
538 bool WatchModel::hasChildren(const QModelIndex &parent) const
540 WatchItem *item = watchItem(parent);
541 return !item || item->hasChildren;
544 WatchItem *WatchModel::watchItem(const QModelIndex &idx) const
547 ? static_cast<WatchItem*>(idx.internalPointer()) : m_root;
550 QModelIndex WatchModel::watchIndex(const WatchItem *item) const
552 return watchIndexHelper(item, m_root, QModelIndex());
555 QModelIndex WatchModel::watchIndexHelper(const WatchItem *needle,
556 const WatchItem *parentItem, const QModelIndex &parentIndex) const
558 if (needle == parentItem)
560 for (int i = parentItem->children.size(); --i >= 0; ) {
561 const WatchItem *childItem = parentItem->children.at(i);
562 QModelIndex childIndex = index(i, 0, parentIndex);
563 QModelIndex idx = watchIndexHelper(needle, childItem, childIndex);
567 return QModelIndex();
570 void WatchModel::emitDataChanged(int column, const QModelIndex &parentIndex)
572 QModelIndex idx1 = index(0, column, parentIndex);
573 QModelIndex idx2 = index(rowCount(parentIndex) - 1, column, parentIndex);
574 if (idx1.isValid() && idx2.isValid())
575 emit dataChanged(idx1, idx2);
576 //qDebug() << "CHANGING:\n" << idx1 << "\n" << idx2 << "\n"
577 // << data(parentIndex, INameRole).toString();
578 for (int i = rowCount(parentIndex); --i >= 0; )
579 emitDataChanged(column, index(i, 0, parentIndex));
582 // Truncate value for item view, maintaining quotes
583 static inline QString truncateValue(QString v)
585 enum { maxLength = 512 };
586 if (v.size() < maxLength)
588 const bool isQuoted = v.endsWith(QLatin1Char('"')); // check for 'char* "Hallo"'
589 v.truncate(maxLength);
590 v += isQuoted ? QLatin1String("...\"") : QLatin1String("...");
594 int WatchModel::itemFormat(const WatchData &data) const
596 const int individualFormat = m_handler->m_individualFormats.value(data.iname, -1);
597 if (individualFormat != -1)
598 return individualFormat;
599 return m_handler->m_typeFormats.value(stripTemplate(data.type), -1);
602 static inline QString expression(const WatchItem *item)
604 if (!item->exp.isEmpty())
605 return QString::fromAscii(item->exp);
606 if (item->address && !item->type.isEmpty()) {
607 return QString::fromAscii("*(%1*)%2").
608 arg(QLatin1String(item->type), QLatin1String(item->hexAddress()));
610 if (const WatchItem *parent = item->parent) {
611 if (!parent->exp.isEmpty())
612 return QString::fromAscii("(%1).%2")
613 .arg(QString::fromLatin1(parent->exp), item->name);
618 QVariant WatchModel::data(const QModelIndex &idx, int role) const
620 const WatchItem *item = watchItem(idx);
621 const WatchItem &data = *item;
624 case LocalsEditTypeRole:
625 return QVariant(editType(data));
628 return QVariant(data.name);
630 case LocalsIntegerBaseRole:
631 if (isPointerType(data.type)) // Pointers using 0x-convention
633 return QVariant(formatToIntegerBase(itemFormat(data)));
636 switch (idx.column()) {
638 return QVariant(expression(item));
640 return editValue(data);
642 // FIXME:: To be tested: Can debuggers handle those?
643 if (!data.displayedType.isEmpty())
644 return data.displayedType;
645 return QString::fromUtf8(data.type);
651 case Qt::DisplayRole: {
652 const QByteArray qtNameSpace = engine()->qtNamespace();
654 switch (idx.column()) {
656 if (data.name.isEmpty())
657 result = tr("<Edit>");
658 else if (data.name == QLatin1String("*") && item->parent)
659 result = QLatin1Char('*') + item->parent->name;
661 result = removeInitialNamespace(data.name, qtNameSpace);
664 result = removeInitialNamespace(truncateValue(
665 formattedValue(data, itemFormat(data))), qtNameSpace);
666 if (data.referencingAddress) {
667 result += QLatin1String(" @");
668 result += QString::fromLatin1(data.hexAddress());
672 result = removeNamespaces(displayType(data), qtNameSpace);
677 if (WatchHandler::m_unprintableBase == 0)
680 foreach (const QChar c, result) {
683 } else if (WatchHandler::m_unprintableBase == 8) {
684 encoded += QString("\\%1")
685 .arg(c.unicode(), 3, 8, QLatin1Char('0'));
687 encoded += QString("\\u%1")
688 .arg(c.unicode(), 4, 16, QLatin1Char('0'));
694 case Qt::ToolTipRole:
695 return debuggerCore()->boolSetting(UseToolTipsInLocalsView)
696 ? data.toToolTip() : QVariant();
698 case Qt::ForegroundRole: {
699 static const QVariant red(QColor(200, 0, 0));
700 static const QVariant gray(QColor(140, 140, 140));
701 switch (idx.column()) {
702 case 1: return !data.valueEnabled ? gray
703 : data.changed ? red : QVariant();
708 case LocalsExpressionRole:
709 return QVariant(expression(item));
711 case LocalsRawExpressionRole:
714 case LocalsINameRole:
717 case LocalsExpandedRole:
718 return m_handler->m_expandedINames.contains(data.iname);
720 case LocalsTypeFormatListRole: {
721 if (data.referencingAddress || isPointerType(data.type))
724 << tr("Latin1 string")
726 << tr("Local 8bit string")
727 << tr("UTF16 string")
728 << tr("UCS4 string");
729 if (data.type.contains("char[") || data.type.contains("char ["))
731 << tr("Latin1 string")
733 << tr("Local 8bit string");
735 (void)data.value.toULongLong(&ok, 0);
736 if ((isIntType(data.type) && data.type != "bool") || ok)
742 // Hack: Compensate for namespaces.
743 QString type = stripTemplate(data.type);
744 int pos = type.indexOf("::Q");
745 if (pos >= 0 && type.count(':') == 2)
746 type = type.mid(pos + 2);
747 pos = type.indexOf('<');
749 type = type.left(pos);
750 type.replace(':', '_');
751 return m_handler->m_reportedTypeFormats.value(type);
754 return removeNamespaces(displayType(data), engine()->qtNamespace());
755 case LocalsRawTypeRole:
756 return QString::fromLatin1(data.type);
757 case LocalsTypeFormatRole:
758 return m_handler->m_typeFormats.value(stripTemplate(data.type), -1);
760 case LocalsIndividualFormatRole:
761 return m_handler->m_individualFormats.value(data.iname, -1);
763 case LocalsRawValueRole:
766 case LocalsPointerValueRole:
767 if (isPointerType(data.type))
768 return pointerValue(data.value);
769 return QVariant(quint64(0));
771 case LocalsIsWatchpointAtAddressRole: {
772 BreakpointParameters bp(WatchpointAtAddress);
773 bp.address = data.coreAddress();
774 return engine()->breakHandler()->findWatchpoint(bp) != 0;
777 case LocalsAddressRole:
778 return QVariant(data.coreAddress());
779 case LocalsReferencingAddressRole:
780 return QVariant(data.referencingAddress);
782 return QVariant(data.size);
784 case LocalsIsWatchpointAtPointerValueRole:
785 if (isPointerType(data.type)) {
786 BreakpointParameters bp(WatchpointAtAddress);
787 bp.address = pointerValue(data.value);
788 return engine()->breakHandler()->findWatchpoint(bp) != 0;
798 bool WatchModel::setData(const QModelIndex &index, const QVariant &value, int role)
800 WatchItem &data = *watchItem(index);
804 switch (index.column()) {
805 case 0: // Watch expression: See delegate.
807 case 1: // Change value
808 engine()->assignValueInDebugger(&data, expression(&data), value);
810 case 2: // TODO: Implement change type.
811 engine()->assignValueInDebugger(&data, expression(&data), value);
814 case LocalsExpandedRole:
815 if (value.toBool()) {
816 // Should already have been triggered by fetchMore()
817 //QTC_ASSERT(m_handler->m_expandedINames.contains(data.iname), /**/);
818 m_handler->m_expandedINames.insert(data.iname);
820 m_handler->m_expandedINames.remove(data.iname);
824 case LocalsTypeFormatRole:
825 m_handler->setFormat(data.type, value.toInt());
826 engine()->updateWatchData(data);
829 case LocalsIndividualFormatRole: {
830 const int format = value.toInt();
832 m_handler->m_individualFormats.remove(data.iname);
834 m_handler->m_individualFormats[data.iname] = format;
836 engine()->updateWatchData(data);
841 emit dataChanged(index, index);
845 Qt::ItemFlags WatchModel::flags(const QModelIndex &idx) const
848 return Qt::ItemFlags();
850 // Enabled, editable, selectable, checkable, and can be used both as the
851 // source of a drag and drop operation and as a drop target.
853 static const Qt::ItemFlags notEditable
854 = Qt::ItemIsSelectable | Qt::ItemIsEnabled;
855 static const Qt::ItemFlags editable = notEditable | Qt::ItemIsEditable;
857 // Disable editing if debuggee is positively running.
858 const bool isRunning = engine() && engine()->state() == InferiorRunOk;
859 if (isRunning && engine() && !engine()->acceptsWatchesWhileRunning())
862 const WatchData &data = *watchItem(idx);
863 if (data.isWatcher()) {
864 if (idx.column() == 0 && data.iname.count('.') == 1)
865 return editable; // Watcher names are editable.
867 if (!data.name.isEmpty()) {
868 // FIXME: Forcing types is not implemented yet.
869 //if (idx.column() == 2)
870 // return editable; // Watcher types can be set by force.
871 if (idx.column() == 1 && data.valueEditable)
872 return editable; // Watcher values are sometimes editable.
875 if (idx.column() == 1 && data.valueEditable)
876 return editable; // Locals values are sometimes editable.
881 QVariant WatchModel::headerData(int section, Qt::Orientation orientation, int role) const
883 if (orientation == Qt::Vertical)
885 if (role == Qt::DisplayRole) {
887 case 0: return QString(tr("Name") + QLatin1String(" "));
888 case 1: return QString(tr("Value") + QLatin1String(" "));
889 case 2: return QString(tr("Type") + QLatin1String(" "));
895 // Determine sort order of watch items by sort order or alphabetical inames
896 // according to setting 'SortStructMembers'. We need a map key for bulkInsert
897 // and a predicate for finding the insertion position of a single item.
899 // Set this before using any of the below according to action
900 static bool sortWatchDataAlphabetically = true;
902 static bool watchDataLessThan(const QByteArray &iname1, int sortId1,
903 const QByteArray &iname2, int sortId2)
905 if (!sortWatchDataAlphabetically)
906 return sortId1 < sortId2;
907 // Get positions of last part of iname 'local.this.i1" -> "i1"
908 int cmpPos1 = iname1.lastIndexOf('.');
914 int cmpPos2 = iname2.lastIndexOf('.');
920 // Are we looking at an array with numerical inames 'local.this.i1.0" ->
922 if (cmpPos1 < iname1.size() && cmpPos2 < iname2.size()
923 && isdigit(iname1.at(cmpPos1)) && isdigit(iname2.at(cmpPos2)))
924 return sortId1 < sortId2;
926 return qstrcmp(iname1.constData() + cmpPos1, iname2.constData() + cmpPos2) < 0;
929 // Sort key for watch data consisting of iname and numerical sort id.
930 struct WatchDataSortKey
932 explicit WatchDataSortKey(const WatchData &wd)
933 : iname(wd.iname), sortId(wd.sortId) {}
938 inline bool operator<(const WatchDataSortKey &k1, const WatchDataSortKey &k2)
940 return watchDataLessThan(k1.iname, k1.sortId, k2.iname, k2.sortId);
943 bool watchItemSorter(const WatchItem *item1, const WatchItem *item2)
945 return watchDataLessThan(item1->iname, item1->sortId, item2->iname, item2->sortId);
948 static int findInsertPosition(const QList<WatchItem *> &list, const WatchItem *item)
950 sortWatchDataAlphabetically = debuggerCore()->boolSetting(SortStructMembers);
951 const QList<WatchItem *>::const_iterator it =
952 qLowerBound(list.begin(), list.end(), item, watchItemSorter);
953 return it - list.begin();
956 void WatchModel::insertData(const WatchData &data)
958 QTC_ASSERT(!data.iname.isEmpty(), qDebug() << data.toString(); return);
959 WatchItem *parent = findItem(parentName(data.iname), m_root);
962 parent.iname = parentName(data.iname);
963 MODEL_DEBUG("\nFIXING MISSING PARENT FOR\n" << data.iname);
964 if (!parent.iname.isEmpty())
968 QModelIndex index = watchIndex(parent);
969 if (WatchItem *oldItem = findItem(data.iname, parent)) {
970 bool hadChildren = oldItem->hasChildren;
971 // Overwrite old entry.
972 bool changed = !data.value.isEmpty()
973 && data.value != oldItem->value
974 && data.value != strNotInScope;
975 oldItem->setData(data);
976 oldItem->changed = changed;
977 oldItem->generation = generationCounter;
978 QModelIndex idx = watchIndex(oldItem);
979 emit dataChanged(idx, idx.sibling(idx.row(), 2));
981 // This works around http://bugreports.qt.nokia.com/browse/QTBUG-7115
982 // by creating and destroying a dummy child item.
983 if (!hadChildren && oldItem->hasChildren) {
984 WatchData dummy = data;
985 dummy.iname = data.iname + ".x";
986 dummy.hasChildren = false;
987 dummy.setAllUnneeded();
989 destroyItem(findItem(dummy.iname, m_root));
993 WatchItem *item = new WatchItem(data);
994 item->parent = parent;
995 item->generation = generationCounter;
996 item->changed = true;
997 const int n = findInsertPosition(parent->children, item);
998 beginInsertRows(index, n, n);
999 parent->children.insert(n, item);
1004 void WatchModel::insertBulkData(const QList<WatchData> &list)
1007 for (int i = 0; i != list.size(); ++i)
1008 insertData(list.at(i));
1011 // This method does not properly insert items in proper "iname sort
1012 // order", leading to random removal of items in removeOutdated();
1014 //qDebug() << "WMI:" << list.toString();
1015 //static int bulk = 0;
1016 //foreach (const WatchItem &data, list)
1017 // qDebug() << "BULK: " << ++bulk << data.toString();
1018 QTC_ASSERT(!list.isEmpty(), return);
1019 QByteArray parentIName = parentName(list.at(0).iname);
1020 WatchItem *parent = findItem(parentIName, m_root);
1023 parent.iname = parentIName;
1025 MODEL_DEBUG("\nFIXING MISSING PARENT FOR\n" << list.at(0).iname);
1028 QModelIndex index = watchIndex(parent);
1030 sortWatchDataAlphabetically = debuggerCore()->boolSetting(SortStructMembers);
1031 QMap<WatchDataSortKey, WatchData> newList;
1032 typedef QMap<WatchDataSortKey, WatchData>::iterator Iterator;
1033 foreach (const WatchItem &data, list)
1034 newList.insert(WatchDataSortKey(data), data);
1035 if (newList.size() != list.size()) {
1036 qDebug() << "LIST: ";
1037 foreach (const WatchItem &data, list)
1038 qDebug() << data.toString();
1039 qDebug() << "NEW LIST: ";
1040 foreach (const WatchItem &data, newList)
1041 qDebug() << data.toString();
1042 qDebug() << "P->CHILDREN: ";
1043 foreach (const WatchItem *item, parent->children)
1044 qDebug() << item->toString();
1046 << "P->CHILDREN.SIZE: " << parent->children.size()
1047 << "NEWLIST SIZE: " << newList.size()
1048 << "LIST SIZE: " << list.size();
1050 QTC_ASSERT(newList.size() == list.size(), return);
1052 foreach (WatchItem *oldItem, parent->children) {
1053 const WatchDataSortKey oldSortKey(*oldItem);
1054 Iterator it = newList.find(oldSortKey);
1055 if (it == newList.end()) {
1056 WatchData data = *oldItem;
1057 data.generation = generationCounter;
1058 newList.insert(oldSortKey, data);
1060 bool changed = !it->value.isEmpty()
1061 && it->value != oldItem->value
1062 && it->value != strNotInScope;
1063 it->changed = changed;
1064 if (it->generation == -1)
1065 it->generation = generationCounter;
1069 for (Iterator it = newList.begin(); it != newList.end(); ++it) {
1070 qDebug() << " NEW: " << it->iname;
1073 // overwrite existing items
1074 Iterator it = newList.begin();
1075 QModelIndex idx = watchIndex(parent);
1076 const int oldCount = parent->children.size();
1077 for (int i = 0; i < oldCount; ++i, ++it) {
1078 if (!parent->children[i]->isEqual(*it)) {
1079 qDebug() << "REPLACING" << parent->children.at(i)->iname
1080 << " WITH " << it->iname << it->generation;
1081 parent->children[i]->setData(*it);
1082 if (parent->children[i]->generation == -1)
1083 parent->children[i]->generation = generationCounter;
1084 //emit dataChanged(idx.sibling(i, 0), idx.sibling(i, 2));
1086 //qDebug() << "SKIPPING REPLACEMENT" << parent->children.at(i)->iname;
1089 emit dataChanged(idx.sibling(0, 0), idx.sibling(oldCount - 1, 2));
1092 if (oldCount < newList.size()) {
1093 beginInsertRows(index, oldCount, newList.size() - 1);
1094 //MODEL_DEBUG("INSERT : " << data.iname << data.value);
1095 for (int i = oldCount; i < newList.size(); ++i, ++it) {
1096 WatchItem *item = new WatchItem(*it);
1097 qDebug() << "ADDING" << it->iname;
1098 item->parent = parent;
1099 if (item->generation == -1)
1100 item->generation = generationCounter;
1101 item->changed = true;
1102 parent->children.append(item);
1106 //qDebug() << "ITEMS: " << parent->children.size();
1110 WatchItem *WatchModel::findItem(const QByteArray &iname, WatchItem *root) const
1112 if (root->iname == iname)
1114 for (int i = root->children.size(); --i >= 0; )
1115 if (WatchItem *item = findItem(iname, root->children.at(i)))
1120 static void debugRecursion(QDebug &d, const WatchItem *item, int depth)
1122 d << QString(2 * depth, QLatin1Char(' ')) << item->toString() << '\n';
1123 foreach (const WatchItem *child, item->children)
1124 debugRecursion(d, child, depth + 1);
1127 QDebug operator<<(QDebug d, const WatchModel &m)
1129 QDebug nospace = d.nospace();
1131 debugRecursion(nospace, m.m_root, 0);
1135 void WatchModel::formatRequests(QByteArray *out, const WatchItem *item) const
1137 int format = m_handler->m_individualFormats.value(item->iname, -1);
1139 format = m_handler->m_typeFormats.value(stripTemplate(item->type), -1);
1141 *out += item->iname + ":format=" + QByteArray::number(format) + ',';
1142 foreach (const WatchItem *child, item->children)
1143 formatRequests(out, child);
1146 ///////////////////////////////////////////////////////////////////////
1150 ///////////////////////////////////////////////////////////////////////
1152 WatchHandler::WatchHandler(DebuggerEngine *engine)
1157 m_return = new WatchModel(this, ReturnWatch);
1158 m_locals = new WatchModel(this, LocalsWatch);
1159 m_watchers = new WatchModel(this, WatchersWatch);
1160 m_tooltips = new WatchModel(this, TooltipsWatch);
1162 connect(debuggerCore()->action(ShowStdNamespace),
1163 SIGNAL(triggered()), SLOT(emitAllChanged()));
1164 connect(debuggerCore()->action(ShowQtNamespace),
1165 SIGNAL(triggered()), SLOT(emitAllChanged()));
1166 connect(debuggerCore()->action(SortStructMembers),
1167 SIGNAL(triggered()), SLOT(emitAllChanged()));
1170 void WatchHandler::beginCycle(bool fullCycle)
1173 ++generationCounter;
1174 m_return->beginCycle();
1175 m_locals->beginCycle();
1176 m_watchers->beginCycle();
1177 m_tooltips->beginCycle();
1180 void WatchHandler::endCycle(bool /*fullCycle*/)
1182 m_return->endCycle();
1183 m_locals->endCycle();
1184 m_watchers->endCycle();
1185 m_tooltips->endCycle();
1186 updateWatchersWindow();
1189 void WatchHandler::cleanup()
1191 m_expandedINames.clear();
1192 m_return->reinitialize();
1193 m_locals->reinitialize();
1194 m_tooltips->reinitialize();
1195 m_return->m_fetchTriggered.clear();
1196 m_locals->m_fetchTriggered.clear();
1197 m_watchers->m_fetchTriggered.clear();
1198 m_tooltips->m_fetchTriggered.clear();
1200 for (EditHandlers::ConstIterator it = m_editHandlers.begin();
1201 it != m_editHandlers.end(); ++it) {
1202 if (!it.value().isNull())
1205 m_editHandlers.clear();
1209 void WatchHandler::emitAllChanged()
1211 m_return->emitAllChanged();
1212 m_locals->emitAllChanged();
1213 m_watchers->emitAllChanged();
1214 m_tooltips->emitAllChanged();
1217 void WatchHandler::insertData(const WatchData &data)
1219 MODEL_DEBUG("INSERTDATA: " << data.toString());
1220 if (!data.isValid()) {
1221 qWarning("%s:%d: Attempt to insert invalid watch item: %s",
1222 __FILE__, __LINE__, qPrintable(data.toString()));
1226 if (data.isSomethingNeeded() && data.iname.contains('.')) {
1227 MODEL_DEBUG("SOMETHING NEEDED: " << data.toString());
1228 if (!m_engine->isSynchronous()) {
1229 WatchModel *model = modelForIName(data.iname);
1230 QTC_ASSERT(model, return);
1231 model->insertData(data);
1233 m_engine->updateWatchData(data);
1235 m_engine->showMessage(QLatin1String("ENDLESS LOOP: SOMETHING NEEDED: ")
1237 WatchData data1 = data;
1238 data1.setAllUnneeded();
1239 data1.setValue(QLatin1String("<unavailable synchronous data>"));
1240 data1.setHasChildren(false);
1241 WatchModel *model = modelForIName(data.iname);
1242 QTC_ASSERT(model, return);
1243 model->insertData(data1);
1246 WatchModel *model = modelForIName(data.iname);
1247 QTC_ASSERT(model, return);
1248 MODEL_DEBUG("NOTHING NEEDED: " << data.toString());
1249 model->insertData(data);
1250 showEditValue(data);
1255 void WatchHandler::insertBulkData(const QList<WatchData> &list)
1258 foreach (const WatchItem &data, list)
1265 QMap<QByteArray, QList<WatchData> > hash;
1267 foreach (const WatchData &data, list) {
1268 // we insert everything, including incomplete stuff
1269 // to reduce the number of row add operations in the model.
1270 if (data.isValid()) {
1271 hash[parentName(data.iname)].append(data);
1273 qWarning("%s:%d: Attempt to bulk-insert invalid watch item: %s",
1274 __FILE__, __LINE__, qPrintable(data.toString()));
1277 foreach (const QByteArray &parentIName, hash.keys()) {
1278 WatchModel *model = modelForIName(parentIName);
1279 QTC_ASSERT(model, return);
1280 model->insertBulkData(hash[parentIName]);
1283 foreach (const WatchData &data, list) {
1284 if (data.isSomethingNeeded())
1285 m_engine->updateWatchData(data);
1289 void WatchHandler::removeData(const QByteArray &iname)
1291 WatchModel *model = modelForIName(iname);
1294 WatchItem *item = model->findItem(iname, model->m_root);
1296 model->destroyItem(item);
1299 QByteArray WatchHandler::watcherName(const QByteArray &exp)
1301 return "watch." + QByteArray::number(m_watcherNames[exp]);
1304 void WatchHandler::watchExpression(const QString &exp)
1306 QTC_ASSERT(m_engine, return);
1307 // Do not insert the same entry more then once.
1308 if (m_watcherNames.value(exp.toLatin1()))
1311 // FIXME: 'exp' can contain illegal characters
1313 data.exp = exp.toLatin1();
1315 m_watcherNames[data.exp] = watcherCounter++;
1319 data.setAllUnneeded();
1320 data.iname = watcherName(data.exp);
1321 if (m_engine->state() == DebuggerNotReady) {
1322 data.setAllUnneeded();
1324 data.setHasChildren(false);
1326 } else if (m_engine->isSynchronous()) {
1327 m_engine->updateWatchData(data);
1331 updateWatchersWindow();
1335 static void swapEndian(char *d, int nchar)
1337 QTC_ASSERT(nchar % 4 == 0, return);
1338 for (int i = 0; i < nchar; i += 4) {
1343 d[i + 1] = d[i + 2];
1348 void WatchHandler::showEditValue(const WatchData &data)
1350 const QByteArray key = data.address ? data.hexAddress() : data.iname;
1351 QObject *w = m_editHandlers.value(key);
1352 if (data.editformat == 0x0) {
1353 m_editHandlers.remove(data.iname);
1355 } else if (data.editformat == 1 || data.editformat == 3) {
1357 QLabel *l = qobject_cast<QLabel *>(w);
1361 const QString title = data.address ?
1362 tr("%1 Object at %2").arg(QLatin1String(data.type),
1363 QLatin1String(data.hexAddress())) :
1364 tr("%1 Object at Unknown Address").arg(QLatin1String(data.type));
1365 l->setWindowTitle(title);
1366 m_editHandlers[key] = l;
1368 int width, height, format;
1371 if (data.editformat == 1) {
1372 ba = QByteArray::fromHex(data.editvalue);
1373 const int *header = (int *)(ba.data());
1374 swapEndian(ba.data(), ba.size());
1375 bits = 12 + (uchar *)(ba.data());
1379 } else { // data.editformat == 3
1380 QTextStream ts(data.editvalue);
1382 ts >> width >> height >> format >> fileName;
1384 f.open(QIODevice::ReadOnly);
1386 bits = (uchar*)ba.data();
1388 QImage im(bits, width, height, QImage::Format(format));
1391 // Qt bug. Enforce copy of image data.
1396 l->setPixmap(QPixmap::fromImage(im));
1397 l->resize(width, height);
1399 } else if (data.editformat == 2) {
1400 // Display QString in a separate widget.
1401 QTextEdit *t = qobject_cast<QTextEdit *>(w);
1405 m_editHandlers[key] = t;
1407 QByteArray ba = QByteArray::fromHex(data.editvalue);
1408 QString str = QString::fromUtf16((ushort *)ba.constData(), ba.size()/2);
1410 t->resize(400, 200);
1412 } else if (data.editformat == 4) {
1414 int pos = data.editvalue.indexOf('|');
1415 QByteArray cmd = data.editvalue.left(pos);
1416 QByteArray input = data.editvalue.mid(pos + 1);
1417 QProcess *p = qobject_cast<QProcess *>(w);
1421 p->waitForStarted();
1422 m_editHandlers[key] = p;
1424 p->write(input + '\n');
1426 QTC_ASSERT(false, qDebug() << "Display format: " << data.editformat);
1430 void WatchHandler::clearWatches()
1432 if (m_watcherNames.isEmpty())
1434 const QList<WatchItem *> watches = m_watchers->rootItem()->children;
1435 for (int i = watches.size() - 1; i >= 0; i--)
1436 m_watchers->destroyItem(watches.at(i));
1437 m_watcherNames.clear();
1439 updateWatchersWindow();
1444 void WatchHandler::removeWatchExpression(const QString &exp0)
1446 QByteArray exp = exp0.toLatin1();
1447 MODEL_DEBUG("REMOVE WATCH: " << exp);
1448 m_watcherNames.remove(exp);
1449 foreach (WatchItem *item, m_watchers->rootItem()->children) {
1450 if (item->exp == exp) {
1451 m_watchers->destroyItem(item);
1453 updateWatchersWindow();
1460 void WatchHandler::updateWatchersWindow()
1462 // Force show/hide of watchers and return view.
1463 debuggerCore()->updateWatchersWindow();
1466 QStringList WatchHandler::watchedExpressions()
1468 // Filter out invalid watchers.
1469 QStringList watcherNames;
1470 QHashIterator<QByteArray, int> it(m_watcherNames);
1471 while (it.hasNext()) {
1473 const QString &watcherName = it.key();
1474 if (!watcherName.isEmpty())
1475 watcherNames.push_back(watcherName);
1477 return watcherNames;
1480 void WatchHandler::saveWatchers()
1482 debuggerCore()->setSessionValue("Watchers", QVariant(watchedExpressions()));
1485 void WatchHandler::loadTypeFormats()
1487 QVariant value = debuggerCore()->sessionValue("DefaultFormats");
1488 QMap<QString, QVariant> typeFormats = value.toMap();
1489 QMapIterator<QString, QVariant> it(typeFormats);
1490 while (it.hasNext()) {
1492 if (!it.key().isEmpty())
1493 m_typeFormats.insert(it.key().toUtf8(), it.value().toInt());
1497 void WatchHandler::saveTypeFormats()
1499 QMap<QString, QVariant> typeFormats;
1500 QHashIterator<QByteArray, int> it(m_typeFormats);
1501 while (it.hasNext()) {
1503 const int format = it.value();
1504 if (format != DecimalFormat) {
1505 const QString key = it.key().trimmed();
1507 typeFormats.insert(key, format);
1510 debuggerCore()->setSessionValue("DefaultFormats", QVariant(typeFormats));
1513 void WatchHandler::saveSessionData()
1519 void WatchHandler::loadSessionData()
1522 m_watcherNames.clear();
1523 QVariant value = debuggerCore()->sessionValue("Watchers");
1524 foreach (WatchItem *item, m_watchers->rootItem()->children)
1525 m_watchers->destroyItem(item);
1526 foreach (const QString &exp, value.toStringList())
1527 watchExpression(exp);
1528 updateWatchersWindow();
1532 void WatchHandler::updateWatchers()
1534 foreach (WatchItem *item, m_watchers->rootItem()->children)
1535 m_watchers->destroyItem(item);
1536 // Copy over all watchers and mark all watchers as incomplete.
1537 foreach (const QByteArray &exp, m_watcherNames.keys()) {
1539 data.iname = watcherName(exp);
1540 data.setAllNeeded();
1547 WatchModel *WatchHandler::model(WatchType type) const
1550 case ReturnWatch: return m_return;
1551 case LocalsWatch: return m_locals;
1552 case WatchersWatch: return m_watchers;
1553 case TooltipsWatch: return m_tooltips;
1555 QTC_ASSERT(false, /**/);
1559 WatchModel *WatchHandler::modelForIName(const QByteArray &iname) const
1561 if (iname.startsWith("return"))
1563 if (iname.startsWith("local"))
1565 if (iname.startsWith("tooltip"))
1567 if (iname.startsWith("watch"))
1569 QTC_ASSERT(false, qDebug() << "INAME: " << iname);
1573 const WatchData *WatchHandler::watchData(WatchType type, const QModelIndex &index) const
1575 if (index.isValid())
1576 if (const WatchModel *m = model(type))
1577 return m->watchItem(index);
1581 const WatchData *WatchHandler::findItem(const QByteArray &iname) const
1583 const WatchModel *model = modelForIName(iname);
1584 QTC_ASSERT(model, return 0);
1585 return model->findItem(iname, model->m_root);
1588 QModelIndex WatchHandler::itemIndex(const QByteArray &iname) const
1590 if (const WatchModel *model = modelForIName(iname))
1591 if (WatchItem *item = model->findItem(iname, model->m_root))
1592 return model->watchIndex(item);
1593 return QModelIndex();
1596 void WatchHandler::setFormat(const QByteArray &type0, int format)
1598 const QByteArray type = stripTemplate(type0);
1600 m_typeFormats.remove(type);
1602 m_typeFormats[type] = format;
1604 m_return->emitDataChanged(1);
1605 m_locals->emitDataChanged(1);
1606 m_watchers->emitDataChanged(1);
1607 m_tooltips->emitDataChanged(1);
1610 int WatchHandler::format(const QByteArray &iname) const
1613 if (const WatchData *item = findItem(iname)) {
1614 int result = m_individualFormats.value(item->iname, -1);
1616 result = m_typeFormats.value(stripTemplate(item->type), -1);
1621 QByteArray WatchHandler::expansionRequests() const
1624 //m_locals->formatRequests(&ba, m_locals->m_root);
1625 //m_watchers->formatRequests(&ba, m_watchers->m_root);
1626 if (!m_expandedINames.isEmpty()) {
1627 QSetIterator<QByteArray> jt(m_expandedINames);
1628 while (jt.hasNext()) {
1629 QByteArray iname = jt.next();
1638 QByteArray WatchHandler::typeFormatRequests() const
1641 if (!m_typeFormats.isEmpty()) {
1642 QHashIterator<QByteArray, int> it(m_typeFormats);
1643 while (it.hasNext()) {
1645 ba.append(it.key().toHex());
1647 ba.append(QByteArray::number(it.value()));
1655 QByteArray WatchHandler::individualFormatRequests() const
1658 if (!m_individualFormats.isEmpty()) {
1659 QHashIterator<QByteArray, int> it(m_individualFormats);
1660 while (it.hasNext()) {
1662 ba.append(it.key());
1664 ba.append(QByteArray::number(it.value()));
1672 void WatchHandler::addTypeFormats(const QByteArray &type, const QStringList &formats)
1674 m_reportedTypeFormats.insert(stripTemplate(type), formats);
1677 QString WatchHandler::editorContents()
1680 showInEditorHelper(&contents, m_locals->m_root, 0);
1681 showInEditorHelper(&contents, m_watchers->m_root, 0);
1685 void WatchHandler::showInEditorHelper(QString *contents, WatchItem *item, int depth)
1687 const QChar tab = QLatin1Char('\t');
1688 const QChar nl = QLatin1Char('\n');
1689 contents->append(QString(depth, tab));
1690 contents->append(item->name);
1691 contents->append(tab);
1692 contents->append(item->value);
1693 contents->append(nl);
1694 foreach (WatchItem *child, item->children)
1695 showInEditorHelper(contents, child, depth + 1);
1698 void WatchHandler::removeTooltip()
1700 m_tooltips->reinitialize();
1701 m_tooltips->emitAllChanged();
1704 void WatchHandler::rebuildModel()
1708 const QList<WatchItem *> watches = m_watchers->rootItem()->children;
1709 for (int i = watches.size() - 1; i >= 0; i--)
1710 m_watchers->destroyItem(watches.at(i));
1712 foreach (const QString &exp, watchedExpressions()) {
1714 data.exp = exp.toLatin1();
1716 data.iname = watcherName(data.exp);
1717 data.setAllUnneeded();
1725 } // namespace Internal
1726 } // namespace Debugger