/****************************************************************************
    Kalkulon - A programmable calculator for programmers

    This file is part of the Kalkulon GUI.

    If you find this source code useful, your are encouraged to use it in your own projects.
    Please give me credit!

    COPYRIGHT by Juergen Holetzeck 2003 - 2007
    e-mail:		contact@kalkulon.de
    homepage:	www.kalkulon.de
****************************************************************************/

#include <QtGui>
#include <QMenu>
#include <QMessageBox>
#include <QSystemTrayIcon>
#include <sstream>

#include "KalkulonDlg.h"
#include "OptionsDlg.h"

#define DEBUG_GUI 0

// on Windows platform use CHM help, on other platforms open browser with plain html
#ifdef Q_OS_WIN
    #define KALKULON_HELPFILE  "/doc/kalkulon.chm"
#else
    #define KALKULON_HELPFILE  "/doc/html/main.htm"
#endif


CKalkulonDlg::CKalkulonDlg(QWidget *parent) : QMainWindow(parent)
{
    // initialize core application settings and tray icon
    QCoreApplication::setOrganizationName("Kalkulon");
    QCoreApplication::setOrganizationDomain("kalkulon.de");
    QCoreApplication::setApplicationName("Kalkulon");
    if(QSystemTrayIcon::isSystemTrayAvailable())
    {
        trayIcon = new QSystemTrayIcon(QIcon(QString::fromUtf8(":/res/Kalkulon16.png")), this);
        connect(trayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)),
             this, SLOT(trayIconActivated(QSystemTrayIcon::ActivationReason)));
    }
    else
        trayIcon = NULL;

    // do base UI setup
    Ui::CKalkulonDlg::setupUi(this);
    setWindowTitle(QString(Kalkulon::version::title_str));//+" "+QString(Kalkulon::version::version_str));

    // create actions,menues etc.
    createActionsAndMenus();

    // register SyntaxHighlighter
    shlInput_ = new SyntaxHighlighter(inputWin->document());
    shlOutput_ = new SyntaxHighlighter(outputWin->document());
    shlBase_ = new SyntaxHighlighter(baseWin->document());

    // register event filter for special key handling for inputWin
    inputWin->installEventFilter(this);
    menuBtn->installEventFilter(this);

    // initialize attributes ...
    doBracketMatchingIsRunning_ = false;
    inputIsMarked_ = false;
    lastCursorPos_ = -1;
    isExpanded_ = true;
    isPinned_ = false;
    iniRect_ = QRect(50,50,500,250);
    fontsize_ = centralwidget->fontInfo().pointSize();
    // ... and read application settings ...
    readSettings();
    // ... to do final UI setup
    setupUIpart2();

    ////////////////////////////////
    // initalize Kalkulon core stuff
    // register print callback for Kalkulon core
        setlocale(LC_ALL, "C"); // since Qt4.5 it's changed by QApplication

    mycallback_ = new MyCallback(this);
    evaluator_.callbackOb = mycallback_;
    // reset everything and load autoload.k
    clearAll();
}

void CKalkulonDlg::setupUIpart2()
{
    // first update expand mode
    isExpanded_ = !isExpanded_; on_shiftBtn_clicked();

    // and set size and position
    #ifdef Q_WS_X11
    // recommended way for X11
    resize(iniRect_.size());
    move(iniRect_.topLeft());
    #else
    // "normal" way, e.g. for Win
    setGeometry(iniRect_);
    #endif

    // set small minimum size so window can be shrunk and expanded by shift button
    centralwidget->setMinimumWidth(10);
    // calculate widget sizes
    setNewPointSize();

    // set static tooltips
    zoominBtn->setToolTip(tr("Enlarge font size"));
    zoomoutBtn->setToolTip(tr("Reduce font size"));
    if(trayIcon!=NULL) trayIcon->setToolTip(Kalkulon::version::title_str);

    // label calcBtn
    lockInput(false);

    // init input queue
    clearInput();

    // set background colors
    bgcolor_ = QApplication::palette().color(QPalette::Disabled, QPalette::Window).name();
    printBase(0);
    printLabel();

    // ... and update pin mode
    isPinned_ = !isPinned_; on_pinBtn_clicked();
}

void CKalkulonDlg::createActionsAndMenus()
{
    openHelpAction_ = new QAction(QIcon(":/res/help.png"), tr("&Help"), this);
    connect(openHelpAction_, SIGNAL(triggered()), this, SLOT(openHelp()));

    clearAllAction_ = new QAction(tr("&Clear all"), this);
    connect(clearAllAction_, SIGNAL(triggered()), this, SLOT(clearAll()));

    openOptionsDlgAction_ = new QAction(tr("&Options ..."), this);
    connect(openOptionsDlgAction_, SIGNAL(triggered()), this, SLOT(openOptionsDlg()));

    aboutAction_ = new QAction(tr("&About"), this);
    connect(aboutAction_, SIGNAL(triggered()), this, SLOT(about()));

    aboutQtAction_ = new QAction(tr("About &Qt"), this);
    connect(aboutQtAction_, SIGNAL(triggered()), qApp, SLOT(aboutQt()));


    mainMenu_ = new QMenu(this);
    mainMenu_->addAction(openHelpAction_);
    mainMenu_->addAction(clearAllAction_);
    mainMenu_->addAction(openOptionsDlgAction_);
    mainMenu_->addSeparator();
    mainMenu_->addAction(aboutAction_);
    mainMenu_->addAction(aboutQtAction_);


    shortcutCopyLastResult = new QShortcut(QKeySequence(tr("Ctrl+Q")), this, SLOT(shortcutCopyLastResultActivated()));

}


void CKalkulonDlg::closeEvent(QCloseEvent *event)
{
    if(calculationIsRunning_)
    {
        evaluator_.BreakSig();
        event->ignore();
    }
    else
    {
        writeSettings();
        if(trayIcon!=NULL) trayIcon->hide();	// neccessary at least for GNOME, otherwise you can't exit
        event->accept();
    }
}

void CKalkulonDlg::writeSettings()
{
    QSettings settings(QSettings::IniFormat, QSettings::UserScope,
        QCoreApplication::organizationName(),
        QCoreApplication::applicationName());

    settings.setValue("configVersion", 1);

    settings.beginGroup("MainWindow");
        settings.setValue("geometry", geometry());
        settings.setValue("fontsize", fontsize_);
        settings.setValue("isPinned", isPinned_);
        settings.setValue("isExpanded", isExpanded_);
        settings.setValue("hasTrayIcon", (trayIcon!=NULL && trayIcon->isVisible()));
    settings.endGroup();

    settings.beginGroup("SyntaxHighlighter");
        // write SytaxHighlighter settings only once, e.g. for outputWin!
        shlOutput_->writeSettings(&settings);
    settings.endGroup();

}

void CKalkulonDlg::readSettings()
{
    QSettings settings(QSettings::IniFormat, QSettings::UserScope,
        QCoreApplication::organizationName(),
        QCoreApplication::applicationName());

    settings.beginGroup("MainWindow");
        iniRect_	= settings.value("geometry", iniRect_).toRect();
        fontsize_	= settings.value("fontsize", fontsize_).toInt();
        isPinned_	= settings.value("isPinned", isPinned_).toBool();
        isExpanded_	= settings.value("isExpanded", isExpanded_).toBool();
        if(trayIcon!=NULL) trayIcon->setVisible(settings.value("hasTrayIcon", false).toBool());
    settings.endGroup();

    settings.beginGroup("SyntaxHighlighter");
        shlInput_->readSettings(&settings);
        shlOutput_->readSettings(&settings);
        shlBase_->readSettings(&settings);
    settings.endGroup();
}

void CKalkulonDlg::setNewPointSize()
{
    int left, top, right, bottom;

    if(fontsize_>MAXPOINTSIZE)
        fontsize_=MAXPOINTSIZE;
    else
        if(fontsize_<MINPOINTSIZE)
            fontsize_=MINPOINTSIZE;

    QFont newFont(centralwidget->font());
    newFont.setPointSize(fontsize_);
    centralwidget->setFont(newFont);

    QFont fixedPitchFont("Courier", fontsize_);
    fixedPitchFont.setFixedPitch(true);
    baseWin->setFont(fixedPitchFont);

    // make input window height same as of a QLineEdit, see QLineEdit::sizeHint()
#if QT_VERSION >= 0x040500
    const int textEditMargin = 8;
#else
    const int textEditMargin = 4;
#endif
    QFontMetrics fm(newFont);
    inputWin->getContentsMargins(&left, &top, &right, &bottom);
    int inputHeight = qMax(fm.lineSpacing(), 14) + textEditMargin + top + bottom;
    inputWin->setFixedHeight(inputHeight);

    // try to make a good guess how large the base window should be ...
    QFontMetrics fmFixed(fixedPitchFont);
    QSize baseWinSize = fmFixed.size(0, "1.23456789012\n1\n0xABCDEF12\n0b00000000000000000000000000000000");
    baseWin->getContentsMargins(&left, &top, &right, &bottom);
    int fixedWidth  = baseWinSize.width()  + textEditMargin + left + right;
    int fixedHeight = baseWinSize.height() + textEditMargin + top  + bottom;

    // set size of widgets on right column
    baseWin-> setFixedSize(fixedWidth, fixedHeight);
    labelWin->setFixedWidth(fixedWidth);
    calcBtn-> setFixedWidth(fixedWidth);

    #if DEBUG_GUI
    print(QString("fontSize = %1").arg(fontsize_));
    print(QString("inputHeight = %1").arg(inputHeight));
    print(QString("fmLineSpacing = %1").arg(fm.lineSpacing()));


    print(QString("fixedWidth = %1").arg(fixedWidth));
    print(QString("fixedHeight = %1").arg(fixedHeight));
    #endif
}

void CKalkulonDlg::shortcutCopyLastResultActivated()
{
    QClipboard *clipboard = QApplication::clipboard();
    clipboard->setText(lastResult);
}

void CKalkulonDlg::on_calcBtn_clicked()
{
    if(calculationIsRunning_)
        evaluator_.BreakSig();
    else
        if(!inputWin->toPlainText().isEmpty()) doCalculation( inputWin->toPlainText());
}

void CKalkulonDlg::on_shiftBtn_clicked()
{
    isExpanded_ = !isExpanded_;

    // size to shrink and expand window
    int adjustwidth = baseWin->width();

    #if DEBUG_GUI
    print(QString("adjustwidth = %1").arg(adjustwidth));
    #endif

    if(isExpanded_)
    {
        shiftBtn->setIcon(QIcon(":/res/lshifter.png"));
        shiftBtn->setToolTip(tr("Hide labels and number formats"));
        labelWin->setVisible(true);
        baseWin-> setVisible(true);
        calcBtn-> setVisible(true);
        resize(width()+adjustwidth, height());
    }
    else
    {
        shiftBtn->setIcon(QIcon(":/res/rshifter.png"));
        shiftBtn->setToolTip(tr("Show labels and number formats"));
        labelWin->setVisible(false);
        baseWin-> setVisible(false);
        calcBtn-> setVisible(false);
        resize(width()-adjustwidth, height());
    }
}

void CKalkulonDlg::on_pinBtn_clicked()
{
    isPinned_ = !isPinned_;

    #if DEBUG_GUI
    print(QString("windowFlags = %1").arg(windowFlags(),8,16));
    #endif
    if(isPinned_)
    {
        pinBtn->setIcon(QIcon(":/res/pinon.png"));
        pinBtn->setToolTip(tr("Leave <i>always in foreground</i> mode"));
        setWindowFlags(windowFlags() | Qt::WindowStaysOnTopHint);
    }
    else
    {
        pinBtn->setIcon(QIcon(":/res/pinoff.png"));
        pinBtn->setToolTip(tr("Switch to <i>always in foreground</i> mode"));
        setWindowFlags(windowFlags() & ~Qt::WindowStaysOnTopHint);
    }
    #if DEBUG_GUI
    print(QString("windowFlags = %1").arg(windowFlags(),8,16));
    #endif

    // update window style now
    show();
}

void CKalkulonDlg::on_zoominBtn_clicked()
{
    ++fontsize_;
    setNewPointSize();
}

void CKalkulonDlg::on_zoomoutBtn_clicked()
{
    --fontsize_;
    setNewPointSize();
}

void CKalkulonDlg::on_menuBtn_clicked()
{
    execMenu();
}

void CKalkulonDlg::on_inputWin_cursorPositionChanged()
{
    if(!doBracketMatchingIsRunning_)
    {
        lastCursorPos_ = inputWin->textCursor().position();
        doBracketMatching();
        #if DEBUG_GUI
        print(QString("cursorPositionChanged = %1").arg(lastCursorPos_));
        #endif
    }
}

void CKalkulonDlg::on_inputWin_textChanged()
{
    if(!doBracketMatchingIsRunning_)
    {
        int pos = inputWin->textCursor().position();
        if(lastCursorPos_ != pos)
        {
            doBracketMatching();
            #if DEBUG_GUI
            print(QString("textChanged = %1").arg(pos));
            #endif
        }
        lastCursorPos_ = -1;
    }
}


void CKalkulonDlg::trayIconActivated(QSystemTrayIcon::ActivationReason reason)
{
    switch (reason)
    {
        case QSystemTrayIcon::Trigger:
            if(!isVisible() || isMinimized())
            {
                showNormal();
                activateWindow();
                raise();
            }
            else
                hide();
            break;
        default:
         /* empty */;
    }

}

void CKalkulonDlg::openHelp()
{
    QString helpUrl(QCoreApplication::applicationDirPath () + KALKULON_HELPFILE);

    bool ok=(QFileInfo(helpUrl).exists() && QDesktopServices::openUrl("file:"+helpUrl));

    if(!ok)
        QMessageBox::warning(this, tr("Online help not found"),
            QString(tr("Unable to open online help file\n%1")).arg(helpUrl));
}

void CKalkulonDlg::openOptionsDlg()
{
    OptionsDlg *dlg = new OptionsDlg(this);
    // tab 1
    if(trayIcon==NULL)
        dlg->checkBoxTrayIcon->setEnabled(false);
    else
        dlg->checkBoxTrayIcon->setCheckState(trayIcon->isVisible()?Qt::Checked:Qt::Unchecked);
    // tab 2
    dlg->setFontSize(fontsize_);
    dlg->setShlSettings(shlOutput_->settings());

    if(QDialog::Accepted == dlg->exec())
    {
        // tab 1
        if(trayIcon!=NULL) trayIcon->setVisible(dlg->checkBoxTrayIcon->checkState());

        // tab 2
        shlOutput_->setSettings(dlg->getShlSettings());
        shlInput_->setSettings(dlg->getShlSettings());
        shlBase_->setSettings(dlg->getShlSettings());
    }

    delete dlg;
}


void CKalkulonDlg::clearAll()
{
    if(calculationIsRunning_) return;

    evaluator_.clearAll();
    outputWin->clear();
    // load autoload.k
    try {evaluator_.load("autoload.k");} catch(...) {};
    printLabel();
}

void CKalkulonDlg::about()
{
    QString aboutText =
        QString("<b>Kalkulon - A programmable calculator for programmers</b><br><br>"
                "%1 %2 - %3<br><br><A HREF=http://%3>%4</A><br><A HREF=mailto:%4>%5</A><br><br>%6").
        arg(Kalkulon::version::title_str).
        arg(Kalkulon::version::version_str).
        arg(Kalkulon::version::compiler_version_str).
        arg(Kalkulon::version::web_str).
        arg(Kalkulon::version::email_str).
        arg(Kalkulon::version::copyright_str);


    QMessageBox::about(this, tr("About Kalkulon"), aboutText);
}

void CKalkulonDlg::execMenu()
{
    mainMenu_->exec(QPoint(geometry().x()+10, geometry().y())+menuBtn->pos());
}

void CKalkulonDlg::keyPressEvent(QKeyEvent* event)
{
    switch(event->key())
    {
        case Qt::Key_Escape:
            if(inputWin->toPlainText().isEmpty() || !inputWin->isEnabled())
                if(trayIcon!=NULL && trayIcon->isVisible())
                    hide();
                else
                    showMinimized();
            else
                clearInput();
            break;
        default:
            QWidget::keyPressEvent(event);
    }
}

bool CKalkulonDlg::eventFilter(QObject *target, QEvent *event)
{
    bool eventHandled = false;

    if(target == inputWin && event->type() == QEvent::KeyPress)
    {
        lastCursorPos_ = -1;

        bool keyLeft = false;
        unsigned int key = ((QKeyEvent*) event)->key();
        switch(key)
        {
            case Qt::Key_Return:
            case Qt::Key_Enter:
                // begin calculation
                on_calcBtn_clicked();
                eventHandled = true;
                break;
            case Qt::Key_Up:
            // move up in input history
                if(idxInput_>0)
                {
                    inputWin->setPlainText(inputs_[--idxInput_]);
                    QTextCursor cur=inputWin->textCursor();
                    cur.movePosition(QTextCursor::End);
                    inputWin->setTextCursor(cur);
                }
                eventHandled = true;
                break;
            case Qt::Key_Down:
            // move down in input history
                if(idxInput_+1<inputs_.size())
                {
                    inputWin->setPlainText(inputs_[++idxInput_]);
                    QTextCursor cur=inputWin->textCursor();
                    cur.movePosition(QTextCursor::End);
                    inputWin->setTextCursor(cur);
                }
                else
                {
                    clearInput();
                }
                eventHandled = true;
                break;
            case Qt::Key_Left:
                keyLeft=true;
                // no break;
            case Qt::Key_Right:
            // set 'out' as input
                if(inputWin->toPlainText().isEmpty() && evaluator_.getOutIdx() >= 0)
                {
                    inputWin->setPlainText(Kalkulon::OUT_STR);
                    QTextCursor cur=inputWin->textCursor();
                    if(keyLeft)
                        cur.movePosition(QTextCursor::Start);
                    else
                        cur.movePosition(QTextCursor::End);
                    inputWin->setTextCursor(cur);
                    eventHandled = true;
                }
                break;
            case Qt::Key_Plus:		//'+'
            case Qt::Key_Minus:		//'-'
            case Qt::Key_Asterisk:	//'*'
            case Qt::Key_Slash:		//'/'
            case Qt::Key_Equal:		//'='
            case Qt::Key_Exclam:	//'!'
            case Qt::Key_Greater:	//'>'
            case Qt::Key_Less:		//'<'
            case Qt::Key_Ampersand:	//'&'
            case Qt::Key_Bar:		//'|'
            // set 'out<op>' as input
                if(inputWin->toPlainText().isEmpty() && evaluator_.getOutIdx() >= 0)
                {
                    inputWin->setPlainText(QString(Kalkulon::OUT_STR)+(QChar) key);
                    QTextCursor cur=inputWin->textCursor();
                    cur.movePosition(QTextCursor::End);
                    inputWin->setTextCursor(cur);
                    eventHandled = true;
                }
                break;
            default:
            ;
        }
    }
    else
    if(target == menuBtn && event->type() == QEvent::ToolTip)
    {
        //instead of tooltip open menu
        execMenu();
        eventHandled = true;
    }

    return eventHandled ? true : QMainWindow::eventFilter(target, event);
}

void CKalkulonDlg::clearInput()
{
    idxInput_=inputs_.size();
    inputWin->clear();
}

void CKalkulonDlg::print(QString text, bool input, bool lf)
{
    QTextCursor cur=outputWin->textCursor();
    cur.movePosition(QTextCursor::End);
    outputWin->setTextCursor(cur);

    if(input)
    {
        outputWin->setAlignment(Qt::AlignLeft);
    }
    else
    {
        outputWin->setAlignment(Qt::AlignRight);
    }
    outputWin->insertPlainText(text);
    if(lf) outputWin->insertPlainText("\n");
    cur.movePosition(QTextCursor::End);
    outputWin->setTextCursor(cur);
}

void CKalkulonDlg::printBase(double d)
{
    QString str;
    quint32 i = static_cast<quint32> (d);
    str = 	QString("<body bgcolor=%1> %2<br>%3<br>0x%4<br>0b%5").
            arg(bgcolor_).
            arg(d,0,'g',12).
            arg(static_cast<qint32> (i)).
            arg(i, 8 , 16, QLatin1Char('0')).
            arg(i, 32,  2, QLatin1Char('0'));
    baseWin->setHtml(str);
}

void CKalkulonDlg::printLabel()
{
    bool isEmpty=true;
    QString text;

    labelWin->setHtml(QString("<body bgcolor=%1>").arg(bgcolor_));
    {
        Kalkulon::CRPNStackEval::symbols_t::iterator it,b,e;
        b=evaluator_.symbols.begin();
        e=evaluator_.symbols.end();
        for(it=b; it!=e; ++it)
        {
            QString label=it->first.c_str();
            if(label!=Kalkulon::OUT_LIST)
            {
                if(isEmpty)
                    isEmpty=false;
                else
                    text+=QString("\n");
                text+=label+" = "+(it->second).formattedString().c_str();
            }
        }
    }
    {
        Kalkulon::CRPNStackEval::functions_t::iterator it,b,e;
        b=evaluator_.functions.begin();
        e=evaluator_.functions.end();
        for(it=b; it!=e; ++it)
        {
            QString label=it->first.c_str();
            if(isEmpty)
                isEmpty=false;
            else
                text+=QString("\n");
            text+=(static_cast<std::string> (it->second)).c_str();
        }
    }
    labelWin->insertPlainText(text);
}

void CKalkulonDlg::doCalculation(QString expr)
{
    print(expr);

    QString out;
    std::istringstream is(expr.toLatin1().constData());
    Kalkulon::RPNStack_t rpn_stack;
    int pos=0, len=0;
    bool error=false;

    try{
        // parse expression
        parser_.parse(&is, &rpn_stack);
    }
    catch(Kalkulon::ErrBase &err)
        {
            pos = err.tok.pos;
            len = ((std::string) err.tok).size();
            out = err.GetErrorMessage();
            error=true;
        }
    catch(...)
        {
            pos = 1;
            len = expr.size();
            out = tr("unknown exception in parser occured");
            error=true;
        }
    if(error)
    {
        // select error in input
        if(pos>0)
        {
            QTextCursor cur=inputWin->textCursor();
            cur.setPosition(pos-1);
            cur.setPosition(pos-1+len, QTextCursor::KeepAnchor);
            inputWin->setTextCursor(cur);
        }
    }
    else
    {
        // add new inputs or move existing inputs to end of history
        int idxExpr = inputs_.lastIndexOf(expr);
        if(-1 != idxExpr)
        {
            inputs_.removeAt(idxExpr);
        }
        inputs_+=expr;

        // evaluate expression
        lockInput();

        Kalkulon::DataObject result;
        try
        {
            result = evaluator_.eval(rpn_stack);
        }
        catch(Kalkulon::ErrBase &err)
            {
                pos = err.tok.pos;
                len = ((std::string) err.tok).size();
                out = err.GetErrorMessage();
                error=true;
            }
        catch(...)
         {
            pos = 1;
            len = expr.size();
            out = tr("unknown exception in evaluator occured");
            error=true;
         }

        if(error)
        {
            // select error in input
            if(pos>0)
            {
                QTextCursor cur=inputWin->textCursor();
                cur.setPosition(pos-1);
                cur.setPosition(pos-1+len, QTextCursor::KeepAnchor);
                inputWin->setTextCursor(cur);
            }
        }
        else
        {
            switch(result.getDid())
            {
            case Kalkulon::DataObject::MESSAGE:
                out = result.formattedString().c_str();
                break;
            case Kalkulon::DataObject::FNUMBER:
                printBase((double) result);
                // no break !!!
            default:
                out =
                    QString("%1[%2] = %3").
                    arg(Kalkulon::OUT_LIST).
                    arg(evaluator_.getOutIdx()).
                    arg(lastResult = result.formattedString().c_str());
            }
            clearInput();
        }
    }
    print(out, false);
    printLabel();

    lockInput(false);
}

void CKalkulonDlg::lockInput(bool lock)
{
    calculationIsRunning_ = lock;
    clearAllAction_->setDisabled(lock);
    if(lock)
    {
        calcBtn->setText(tr("Break ..."));
        inputWin->setEnabled(false);
    }
    else
    {
        calcBtn->setText(tr("Calculate"));
        inputWin->setEnabled(true);
        inputWin->setFocus();
    }
}

void CKalkulonDlg::doBracketMatching()
{
    // on each cursor position change try to mark matching parenthesis, brackets etc.
    static const char symbols[][2]=
    {
        {'(',')'},
        {'{','}'},
        {'[',']'},
    };

    // avoid unlimited recursion,
    // because doBracketMatching itself triggers events
    doBracketMatchingIsRunning_=true;

    bool match = false;
    int match_pos = 0;

    QTextCursor cur=inputWin->textCursor();
    QTextCursor old(cur);
    QString text(inputWin->toPlainText());
    int max_pos = text.length();

    if(max_pos>1)	// there must be at least 2 characters
    {
        int cur_pos = cur.position();

        // check if right side of cursor is one of 'symbols' else move cursor one to the left
        if(cur_pos>0)
        {
            bool move_left=true;
            for(size_t i=0; i<sizeof(symbols)/sizeof(symbols[0]); ++i)
                if(text[cur_pos]==symbols[i][0] || text[cur_pos]==symbols[i][1])
                    {move_left=false; break;}
            if(move_left) --cur_pos;
        }

        // check if right side of cursor is one of 'symbols' and find matching pair
        for(size_t i=0; i<sizeof(symbols)/sizeof(symbols[0]); ++i)
        {
            char ls=symbols[i][0], rs=symbols[i][1];
            match_pos = cur_pos;

            if(match_pos>0 && text[match_pos]==rs)
            {
                // find matching left side symbols
                int depth = 0;
                while(!match && match_pos>0)
                {
                    --match_pos;
                    if(text[match_pos]==rs) {++depth; continue;}
                    if(text[match_pos]==ls)
                    {
                        if(depth)
                            {--depth; continue;}
                        else
                            {match=true; continue;}
                    }
                }
                break;
            }
            else
            if(match_pos+1<max_pos && text[match_pos]==ls)
            {
                // find matching right side symbol
                int depth = 0;
                while(!match && match_pos+1<max_pos)
                {
                    ++match_pos;
                    if(text[match_pos]==ls) {++depth; continue;}
                    if(text[match_pos]==rs)
                    {
                        if(depth)
                            {--depth; continue;}
                        else
                            {match=true; continue;}
                    }
                }
                break;
            }
        }
    }
    // delete old marks
    bool cursor_changed=false;
    if(inputIsMarked_)
    {
        inputIsMarked_ = false;
        cursor_changed = true;

        QTextCharFormat fmt_norm;
        fmt_norm.clearBackground();

        cur.select(QTextCursor::LineUnderCursor);
        cur.setCharFormat(fmt_norm);
        inputWin->setTextCursor(cur);
    }
    // mark matching symbol
    if(match)
    {
        inputIsMarked_ = true;
        cursor_changed = true;

        QTextCharFormat fmt_mark;
        fmt_mark.setBackground(QColor("lightGray"));

        cur.setPosition(match_pos);
        cur.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor);
        cur.setCharFormat(fmt_mark);
        inputWin->setTextCursor(cur);
    }
    if(cursor_changed) inputWin->setTextCursor(old);

    doBracketMatchingIsRunning_=false;
}

