Subversion Repositories Code-Repo

Rev

Blame | Last modification | View Log | RSS feed

#include "MacroWidget.h"

MacroWidget::MacroWidget(QWidget *parent) : QWidget(parent)
{
    sigmapTransmit = new QSignalMapper();
    sigmapKeybind = new QSignalMapper();

    btnExport = new QPushButton("&Export");
    btnImport = new QPushButton("&Import");
    btnAddMacro = new QPushButton("&Add");
    btnRemoveMacro = new QPushButton("&Remove");
    btnClear = new QPushButton("&Clear");
    currKeyBindInfo = QPair<QPushButton*,int>(NULL, 0);

    mainLayout = new QGridLayout();

    macroCount = 0;
    connected = false;
    lastKnownFilePath = ".";

    ioLayout = new QHBoxLayout();
    ioLayout->addWidget(btnAddMacro);
    ioLayout->addWidget(btnRemoveMacro);
    ioLayout->addWidget(btnClear);
    ioLayout->addWidget(btnExport);
    ioLayout->addWidget(btnImport);
    ioLayout->addStretch();
    mainLayout->addLayout(ioLayout, 0, 0, 1, 2);

    for (int i = 0; i < MACRO_DEFAULT_COUNT; i++) {
        Macro_AddEntry();
    }

    setLayout(mainLayout);

    connect(btnAddMacro, SIGNAL(clicked()), this, SLOT(Macro_AddEntry()));
    connect(btnRemoveMacro, SIGNAL(clicked()), this, SLOT(Macro_RemoveEntry()));
    connect(btnClear, SIGNAL(clicked()), this, SLOT(Macro_Clear()));
    connect(btnExport, SIGNAL(clicked()), this, SLOT(Macro_WriteToFile()));
    connect(btnImport, SIGNAL(clicked()), this, SLOT(Macro_ReadFromFile()));
    connect(sigmapTransmit, SIGNAL(mapped(QWidget*)), this, SLOT(Macro_InitTransmit(QWidget*)));
    connect(sigmapKeybind, SIGNAL(mapped(int)), this, SLOT(Macro_KeybindPrompt(int)));

    // Register global event process for keyboard shortcut handling
    qApp->installEventFilter(this);
}

MacroWidget::~MacroWidget()
{

}

QSize MacroWidget::sizeHint() const
{
    return this->minimumSizeHint();
}

void MacroWidget::Macro_EnableTransmit()
{
    connected = true;
    for (int i = 0; i < macroBtnSendList.size(); i++) {
        macroBtnSendList[i]->setEnabled(true);
    }
}

void MacroWidget::Macro_DisableTransmit()
{
    connected = false;
    for (int i = 0; i < macroBtnSendList.size(); i++) {
        macroBtnSendList[i]->setEnabled(false);
    }
}

void MacroWidget::Macro_InitTransmit(QWidget *t)
{
    if (connected) {
        QTextEdit *text = qobject_cast<QTextEdit*>(t);
        emit Macro_TransmitText(text->toPlainText().toUtf8());
    }
}

void MacroWidget::Macro_KeybindPrompt(int id)
{
    QPushButton *btn = qobject_cast<QPushButton*>(sigmapKeybind->mapping(id));

    // Check to make sure we arn't processing another key first
    if (currKeyBindInfo.first != NULL) {
        currKeyBindInfo.first->setText("Hotkey: None");
        currKeyBindInfo.first->setDown(false);
        currKeyBindInfo.first->removeEventFilter(this);
    }

    // Mark and save button as waiting for key sequence
    btn->setDown(true);
    btn->setText("Waiting for Key..");
    currKeyBindInfo = QPair<QPushButton*,int>(btn, id);

    // Remove current keybinding for this macro
    int index = registeredKeyMacroIDs.indexOf(id);
    if (index >= 0) {
        registeredKeySequences.removeAt(index);
        registeredKeyMacroIDs.removeAt(index);
    }

    // Inspect all following keyboard events
    btn->installEventFilter(this);
}

void MacroWidget::Macro_AddEntry()
{
    macroCount++;

    // Create new layout/widgets
    QLineEdit *lineEdit = new QLineEdit(QString("Macro %1").arg(macroCount));
    lineEdit->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed);
    lineEdit->setMinimumWidth(100);
    lineEdit->setAlignment(Qt::AlignCenter);
    macroNameList.append(lineEdit);

    QTextEdit *textEdit = new QTextEdit();
    textEdit->setMinimumHeight(50);
    textEdit->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
    textEdit->setWordWrapMode(QTextOption::NoWrap);
    textEdit->setFont(QFont("Consolas", 8));
    textEdit->setAcceptRichText(false);
    textEdit->setTabChangesFocus(true);
    macroValueList.append(textEdit);

    QPushButton *keyButton = new QPushButton("Hotkey: None");
    keyButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
    macroBtnKeyList.append(keyButton);

    QPushButton *pushButton = new QPushButton("Send Macro");
    pushButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
    pushButton->setEnabled(connected);
    macroBtnSendList.append(pushButton);

    QVBoxLayout *tmpLayout = new QVBoxLayout();
    tmpLayout->addWidget(lineEdit);
    tmpLayout->addWidget(keyButton);
    tmpLayout->addWidget(pushButton);

    // Add layout/widgets to main layout
    mainLayout->addLayout(tmpLayout, macroCount, 0);
    mainLayout->addWidget(textEdit, macroCount, 1);
    mainLayout->setColumnStretch(1, 1);

    // Associate KeyButton with its corresponding ID
    sigmapKeybind->setMapping(keyButton, macroCount-1);
    connect(keyButton, SIGNAL(clicked()), sigmapKeybind, SLOT(map()));

    // Associate PushButton with its corresponding TextEdit
    sigmapTransmit->setMapping(pushButton, textEdit);
    connect(pushButton, SIGNAL(clicked()), sigmapTransmit, SLOT(map()));

    QLayoutItem *item = mainLayout->itemAtPosition(1, 1);
    int height = item->widget()->height() + mainLayout->verticalSpacing();

    QDockWidget *parent = qobject_cast<QDockWidget*>(this->parent());
    if (parent->isFloating())
        parent->resize(parent->width(), parent->height() + height);

    btnRemoveMacro->setEnabled(true);
}

void MacroWidget::Macro_RemoveEntry()
{

    // Remove and delete layout/widgets at last macro slot
    QLayoutItem *item = mainLayout->itemAtPosition(macroCount, 0);
    while(!item->isEmpty())
        delete item->layout()->takeAt(0)->widget();
    delete item;

    item = mainLayout->itemAtPosition(macroCount, 1);
    delete item->widget();

    item = mainLayout->itemAtPosition(1, 1);
    int height = item->widget()->height() + mainLayout->verticalSpacing();

    // Unmap and remove widgets from lists
    QPushButton *pushButton = macroBtnSendList.back();
    sigmapTransmit->removeMappings(pushButton);

    QPushButton *keyButton = macroBtnKeyList.back();
    sigmapKeybind->removeMappings(keyButton);

    macroNameList.pop_back();
    macroValueList.pop_back();
    macroBtnSendList.pop_back();
    macroBtnKeyList.pop_back();

    int index = registeredKeyMacroIDs.indexOf(macroCount-1);
    if (index >= 0) {
        registeredKeySequences.removeAt(index);
        registeredKeyMacroIDs.removeAt(index);
    }

    macroCount--;

    QDockWidget *parent = qobject_cast<QDockWidget*>(this->parent());
    if (parent->isFloating())
        parent->resize(parent->width(), parent->height() - height);

    if (macroCount == 1)
        btnRemoveMacro->setEnabled(false);
}

void MacroWidget::Macro_Clear()
{
    for (int i = 0; i < macroCount; i++) {
        macroNameList[i]->setText(QString("Macro %1").arg(i+1));
        macroValueList[i]->clear();
        macroBtnKeyList[i]->setText("Hotkey: None");
    }
    registeredKeyMacroIDs.clear();
    registeredKeySequences.clear();
}

void MacroWidget::Macro_WriteToFile()
{
    QString file = QFileDialog::getSaveFileName(this, "Settings XML File", lastKnownFilePath, "XML File (*.xml)");
    QCoreApplication::processEvents(); // Wait for dialog to close

    if (file.size() == 0) return;

    // If file was selected, save directory for next time
    QFileInfo fileInfo = QFileInfo(file);
    lastKnownFilePath = fileInfo.absolutePath();

    if (file.size() != 0)
        QFile::remove(file);

    QFile inputFile(file);
    if (!inputFile.open(QIODevice::ReadWrite | QIODevice::Text)) return;

    QXmlStreamWriter stream(&inputFile);
    stream.setAutoFormatting(true);

    stream.writeStartDocument();
    stream.writeStartElement("Settings");
    stream.writeStartElement("Macro");

    for (int i = 0; i < macroCount; i++) {
        stream.writeStartElement("Macro_Entry");

        stream.writeAttribute("Name", macroNameList[i]->text());
        stream.writeTextElement("Value", macroValueList[i]->toPlainText());

        int index = registeredKeyMacroIDs.indexOf(i);
        if (index >= 0) {
            stream.writeTextElement("Keybinding", registeredKeySequences[index].toString());
        } else {
            stream.writeTextElement("Keybinding", "");
        }

        stream.writeEndElement(); // Macro Entry
    }

    stream.writeEndElement(); // Macro
    stream.writeEndElement(); // Settings
    stream.writeEndDocument();

    inputFile.close();
}

void MacroWidget::Macro_ReadFromFile()
{
    int counter = 0;

    QString file = QFileDialog::getOpenFileName(this, "Settings XML File", lastKnownFilePath, "XML File (*.xml)");
    QCoreApplication::processEvents(); // Wait for dialog to close

    if (file.size() == 0) return;

    Macro_Clear();

    // If file was selected, save directory for next time
    QFileInfo fileInfo = QFileInfo(file);
    lastKnownFilePath = fileInfo.absolutePath();

    QFile inputFile(file);
    if (!inputFile.open(QIODevice::ReadWrite | QIODevice::Text)) return;

    QXmlStreamReader stream(&inputFile);

    // Parse the XML file till we reach the end  (or errors)
    while (!stream.atEnd() && !stream.hasError()) {
        QXmlStreamReader::TokenType token = stream.readNext();

        // Ignore StartDocument
        if (token == QXmlStreamReader::StartDocument) continue;

        // Parse StartElements
        if (token == QXmlStreamReader::StartElement) {

            // Ignore elements <Settings> and <Macro>
            if (stream.name() == "Settings") continue;
            if (stream.name() == "Macro") continue;

            // Parse element <Macro_Entry>
            if (stream.name() == "Macro_Entry") {
                QString name, value, keybinding;

                // Read and save attribute value
                QXmlStreamAttributes attr = stream.attributes();
                if (attr.hasAttribute("Name"))
                    name = attr.value("Name").toString();

                stream.readNext();

                // Loop in this element to find all sub-elements
                while (!(stream.tokenType() == QXmlStreamReader::EndElement && stream.name() == "Macro_Entry")) {

                    // Parse and save element value
                    if (stream.tokenType() == QXmlStreamReader::StartElement) {
                        if (stream.name() == "Value") {
                            stream.readNext();
                            value = stream.text().toString();
                        }
                        if (stream.name() == "Keybinding") {
                            stream.readNext();
                            keybinding = stream.text().toString();
                        }
                    }
                    stream.readNext();
                }

                // Write values to GUI
                if (counter == macroCount)
                    Macro_AddEntry();

                macroNameList[counter]->setText(name);
                macroValueList[counter]->setText(value);
                if (keybinding != "") {
                    registeredKeySequences.append(QKeySequence(keybinding));
                    registeredKeyMacroIDs.append(counter);
                    macroBtnKeyList[counter]->setText("Hotkey: " + keybinding);
                }
                counter++;
            }
        }
    }
}

bool MacroWidget::eventFilter(QObject *obj, QEvent *event)
{
    // Only process keyboard events
    if (event->type() == QEvent::KeyPress) {
        QKeyEvent *keyevent = static_cast<QKeyEvent*>(event);
        QKeySequence seq = keyevent->modifiers() + keyevent->key();
        if ((keyevent->key() >= 0x21 && keyevent->key() <= 0x2F) ||
            (keyevent->key() >= 0x3A && keyevent->key() <= 0x40) ||
            (keyevent->key() >= 0x5E && keyevent->key() <= 0x7E) ) {
            seq = keyevent->key();
        }

        if (connected) {
            // First check if key sequence matches any saved ones
            if (!registeredKeySequences.isEmpty()) {
                int index = registeredKeySequences.indexOf(seq);
                if (index >= 0) {
                    Macro_InitTransmit(macroValueList[registeredKeyMacroIDs[index]]);
                    return true;
                }
            }
        }

        // Then save key sequence if needed
        if (currKeyBindInfo.first != NULL) {
            // Ignore modifier keys and locks
            if (keyevent->key() == Qt::Key_Shift || keyevent->key() == Qt::Key_Control ||
                keyevent->key() == Qt::Key_Meta || keyevent->key() == Qt::Key_Alt ||
                keyevent->key() == Qt::Key_AltGr || keyevent->key() == Qt::Key_CapsLock ||
                keyevent->key() == Qt::Key_NumLock || keyevent->key() == Qt::Key_ScrollLock)
                return true;

            // Reset on ESC key
            if (keyevent->key() == Qt::Key_Escape) {
                currKeyBindInfo.first->setText("Hotkey: None");
                currKeyBindInfo.first->setDown(false);
                currKeyBindInfo.first->removeEventFilter(this);
                currKeyBindInfo = QPair<QPushButton*, int>(NULL, 0);
                return true;
            }

            // Otherwise save key sequence if it doesnt already exist
            if (!registeredKeySequences.contains(seq)) {
                registeredKeySequences.append(seq);
                registeredKeyMacroIDs.append(currKeyBindInfo.second);
                currKeyBindInfo.first->setText("Hotkey: " + seq.toString());
                currKeyBindInfo.first->setDown(false);
                currKeyBindInfo.first->removeEventFilter(this);
                currKeyBindInfo = QPair<QPushButton*, int>(NULL, 0);
                return true;
            }
        }
    }

    return QWidget::eventFilter(obj, event);
}