Make full screen switching more robust. Add in/out traces.
authorAkos Polster <polster@marzipan.pipacs.com>
Fri, 30 Jul 2010 20:24:40 +0000 (22:24 +0200)
committerAkos Polster <polster@marzipan.pipacs.com>
Fri, 30 Jul 2010 20:24:40 +0000 (22:24 +0200)
13 files changed:
book.cpp
bookview.cpp
bookview.h
dorian.pro
fullscreenwindow.cpp
fullscreenwindow.h
library.cpp
mainwindow.cpp
mainwindow.h
pkg/changelog
pkg/version.txt
trace.cpp [new file with mode: 0644]
trace.h [new file with mode: 0644]

index 52727ac..f5109f4 100644 (file)
--- a/book.cpp
+++ b/book.cpp
@@ -14,6 +14,7 @@
 #include "library.h"
 #include "containerhandler.h"
 #include "ncxhandler.h"
+#include "trace.h"
 
 Book::Book()
 {
@@ -37,7 +38,8 @@ QString Book::path() const
 
 void Book::open()
 {
-    qDebug() << "Book::open" << path();
+    Trace t("Book::open");
+    t.trace(path());
     close();
     clear();
     if (path() == "") {
@@ -57,7 +59,7 @@ void Book::open()
 
 void Book::close()
 {
-    qDebug() << "Book::close";
+    Trace t("Book::close");
     content.clear();
     toc.clear();
     QDir::setCurrent(QDir::rootPath());
@@ -83,9 +85,10 @@ void Book::fail(const QString &details, const QString &error)
 
 bool Book::extract()
 {
+    Trace t("Book::extract");
     bool ret = false;
     QString tmp = tmpDir();
-    qDebug() << "Book::extract: Extracting" << mPath << "to" << tmp;
+    t.trace("Extracting " + mPath + " to " + tmp);
 
     QDir::setCurrent(QDir::rootPath());
     if (!clearDir(tmp)) {
@@ -126,11 +129,11 @@ bool Book::extract()
 
 bool Book::parse()
 {
-    qDebug() << "Book::parse";
+    Trace t("Book::parse");
 
     bool ret = false;
     QString opsFileName = opsPath();
-    qDebug() << " Parsing OPS file" << opsFileName;
+    t.trace("Parsing OPS file" + opsFileName);
     QFile opsFile(opsFileName);
     QXmlSimpleReader reader;
     QXmlInputSource *source = new QXmlInputSource(&opsFile);
@@ -147,7 +150,7 @@ bool Book::parse()
     // contents
     if (content.contains("ncx")) {
         QString ncxFileName = content["ncx"].href;
-        qDebug() << " Parsing NCX file" << ncxFileName;
+        t.trace("Parsing NCX file " + ncxFileName);
         QFile ncxFile(ncxFileName);
         source = new QXmlInputSource(&ncxFile);
         NcxHandler *ncxHandler = new NcxHandler(*this);
@@ -208,14 +211,15 @@ void Book::clear()
 
 void Book::load()
 {
-    qDebug() << "Book::load" << path();
+    Trace t("Book::load");
+    t.trace("path: " + path());
     QSettings settings;
     QString key = "book/" + path() + "/";
-    qDebug() << " key" << key;
+    t.trace("key: " + key);
 
     // Load book info
     title = settings.value(key + "title").toString();
-    qDebug() << " title" << title;
+    t.trace(title);
     creators = settings.value(key + "creators").toStringList();
     date = settings.value(key + "date").toString();
     publisher = settings.value(key + "publisher").toString();
@@ -233,21 +237,22 @@ void Book::load()
                                      "/chapter").toInt();
         qreal pos = settings.value(key + "bookmark" + QString::number(i) +
                                    "/pos").toReal();
-        qDebug() << " Bookmark" << i << "at" << chapter << "," << pos;
+        t.trace(QString("Bookmark %1 at chapter %2, %3").
+                arg(i).arg(chapter).arg(pos));
         mBookmarks.append(Bookmark(chapter, pos));
     }
 }
 
 void Book::save()
 {
-    qDebug() << "Book::save";
+    Trace t("Book::save");
     QSettings settings;
     QString key = "book/" + path() + "/";
-    qDebug() << " key" << key;
+    t.trace("key: " + key);
 
     // Save book info
     settings.setValue(key + "title", title);
-    qDebug() << " title" << title;
+    t.trace("title: " + title);
     settings.setValue(key + "creators", creators);
     settings.setValue(key + "date", date);
     settings.setValue(key + "publisher", publisher);
@@ -261,8 +266,8 @@ void Book::save()
     // Save bookmarks
     settings.setValue(key + "bookmarks", mBookmarks.size());
     for (int i = 0; i < mBookmarks.size(); i++) {
-        qDebug() << " Bookmark" << i << "at" << mBookmarks[i].chapter << ","
-                << mBookmarks[i].pos;
+        t.trace(QString("Bookmark %1 at %2, %3").
+                arg(i).arg(mBookmarks[i].chapter).arg(mBookmarks[i].pos));
         settings.setValue(key + "bookmark" + QString::number(i) + "/chapter",
                           mBookmarks[i].chapter);
         settings.setValue(key + "bookmark" + QString::number(i) + "/pos",
@@ -302,10 +307,11 @@ QList<Book::Bookmark> Book::bookmarks() const
 
 QString Book::opsPath()
 {
+    Trace t("Book::opsPath");
     QString ret;
 
     QFile container(tmpDir() + "/META-INF/container.xml");
-    qDebug() << "Book::opsPath" << container.fileName();
+    t.trace(container.fileName());
     QXmlSimpleReader reader;
     QXmlInputSource *source = new QXmlInputSource(&container);
     ContainerHandler *containerHandler = new ContainerHandler();
@@ -315,8 +321,8 @@ QString Book::opsPath()
     if (reader.parse(source)) {
         ret = tmpDir() + "/" + containerHandler->rootFile;
         mRootPath = QFileInfo(ret).absoluteDir().absolutePath();
-        qDebug() << " OSP path" << ret;
-        qDebug() << " Root dir" << mRootPath;
+        t.trace("OSP path: " + ret);
+        t.trace("Root dir: " + mRootPath);
     }
     delete errorHandler;
     delete containerHandler;
index cea29f2..25a18b2 100644 (file)
@@ -3,12 +3,14 @@
 #include <QMouseEvent>
 #include <QFile>
 #include <QDir>
+#include <QTimer>
 
 #include "book.h"
 #include "bookview.h"
 #include "library.h"
 #include "selectionsuppressor.h"
 #include "settings.h"
+#include "trace.h"
 
 #ifdef Q_WS_MAC
 #   define ICON_PREFIX ":/icons/mac/"
 #endif
 
 BookView::BookView(QWidget *parent):
-    QWebView(parent), contentIndex(-1), mBook(0),
-    restore(true), restorePos(0), loadFinished(false)
+    QWebView(parent), contentIndex(-1), mBook(0), restore(true),
+    positionAfterLoad(0), loaded(false)
 {
+    Trace t("BookView::BookView");
     settings()->setAttribute(QWebSettings::AutoLoadImages, true);
     settings()->setAttribute(QWebSettings::JavascriptEnabled, true);
     settings()->setAttribute(QWebSettings::PluginsEnabled, false);
@@ -61,11 +64,13 @@ BookView::BookView(QWidget *parent):
 
 BookView::~BookView()
 {
+    Trace t("BookView::~BookView");
     removeIcons();
 }
 
 void BookView::loadContent(int index)
 {
+    Trace t("BookView::loadContent");
     if (!mBook) {
         return;
     }
@@ -78,7 +83,7 @@ void BookView::loadContent(int index)
         setHtml(contentFile);
     }
     else {
-        loadFinished = false;
+        loaded = false;
         emit chapterLoadStart(index);
         load(QUrl(contentFile));
     }
@@ -87,8 +92,7 @@ void BookView::loadContent(int index)
 
 void BookView::setBook(Book *book)
 {
-    qDebug() << "Book::setBook" << (book? book->path(): "");
-
+    Trace t("BookView::setBook");
     setLastBookmark();
     if (book != mBook) {
         mBook = book;
@@ -111,17 +115,19 @@ Book *BookView::book()
 
 void BookView::goPrevious()
 {
+    Trace t("BookView::goPrevious");
     loadContent(contentIndex - 1);
 }
 
 void BookView::goNext()
 {
+    Trace t("BookView::goNext");
     loadContent(contentIndex + 1);
 }
 
 void BookView::setLastBookmark()
 {
-    qDebug() << "BookView::setLastBookmark";
+    Trace t("BookView::saveLastBookmark");
     if (mBook) {
         int height = page()->mainFrame()->contentsSize().height();
         int pos = page()->mainFrame()->scrollPosition().y();
@@ -131,9 +137,10 @@ void BookView::setLastBookmark()
 
 void BookView::goToBookmark(const Book::Bookmark &bookmark)
 {
+    Trace t("BookView::goToBookmark");
     if (mBook) {
         restore = true;
-        restorePos = bookmark.pos;
+        positionAfterLoad = bookmark.pos;
         if (bookmark.chapter != contentIndex) {
             loadContent(bookmark.chapter);
         } else {
@@ -144,16 +151,18 @@ void BookView::goToBookmark(const Book::Bookmark &bookmark)
 
 void BookView::onLoadFinished(bool ok)
 {
-    qDebug() << "BookView::onLoadFinished" << ok;
-    loadFinished = true;
-    addNavigationBar();
+    Trace t(QString("BookView::onLoadFinished: %1").arg(ok));
+    loaded = true;
+    if (ok) {
+        addNavigationBar();
+    }
     onSettingsChanged("scheme");
     emit chapterLoadEnd(contentIndex);
     if (restore) {
         restore = false;
         if (ok && mBook) {
             int height = page()->mainFrame()->contentsSize().height();
-            int scrollPos = (qreal)height * restorePos;
+            int scrollPos = (qreal)height * positionAfterLoad;
             page()->mainFrame()->setScrollPosition(QPoint(0, scrollPos));
         }
     }
@@ -161,7 +170,7 @@ void BookView::onLoadFinished(bool ok)
 
 void BookView::onSettingsChanged(const QString &key)
 {
-    qDebug() << "BookView::onSettingsChanged" << key;
+    Trace t("BookView::onSettingsChanged " + key);
     if (key == "zoom") {
         setZoomFactor(Settings::instance()->value(key).toFloat() / 100.);
     }
@@ -187,14 +196,11 @@ void BookView::onSettingsChanged(const QString &key)
 void BookView::paintEvent(QPaintEvent *e)
 {
     QWebView::paintEvent(e);
-    if (!mBook) {
+    if (!mBook || !loaded) {
         return;
     }
 
     // Paint bookmarks
-    if (!loadFinished) {
-        return;
-    }
     QPoint scrollPos = page()->mainFrame()->scrollPosition();
     QPixmap bookmarkPixmap = QPixmap::fromImage(bookmarkImage);
     QPainter painter(this);
@@ -217,21 +223,23 @@ void BookView::mousePressEvent(QMouseEvent *e)
         e->accept();
         return;
     }
-#endif
+#endif // Q_WS_MAEMO_5
     e->ignore();
 }
 
 void BookView::addBookmark()
 {
+    Trace t("BookView::addBookmark");
     int y = page()->mainFrame()->scrollPosition().y();
     int height = page()->mainFrame()->contentsSize().height();
-    qDebug() << "BookView::addBookMark" << ((qreal)y / (qreal)height);
+    t.trace(QString().setNum((qreal)y / (qreal)height));
     mBook->addBookmark(contentIndex, (qreal)y / (qreal)height);
     repaint();
 }
 
 void BookView::addNavigationBar()
 {
+    Trace t("BookView::addNavigationBar");
     if (!mBook) {
         return;
     }
@@ -275,8 +283,6 @@ QString BookView::tmpPath()
 
 void BookView::extractIcons()
 {
-    qDebug() << "BookView::extractIcons: Extracting to" << tmpPath();
-
     QFile next(ICON_PREFIX + QString("/next.png"));
     QFile prev(ICON_PREFIX + QString("/previous.png"));
 
index b333228..eea5208 100644 (file)
@@ -49,9 +49,9 @@ private:
     int contentIndex;
     Book *mBook;
     bool restore;
-    qreal restorePos;
+    qreal positionAfterLoad;
     QImage bookmarkImage;
-    bool loadFinished;
+    bool loaded;
 };
 
 #endif // BOOKVIEW_H
index 5fe929b..cd1cdd2 100644 (file)
@@ -20,7 +20,8 @@ SOURCES += \
     bookmarkinfodialog.cpp \
     dialog.cpp \
     chaptersdialog.cpp \
-    fullscreenwindow.cpp
+    fullscreenwindow.cpp \
+    trace.cpp
 
 HEADERS += \
     mainwindow.h \
@@ -46,7 +47,8 @@ HEADERS += \
     bookmarkinfodialog.h \
     dialog.h \
     chaptersdialog.h \
-    fullscreenwindow.h
+    fullscreenwindow.h \
+    trace.h
 
 RESOURCES += \
     dorian.qrc
index e5dc3c0..bdea6a4 100644 (file)
@@ -3,24 +3,29 @@
 #include "fullscreenwindow.h"
 #include "translucentbutton.h"
 
-FullScreenWindow::FullScreenWindow(QWidget *child, QWidget *parent):
-        QMainWindow(parent)
+FullScreenWindow::FullScreenWindow(QWidget *parent): QMainWindow(parent), child(0)
 {
     Q_ASSERT(parent);
 #ifdef Q_WS_MAEMO_5
     setAttribute(Qt::WA_Maemo5StackedWindow, true);
-    setAttribute(Qt::WA_Maemo5PortraitOrientation,
-                 parent->testAttribute(Qt::WA_Maemo5PortraitOrientation));
-    setAttribute(Qt::WA_Maemo5LandscapeOrientation,
-                 parent->testAttribute(Qt::WA_Maemo5LandscapeOrientation));
+    setAttribute(Qt::WA_Maemo5NonComposited, true);
 #endif // Q_WS_MAEMO_5
-    child->setParent(this);
-    setCentralWidget(child);
+    QFrame *frame = new QFrame(this);
+    QVBoxLayout *layout = new QVBoxLayout(frame);
+    layout->setMargin(0);
+    frame->setLayout(layout);
+    setCentralWidget(frame);
     restoreButton = new TranslucentButton("view-fullscreen", this);
 }
 
 void FullScreenWindow::showFullScreen()
 {
+#ifdef Q_WS_MAEMO_5
+    setAttribute(Qt::WA_Maemo5PortraitOrientation, parentWidget()->
+                 testAttribute(Qt::WA_Maemo5PortraitOrientation));
+    setAttribute(Qt::WA_Maemo5LandscapeOrientation, parentWidget()->
+                 testAttribute(Qt::WA_Maemo5LandscapeOrientation));
+#endif // Q_WS_MAEMO_5
     QWidget::showFullScreen();
     restoreButton->flash();
 }
@@ -43,3 +48,21 @@ void FullScreenWindow::resizeEvent(QResizeEvent *e)
     restoreButton->setGeometry(fullScreenZone());
     QMainWindow::resizeEvent(e);
 }
+
+void FullScreenWindow::takeChild(QWidget *c)
+{
+    leaveChild();
+    if (c) {
+        child = c;
+        child->setParent(centralWidget());
+        centralWidget()->layout()->addWidget(child);
+    }
+}
+
+void FullScreenWindow::leaveChild()
+{
+    if (child) {
+        centralWidget()->layout()->removeWidget(child);
+        child = 0;
+    }
+}
index f7844a4..5c8a15f 100644 (file)
@@ -9,8 +9,10 @@ class FullScreenWindow: public QMainWindow
 {
     Q_OBJECT
 public:
-    explicit FullScreenWindow(QWidget *child, QWidget *parent);
+    explicit FullScreenWindow(QWidget *parent);
     void showFullScreen();
+    void takeChild(QWidget *child);
+    void leaveChild();
 
 signals:
     void restore();
@@ -27,6 +29,7 @@ protected:
     virtual void resizeEvent(QResizeEvent *event);
     QRect fullScreenZone() const;
     TranslucentButton *restoreButton;
+    QWidget *child;
 };
 
 #endif // FULLSCREENWINDOW_H
index 873b343..c40fb0f 100644 (file)
@@ -4,6 +4,7 @@
 
 #include "library.h"
 #include "book.h"
+#include "trace.h"
 
 Library *Library::mInstance = 0;
 
@@ -54,7 +55,7 @@ Book *Library::book(const QModelIndex &index)
         if ((index.row() >= 0) && (index.row() < mBooks.size())) {
             return mBooks[index.row()];
         } else {
-            qWarning() << "*** Library::book: Bad index" << index.row();
+            qCritical() << "*** Library::book: Bad index" << index.row();
         }
     }
     return 0;
@@ -97,12 +98,13 @@ void Library::save()
 
 bool Library::add(QString path)
 {
+    Trace t("Library::add " + path);
     if (path == "") {
-        qWarning() << "*** Library::add: Empty path";
+        qCritical() << "*** Library::add: Empty path";
         return false;
     }
     if (find(path).isValid()) {
-        qDebug() << "Library::add: Book already exists in library";
+        t.trace("Book already exists in library");
         return false;
     }
     int size = mBooks.size();
@@ -134,7 +136,6 @@ void Library::remove(const QModelIndex &index)
 
 QModelIndex Library::nowReading() const
 {
-    qDebug() << "Library::nowReading" << mNowReading.row();
     return mNowReading;
 }
 
index 6392631..dadcbca 100755 (executable)
@@ -19,6 +19,7 @@
 #include "settings.h"
 #include "chaptersdialog.h"
 #include "fullscreenwindow.h"
+#include "trace.h"
 
 #ifdef DORIAN_TEST_MODEL
 #include "modeltest.h"
 #   define ICON_PREFIX ":/icons/"
 #endif
 
-const Qt::WindowFlags WIN_BIG_FLAGS =
-        Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint;
-const int WIN_BIG_TIMER = 3000;
-
-MainWindow::MainWindow(QWidget *parent):
-        QMainWindow(parent), view(0), fullScreenWindow(0)
+MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), view(0)
 {
 #ifdef Q_WS_MAEMO_5
     setAttribute(Qt::WA_Maemo5StackedWindow, true);
 #endif
     setWindowTitle("Dorian");
 
+    // Central widget. Must be an intermediate because of reparenting the book view
+    QFrame *central = new QFrame(this);
+    QVBoxLayout *layout = new QVBoxLayout(central);
+    layout->setMargin(0);
+    central->setLayout(layout);
+    setCentralWidget(central);
+
     // Book view
-    view = new BookView(this);
-    setCentralWidget(view);
+    view = new BookView(central);
+    view->show();
+    layout->addWidget(view);
 
     // Tool bar
     setUnifiedTitleAndToolBarOnMac(true);
@@ -84,8 +88,7 @@ MainWindow::MainWindow(QWidget *parent):
     frame->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
     toolBar->addWidget(frame);
 
-    fullScreenAction = addToolBarAction(this, SLOT(showFullScreen()),
-                                        "view-fullscreen");
+    fullScreenAction = addToolBarAction(this, SLOT(showBig()), "view-fullscreen");
 
     // Handle model changes
     connect(Library::instance(), SIGNAL(nowReadingChanged()),
@@ -126,6 +129,10 @@ MainWindow::MainWindow(QWidget *parent):
     connect(view, SIGNAL(chapterLoadEnd(int)),
             this, SLOT(onChapterLoadEnd(int)));
 
+    // Shadow window for full screen
+    fullScreenWindow = new FullScreenWindow(this);
+    connect(fullScreenWindow, SIGNAL(restore()), this, SLOT(showRegular()));
+
 #ifdef DORIAN_TEST_MODEL
     (void)new ModelTest(Library::instance(), this);
 #endif
@@ -136,22 +143,21 @@ void MainWindow::onCurrentBookChanged()
     setCurrentBook(Library::instance()->nowReading());
 }
 
-void MainWindow::showNormal()
+void MainWindow::showRegular()
 {
-    qDebug() << "MainWindow::showNormal";
-    view->setParent(this);
-    setCentralWidget(view);
-    show();
-    delete fullScreenWindow;
-    fullScreenWindow = 0;
+    Trace t("MainWindow::showRegular");
+    fullScreenWindow->hide();
+    fullScreenWindow->leaveChild();
+    view->setParent(centralWidget());
+    centralWidget()->layout()->addWidget(view);
 }
 
-void MainWindow::showFullScreen()
+void MainWindow::showBig()
 {
-    qDebug() << "MainWindow::showFullscreen";
-    fullScreenWindow = new FullScreenWindow(view, this);
+    Trace t("MainWindow::showBig");
+    centralWidget()->layout()->removeWidget(view);
+    fullScreenWindow->takeChild(view);
     fullScreenWindow->showFullScreen();
-    connect(fullScreenWindow, SIGNAL(restore()), this, SLOT(showNormal()));
 }
 
 void MainWindow::setCurrentBook(const QModelIndex &current)
@@ -212,13 +218,14 @@ void MainWindow::showBookmarks()
 
 void MainWindow::closeEvent(QCloseEvent *event)
 {
-    qDebug() << "MainWindow::closeEvent";
+    Trace t("MainWindow::closeEvent");
     view->setLastBookmark();
     event->accept();
 }
 
 void MainWindow::onSettingsChanged(const QString &key)
 {
+    Trace t("MainWindow::onSettingsChanged");
 #ifdef Q_WS_MAEMO_5
     if (key == "orientation") {
         QString value = Settings::instance()->value(key).toString();
@@ -238,6 +245,7 @@ void MainWindow::onSettingsChanged(const QString &key)
 
 void MainWindow::onChapterLoadStart()
 {
+    Trace t("MainWindow::onChapterLoadStart");
 #ifdef Q_WS_MAEMO_5
     setAttribute(Qt::WA_Maemo5ShowProgressIndicator, true);
 #endif
@@ -245,6 +253,7 @@ void MainWindow::onChapterLoadStart()
 
 void MainWindow::onChapterLoadEnd(int index)
 {
+    Trace t("MainWindow::onChapterLoadEnd");
     bool enablePrevious = false;
     bool enableNext = false;
     Book *book = Library::instance()->book(mCurrent);
@@ -269,11 +278,13 @@ void MainWindow::onChapterLoadEnd(int index)
 
 void MainWindow::onAddBookmark()
 {
+    Trace t("MainWindow:onAddBookmark");
     view->addBookmark();
 }
 
 void MainWindow::onGoToBookmark(int index)
 {
+    Trace t("MainWindow::onGoToBookmark");
     Book *book = Library::instance()->book(mCurrent);
     view->goToBookmark(book->bookmarks()[index]);
 }
@@ -294,4 +305,3 @@ void MainWindow::onGoToChapter(int index)
 {
     view->goToBookmark(Book::Bookmark(index, 0));
 }
-
index f2a5f6f..a8a2928 100755 (executable)
@@ -25,8 +25,8 @@ public slots:
     void showDevTools();
     void showBookmarks();
     void onCurrentBookChanged();
-    void showNormal();
-    void showFullScreen();
+    void showRegular();
+    void showBig();
     void onSettingsChanged(const QString &key);
     void onChapterLoadStart();
     void onChapterLoadEnd(int index);
index 0351bfa..69522ec 100644 (file)
@@ -1,8 +1,15 @@
+dorian (0.0.13-1) unstable; urgency=low
+
+  * Fix multiple navigation arrows [#6041]
+  * Make full screen switching more robust
+
+ -- Akos Polster <akos@pipacs.com>  Thu, 29 Jul 2010 20:00:00 +0200
+
 dorian (0.0.12-1) unstable; urgency=low
 
   * Make full screen truly full screen
 
- -- Akos Polster <akos@pipacs.com>  Tue, 28 Jul 2010 20:00:00 +0200
+ -- Akos Polster <akos@pipacs.com>  Wed, 28 Jul 2010 20:00:00 +0200
 
 dorian (0.0.11-1) unstable; urgency=low
 
index 8cbf02c..43b2961 100644 (file)
@@ -1 +1 @@
-0.0.12
+0.0.13
diff --git a/trace.cpp b/trace.cpp
new file mode 100644 (file)
index 0000000..2939a6d
--- /dev/null
+++ b/trace.cpp
@@ -0,0 +1,4 @@
+#include "trace.h"
+
+int Trace::indent = 0;
+
diff --git a/trace.h b/trace.h
new file mode 100644 (file)
index 0000000..d3ceb40
--- /dev/null
+++ b/trace.h
@@ -0,0 +1,28 @@
+#ifndef TRACE_H
+#define TRACE_H
+
+#include <QtDebug>
+
+class Trace
+{
+public:
+    Trace(const QString &s): name(s) {
+        qDebug() << QString(" ").repeated(indent) + ">" + name;
+        indent++;
+    }
+    ~Trace() {
+        if (--indent < 0) {
+            indent = 0;
+        }
+        qDebug() << QString(" ").repeated(indent) + "<" + name;
+    }
+    void trace(const QString &s) const {
+        qDebug() << QString(" ").repeated(indent)  + name + ": " + s;
+    }
+
+protected:
+    QString name;
+    static int indent;
+};
+
+#endif // TRACE_H