/*! @file

    @id $Id$
    */
//       1         2         3         4         5         6         7         8
// 45678901234567890123456789012345678901234567890123456789012345678901234567890
#ifndef TESTGUI_HXX
#define TESTGUI_HXX

#include <webpage.hxx>
#include <editor.hxx>
#include <commands.hxx>
#include <scriptfile.hxx>
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wconversion"
#include <QMainWindow>
#include <QSettings>
#include <QWebFrame>
#include <QWebElement>
#include <QFileDialog>
#include <QScrollBar>
#include <QFile>
#include <QMessageBox>
#include <QShortcut>
#include <QCompleter>
#include <ui_testgui.hxx>
#include <QNetworkReply>
#include <QEvent>
#pragma GCC diagnostic pop
#include <stdexcept>
#include <mrw/stdext.hxx>

class TestGUI: public QMainWindow, protected Ui::TestGUI {
    Q_OBJECT
  public:
    explicit TestGUI(QWidget *parent = 0,
                     QString url = QString(),
                     QString setupScript = QString(),
                     QString scriptFile = QString()):
        QMainWindow(parent),
        _typing(false),
        _inEventFilter(false) {
      setWindowTitle("[*]");
      setupUi(this);
      #if QT_VERSION >= 0x050600
      setDockOptions(dockOptions()|QMainWindow::GroupedDragging);
      #endif
      menuViews->addAction(_setupScriptDock->toggleViewAction());
      menuViews->addAction(_helpDock->toggleViewAction());
      menuViews->addAction(_domDock->toggleViewAction());
      menuViews->addAction(_linksDock->toggleViewAction());
      menuViews->addAction(_formsDock->toggleViewAction());
      menuViews->addAction(_logDock->toggleViewAction());
      menuViews->addAction(_sourceDock->toggleViewAction());
      menuViews->addAction(_executeDock->toggleViewAction());
      menuHelp->addAction(_helpDock->toggleViewAction());
      statusbar->addPermanentWidget(_progress = new QProgressBar(), 1);
      _progress->hide();
      QSettings settings("mrw", "webtester");
      restoreGeometry(settings.value("geometry").toByteArray());
      restoreState(settings.value("windowstate").toByteArray());
      _actionCursorFollowsFiles->setChecked(settings.value("cursorfollowsfiles").toBool());
      _url->completer()->setFilterMode(Qt::MatchContains);
      _url->completer()->setCaseSensitivity(Qt::CaseInsensitive);
      _url->completer()->setCompletionMode(QCompleter::PopupCompletion);
      if (!url.isEmpty()) {
        storeUrl(url);
      }
      TestWebPage* pg(new TestWebPage(_web));
      _web->setPage(pg);
      _web->installEventFilter(this); // track mouse and keyboard
      pg->setForwardUnsupportedContent(true);
      assert(connect(menuFile, SIGNAL(aboutToShow()), SLOT(fileMenuOpened())));
      assert(connect(QApplication::instance(), SIGNAL(focusChanged(QWidget*, QWidget*)),
                      SLOT(focusChanged(QWidget*, QWidget*))));
      assert(connect(pg, SIGNAL(uploadFile(QString)), SLOT(uploadFile(QString))));
      assert(connect(pg, SIGNAL(unsupportedContent(QNetworkReply*)),
                     SLOT(unsupportedContent(QNetworkReply*))));
      assert(connect(pg, SIGNAL(downloadRequested(const QNetworkRequest&)),
                     SLOT(downloadRequested(const QNetworkRequest&))));
      if (setupScript.size()) loadSetup(setupScript);
      if (scriptFile.size()) load(scriptFile);
    }
    virtual ~TestGUI() {}
  public Q_SLOTS:
    void on__load_clicked() {
      enterText(true);
      appendCommand("load "+map(_url->currentText()));
      storeUrl(_url->currentText());
      _webprogress->setFormat(_url->currentText());
      _web->load(_url->currentText());
    }
    void on__abort_clicked() {
      enterText(true);
      _web->stop();
    }
    void on__actionAbout_triggered() {
      QMessageBox::about(this, tr("About"),
                         tr("Webtester\n"
                            "Version: %1\n"
                            "Builddate: %2\n"
                            "Release: %3\n"
                            "Libraries:\n"
                            "qt-%4 (%5)\n")
                         .arg(VERSION)
                         .arg(BUILD_DATE)
                         .arg(PACKAGE_VERSION)
                         .arg(qVersion())
                         .arg(QT_VERSION_STR));
    }
    void on__actionNew_triggered() {
      QString name(QFileDialog::getSaveFileName(this, tr("Filename for new Test Script")));
      QFileInfo info(name);
      if (info.absoluteDir()==QDir::current()) name = info.fileName();
      if (!name.isEmpty()) newScript(name);
    }
    void on__actionOpen_triggered() {
      QString name(QFileDialog::getOpenFileName(this, tr("Open Test Script")));
      if (name.isEmpty()) return;
      load(name);
    }
    void on__actionOpenSetupScript_triggered() {
      QString name(QFileDialog::getOpenFileName(this, tr("Open Setup Script")));
      if (name.isEmpty()) return;
      loadSetup(name);
    }
    void on__actionRevertToSaved_triggered() {
      ScriptFile* active(activeScriptFile());
      if (active) active->load();
    }
    void on__actionSaveAs_triggered() {
      ScriptFile* active(activeScriptFile());
      if (!active) return;
      QString name(QFileDialog::getSaveFileName(this, tr("Save Test Script")));
      if (!name.isEmpty()) active->save(name);
    }
    void on__actionSave_triggered() {
      ScriptFile* active(activeScriptFile());
      if (active) active->save();
    }
    void on__actionClear_triggered() {
      ScriptFile* active(activeScriptFile());
      if (active) active->clear();
    }
    void on__focused_clicked() {
      enterText(true);
      QWebElement element(focused());
      if (element.isNull()) return;
      highlight(element);
      _focusedText->setText(selector(element));
    }
    void on__select_clicked() {
      enterText(true);
      highlight(_web->page()->mainFrame()->documentElement()
                .findFirst(_selector->text()));
    }
    void on__jsExecute_clicked() {
      enterText(true);
      qDebug()<<"on__jsExecute_clicked"<<selector()<<_javascriptCode->toPlainText();
      _jsResult->setText(execute(selector(), _javascriptCode->toPlainText()));
    }
    void on__web_linkClicked(const QUrl& url) {
      enterText(true);
      appendCommand("load "+map(url.url()));
    }
    void on__web_loadProgress(int progress) {
      enterText(true);
      _webprogress->setValue(progress);
    }
    void on__web_loadStarted() {
      enterText(true);
      appendCommand("expect "+map("loadStarted"));
      _webprogress->setValue(0);
      _urlStack->setCurrentIndex(PROGRESS_VIEW);
    }
    void on__web_statusBarMessage(const QString&) {
      //std::cout<<"statusBarMessage: "<<text.toStdString()<<std::endl;
    }
    void on__web_titleChanged(const QString&) {
      //std::cout<<"titleChanged: "<<title.toStdString()<<std::endl;
    }
    void on__web_urlChanged(const QUrl& url) {
      _webprogress->setFormat(url.url());
      storeUrl(url);
      enterText(true);
      appendCommand("expect "+map("urlChanged "+url.url()));
    }
    void on__web_selectionChanged() {
      _source->setPlainText(_web->hasSelection()
                            ? _web->selectedHtml()
                            : _web->page()->mainFrame()->toHtml());
    }
    void on__web_loadFinished(bool ok) {
      enterText(true);
      for (auto testscript: _testscripts)
        testscript->appendWebLoadFinished(ok);
      _urlStack->setCurrentIndex(URL_VIEW);
      on__web_selectionChanged();
      setLinks();
      setForms();
      setDom();
    }
    void on__setupscript_textChanged() {
      for (auto testscript: _testscripts) testscript->runEnabled(false);
      _setupscriptactive->setEnabled(false);
      try {
        _setupscriptstatus->setText(trUtf8("?"));
        std::shared_ptr<xml::Node> testsuites(new xml::Node("testsuite"));
        Script script;
        TestWebPage pg(0, true);
        script.parse(_setupscript->toPlainText().split('\n'), "setup");
        script.run(pg.mainFrame(), testsuites, QString(), false);
        _setupScript.cleanup();
        _setupScript.parse(_setupscript->toPlainText().split('\n'), "setup");
        _setupScript.run(pg.mainFrame(), testsuites, QString(), false);
        _setupscriptstatus->setText(trUtf8("✔"));
        _setupscriptactive->setEnabled(true);
      } catch (std::exception &x) {
        _setupscriptstatus->setText(trUtf8("✘"));
      }
      for (auto testscript: _testscripts) testscript->runEnabled(true);
    }
    void on__forms_currentItemChanged(QTreeWidgetItem* item, QTreeWidgetItem*) {
      if (!item) return;
      _source->setPlainText(item->data(0, Qt::UserRole).toString());
    }
    void on__dom_currentItemChanged(QTreeWidgetItem* item, QTreeWidgetItem*) {
      if (!item) return;
      _source->setPlainText(item->data(0, Qt::UserRole).toString());
    }
    void uploadFile(QString filename) {
      enterText(true);
      appendCommand("upload "+map(filename));
    }
    void unsupportedContent(QNetworkReply* reply) {
      for (auto testscript: _testscripts) testscript->unsupportedContent(reply);
    }
    void downloadRequested(const QNetworkRequest&) {
      appendCommand("download2");
    }
    void logging(const QString& txt) {
      _log->appendPlainText(txt);
      QScrollBar *vb(_log->verticalScrollBar());
      if (!vb) return;
      vb->setValue(vb->maximum());
    }
    void fileMenuOpened() {
      focusChanged(nullptr, nullptr);
    }
    void modified(ScriptFile* win) {
      focusChanged(nullptr, win);
    }
    void focusChanged(QWidget*, QWidget* focus) {
      ScriptFile* active(activeScriptFile(focus));
      if (active)
        setWindowFilePath(active->name());
      else
        setWindowFilePath(QString());
      _actionRevertToSaved->setEnabled(active);
      _actionSaveAs->setEnabled(active);
      _actionSave->setEnabled(active&&active->isWindowModified());
      _actionClear->setEnabled(active);
    }
    QString activate(QString name) {
      QFileInfo info(name);
      if (info.absoluteDir()==QDir::current()) name = info.fileName();
      if (!_testscripts.contains(name)) return load(name);
      _testscripts[name]->show();
      _testscripts[name]->raise();
      return name;
    }
    QString load(QString name) {
      QFileInfo info(name);
      if (info.absoluteDir()==QDir::current()) name = info.fileName();
      if (_testscripts.contains(name)) try {
          _testscripts[name]->load(name);
          return activate(name);
        } catch(const std::exception& x) {
          remove(_testscripts[name]);
        }
      newScript(name);
      try {
        _testscripts[name]->load(name);
        return activate(name);
      } catch(const std::exception& x) {
        remove(_testscripts[name]);
        return QString();
      }
    }
    QString newScript(QString name) {
      if (_testscripts.contains(name)) return activate(name);
      QDockWidget* first(_testscripts.isEmpty()?_setupScriptDock:_testscripts.last());
      _testscripts[name] = new ScriptFile(this, name);
      assert(connect(_testscripts[name], SIGNAL(modified(ScriptFile*)), SLOT(modified(ScriptFile*))));
      assert(connect(_testscripts[name], SIGNAL(link(QString)), SLOT(activate(QString))));
      assert(connect(_testscripts[name], SIGNAL(close(ScriptFile*)), SLOT(remove(ScriptFile*))));
      assert(connect(_testscripts[name], SIGNAL(run(const QString&, const QString&, bool, Script&)),
                     SLOT(run(const QString&, const QString&, bool, Script&))));
      assert(connect(_testscripts[name], SIGNAL(progress(QString, int, int, int)),
                     SLOT(progress(QString, int, int, int))));
      tabifyDockWidget(first, _testscripts[name]);
      return name;
    }
    void remove(ScriptFile* scriptfile) {
      /// @todo check if modified
      _testscripts.remove(scriptfile->name());
      delete scriptfile;
    }
    void run(const QString& name, const QString& text, bool screenshots, Script& script) {
      std::shared_ptr<xml::Node> testsuites(new xml::Node("testsuite"));
      assert(connect(&script, SIGNAL(logging(QString)), SLOT(logging(QString))));
      if (_setupscriptactive->isEnabled()
          && _setupscriptactive->isChecked()) {
        script.parse(_setupscript->toPlainText().split('\n'), "setup");
        script.run(_web->page()->mainFrame(), testsuites, QString(), screenshots);
        script.reset();
      }
      script.parse(text.split('\n'), name);
      script.run(_web->page()->mainFrame(), testsuites, QString(), screenshots);
    }
    void progress(QString file, int line, int pos, int max) {
      _progress->setVisible(max>0&&pos!=max);
      _progress->setFormat(QString("%1:%2 — %p%").arg(file).arg(line));
      _progress->setMinimum(0);
      _progress->setMaximum(max);
      _progress->setValue(pos);
      if (!_actionCursorFollowsFiles->isChecked() || file.isEmpty() || file=="setup") return;
      try {
        QString name(activate(file));
        if (!name.isEmpty() && _testscripts.contains(name))
          _testscripts[name]->gotoLine(line);
      } catch (...) {} // ignore
    }
  protected:
    ScriptFile* activeScriptFile(QWidget* focus=nullptr) {
      ScriptFile* active(nullptr);
      for (QObject* wid(focus?focus:QApplication::focusWidget()); !active && wid; wid = wid->parent())
        active = qobject_cast<ScriptFile*>(wid);
      return active;
    }
    void closeEvent(QCloseEvent* event) {
      QSettings settings("mrw", "webtester");
      settings.setValue("geometry", saveGeometry());
      settings.setValue("windowstate", saveState());
      settings.setValue("cursorfollowsfiles", _actionCursorFollowsFiles->isChecked());
      if (isWindowModified() &&
          QMessageBox::question(this, tr("Changes Not Saved"),
                                tr("Leave without saving changes?"))
          != QMessageBox::Yes) {
        event->ignore();
        return;
      }
      QMainWindow::closeEvent(event);
    }
    bool eventFilter(QObject* target, QEvent* event) {
      if (_inEventFilter) return false;
      _inEventFilter = true;
      enterText();
      QWebElement element(focused(dynamic_cast<QMouseEvent*>(event)));
      switch (event->type()) {
        case QEvent::KeyPress: {
          QKeyEvent* k(dynamic_cast<QKeyEvent*>(event));
          switch (k->key()) {
            case Qt::Key_Tab:
            case Qt::Key_Backtab: {
              enterText(true);
            } break;
            case Qt::Key_Backspace: {
              _keyStrokes.chop(1);
            } break;
            case Qt::Key_Shift: break;
            case Qt::Key_Enter:
            case Qt::Key_Return: {
              _keyStrokes += "\\n";
              _lastFocused=element;
              _typing = true;
            } break;
            default: {
              _keyStrokes += k->text();
              _lastFocused=element;
              _typing = true;
            }
          }
        } break;
        case QEvent::MouseButtonRelease: {
          enterText(true);
          _lastFocused=element;
          if (!element.isNull()) {
            QString selected(selector(element));
            if (handleMooTools(_lastFocused)) {
              // handled in handleMooTools
            } else if (_lastFocused.tagName()=="SELECT") {
              // click on a select results in a value change
              // find all selected options ...
              QStringList v;
              for (QWebElement option: _lastFocused.findAll("option")) {
                //! @bug QT does not support selected
                if (option.evaluateJavaScript("this.selected").toBool())
                  v += value(option);
              }
              setValue(selected, v);
            } else if (_lastFocused.tagName()=="TEXTAREA" ||
                       (_lastFocused.tagName()=="INPUT" &&
                        _lastFocused.attribute("type")=="text")) {
              // user clickt in a text edit field, so not the klick
              // is important, but the text that will be typed
              _typing = true;
            } else {
              if (_web->page()->selectedText() != "") {
                // user has selected a text, append a check
                appendCommand("exists "+map(selected)
                              +" -> "+_web->page()->selectedText());
                _web->page()->findText(QString());
              } else {
                // user has clicked without selection, append a click
                appendCommand("click "+map(selected));
              }
            }
          } else {
            appendCommand("# click, but where?");
          }
        } break;
        case QEvent::MouseButtonPress: {
        } break;
        case QEvent::ChildRemoved: { // select option value changed
          enterText(true);
          _typing = true;
          _lastFocused=element;
        } break;
        case QEvent::InputMethodQuery:
        case QEvent::ToolTipChange:
        case QEvent::MouseMove:
        case QEvent::UpdateLater:
        case QEvent::Paint: break;
        default:;
          /* logging(QString("Event: %1")
              .arg(eventName(event->type()))); */
      }
      _inEventFilter = false;
      return false;
    }
  private:
    void storeUrl(QUrl u) {
      if (_url->findText(u.url())==-1) {
        _url->addItem(u.url());
        _url->setCurrentText(u.url());
      } else {
        _url->setCurrentText(u.url());
      }
    }
    void loadSetup(QString name) {
      QFile file(name);
      try {
        if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
          throw std::runtime_error("file open failed");
        _setupscript->setPlainText(QString::fromUtf8(file.readAll()));
        if (file.error()!=QFileDevice::NoError)
          throw std::runtime_error("file read failed");
        on__setupscript_textChanged();
      } catch(const std::exception& x) {
        QMessageBox::critical(this, tr("Open Failed"),
                              tr("Reading test script failed, %2. "
                                 "Cannot read test script from file %1.")
                              .arg(name).arg(x.what()));
      }
    }
    void enterText(bool force=false) {
      if (!force && (!_typing || _lastFocused==focused())) return;
      if (_typing && !_lastFocused.isNull())
        setValue(selector(_lastFocused), value(_lastFocused));
      _lastFocused = QWebElement();
      _keyStrokes.clear();
      _typing = false;
    }
    QWebElement selected() {
      return _web->page()->mainFrame()->documentElement().findFirst(selector());
    }
    QString selector() {
      if (_takeFocused->isChecked())
        return selector(focused());
      else if (_takeSelect->isChecked())
        return _selector->text();
      else
        return QString(); // error
    }
    void highlight(QWebElement element) {
      element
        .evaluateJavaScript("var selection = window.getSelection();"
                            "selection.setBaseAndExtent(this, 0, this, 1);");
    }
    QWebElement focused(QMouseEvent* event = 0) {
      for (QWebElement element: _web->page()->currentFrame()->findAllElements("*")) {
        if (element.hasFocus()) {
          return element;
        }
      }
      if (event) { // try to find element using mouse position
        QWebFrame* frame(_web->page()->frameAt(event->pos()));
        if (frame) {
          QWebHitTestResult hit(frame->hitTestContent(event->pos()));
          if (!hit.element().isNull())
            return hit.element();
          if (!hit.enclosingBlockElement().isNull())
            return hit.enclosingBlockElement();
        }
      }
      return QWebElement();
    }
    bool unique(QString selector) {
      return _web->page()->mainFrame()->findAllElements(selector).count()==1;
    }
    QString quote(QString txt) {
      if (txt.contains('"')) return "'"+txt+"'";
      return '"'+txt+'"';
    }
    QString selector(const QWebElement& element) {
      if (element.isNull()) return QString();
      if (element.hasAttribute("id") && unique("#"+element.attribute("id"))) {
        return "#"+element.attribute("id");
      } else if (element.hasAttribute("name") &&
                 unique(element.tagName().toLower()
                        +"[name="+quote(element.attribute("name"))+"]")) {
        return element.tagName().toLower()
          +"[name="+quote(element.attribute("name"))+"]";
      } else {
        QString res;
        for (QString attr: element.attributeNames()) {
          if (attr=="id")
            res = "#"+element.attribute("id")+res;
          else if (attr=="class")
            for (QString c: element.attribute(attr).split(' ')) {
              if (!c.isEmpty()) res = '.'+c+res;
            }
          else if (element.attribute(attr).isEmpty())
            res+="["+attr+"]";
          else
            res+="["+attr+"="+quote(element.attribute(attr))+"]";
          if (unique(element.tagName().toLower()+res))
            return element.tagName().toLower()+res;
        }
        QString p(selector(element.parent()));
        if (unique(p+">"+element.tagName().toLower()+res))
          return p+">"+element.tagName().toLower()+res;
        QString s(selector(element.previousSibling()));
        if (unique(s+"+"+element.tagName().toLower()+res))
          return s+"+"+element.tagName().toLower()+res;
        if (!p.isEmpty())
          return p+">"+element.tagName().toLower()+res;
        if (!s.isEmpty())
          return s+"+"+element.tagName().toLower()+res;
        return element.tagName().toLower()+res;
      }
    }
    QString value(QWebElement element) {
      return element.evaluateJavaScript("this.value").toString();
      //! @bug Bug in Qt, attribute("value") is always empty
      // if (element.hasAttribute("value"))
      //   return element.attribute("value");
      // else
      //   return element.toPlainText();
    }
    QString map(QString in) {
      if (_setupscriptactive->isEnabled()
          && _setupscriptactive->isChecked()) {
        return _setupScript.insertvars(in);
      }
      return in;
    }
    void javascript(const QString& selector, QString code) {
      appendCommand("do "+map(selector)+"\n  "
                    +map(code).replace("\n", "\n  "));
    }
    void setValue(const QString& selector, QString code) {
      appendCommand(selector,
                    "setvalue "+map(selector)+" -> '"
                    +map(code).replace("'", "\\'").replace("\n", "\\n")+"'");
    }
    void setValue(const QString& selector, QStringList code) {
      appendCommand(selector,
                    "setvalue "+map(selector)+" -> '"+
                    map(code.replaceInStrings("'", "\\'")
                        .replaceInStrings("\n", "\\n")
                        .join("', '")+"'"));
    }
    void appendCommand(const QString& txt) {
      for (auto testscript: _testscripts) testscript->appendCommand(txt);
    }
    void appendCommand(const QString& selector, const QString& txt) {
      for (auto testscript: _testscripts) testscript->appendCommand(selector, txt);
    }
    bool handleMooTools(QWebElement element) {
      QString selected(selector(element));
      QRegularExpressionMatch mooCombo
        (QRegularExpression("^(#jform_[_A-Za-z0-9]+)_chzn>.*$")
         .match(selected));
      QRegularExpressionMatch mooComboItem
        (QRegularExpression
         ("^li\\.highlighted(\\.result-selected)?\\.active-result$")
         .match(selected));
      if (mooCombo.hasMatch()) {
        // special treatment for moo tools combobox (e.g. used
        // in joomla)
        appendCommand("click realmouse "+map(mooCombo.captured(1)+">a"));
        appendCommand("sleep "+map("1"));
        return true;
      } else if (mooComboItem.hasMatch()) {
        // special treatment for item in moo tools combobox
        appendCommand("click realmouse "+map("li.active-result[data-option-array-index=\""
                                             +element.attribute("data-option-array-index")
                                             +"\"]"));
        appendCommand("sleep "+map("1"));
        return true;
      } else if (element.tagName()=="INPUT") {
        for (QWebElement e(element); !(e=e.parent()).isNull();)
          if (e.hasClass("chzn-container")) {
            _lastFocused = e;
            appendCommand("# moo tools combobox with search field");
            appendCommand("# first set all elements visible");
            javascript(map(selector(e)+" ul"),
                       "this.style.height = \"auto\";\n"
                       "this.style.maxHeight = \"auto\";\n"
                       "this.style.overflow = \"visible\"");
            appendCommand("sleep "+map("1"));
            appendCommand("# moo tools open combobox");
            appendCommand("click realmouse "+map(selector(e)));
            return true;
          }
      } else if (element.hasAttribute("id") && // is it a selected option?
                 element.attribute("id").contains("_chzn_o_")) {
        appendCommand("# moo tools select item");
        appendCommand("click realmouse "+map(selector(element)));
        _lastFocused = element;
        return true;
      }
      return false;
    }
    QString execute(const QString& selector, const QString& code) {
      javascript(selector, code);
      QVariant res(_web->page()->mainFrame()->documentElement().findFirst(selector)
                   .evaluateJavaScript(code));
      qDebug()<<"EXECUTE"<<selector<<res;
      return res.toString();
    }
    void setLinks() {
      QWebElementCollection links(_web->page()->mainFrame()->documentElement()
                                  .findAll("a"));
      _links->setRowCount(links.count());
      for (int row(0); row<_links->rowCount(); ++row) {
        {
          QTableWidgetItem* item(new QTableWidgetItem());
          item->setText(links[row].attribute("href"));
          _links->setItem(row, 0, item);
        } {
          QTableWidgetItem* item(new QTableWidgetItem());
          item->setText(links[row].hasAttribute("title")
                        ? links[row].attribute("title")
                        : links[row].toInnerXml());
          _links->setItem(row, 1, item);
        }
        _links->horizontalHeader()->resizeSections(QHeaderView::Stretch);
      }
      
    }
    void setForms() {
      QWebElementCollection forms(_web->page()->mainFrame()->documentElement()
                                  .findAll("form"));
      _forms->clear();
      for (const QWebElement &form: forms) {
        addDomElement(form, _forms->invisibleRootItem());
      }
      
    }
    void setDom() {
      _dom->clear();
      addDomElement(_web->page()->mainFrame()->documentElement(),
                    _dom->invisibleRootItem());
    }
    //void addDomChildren(const QWebElement&, QTreeWidgetItem*);
    void addDomElement(const QWebElement &element,
                       QTreeWidgetItem *parent) {
      QTreeWidgetItem *item(new QTreeWidgetItem());
      item->setText(0, element.tagName());
      item->setData(0, Qt::UserRole, element.toOuterXml());
      parent->addChild(item);
      addDomChildren(element, item);
    }
    void addDomChildren(const QWebElement &parentElement,
                        QTreeWidgetItem *parentItem) {
      for (QWebElement element = parentElement.firstChild();
           !element.isNull();
           element = element.nextSibling()) {
        addDomElement(element, parentItem);
      }
    }
    QString eventName(QEvent::Type t) {
      switch (t) {
        case QEvent::None: return "QEvent::None - Not an event.";
        case QEvent::ActionAdded: return "QEvent::ActionAdded - A new action has been added (QActionEvent).";
        case QEvent::ActionChanged: return "QEvent::ActionChanged - An action has been changed (QActionEvent).";
        case QEvent::ActionRemoved: return "QEvent::ActionRemoved - An action has been removed (QActionEvent).";
        case QEvent::ActivationChange: return "QEvent::ActivationChange - A widget's top-level window activation state has changed.";
        case QEvent::ApplicationActivate: return "QEvent::ApplicationActivate - This enum has been deprecated. Use ApplicationStateChange instead.";
          //case QEvent::ApplicationActivated: return "QEvent::ApplicationActivated - This enum has been deprecated. Use ApplicationStateChange instead.";
        case QEvent::ApplicationDeactivate: return "QEvent::ApplicationDeactivate - This enum has been deprecated. Use ApplicationStateChange instead.";
        case QEvent::ApplicationFontChange: return "QEvent::ApplicationFontChange - The default application font has changed.";
        case QEvent::ApplicationLayoutDirectionChange: return "QEvent::ApplicationLayoutDirectionChange - The default application layout direction has changed.";
        case QEvent::ApplicationPaletteChange: return "QEvent::ApplicationPaletteChange - The default application palette has changed.";
        case QEvent::ApplicationStateChange: return "QEvent::ApplicationStateChange - The state of the application has changed.";
        case QEvent::ApplicationWindowIconChange: return "QEvent::ApplicationWindowIconChange - The application's icon has changed.";
        case QEvent::ChildAdded: return "QEvent::ChildAdded - An object gets a child (QChildEvent).";
        case QEvent::ChildPolished: return "QEvent::ChildPolished - A widget child gets polished (QChildEvent).";
        case QEvent::ChildRemoved: return "QEvent::ChildRemoved - An object loses a child (QChildEvent).";
        case QEvent::Clipboard: return "QEvent::Clipboard - The clipboard contents have changed (QClipboardEvent).";
        case QEvent::Close: return "QEvent::Close - Widget was closed (QCloseEvent).";
        case QEvent::CloseSoftwareInputPanel: return "QEvent::CloseSoftwareInputPanel - A widget wants to close the software input panel (SIP).";
        case QEvent::ContentsRectChange: return "QEvent::ContentsRectChange - The margins of the widget's content rect changed.";
        case QEvent::ContextMenu: return "QEvent::ContextMenu - Context popup menu (QContextMenuEvent).";
        case QEvent::CursorChange: return "QEvent::CursorChange - The widget's cursor has changed.";
        case QEvent::DeferredDelete: return "QEvent::DeferredDelete - The object will be deleted after it has cleaned up (QDeferredDeleteEvent).";
        case QEvent::DragEnter: return "QEvent::DragEnter - The cursor enters a widget during a drag and drop operation (QDragEnterEvent).";
        case QEvent::DragLeave: return "QEvent::DragLeave - The cursor leaves a widget during a drag and drop operation (QDragLeaveEvent).";
        case QEvent::DragMove: return "QEvent::DragMove - A drag and drop operation is in progress (QDragMoveEvent).";
        case QEvent::Drop: return "QEvent::Drop - A drag and drop operation is completed (QDropEvent).";
        case QEvent::DynamicPropertyChange: return "QEvent::DynamicPropertyChange - A dynamic property was added, changed, or removed from the object.";
        case QEvent::EnabledChange: return "QEvent::EnabledChange - Widget's enabled state has changed.";
        case QEvent::Enter: return "QEvent::Enter - Mouse enters widget's boundaries (QEnterEvent).";
          //case QEvent::EnterEditFocus: return "QEvent::EnterEditFocus - An editor widget gains focus for editing. QT_KEYPAD_NAVIGATION must be defined.";
        case QEvent::EnterWhatsThisMode: return "QEvent::EnterWhatsThisMode - Send to toplevel widgets when the application enters \"What's This?\" mode.";
        case QEvent::Expose: return "QEvent::Expose - Sent to a window when its on-screen contents are invalidated and need to be flushed from the backing store.";
        case QEvent::FileOpen: return "QEvent::FileOpen - File open request (QFileOpenEvent).";
        case QEvent::FocusIn: return "QEvent::FocusIn - Widget or Window gains keyboard focus (QFocusEvent).";
        case QEvent::FocusOut: return "QEvent::FocusOut - Widget or Window loses keyboard focus (QFocusEvent).";
        case QEvent::FocusAboutToChange: return "QEvent::FocusAboutToChange - Widget or Window focus is about to change (QFocusEvent)";
        case QEvent::FontChange: return "QEvent::FontChange - Widget's font has changed.";
        case QEvent::Gesture: return "QEvent::Gesture - A gesture was triggered (QGestureEvent).";
        case QEvent::GestureOverride: return "QEvent::GestureOverride - A gesture override was triggered (QGestureEvent).";
        case QEvent::GrabKeyboard: return "QEvent::GrabKeyboard - Item gains keyboard grab (QGraphicsItem only).";
        case QEvent::GrabMouse: return "QEvent::GrabMouse - Item gains mouse grab (QGraphicsItem only).";
        case QEvent::GraphicsSceneContextMenu: return "QEvent::GraphicsSceneContextMenu - Context popup menu over a graphics scene (QGraphicsSceneContextMenuEvent).";
        case QEvent::GraphicsSceneDragEnter: return "QEvent::GraphicsSceneDragEnter - The cursor enters a graphics scene during a drag and drop operation (QGraphicsSceneDragDropEvent).";
        case QEvent::GraphicsSceneDragLeave: return "QEvent::GraphicsSceneDragLeave - The cursor leaves a graphics scene during a drag and drop operation (QGraphicsSceneDragDropEvent).";
        case QEvent::GraphicsSceneDragMove: return "QEvent::GraphicsSceneDragMove - A drag and drop operation is in progress over a scene (QGraphicsSceneDragDropEvent).";
        case QEvent::GraphicsSceneDrop: return "QEvent::GraphicsSceneDrop - A drag and drop operation is completed over a scene (QGraphicsSceneDragDropEvent).";
        case QEvent::GraphicsSceneHelp: return "QEvent::GraphicsSceneHelp - The user requests help for a graphics scene (QHelpEvent).";
        case QEvent::GraphicsSceneHoverEnter: return "QEvent::GraphicsSceneHoverEnter - The mouse cursor enters a hover item in a graphics scene (QGraphicsSceneHoverEvent).";
        case QEvent::GraphicsSceneHoverLeave: return "QEvent::GraphicsSceneHoverLeave - The mouse cursor leaves a hover item in a graphics scene (QGraphicsSceneHoverEvent).";
        case QEvent::GraphicsSceneHoverMove: return "QEvent::GraphicsSceneHoverMove - The mouse cursor moves inside a hover item in a graphics scene (QGraphicsSceneHoverEvent).";
        case QEvent::GraphicsSceneMouseDoubleClick: return "QEvent::GraphicsSceneMouseDoubleClick - Mouse press again (double click) in a graphics scene (QGraphicsSceneMouseEvent).";
        case QEvent::GraphicsSceneMouseMove: return "QEvent::GraphicsSceneMouseMove - Move mouse in a graphics scene (QGraphicsSceneMouseEvent).";
        case QEvent::GraphicsSceneMousePress: return "QEvent::GraphicsSceneMousePress - Mouse press in a graphics scene (QGraphicsSceneMouseEvent).";
        case QEvent::GraphicsSceneMouseRelease: return "QEvent::GraphicsSceneMouseRelease - Mouse release in a graphics scene (QGraphicsSceneMouseEvent).";
        case QEvent::GraphicsSceneMove: return "QEvent::GraphicsSceneMove - Widget was moved (QGraphicsSceneMoveEvent).";
        case QEvent::GraphicsSceneResize: return "QEvent::GraphicsSceneResize - Widget was resized (QGraphicsSceneResizeEvent).";
        case QEvent::GraphicsSceneWheel: return "QEvent::GraphicsSceneWheel - Mouse wheel rolled in a graphics scene (QGraphicsSceneWheelEvent).";
        case QEvent::Hide: return "QEvent::Hide - Widget was hidden (QHideEvent).";
        case QEvent::HideToParent: return "QEvent::HideToParent - A child widget has been hidden.";
        case QEvent::HoverEnter: return "QEvent::HoverEnter - The mouse cursor enters a hover widget (QHoverEvent).";
        case QEvent::HoverLeave: return "QEvent::HoverLeave - The mouse cursor leaves a hover widget (QHoverEvent).";
        case QEvent::HoverMove: return "QEvent::HoverMove - The mouse cursor moves inside a hover widget (QHoverEvent).";
        case QEvent::IconDrag: return "QEvent::IconDrag - The main icon of a window has been dragged away (QIconDragEvent).";
        case QEvent::IconTextChange: return "QEvent::IconTextChange - Widget's icon text has been changed.";
        case QEvent::InputMethod: return "QEvent::InputMethod - An input method is being used (QInputMethodEvent).";
        case QEvent::InputMethodQuery: return "QEvent::InputMethodQuery - A input method query event (QInputMethodQueryEvent)";
        case QEvent::KeyboardLayoutChange: return "QEvent::KeyboardLayoutChange - The keyboard layout has changed.";
        case QEvent::KeyPress: return "QEvent::KeyPress - Key press (QKeyEvent).";
        case QEvent::KeyRelease: return "QEvent::KeyRelease - Key release (QKeyEvent).";
        case QEvent::LanguageChange: return "QEvent::LanguageChange - The application translation changed.";
        case QEvent::LayoutDirectionChange: return "QEvent::LayoutDirectionChange - The direction of layouts changed.";
        case QEvent::LayoutRequest: return "QEvent::LayoutRequest - Widget layout needs to be redone.";
        case QEvent::Leave: return "QEvent::Leave - Mouse leaves widget's boundaries.";
          //case QEvent::LeaveEditFocus: return "QEvent::LeaveEditFocus - An editor widget loses focus for editing. QT_KEYPAD_NAVIGATION must be defined.";
        case QEvent::LeaveWhatsThisMode: return "QEvent::LeaveWhatsThisMode - Send to toplevel widgets when the application leaves \"What's This?\" mode.";
        case QEvent::LocaleChange: return "QEvent::LocaleChange - The system locale has changed.";
        case QEvent::NonClientAreaMouseButtonDblClick: return "QEvent::NonClientAreaMouseButtonDblClick - A mouse double click occurred outside the client area.";
        case QEvent::NonClientAreaMouseButtonPress: return "QEvent::NonClientAreaMouseButtonPress - A mouse button press occurred outside the client area.";
        case QEvent::NonClientAreaMouseButtonRelease: return "QEvent::NonClientAreaMouseButtonRelease - A mouse button release occurred outside the client area.";
        case QEvent::NonClientAreaMouseMove: return "QEvent::NonClientAreaMouseMove - A mouse move occurred outside the client area.";
        case QEvent::MacSizeChange: return "QEvent::MacSizeChange - The user changed his widget sizes (Mac OS X only).";
        case QEvent::MetaCall: return "QEvent::MetaCall - An asynchronous method invocation via QMetaObject::invokeMethod().";
        case QEvent::ModifiedChange: return "QEvent::ModifiedChange - Widgets modification state has been changed.";
        case QEvent::MouseButtonDblClick: return "QEvent::MouseButtonDblClick - Mouse press again (QMouseEvent).";
        case QEvent::MouseButtonPress: return "QEvent::MouseButtonPress - Mouse press (QMouseEvent).";
        case QEvent::MouseButtonRelease: return "QEvent::MouseButtonRelease - Mouse release (QMouseEvent).";
        case QEvent::MouseMove: return "QEvent::MouseMove - Mouse move (QMouseEvent).";
        case QEvent::MouseTrackingChange: return "QEvent::MouseTrackingChange - The mouse tracking state has changed.";
        case QEvent::Move: return "QEvent::Move - Widget's position changed (QMoveEvent).";
        case QEvent::NativeGesture: return "QEvent::NativeGesture - The system has detected a gesture (QNativeGestureEvent).";
        case QEvent::OrientationChange: return "QEvent::OrientationChange - The screens orientation has changes (QScreenOrientationChangeEvent)";
        case QEvent::Paint: return "QEvent::Paint - Screen update necessary (QPaintEvent).";
        case QEvent::PaletteChange: return "QEvent::PaletteChange - Palette of the widget changed.";
        case QEvent::ParentAboutToChange: return "QEvent::ParentAboutToChange - The widget parent is about to change.";
        case QEvent::ParentChange: return "QEvent::ParentChange - The widget parent has changed.";
        case QEvent::PlatformPanel: return "QEvent::PlatformPanel - A platform specific panel has been requested.";
        case QEvent::Polish: return "QEvent::Polish - The widget is polished.";
        case QEvent::PolishRequest: return "QEvent::PolishRequest - The widget should be polished.";
        case QEvent::QueryWhatsThis: return "QEvent::QueryWhatsThis - The widget should accept the event if it has \"What's This?\" help.";
          //case QEvent::ReadOnlyChange: return "QEvent::ReadOnlyChange - Widget's read-only state has changed (since Qt 5.4).";
        case QEvent::RequestSoftwareInputPanel: return "QEvent::RequestSoftwareInputPanel - A widget wants to open a software input panel (SIP).";
        case QEvent::Resize: return "QEvent::Resize - Widget's size changed (QResizeEvent).";
        case QEvent::ScrollPrepare: return "QEvent::ScrollPrepare - The object needs to fill in its geometry information (QScrollPrepareEvent).";
        case QEvent::Scroll: return "QEvent::Scroll - The object needs to scroll to the supplied position (QScrollEvent).";
        case QEvent::Shortcut: return "QEvent::Shortcut - Key press in child for shortcut key handling (QShortcutEvent).";
        case QEvent::ShortcutOverride: return "QEvent::ShortcutOverride - Key press in child, for overriding shortcut key handling (QKeyEvent).";
        case QEvent::Show: return "QEvent::Show - Widget was shown on screen (QShowEvent).";
        case QEvent::ShowToParent: return "QEvent::ShowToParent - A child widget has been shown.";
        case QEvent::SockAct: return "QEvent::SockAct - Socket activated, used to implement QSocketNotifier.";
        case QEvent::StateMachineSignal: return "QEvent::StateMachineSignal - A signal delivered to a state machine (QStateMachine::SignalEvent).";
        case QEvent::StateMachineWrapped: return "QEvent::StateMachineWrapped - The event is a wrapper for, i.e., contains, another event (QStateMachine::WrappedEvent).";
        case QEvent::StatusTip: return "QEvent::StatusTip - A status tip is requested (QStatusTipEvent).";
        case QEvent::StyleChange: return "QEvent::StyleChange - Widget's style has been changed.";
        case QEvent::TabletMove: return "QEvent::TabletMove - Wacom tablet move (QTabletEvent).";
        case QEvent::TabletPress: return "QEvent::TabletPress - Wacom tablet press (QTabletEvent).";
        case QEvent::TabletRelease: return "QEvent::TabletRelease - Wacom tablet release (QTabletEvent).";
        case QEvent::OkRequest: return "QEvent::OkRequest - Ok button in decoration pressed. Supported only for Windows CE.";
        case QEvent::TabletEnterProximity: return "QEvent::TabletEnterProximity - Wacom tablet enter proximity event (QTabletEvent), sent to QApplication.";
        case QEvent::TabletLeaveProximity: return "QEvent::TabletLeaveProximity - Wacom tablet leave proximity event (QTabletEvent), sent to QApplication.";
        case QEvent::ThreadChange: return "QEvent::ThreadChange - The object is moved to another thread. This is the last event sent to this object in the previous thread. See QObject::moveToThread().";
        case QEvent::Timer: return "QEvent::Timer - Regular timer events (QTimerEvent).";
        case QEvent::ToolBarChange: return "QEvent::ToolBarChange - The toolbar button is toggled on Mac OS X.";
        case QEvent::ToolTip: return "QEvent::ToolTip - A tooltip was requested (QHelpEvent).";
        case QEvent::ToolTipChange: return "QEvent::ToolTipChange - The widget's tooltip has changed.";
        case QEvent::TouchBegin: return "QEvent::TouchBegin - Beginning of a sequence of touch-screen or track-pad events (QTouchEvent).";
        case QEvent::TouchCancel: return "QEvent::TouchCancel - Cancellation of touch-event sequence (QTouchEvent).";
        case QEvent::TouchEnd: return "QEvent::TouchEnd - End of touch-event sequence (QTouchEvent).";
        case QEvent::TouchUpdate: return "QEvent::TouchUpdate - Touch-screen event (QTouchEvent).";
        case QEvent::UngrabKeyboard: return "QEvent::UngrabKeyboard - Item loses keyboard grab (QGraphicsItem only).";
        case QEvent::UngrabMouse: return "QEvent::UngrabMouse - Item loses mouse grab (QGraphicsItem only).";
        case QEvent::UpdateLater: return "QEvent::UpdateLater - The widget should be queued to be repainted at a later time.";
        case QEvent::UpdateRequest: return "QEvent::UpdateRequest - The widget should be repainted.";
        case QEvent::WhatsThis: return "QEvent::WhatsThis - The widget should reveal \"What's This?\" help (QHelpEvent).";
        case QEvent::WhatsThisClicked: return "QEvent::WhatsThisClicked - A link in a widget's \"What's This?\" help was clicked.";
        case QEvent::Wheel: return "QEvent::Wheel - Mouse wheel rolled (QWheelEvent).";
        case QEvent::WinEventAct: return "QEvent::WinEventAct - A Windows-specific activation event has occurred.";
        case QEvent::WindowActivate: return "QEvent::WindowActivate - Window was activated.";
        case QEvent::WindowBlocked: return "QEvent::WindowBlocked - The window is blocked by a modal dialog.";
        case QEvent::WindowDeactivate: return "QEvent::WindowDeactivate - Window was deactivated.";
        case QEvent::WindowIconChange: return "QEvent::WindowIconChange - The window's icon has changed.";
        case QEvent::WindowStateChange: return "QEvent::WindowStateChange - The window's state (minimized, maximized or full-screen) has changed (QWindowStateChangeEvent).";
        case QEvent::WindowTitleChange: return "QEvent::WindowTitleChange - The window title has changed.";
        case QEvent::WindowUnblocked: return "QEvent::WindowUnblocked - The window is unblocked after a modal dialog exited.";
        case QEvent::WinIdChange: return "QEvent::WinIdChange - The window system identifer for this native widget has changed.";
        case QEvent::ZOrderChange: return "QEvent::ZOrderChange - The widget's z-order has changed. This event is never sent to top level windows.";
        default:
          return QString("%1").arg(t);
      }
    }
  private:
    enum UrlStack {
      URL_VIEW = 0,
      PROGRESS_VIEW
    };
  private:
    QString _filename;
    QWebElement _lastFocused; // cache for last focussed element
    QString _keyStrokes; // collect key strokes
    bool _typing; // user is typing
    bool _inEventFilter; // actually handling event filter
    Script _setupScript;
    QMap<QString, ScriptFile*> _testscripts;
    QProgressBar* _progress = nullptr;
};

#endif // TESTGUI_HXX
