# # # patch "src/model/Manifest.cpp" # from [35394fcd74604e4fb1e21bbf5885415a17cfb4cf] # to [d53ea49f997e97d41c9200786931a057738739e4] # # patch "src/model/Manifest.h" # from [0427f245fa7314ac16fa40f8053c1894fe461fd5] # to [c58674709d7f23f8c8d372d2a367bc3acda75d33] # # patch "src/model/Workspace.cpp" # from [2ffc6109d1b6b53b8bae6d37909abf3a54e4be21] # to [815940c0c7570f256aab0477e5a9a4f4a5958cae] # # patch "src/model/Workspace.h" # from [2a69ad87ac4bab25922718f11bb5fab1a98cdff2] # to [53769cd4c4eace8e82bfae5050ed276f9297c48e] # # patch "src/monotone/Monotone.cpp" # from [bf0826043e54b20fe041910b5448ae9f4850fc14] # to [184a9729ae53896d5a441f5e514013c9f5b700ca] # # patch "src/monotone/Monotone.h" # from [b4a518819715f621bfaa7acdae73d851b47c69b6] # to [373ec2196cef5fe25dc63885232cb8290971fe90] # # patch "src/util/StanzaParser.cpp" # from [ffea7b64010ce6f0402560e00cf5558d8a1c92c6] # to [b954b8f7012e4de0939709bf38ec6d2df6fdfd5b] # # patch "src/util/StanzaParser.h" # from [bcaf0eb24633167ee9b5b033edcc1b1c6b37bef4] # to [6817ca33907934966069ead9098a0b912d7aeb01] # # patch "src/view/Guitone.cpp" # from [00d963cf85f3e884aa359622d75eee11427144f1] # to [b4f62968bf30cd6d39ee46af4cb612d2d935ca03] # # patch "src/view/Guitone.h" # from [82aaf4f24d417cdfd562b92ee5baca58aadf6ae6] # to [425fc83fd7cad82414a5ebc7f1406482e92fabff] # # patch "src/view/PropertiesView.cpp" # from [fd0b77b8fe67c926bd1aee5f8865671f2ecc8ac9] # to [95b4064e8573ad76fd410b5035fa11ab2be81c8c] # # patch "src/view/PropertiesView.h" # from [f11261520fd7029630cb6dd8940868f5b201fdb9] # to [2c0d70e2904ccdb7c4f3db5fcc0e165a8080e77e] # ============================================================ --- src/model/Manifest.cpp 35394fcd74604e4fb1e21bbf5885415a17cfb4cf +++ src/model/Manifest.cpp d53ea49f997e97d41c9200786931a057738739e4 @@ -22,11 +22,24 @@ #include "../monotone/Monotone.h" #include "../util/StanzaParser.h" -Manifest::Manifest() : revision("") {} +Manifest::Manifest(QObject *parent) + : QAbstractItemModel(parent) +{ + map = 0; + revision = ""; +} -Manifest::Manifest(QString rev) : revision(rev) {} +Manifest::Manifest(QObject *parent, QString rev) + : QAbstractItemModel(parent) +{ + map = 0; + revision = rev; +} -Manifest::~Manifest() {} +Manifest::~Manifest() +{ + delete map; +} bool Manifest::readManifest() { @@ -39,21 +52,23 @@ Monotone *mtn = Monotone::singleton(); connect( - mtn, SIGNAL(commandFinished(int)), - this, SLOT(parseOutput(int)) + mtn, SIGNAL(commandFinished(QObject*, int)), + this, SLOT(parseOutput(QObject*, int)) ); - return mtn->triggerCommand(cmd); + return mtn->triggerCommand(this, cmd); } -void Manifest::parseOutput(int returnCode) +void Manifest::parseOutput(QObject *caller, int returnCode) { - QStringList *output = Monotone::singleton()->getOutput(); + // if not we are the caller, omit the parsing + if (caller != this) return; + QString *output = Monotone::singleton()->getOutput(); + if ((returnCode > 0) && (!output->isEmpty())) { - QString error = output->front(); - qWarning("Manifest::parseOutput: A monotone error occured: %s", qPrintable(error)); + qWarning("Manifest::parseOutput: A monotone error occured: %s", qPrintable(*output)); return; } else if (returnCode > 0) @@ -62,9 +77,11 @@ return; } - StanzaParser* parser = new StanzaParser(output->join("\n")); + StanzaParser* parser = new StanzaParser(*output); StanzaList list = parser->getStanzas(); - ManifestMap* map = new ManifestMap(); + + if (map) delete map; + map = new ManifestMap(); for (int i=0, size = list.size(); i < size; ++i) { @@ -77,6 +94,7 @@ for (int j=0, size2 = stanza.size(); j < size2; j++) { StanzaEntry entry = stanza.at(j); + if (i == 0 && j == 0) { if (entry.sym != "format_version") @@ -112,7 +130,7 @@ if (entry.sym == "attr") { Q_ASSERT(entry.vals.size() == 2); - item.attr[entry.vals.at(0)] = entry.vals.at(1); + item.attr.append(ItemAttribute(entry.vals.at(0), entry.vals.at(1))); continue; } qWarning("Manifest::parseOutput(): Unknown symbol %s.", qPrintable(entry.sym)); @@ -123,5 +141,84 @@ map->insert(path, item); } - emit manifestRead(map); + emit manifestRead(); } + +void Manifest::setItemPath(QString path) +{ + curItemPath = path; + // reset the attached views since we display completly different data now + this->reset(); +} + +int Manifest::columnCount(const QModelIndex &parent) const +{ + return 2; +} + +QVariant Manifest::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) + { + return QVariant(); + } + + if (role != Qt::DisplayRole) + { + return QVariant(); + } + + if (!map) return QVariant(); + if (!map->contains(curItemPath)) return QVariant(); + + ItemAttributes attrs = map->value(curItemPath).attr; + if (index.row() >= attrs.size()) + { + return QVariant(); + } + ItemAttribute attr = attrs.at(index.row()); + switch (index.column()) + { + case 0: return QVariant(attr.first); + case 1: return QVariant(attr.second); + } + return QVariant(); +} + +Qt::ItemFlags Manifest::flags(const QModelIndex &index) const +{ + // TODO: add ItemIsEditable as soon as we can set/drop attributes through + // the automation interface, implement setData() then as well + return Qt::ItemIsEnabled | Qt::ItemIsSelectable; +} + +QVariant Manifest::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (orientation == Qt::Horizontal && role == Qt::DisplayRole) + { + switch (section) + { + case 0: return QVariant(tr("Attribute Name")); + case 1: return QVariant(tr("Attribute Value")); + } + } + + return QVariant(); +} + +int Manifest::rowCount(const QModelIndex& parent) const +{ + if (!map) return 0; + if (!map->contains(curItemPath)) return 0; + return map->value(curItemPath).attr.size(); +} + +QModelIndex Manifest::index(int row, int column, const QModelIndex& parent) const +{ + return hasIndex(row, column, parent) ? createIndex(row, column, 0) : QModelIndex(); +} + +QModelIndex Manifest::parent(const QModelIndex& index) const +{ + return QModelIndex(); +} ============================================================ --- src/model/Manifest.h 0427f245fa7314ac16fa40f8053c1894fe461fd5 +++ src/model/Manifest.h c58674709d7f23f8c8d372d2a367bc3acda75d33 @@ -21,30 +21,45 @@ #ifndef MANIFEST_H #define MANIFEST_H -typedef QMap ItemAttributes; +typedef QPair ItemAttribute; +typedef QList ItemAttributes; typedef struct { QString fileId; ItemAttributes attr; } ManifestItem; typedef QMap ManifestMap; -class Manifest : public QObject +class Manifest : public QAbstractItemModel { Q_OBJECT public: - Manifest(); - Manifest(QString); + Manifest(QObject*); + Manifest(QObject*, QString); virtual ~Manifest(); + void setItemPath(QString); + + // needed Qt Model methods + QVariant data(const QModelIndex&, int) const; + Qt::ItemFlags flags(const QModelIndex&) const; + QVariant headerData(int, Qt::Orientation, int) const; + QModelIndex index(int, int, const QModelIndex&) const; + QModelIndex parent(const QModelIndex&) const; + int rowCount(const QModelIndex&) const; + int columnCount(const QModelIndex&) const; + +public slots: bool readManifest(); signals: - void manifestRead(ManifestMap*); + void manifestRead(); private slots: - void parseOutput(int); + void parseOutput(QObject*, int); private: QString revision; + QString curItemPath; + ManifestMap *map; }; #endif ============================================================ --- src/model/Workspace.cpp 2ffc6109d1b6b53b8bae6d37909abf3a54e4be21 +++ src/model/Workspace.cpp 815940c0c7570f256aab0477e5a9a4f4a5958cae @@ -89,32 +89,33 @@ monotone = Monotone::singleton(); connect( - monotone, SIGNAL(commandFinished(int)), - this, SLOT(createModel(int)) - ); + monotone, SIGNAL(commandFinished(QObject*, int)), + this, SLOT(parseOutput(QObject*, int)) + ); // setup a new process instance with a new working directory monotone->setup(workspaceDir); - return monotone->triggerCommand("inventory"); + return monotone->triggerCommand(this, "inventory"); } -void Workspace::createModel(int returnCode) +void Workspace::parseOutput(QObject* caller, int returnCode) { + // if not we are the caller, skip processing + if (caller != this) return; + if (modelPresent) { - qWarning("Workspace::createModel: A model is already created!"); + qWarning("Workspace::parseOutput: A model is already created!"); return; } - QStringList *output = monotone->getOutput(); - + QString *output = monotone->getOutput(); + // FIXME: we should throw the monotone interface errors further // to the user or at least log them somewhere... if ((returnCode > 0) && (!output->isEmpty())) { - QString error = output->front(); - - qWarning("Workspace::parseInventory: A monotone error occured: %s", qPrintable(error)); + qWarning("Workspace::parseInventory: A monotone error occured: %s", qPrintable(*output)); // restore the normal cursor qApp->restoreOverrideCursor(); return; @@ -127,6 +128,7 @@ return; } + QStringList lines = output->split("\n", QString::SkipEmptyParts); QMap renameMap; QMap::iterator renameIter; @@ -138,7 +140,7 @@ QString path(""); bool isDirectory(false); - for (QStringList::Iterator it = output->begin(); it != output->end(); ++it) + for (QStringList::Iterator it = lines.begin(); it != lines.end(); ++it) { if (!parseInventoryLine(*it, status, from_id, to_id, path, isDirectory)) { @@ -186,7 +188,7 @@ modelPresent = true; - return; + emit modelCreated(); } QList Workspace::buildTreeRecursive(QList &items, WorkspaceItem* parentItem) @@ -444,8 +446,8 @@ void Workspace::deleteModel(void) { disconnect( - monotone, SIGNAL(commandFinished(int)), - this, SLOT(createModel(int)) + monotone, SIGNAL(commandFinished(QObject*, int)), + this, SLOT(parseOutput(QObject*, int)) ); rootItem->deleteAllChildren(); modelPresent = false; ============================================================ --- src/model/Workspace.h 2a69ad87ac4bab25922718f11bb5fab1a98cdff2 +++ src/model/Workspace.h 53769cd4c4eace8e82bfae5050ed276f9297c48e @@ -62,7 +62,10 @@ bool modelPresent; private slots: - void createModel(int); + void parseOutput(QObject*, int); + + signals: + void modelCreated(); }; #endif ============================================================ --- src/monotone/Monotone.cpp bf0826043e54b20fe041910b5448ae9f4850fc14 +++ src/monotone/Monotone.cpp 184a9729ae53896d5a441f5e514013c9f5b700ca @@ -28,7 +28,8 @@ isProcessingData = false; // true if the process is destroyed in the destructor isCleanExit = false; - output = new QStringList(); + input = new QString(); + output = new QString(); } void Monotone::setup(QDir *workingDirectory) @@ -50,7 +51,7 @@ // read & parse mtn's output as soon as it gets available connect( process, SIGNAL(readyReadStandardOutput()), - this, SLOT(parseLineFromStdout()) + this, SLOT(readAndParseStdout()) ); // monitor if the process is exited unexpectedly @@ -82,7 +83,8 @@ { isCleanExit = true; delete process; - delete output; + delete input; + delete output; process = 0; } @@ -106,7 +108,10 @@ emit criticalError(tr("The connection to the monotone process was terminated (Code %1).").arg(code)); } -bool Monotone::triggerCommand(QString cmd) +// TODO: we might want to implement a Queue for that some time so commands +// are executed sequentially in the order they've come in and don't need to +// wait for each other +bool Monotone::triggerCommand(QObject *caller, QString cmd) { // wait until the process has been started up if it is not already // running. Notice that if waitForStartup times out after 15 seconds @@ -125,8 +130,10 @@ } isProcessingData = true; - output->clear(); - + lastCaller = caller; + input->clear(); + output->clear(); + QString finalCmd; QStringList parts = cmd.split(' '); @@ -156,54 +163,79 @@ return true; } -void Monotone::parseLineFromStdout() +void Monotone::readAndParseStdout() { QByteArray byteArray; - // read all data from stdin - //byteArray = readStdout(); - byteArray = process->readAllStandardOutput(); - QString temp(byteArray); - - // splits the input into lines - QStringList inputList = temp.split("\n"); - - QString lineFromStdIn(""); - - // parse out the contents of each line - QRegExp regex("^(\\d+):(\\d+):([ml]):(\\d+):([^\\n]*)"); - for (QStringList::Iterator it = inputList.begin(); it != inputList.end(); ++it ) - { - lineFromStdIn = *it; - // FIXME: skip empty lines, they pop up otherwise, but - // I have no idea where these should come from - if (lineFromStdIn.size() == 0) continue; - - if (regex.indexIn(lineFromStdIn) == -1) + // read all data from stdout of the process and + // append it to previous output which couldn't be processed yet + input->append(process->readAllStandardOutput()); + + // parse out the contents of each line + QRegExp regex("^(\\d+):(\\d+):([ml]):(\\d+):"); + + while (true) + { + // FIXME: for some weird reason sometimes there are some extra linebreaks + // added which have to be removed otherwise the parsing will fail + //input->replace("^\\n+", ""); + // apparently we can't parse the string, this could be because not + // enough data have been flushed or the format is somehow screwed up + if (regex.indexIn(*input) == -1) { - qWarning("Monotone::parseLineFromStdout: Can't parse data %s", qPrintable(lineFromStdIn)); - continue; + qWarning("Monotone::readAndParseStdout: Can't parse data header, waiting for next flush."); + return; } - QStringList list = regex.capturedTexts(); - - // valid output, append it - if (list[5].size() > 0) + + qDebug("Monotone::readAndParseStdout: Capture: %s", qPrintable(regex.cap())); + QStringList list = regex.capturedTexts(); + + Q_ASSERT(input->length() >= regex.matchedLength()); + + // at first, throw away the data header + *input = input->right(input->length() - regex.matchedLength()); + + // now determine how many bytes of real output we have + int outBytes = list[4].toInt(); + + qDebug("Monotone::readAndParseStdout: Should read %d Bytes, can read %d Bytes", outBytes, input->length()); + + // check if we already got all output, if not, do the processing + // when subsequent data are flushed and processed here + if (input->length() < outBytes) + { + // prepend the data header again + input->prepend(QString("%1:%2:%3:%4:") + .arg(list[0]) + .arg(list[1]) + .arg(list[2]) + .arg(list[3]) + ); + qDebug("Monotone::readAndParseStdout: Didn't get enough data, waiting for next flush."); + return; + } + + // add the byte amount to the output string + output->append(input->left(outBytes)); + // and remove it from the input string + *input = input->right(input->length() - outBytes); + + // check if this was the last output + if (list[3].compare("l") == 0) { - output->append(list[5]); + Q_ASSERT(input->length() == 0); + isProcessingData = false; + emit commandFinished(lastCaller, list[2].toInt()); + return; } - // last output? then this contains just the status, - // and no additional information - if (list[3].compare("l") == 0) - { - isProcessingData = false; - emit commandFinished(list[2].toInt()); - break; - } - } + // if this was not the last output, but there are no contents left, + // we need to wait for further data + if (input->size() == 0) + { + qDebug("Monotone::readAndParseStdout: Command not finished yet, waiting for next flush."); + return; + } + } } -QStringList* Monotone::getOutput() -{ - return output; -} ============================================================ --- src/monotone/Monotone.h b4a518819715f621bfaa7acdae73d851b47c69b6 +++ src/monotone/Monotone.h 373ec2196cef5fe25dc63885232cb8290971fe90 @@ -36,26 +36,28 @@ void setup(QDir*); virtual ~Monotone(); - bool triggerCommand(QString cmd); - QStringList* getOutput(); + bool triggerCommand(QObject*, QString); + inline QString* getOutput() { return output; } protected: Monotone(QObject *); private: - QStringList *output; + QString *input; + QString *output; + QObject* lastCaller; bool isProcessingData; bool isCleanExit; static Monotone* instance; QProcess * process; private slots: - void parseLineFromStdout(); + void readAndParseStdout(); void processTerminated(int, QProcess::ExitStatus); void startupError(QProcess::ProcessError); signals: - void commandFinished(int returnCode); + void commandFinished(QObject*, int); void criticalError(const QString &); }; ============================================================ --- src/util/StanzaParser.cpp ffea7b64010ce6f0402560e00cf5558d8a1c92c6 +++ src/util/StanzaParser.cpp b954b8f7012e4de0939709bf38ec6d2df6fdfd5b @@ -23,7 +23,7 @@ StanzaParser::StanzaParser(const QString input) { in = input; - size = in.size(); + size = in.length(); charPos = 0; } @@ -37,6 +37,7 @@ Stanza stanza = getStanza(); if (stanza.empty()) break; list.append(stanza); + advance(); } return list; } @@ -57,32 +58,37 @@ qWarning("StanzaParser::getStanza(): %d: Couldn't get symbol.", charPos); } QString hash(getHash()); - // not a hash? - if (hash.size() == 0) + // was this a hash? + if (!hash.isNull()) { + // for now we do not make a distinction between normal hashes + // and content value lists + entry.vals.append(hash); + } + else + { //grab all string values and put them into a list while (true) { // get value returns an empty string if there are no // more opening quotes QString value(getValue()); - if (value.size() == 0) break; + if (value.isNull()) break; entry.vals.append(value); } } - stanza.append(entry); eatSpaces(); QChar cur = getNext(); if (cur != QChar('\n')) - qWarning("StanzaParser::getStanza(): %d: expected '\\n', got %c", charPos, cur.toLatin1()); + qWarning("StanzaParser::getStanza(): %d: expected '\\n', got '%c'", charPos, cur.toLatin1()); } return stanza; } -QChar StanzaParser::whatsNext(int count) +QChar StanzaParser::whatsNext(int count /* = 0 */) { if (charPos + count >= size) return QChar('\0'); return in.at(charPos + count); @@ -109,7 +115,7 @@ if (whatsNext() != QChar('"')) return QString(); advance(); - QString ret; + QString ret(""); QChar last('\0'); while (true) { @@ -132,14 +138,14 @@ if (whatsNext() == QChar(']')) { advance(); - return QString(); + return QString(""); } int hashLen = 40; QString ret = in.mid(charPos, hashLen); advance(hashLen); - QChar ch = getNext(); + QChar ch = whatsNext(); if (ch == QChar(']')) advance(); else @@ -148,8 +154,9 @@ return ret; } -void StanzaParser::advance(int count) +void StanzaParser::advance(int count /* = 1 */) { + if (charPos + count >= size) return; charPos += count; } ============================================================ --- src/util/StanzaParser.h bcaf0eb24633167ee9b5b033edcc1b1c6b37bef4 +++ src/util/StanzaParser.h 6817ca33907934966069ead9098a0b912d7aeb01 @@ -37,7 +37,7 @@ ~StanzaParser(); StanzaList getStanzas(); private: - QChar whatsNext(int count = 1); + QChar whatsNext(int count = 0); QChar getNext(); Stanza getStanza(); void eatSpaces(); ============================================================ --- src/view/Guitone.cpp 00d963cf85f3e884aa359622d75eee11427144f1 +++ src/view/Guitone.cpp b4f62968bf30cd6d39ee46af4cb612d2d935ca03 @@ -21,6 +21,7 @@ #include "Guitone.h" #include "../monotone/Monotone.h" #include "../model/Workspace.h" +#include "../model/Manifest.h" #include "../model/ProxyModel.h" #include "../view/WorkspaceView.h" #include "../view/PropertiesView.h" @@ -34,11 +35,16 @@ // create Workspace model myWorkspace = new Workspace(this); - - // create Properties model - //myProperties = new Properties(this); - - // connect to Monotone to catch critical errors + + // create Manifest model and connect it with the workspace model + // the manifest is read in after the inventory is read + myManifest = new Manifest(this); + connect( + myWorkspace, SIGNAL(modelCreated()), + myManifest, SLOT(readManifest()) + ); + + // connect to Monotone to catch critical errors connect( Monotone::singleton(this), SIGNAL(criticalError(const QString &)), this, SLOT(criticalMtnError(const QString &)) @@ -124,9 +130,18 @@ this, SLOT(slotMapFileListToFolderTree(const QModelIndex &)) ); - propView = new PropertiesView(listSplitter); - //propView->setModel(propModel); + propView = new PropertiesView(listSplitter); + propView->setModel(myManifest); + // each time an item is clicked in the list we notify the properties view/model + // FIXME: do we really need to put the slot in this class? + // currently we do, since we can't map the proxy model index to a real model + // index / model in the PropertiesView itself + connect( + listView, SIGNAL(clicked(const QModelIndex &)), + this, SLOT(slotReloadProperties(const QModelIndex &)) + ); + setCentralWidget(mainSplitter); resize(Settings::getStartupSize()); @@ -142,14 +157,23 @@ Guitone::~Guitone() {} -void Guitone::slotMapFolderTreeToFileList( const QModelIndex &proxyIndex ) +void Guitone::slotReloadProperties(const QModelIndex &proxyFileIndex) { - QModelIndex index = proxyModelFolderTree->mapToSource( proxyIndex ); - index = proxyModelFileList->mapFromSource( index ); + QModelIndex workspaceIndex = proxyModelFileList->mapToSource(proxyFileIndex); + WorkspaceItem *item = static_cast(workspaceIndex.internalPointer()); + + // FIXME: if you find a better way to connect both models, we're open =) + myManifest->setItemPath(item->getPath()); +} + +void Guitone::slotMapFolderTreeToFileList(const QModelIndex &proxyIndex) +{ + QModelIndex index = proxyModelFolderTree->mapToSource(proxyIndex); + index = proxyModelFileList->mapFromSource(index); listView->setRootIndex(index); } -void Guitone::slotMapFileListToFolderTree( const QModelIndex &proxyFileIndex ) +void Guitone::slotMapFileListToFolderTree(const QModelIndex &proxyFileIndex) { // get the model index of the Workspace model QModelIndex workspaceIndex = proxyModelFileList->mapToSource(proxyFileIndex); ============================================================ --- src/view/Guitone.h 82aaf4f24d417cdfd562b92ee5baca58aadf6ae6 +++ src/view/Guitone.h 425fc83fd7cad82414a5ebc7f1406482e92fabff @@ -27,6 +27,7 @@ class QMainWindow; class Workspace; +class Manifest; class ProxyModel; class WorkspaceView; class PropertiesView; @@ -42,13 +43,13 @@ void chooseWorkspace(); void openRecentWorkspace(); void criticalMtnError(const QString &); - //void doFindAndSelectItem( WorkspaceItem* ); - void slotMapFolderTreeToFileList( const QModelIndex &proxyIndex ); - void slotMapFileListToFolderTree( const QModelIndex &proxyIndex ); + void slotReloadProperties(const QModelIndex &); + void slotMapFolderTreeToFileList(const QModelIndex &); + void slotMapFileListToFolderTree(const QModelIndex &); void showHideIgnoredFiles(); private: - void closeEvent(QCloseEvent *event); + void closeEvent(QCloseEvent *); void loadWorkspace(QString); void updatePreviousWorkspacesMenu(); @@ -57,6 +58,7 @@ QAction *actShowHideIgnored; QToolBar *toolBar; Workspace *myWorkspace; + Manifest *myManifest; ProxyModel *proxyModelFolderTree; ProxyModel *proxyModelFileList; WorkspaceView *treeView; ============================================================ --- src/view/PropertiesView.cpp fd0b77b8fe67c926bd1aee5f8865671f2ecc8ac9 +++ src/view/PropertiesView.cpp 95b4064e8573ad76fd410b5035fa11ab2be81c8c @@ -23,7 +23,10 @@ #include "../util/Settings.h" PropertiesView::PropertiesView(QWidget* parent) -: QListView(parent) -{} +: QTreeView(parent) +{ + setRootIsDecorated(false); + setItemsExpandable(false); +} PropertiesView::~PropertiesView() {} ============================================================ --- src/view/PropertiesView.h f11261520fd7029630cb6dd8940868f5b201fdb9 +++ src/view/PropertiesView.h 2c0d70e2904ccdb7c4f3db5fcc0e165a8080e77e @@ -21,7 +21,7 @@ #ifndef PROPERTIES_VIEW_H #define PROPERTIES_VIEW_H -class PropertiesView : public QListView +class PropertiesView : public QTreeView { Q_OBJECT