summaryrefslogtreecommitdiff
path: root/src/plugins/debugger/watchhandler.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/debugger/watchhandler.cpp')
-rw-r--r--src/plugins/debugger/watchhandler.cpp1137
1 files changed, 1137 insertions, 0 deletions
diff --git a/src/plugins/debugger/watchhandler.cpp b/src/plugins/debugger/watchhandler.cpp
new file mode 100644
index 0000000000..9147aa308e
--- /dev/null
+++ b/src/plugins/debugger/watchhandler.cpp
@@ -0,0 +1,1137 @@
+/***************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Qt Software Information (qt-info@nokia.com)
+**
+**
+** Non-Open Source Usage
+**
+** Licensees may use this file in accordance with the Qt Beta Version
+** License Agreement, Agreement version 2.2 provided with the Software or,
+** alternatively, in accordance with the terms contained in a written
+** agreement between you and Nokia.
+**
+** GNU General Public License Usage
+**
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License versions 2.0 or 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the packaging
+** of this file. Please review the following information to ensure GNU
+** General Public Licensing requirements will be met:
+**
+** http://www.fsf.org/licensing/licenses/info/GPLv2.html and
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt GPL Exception version
+** 1.2, included in the file GPL_EXCEPTION.txt in this package.
+**
+***************************************************************************/
+#include "watchhandler.h"
+
+#if USE_MODEL_TEST
+#include "modeltest.h"
+#endif
+
+#include "assert.h"
+
+#include <QtCore/QDebug>
+#include <QtCore/QEvent>
+
+#include <QtGui/QApplication>
+#include <QtGui/QLabel>
+#include <QtGui/QToolTip>
+#include <QtGui/QTextEdit>
+
+#include <ctype.h>
+
+// 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("<not in scope>");
+
+static bool isIntOrFloatType(const QString &type)
+{
+ static const QStringList types = QStringList()
+ << "char" << "int" << "short" << "float" << "double" << "long"
+ << "bool" << "signed char" << "unsigned" << "unsigned char"
+ << "unsigned int" << "unsigned long" << "long long";
+ return types.contains(type);
+}
+
+static bool isPointerType(const QString &type)
+{
+ return type.endsWith("*") || type.endsWith("* const");
+}
+
+static QString htmlQuote(const QString &str0)
+{
+ QString str = str0;
+ str.replace('&', "&amp;");
+ str.replace('<', "&lt;");
+ str.replace('>', "&gt;");
+ return str;
+}
+
+////////////////////////////////////////////////////////////////////
+//
+// WatchData
+//
+////////////////////////////////////////////////////////////////////
+
+WatchData::WatchData()
+{
+ valuedisabled = false;
+ state = InitialState;
+ childCount = -1;
+ parentIndex = -1;
+ row = -1;
+ level = -1;
+ changed = false;
+}
+
+void WatchData::setError(const QString &msg)
+{
+ setAllUnneeded();
+ value = msg;
+ setChildCount(0);
+ valuedisabled = true;
+}
+
+static QByteArray quoteUnprintable(const QByteArray &ba)
+{
+ QByteArray res;
+ char buf[10];
+ for (int i = 0, n = ba.size(); i != n; ++i) {
+ char c = ba.at(i);
+ if (isprint(c)) {
+ res += c;
+ } else {
+ qsnprintf(buf, sizeof(buf) - 1, "\\%x", int(c));
+ res += buf;
+ }
+ }
+ return res;
+}
+
+void WatchData::setValue(const QByteArray &value0)
+{
+ value = quoteUnprintable(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 != "<null>");
+
+ // 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("const"))
+ type.chop(5);
+ else if (type.endsWith(" "))
+ type.chop(1);
+ else if (type.endsWith("&"))
+ type.chop(1);
+ else if (type.startsWith("const "))
+ type = type.mid(6);
+ else if (type.startsWith("volatile "))
+ type = type.mid(9);
+ else if (type.startsWith("class "))
+ type = type.mid(6);
+ else if (type.startsWith("struct "))
+ type = type.mid(6);
+ else if (type.startsWith(" "))
+ type = type.mid(1);
+ else
+ changed = false;
+ }
+ setTypeUnneeded();
+ if (isIntOrFloatType(type))
+ setChildCount(0);
+}
+
+void WatchData::setAddress(const QString & str)
+{
+ addr = str;
+}
+
+QString WatchData::toString() const
+{
+ QString res = "{";
+
+ res += "level=\"" + QString::number(level) + "\",";
+ res += "parent=\"" + QString::number(parentIndex) + "\",";
+ res += "row=\"" + QString::number(row) + "\",";
+ res += "child=\"";
+ foreach (int index, childIndex)
+ res += QString::number(index) + ",";
+ if (res.endsWith(','))
+ res[res.size() - 1] = '"';
+ else
+ res += '"';
+ res += ",";
+
+
+ if (!iname.isEmpty())
+ res += "iname=\"" + iname + "\",";
+ if (!exp.isEmpty())
+ res += "exp=\"" + exp + "\",";
+
+ if (!variable.isEmpty())
+ res += "variable=\"" + variable + "\",";
+
+ if (isValueNeeded())
+ res += "value=<needed>,";
+ if (isValueKnown() && !value.isEmpty())
+ res += "value=\"" + value + "\",";
+
+ if (!editvalue.isEmpty())
+ res += "editvalue=\"" + editvalue + "\",";
+
+ if (isTypeNeeded())
+ res += "type=<needed>,";
+ if (isTypeKnown() && !type.isEmpty())
+ res += "type=\"" + type + "\",";
+
+ if (isChildCountNeeded())
+ res += "numchild=<needed>,";
+ if (isChildCountKnown() && childCount == -1)
+ res += "numchild=\"" + QString::number(childCount) + "\",";
+
+ if (isChildrenNeeded())
+ res += "children=<needed>,";
+
+ if (res.endsWith(','))
+ res[res.size() - 1] = '}';
+ else
+ res += '}';
+
+ return res;
+}
+
+
+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) {
+ // This formerly used inames. in this case 'lastIndexOf' probably
+ // makes more sense.
+ if (name1.startsWith('[') && name2.startsWith('[')) {
+ return name1.mid(1, name1.indexOf(']') - 1).toInt()
+ < name2.mid(1, name2.indexOf(']') - 1).toInt();
+ // numbers should be sorted according to their numerical value
+ //int pos = d1.name.lastIndexOf('.');
+ //if (pos != -1 && pos + 1 != d1.name.size() && d1.name.at(pos + 1).isDigit())
+ // return d1.name.size() < d2.name.size();
+ // fall through
+ }
+ return name1 < name2;
+ }
+ }
+ return false;
+}
+
+static QString parentName(const QString &iname)
+{
+ int pos = iname.lastIndexOf(".");
+ if (pos == -1)
+ return QString();
+ return iname.left(pos);
+}
+
+
+static void insertDataHelper(QList<WatchData> &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<WatchData> *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();
+}
+
+
+///////////////////////////////////////////////////////////////////////
+//
+// WatchHandler
+//
+///////////////////////////////////////////////////////////////////////
+
+WatchHandler::WatchHandler()
+{
+ m_expandPointers = true;
+ m_inFetchMore = false;
+ m_inChange = false;
+
+ cleanModel();
+ m_displaySet = m_completeSet;
+}
+
+bool WatchHandler::setData(const QModelIndex &idx,
+ const QVariant &value, int role)
+{
+/*
+ Q_UNUSED(idx);
+ Q_UNUSED(value);
+ Q_UNUSED(role);
+ if (role == VisualRole) {
+ QString iname = inameFromIndex(index);
+ setDisplayedIName(iname, value.toBool());
+ return true;
+ }
+ return true;
+*/
+ return QAbstractItemModel::setData(idx, value, role);
+}
+
+static QString niceType(QString type)
+{
+ if (type.contains("std::")) {
+ static QRegExp re("std::vector<(.*)\\s*,std::allocator<(.*)>\\s*>");
+ re.setMinimal(true);
+
+ type.replace("std::basic_string<char, std::char_traits<char>, "
+ "std::allocator<char> >", "std::string");
+ type.replace("std::basic_string<wchar_t, std::char_traits<wchar_t>, "
+ "std::allocator<wchar_t> >", "std::wstring");
+
+ for (int i = 0; i != 10; ++i) {
+ if (re.indexIn(type) == -1 || re.cap(1) != re.cap(2))
+ break;
+ type.replace(re.cap(0), "std::vector<" + re.cap(1) + ">");
+ }
+
+ type.replace(" >", ">");
+ }
+ return type;
+}
+
+QVariant WatchHandler::data(const QModelIndex &idx, int role) const
+{
+ int node = idx.internalId();
+ if (node < 0)
+ 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) + " ... <cut off>";
+
+ QString tt = "<table>";
+ //tt += "<tr><td>internal name</td><td> : </td><td>";
+ //tt += htmlQuote(iname) + "</td></tr>";
+ tt += "<tr><td>expression</td><td> : </td><td>";
+ tt += htmlQuote(data.exp) + "</td></tr>";
+ tt += "<tr><td>type</td><td> : </td><td>";
+ tt += htmlQuote(data.type) + "</td></tr>";
+ //if (!valuetooltip.isEmpty())
+ // tt += valuetooltip;
+ //else
+ tt += "<tr><td>value</td><td> : </td><td>";
+ tt += htmlQuote(data.value) + "</td></tr>";
+ tt += "<tr><td>addr</td><td> : </td><td>";
+ tt += htmlQuote(data.addr) + "</td></tr>";
+ tt += "<tr><td>iname</td><td> : </td><td>";
+ tt += htmlQuote(data.iname) + "</td></tr>";
+ tt += "</table>";
+ tt.replace("@value@", htmlQuote(data.value));
+
+ if (tt.size() > 10000)
+ tt = tt.left(10000) + " ... <cut off>";
+ 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 INameRole:
+ return data.iname;
+
+ case VisualRole:
+ return m_displayedINames.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 DefaultNotEditable =
+ ItemIsSelectable
+ | ItemIsDragEnabled
+ | ItemIsDropEnabled
+ // | ItemIsUserCheckable
+ // | ItemIsTristate
+ | ItemIsEnabled;
+
+ static const ItemFlags DefaultEditable =
+ DefaultNotEditable | ItemIsEditable;
+
+ const WatchData &data = m_displaySet.at(node);
+ return idx.column() == 1 &&
+ data.isWatcher() ? DefaultEditable : DefaultNotEditable;
+}
+
+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") + " ";
+ case 1: return tr("Value") + " ";
+ case 2: return tr("Type") + " ";
+ }
+ }
+ return QVariant();
+}
+
+QString WatchHandler::toString() const
+{
+ QString res;
+ res += "\nIncomplete:\n";
+ for (int i = 0, n = m_incompleteSet.size(); i != n; ++i) {
+ res += QString("%1: ").arg(i);
+ res += m_incompleteSet.at(i).toString();
+ res += '\n';
+ }
+ res += "\nComplete:\n";
+ for (int i = 0, n = m_completeSet.size(); i != n; ++i) {
+ res += QString("%1: ").arg(i);
+ res += m_completeSet.at(i).toString();
+ res += '\n';
+ }
+ res += "\nDisplay:\n";
+ for (int i = 0, n = m_displaySet.size(); i != n; ++i) {
+ res += QString("%1: ").arg(i);
+ res += m_displaySet.at(i).toString();
+ res += '\n';
+ }
+#if 0
+ res += "\nOld:\n";
+ for (int i = 0, n = m_oldSet.size(); i != n; ++i) {
+ res += m_oldSet.at(i).toString();
+ res += '\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<WatchData> WatchHandler::takeCurrentIncompletes()
+{
+ QList<WatchData> 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<QString, QString> oldValues;
+ for (int i = 0, n = m_oldSet.size(); i != n; ++i) {
+ WatchData &data = m_oldSet[i];
+ oldValues[data.iname] = data.value;
+ }
+ #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);
+
+ QHash<QString, int> iname2idx;
+
+ for (int i = m_completeSet.size(); --i > 0; ) {
+ WatchData &data = m_completeSet[i];
+ data.parentIndex = 0;
+ data.childIndex.clear();
+ iname2idx[data.iname] = i;
+ }
+
+ 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();
+
+ 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 + ".dummy";
+ //dummy.name = data.iname + ".dummy";
+ //dummy.name = "<loading>";
+ 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;
+ QWB_ASSERT(m_displaySet.size() >= 2, ok = false);
+ QWB_ASSERT(m_displaySet.at(1).iname == "local", ok = false);
+ QWB_ASSERT(m_displaySet.at(2).iname == "tooltip", ok = false);
+ QWB_ASSERT(m_displaySet.at(3).iname == "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 = "local.dummy";
+ dummy.name = "<No Locals>";
+ } else if (i == 2) {
+ dummy.iname = "tooltip.dummy";
+ dummy.name = "<No Tooltip>";
+ } else {
+ dummy.iname = "watch.dummy";
+ dummy.name = "<No Watchers>";
+ }
+ 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();
+ //qDebug() << "WATCHHANDLER: RESET EMITTED";
+ m_inChange = false;
+ //emit layoutChanged();
+ //QSet<QString> einames = m_expandedINames;
+ //einames.insert("local");
+ //einames.insert("watch");
+ //emit expandedItems(einames);
+
+ #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();
+ cleanModel();
+ 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(const QModelIndex &idx)
+{
+ if (m_inChange || m_completeSet.isEmpty()) {
+ //qDebug() << "WATCHHANDLER: COLLAPSE IGNORED" << idx;
+ return;
+ }
+ QWB_ASSERT(checkIndex(idx.internalId()), return);
+#if 0
+ QString iname0 = m_displaySet.at(idx.internalId()).iname;
+ MODEL_DEBUG("COLLAPSE NODE" << iname0);
+ 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);
+ }
+ }
+ m_expandedINames.remove(iname0);
+ //MODEL_DEBUG(toString());
+ //rebuildModel();
+#endif
+}
+
+void WatchHandler::expandChildren(const QModelIndex &idx)
+{
+ if (m_inChange || m_completeSet.isEmpty()) {
+ //qDebug() << "WATCHHANDLER: EXPAND IGNORED" << idx;
+ return;
+ }
+ int index = idx.internalId();
+ if (index == 0)
+ return;
+ QWB_ASSERT(index >= 0, qDebug() << toString() << index; return);
+ QWB_ASSERT(index < m_completeSet.size(), qDebug() << toString() << index; return);
+ const WatchData &display = m_displaySet.at(index);
+ QWB_ASSERT(index >= 0, qDebug() << toString() << index; return);
+ QWB_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"
+ << idx;
+ //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());
+ QWB_ASSERT(data.isValid(), return);
+ if (data.isSomethingNeeded())
+ insertDataHelper(m_incompleteSet, data);
+ else
+ insertDataHelper(m_completeSet, data);
+ //MODEL_DEBUG("INSERT RESULT" << 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 = "watch." + exp;
+ insertData(data);
+}
+
+
+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 == "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<QImage>();
+ if (QLabel *l = qobject_cast<QLabel *>(w))
+ l->setPixmap(QPixmap::fromImage(im));
+ } else if (data.type == "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<QPixmap>();
+ if (QLabel *l = qobject_cast<QLabel *>(w))
+ l->setPixmap(im);
+ } else if (data.type == "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<QString>();
+#else
+ MODEL_DEBUG("DATA: " << ba);
+ QString str = QString::fromUtf16((ushort *)ba.constData(), ba.size()/2);
+#endif
+ if (QTextEdit *t = qobject_cast<QTextEdit *>(w))
+ t->setText(str);
+ }
+ if (w)
+ w->show();
+}
+
+void WatchHandler::removeWatchExpression(const QString &iname)
+{
+ MODEL_DEBUG("REMOVE WATCH: " << iname);
+ (void) takeData(iname);
+ emit watchModelUpdateRequested();
+}
+
+void WatchHandler::cleanModel()
+{
+ // This uses data stored in m_oldSet to re-create a new set
+ // one-by-one
+ m_completeSet.clear();
+ m_incompleteSet.clear();
+
+ WatchData root;
+ root.state = 0;
+ root.level = 0;
+ root.row = 0;
+ root.name = "Root";
+ root.parentIndex = -1;
+ root.childIndex.append(1);
+ root.childIndex.append(2);
+ root.childIndex.append(3);
+ m_completeSet.append(root);
+
+ WatchData local;
+ local.iname = "local";
+ local.name = "Locals";
+ local.state = 0;
+ local.level = 1;
+ local.row = 0;
+ local.parentIndex = 0;
+ m_completeSet.append(local);
+
+ WatchData tooltip;
+ tooltip.iname = "tooltip";
+ tooltip.name = "Tooltip";
+ tooltip.state = 0;
+ tooltip.level = 1;
+ tooltip.row = 1;
+ tooltip.parentIndex = 0;
+ m_completeSet.append(tooltip);
+
+ WatchData watch;
+ watch.iname = "watch";
+ watch.name = "Watchers";
+ watch.state = 0;
+ watch.level = 1;
+ watch.row = 2;
+ watch.parentIndex = 0;
+ m_completeSet.append(watch);
+}
+
+
+void WatchHandler::reinitializeWatchers()
+{
+ cleanModel();
+
+ // copy over all watchers and mark all watchers as incomplete
+ for (int i = 0, n = m_oldSet.size(); i < n; ++i) {
+ WatchData data = m_oldSet.at(i);
+ if (data.isWatcher()) {
+ data.level = -1;
+ data.row = -1;
+ data.parentIndex = -1;
+ data.variable.clear();
+ data.setAllNeeded();
+ data.valuedisabled = false;
+ insertData(data); // properly handles "neededChildren"
+ }
+ }
+}
+
+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;
+ QWB_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;
+
+ QWB_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();
+ }
+ QWB_ASSERT(checkIndex(parentIndex), return QModelIndex());
+ const WatchData &data = m_displaySet.at(parentIndex);
+ QWB_ASSERT(row >= 0, qDebug() << "ROW: " << row << "PARENT: " << parent
+ << data.toString() << toString(); return QModelIndex());
+ QWB_ASSERT(row < data.childIndex.size(),
+ MODEL_DEBUG("ROW: " << row << data.toString() << toString());
+ return QModelIndex());
+ QModelIndex idx = createIndex(row, col, data.childIndex.at(row));
+ QWB_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();
+ QWB_ASSERT(checkIndex(currentIndex), return QModelIndex());
+ QWB_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();
+ }
+ QWB_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();
+ QWB_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) ");
+ QWB_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;
+ QWB_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;
+}