From e49e2a5bf0080f4f494ff35f67877b4b4116ae31 Mon Sep 17 00:00:00 2001 From: heyuanjie Date: Wed, 4 Jan 2017 10:48:00 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E7=AC=AC=E4=B8=80=E7=89=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Modem/Modem.cpp | 62 ++++++++ Modem/Modem.h | 42 +++++ Modem/Ymodem.cpp | 307 ++++++++++++++++++++++++++++++++++++ Modem/Ymodem.h | 88 +++++++++++ Modem/crc.h | 16 ++ Modem/crc16.c | 75 +++++++++ Modem/modem.ui | 48 ++++++ QTermWidget/QTermScreen.cpp | 189 ++++++++++++++++++++++ QTermWidget/QTermScreen.h | 41 +++++ QTermWidget/QTermWidget.cpp | 295 ++++++++++++++++++++++++++++++++++ QTermWidget/QTermWidget.h | 37 +++++ SendSave/SSWorker.cpp | 138 ++++++++++++++++ SendSave/SSWorker.h | 34 ++++ SendSave/SendSave.cpp | 214 +++++++++++++++++++++++++ SendSave/SendSave.h | 55 +++++++ SendSave/SendSave.ui | 66 ++++++++ images/application-exit.png | Bin 0 -> 11200 bytes images/clear.png | Bin 0 -> 12543 bytes images/connect.png | Bin 0 -> 15374 bytes images/disconnect.png | Bin 0 -> 15092 bytes images/settings.png | Bin 0 -> 16039 bytes main.cpp | 65 ++++++++ mainwindow.cpp | 281 +++++++++++++++++++++++++++++++++ mainwindow.h | 124 +++++++++++++++ mainwindow.ui | 185 ++++++++++++++++++++++ settingsdialog.cpp | 209 ++++++++++++++++++++++++ settingsdialog.h | 105 ++++++++++++ settingsdialog.ui | 123 +++++++++++++++ terminal.pro | 36 +++++ terminal.qrc | 9 ++ 30 files changed, 2844 insertions(+) create mode 100644 Modem/Modem.cpp create mode 100644 Modem/Modem.h create mode 100644 Modem/Ymodem.cpp create mode 100644 Modem/Ymodem.h create mode 100644 Modem/crc.h create mode 100644 Modem/crc16.c create mode 100644 Modem/modem.ui create mode 100644 QTermWidget/QTermScreen.cpp create mode 100644 QTermWidget/QTermScreen.h create mode 100644 QTermWidget/QTermWidget.cpp create mode 100644 QTermWidget/QTermWidget.h create mode 100644 SendSave/SSWorker.cpp create mode 100644 SendSave/SSWorker.h create mode 100644 SendSave/SendSave.cpp create mode 100644 SendSave/SendSave.h create mode 100644 SendSave/SendSave.ui create mode 100644 images/application-exit.png create mode 100644 images/clear.png create mode 100644 images/connect.png create mode 100644 images/disconnect.png create mode 100644 images/settings.png create mode 100644 main.cpp create mode 100644 mainwindow.cpp create mode 100644 mainwindow.h create mode 100644 mainwindow.ui create mode 100644 settingsdialog.cpp create mode 100644 settingsdialog.h create mode 100644 settingsdialog.ui create mode 100644 terminal.pro create mode 100644 terminal.qrc diff --git a/Modem/Modem.cpp b/Modem/Modem.cpp new file mode 100644 index 0000000..3e44c8a --- /dev/null +++ b/Modem/Modem.cpp @@ -0,0 +1,62 @@ +#include "Modem.h" +#include "ui_modem.h" + +#include "Ymodem.h" + +Modem::Modem(QWidget *parent) : + QDialog(parent), + ui(new Ui::Modem) +{ + ui->setupUi(this); + + ym = new Ymodem(this); + connect(ym, &ym->showTransfer, this, &this->showTransfer); + connect(ym, &ym->finished, this, &this->closed); +} + +Modem::~Modem() +{ + delete ui; +} + +void Modem::getFile(QString &name) +{ + name = filename; +} + +void Modem::setFile(QString &name) +{ + filename = name; +} + +void Modem::showTransfer(int total, int remain, float speed) +{ + float p; + QString fmt; + + p = ((total - remain)/(float)total) * 100; + fmt = fmt.fromLocal8Bit("%1%").arg(QString::number(p, 'f', 1)); + ui->progress->setValue((int)p); + ui->progress->setFormat(fmt); +} + +void Modem::startTransfer(char type) +{ + show(); + ym->start(); +} + +void Modem::putData(const QByteArray &data) +{ + ym->put(data); +} + +void Modem::showStatus(const char *s) +{ + ui->lbmsg->setText(s); +} + +void Modem::closed() +{ + emit exitTransfer(); +} diff --git a/Modem/Modem.h b/Modem/Modem.h new file mode 100644 index 0000000..23cd9be --- /dev/null +++ b/Modem/Modem.h @@ -0,0 +1,42 @@ +#ifndef MODEM_H +#define MODEM_H + +#include + +namespace Ui { +class Modem; +} + +class Ymodem; + +class Modem : public QDialog +{ + Q_OBJECT + +public: + explicit Modem(QWidget *parent = 0); + ~Modem(); + + void setFile(QString &name); + void getFile(QString &name); + void startTransfer(char type = 'C'); + void showStatus(const char *s); + +public Q_SLOTS: + void putData(const QByteArray &data); + +private Q_SLOTS: + void showTransfer(int total, int remain, float speed); + void closed(); + +Q_SIGNALS: + void outData(const QByteArray &data); + void exitTransfer(); + +private: + Ui::Modem *ui; + Ymodem *ym; + QString filename; +}; + +#endif // MODEM_H diff --git a/Modem/Ymodem.cpp b/Modem/Ymodem.cpp new file mode 100644 index 0000000..b2de4c9 --- /dev/null +++ b/Modem/Ymodem.cpp @@ -0,0 +1,307 @@ +#include "Ymodem.h" +#include "Modem.h" +#include "crc.h" + +#include +#include +#include + +Ymodem::Ymodem(Modem *parent) +{ + Stage = msFirst; + sn = 0; + isrun = false; + ui = parent; +} + +void Ymodem::close() +{ + isrun = false; + if (!isFinished()) + { + wait(); + } +} + +void Ymodem::time_start() +{ + stx_time = time(NULL); +} + +float Ymodem::speed_clc(int total, int remain) +{ + float s = 0; + int t; + + t = (int)(time(NULL) - stx_time); + s = (total - remain)/(float)t; + + return s; +} + +void Ymodem::put(const QByteArray &data) +{ + msgq_push(data.at(0)); +} + +int Ymodem::makeFirstRsp(string &name, int size, QByteArray &byte) +{ + int len = 133; + ymhead_t *pkt; + uint16_t *sum; + + byte.resize(len); + sn = 0; + pkt = (ymhead_t*)byte.data(); + pkt->start = 0x01; + pkt->sn = 0; + pkt->nsn = 0xFF; + memset(pkt->data, 0, sizeof(pkt->data)); + strcpy(pkt->data, name.c_str()); + sprintf(&pkt->data[name.size() + 1], "%d", size); + + sum = (uint16_t*)(((char*)pkt) + 131); + *sum = crc16(pkt->data, 128); + + return len; +} + +int Ymodem::makeNextRsp(char *data, int size, QByteArray &byte) +{ + int len = 0; + ymhead_t *pkt; + uint16_t *sum; + + byte.resize(3 + 1024 + 2); + pkt = (ymhead_t *)byte.data(); + sn ++; + pkt->start = 0x02; + pkt->sn = sn; + pkt->nsn = 0xFF - sn; + memcpy(pkt->data, data, size); + if (size < 1024) + { + memset(&pkt->data[size], 0, 1024 - size); + } + len = 1024 + 3; + sum = (uint16_t*)(((char*)pkt) + len); + *sum = crc16(pkt->data, 1024); + len += 2; + + return len; +} + +uint16_t Ymodem::crc16(char *data, int size) +{ + uint16_t sum; + + sum = crc16_ccitt(0, (uint8_t*)data, size); + sum = ((sum >> 8) | (sum << 8)); + + return sum; +} + +void Ymodem::msgq_push(int msg) +{ + msgq.push(msg); +} + +bool Ymodem::msgq_get(int &msg) +{ + if (msgq.empty()) + return false; + + msg = msgq.front(); + msgq.pop(); + + return true; +} + +int Ymodem::makeFinishRsp(QByteArray &byte) +{ + int len = 133; + ymhead_t *pkt; + uint16_t *sum; + + byte.resize(len); + + pkt = (ymhead_t*)byte.data(); + pkt->start = 0x01; + pkt->sn = 0; + pkt->nsn = 0xFF; + memset(pkt->data, 0, sizeof(pkt->data)); + + sum = (uint16_t*)(byte.data() + 131); + *sum = crc16(pkt->data, 128); + + return len; +} + +int Ymodem::makeEotRsp(QByteArray &byte) +{ + byte.resize(1); + + byte[0] = mcEOT; + + return 1; +} + +void Ymodem::outData(const QByteArray &data) +{ + emit ui->outData(data); +} + +void Ymodem::showStatus(const char *s) +{ + ui->showStatus(s); +} + +void Ymodem::run() +{ + QString filename; + char fbuf[1024]; + bool isread = false; + QByteArray byte; + int filesize = 0; + QFile file; + string stext; + int remain = 0; + + showStatus("已启动Ymodem"); + ui->getFile(filename); + if (filename.isEmpty()) + { + showStatus("错误:文件名为空"); + goto err; + } + + file.setFileName(filename); + if (!file.open(QFile::ReadOnly)) + { + showStatus("错误:打开文件失败"); + goto err; + } + + msgq_push(mcREQ); + + isrun = true; + while (isrun) + { + int msg = 0; + + msgq_get(msg); + + switch (Stage) + { + case msFirst: + { + switch (msg) + { + case mcREQ: + { + QFileInfo info(filename); + + showStatus("第一次传输请求"); + stext = info.fileName().toStdString(); + remain = file.size(); + filesize = remain; + makeFirstRsp(stext, remain, byte); + outData(byte); + } + break; + case mcACK: + { + Stage = msReady; + } + break; + } + } + break; + case msReady: + { + switch (msg) + { + case mcREQ: + { + showStatus("第二次传输请求"); + Stage = msTrans; + time_start(); + } + break; + } + } + break; + case msTrans: + { + if (!isread) + { + int size; + + size = file.read(fbuf, 1024); + remain -= size; + makeNextRsp(fbuf, size, byte); + + isread = true; + outData(byte); + } + + switch (msg) + { + case mcACK: + float speed; + + isread = false; + speed = speed_clc(filesize, remain); + emit showTransfer(filesize, remain, speed); + if (remain == 0) + { + Stage = msEnding; + msgq_push(mcACK); + } + break; + } + } + break; + case msRepeat: + { + outData(byte); + } + break; + case msEnding: + { + switch (msg) + { + case mcACK: + makeEotRsp(byte); + outData(byte); + Stage = msFinish; + break; + } + } + break; + case msFinish: + { + switch (msg) + { + case mcACK: + makeFinishRsp(byte); + outData(byte); + goto err; + break; + case mcNAK: + makeEotRsp(byte); + outData(byte); + break; + } + } + break; + default: + break; + } + + msleep(10); + } + +err: + showStatus("退出Ymodem"); +} diff --git a/Modem/Ymodem.h b/Modem/Ymodem.h new file mode 100644 index 0000000..a54af28 --- /dev/null +++ b/Modem/Ymodem.h @@ -0,0 +1,88 @@ +#ifndef YMODEM_H +#define YMODEM_H + +#include +#include +#include +#include + +using namespace std; +class Modem; + +class Ymodem : public QThread +{ + Q_OBJECT + +public: + Ymodem(Modem *parent); + + void close(); + void put(const QByteArray &data); + + int makeFirstRsp(string &name, int size, QByteArray &byte); + int makeNextRsp(char *data, int size, QByteArray &byte); + int makeEotRsp(QByteArray &byte); + int makeFinishRsp(QByteArray &byte); + +signals: + void showTransfer(int total, int remain, float speed); + +private: + enum modemWaitfor + { + mwNon = 0x00, + mwReq = 0x43, + mwAck = 0x06, + }; + + enum modemStage + { + msFirst, + msReady, + msTrans, + msRepeat, + msEnding, + msFinish, + }; + + enum modemCode + { + mcSOH = 0x01, + mcSTX = 0x02, + mcEOT = 0x04, + mcACK = 0x06, + mcNAK = 0x15, + mcCAN = 0x18, + mcREQ = 0x43, + }; + + typedef struct + { + uint8_t start; + uint8_t sn; + uint8_t nsn; + char data[128]; + //short crc; + }ymhead_t; + +private: + void run(); + void msgq_push(int msg); + bool msgq_get(int &msg); + uint16_t crc16(char *data, int size); + void time_start(); + float speed_clc(int total, int remain); + void outData(const QByteArray &data); + void showStatus(const char *s); + +private: + enum modemStage Stage; + enum modemWaitfor Wait; + bool isrun; + Modem *ui; + uint8_t sn; + queue msgq; + time_t stx_time; +}; + +#endif // YMODEM_H diff --git a/Modem/crc.h b/Modem/crc.h new file mode 100644 index 0000000..c9bba84 --- /dev/null +++ b/Modem/crc.h @@ -0,0 +1,16 @@ +#ifndef CRC_H +#define CRC_H + +#ifdef __cplusplus + extern "C" { +#endif + +#include + +uint16_t crc16_ccitt(uint16_t crc_start, uint8_t *buf, int len); + +#ifdef __cplusplus +} +#endif + +#endif // CRC_H diff --git a/Modem/crc16.c b/Modem/crc16.c new file mode 100644 index 0000000..c46c99c --- /dev/null +++ b/Modem/crc16.c @@ -0,0 +1,75 @@ +/* + *========================================================================== + * + * crc16.c + * + * 16 bit CRC with polynomial x^16+x^12+x^5+1 + * + *========================================================================== + * SPDX-License-Identifier: eCos-2.0 + *========================================================================== + *#####DESCRIPTIONBEGIN#### + * + * Author(s): gthomas + * Contributors: gthomas,asl + * Date: 2001-01-31 + * Purpose: + * Description: + * + * This code is part of eCos (tm). + * + *####DESCRIPTIONEND#### + * + *========================================================================== + */ + +#include "crc.h" + +/* Table of CRC constants - implements x^16+x^12+x^5+1 */ +static const uint16_t crc16_tab[] = +{ + 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, + 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, + 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, + 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, + 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, + 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, + 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4, + 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, + 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, + 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, + 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, + 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, + 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, + 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49, + 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, + 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78, + 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f, + 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, + 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, + 0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, + 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, + 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, + 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c, + 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, + 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab, + 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3, + 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, + 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, + 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, + 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, + 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, + 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0, +}; + +uint16_t crc16_ccitt(uint16_t crc_start, uint8_t *buf, int len) +{ + int i; + uint16_t cksum; + + cksum = crc_start; + for (i = 0; i < len; i++) + cksum = crc16_tab[((cksum>>8) ^ *buf++) & 0xff] ^ (cksum << 8); + + return cksum; +} diff --git a/Modem/modem.ui b/Modem/modem.ui new file mode 100644 index 0000000..cdd15a0 --- /dev/null +++ b/Modem/modem.ui @@ -0,0 +1,48 @@ + + + Modem + + + + 0 + 0 + 400 + 300 + + + + Ymodem + + + + + 60 + 210 + 281 + 23 + + + + 0 + + + %p% + + + + + + 10 + 274 + 351 + 21 + + + + + + + + + + diff --git a/QTermWidget/QTermScreen.cpp b/QTermWidget/QTermScreen.cpp new file mode 100644 index 0000000..09e64d7 --- /dev/null +++ b/QTermWidget/QTermScreen.cpp @@ -0,0 +1,189 @@ +#include "QTermScreen.h" + +#include + +QTermScreen::QTermScreen(QWidget *parent): + QPlainTextEdit(parent) +{ + QPalette p = palette(); + p.setColor(QPalette::Base, Qt::black); + p.setColor(QPalette::Text, Qt::white); + setPalette(p); + setLineWrapMode(NoWrap); +} + +void QTermScreen::CursorStartOfLine() +{ + QTextCursor tc = textCursor(); + + tc.movePosition(QTextCursor::StartOfBlock); + setTextCursor(tc); +} + +void QTermScreen::CursorNewLine() +{ + QTextCursor tc = textCursor(); + + tc.movePosition(QTextCursor::EndOfBlock); + if (tc.atEnd()) + { + tc.insertBlock(); + } + else + { + tc.movePosition(QTextCursor::NextBlock); + } + + setTextCursor(tc); +} + +void QTermScreen::SelectRight(int n) +{ + QTextCursor tc = textCursor(); + + if (!tc.atBlockEnd()) + { + tc.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor, n); + setTextCursor(tc); + } +} + +void QTermScreen::CursorUp(int n) +{ + QTextCursor tc = textCursor(); + + tc.movePosition(QTextCursor::Up, QTextCursor::MoveAnchor, n); + setTextCursor(tc); +} + +void QTermScreen::CursorDown(int n) +{ + QTextCursor tc = textCursor(); + + tc.movePosition(QTextCursor::Down, QTextCursor::MoveAnchor, n); + setTextCursor(tc); +} + +void QTermScreen::CursorLeft(int n) +{ + QTextCursor tc = textCursor(); + + tc.movePosition(QTextCursor::Left, QTextCursor::MoveAnchor, n); + setTextCursor(tc); +} + +void QTermScreen::CursorRight(int n) +{ + QTextCursor tc = textCursor(); + + tc.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, n); + setTextCursor(tc); +} + +void QTermScreen::CursorHome(int row, int column) +{ + QTextCursor tc = cursorForPosition(QPoint(0, 0)); + + for (int i = 0; i < row - 1; i ++) + { + tc.movePosition(QTextCursor::Down); + } + for (int i = 0; i < column - 1; i ++) + { + tc.movePosition(QTextCursor::Right); + } + setTextCursor( tc ); +} + +void QTermScreen::DisplayForeground(QColor &color) +{ + QTextCharFormat fmt; + + fmt.setForeground(color); + QTextCursor cursor = textCursor(); + cursor.mergeCharFormat(fmt); + setTextCursor(cursor); +} + +void QTermScreen::DisplayBackground(QColor &color) +{ + QTextCharFormat fmt; + + fmt.setBackground(color); + QTextCursor cursor = textCursor(); + cursor.mergeCharFormat(fmt); + setTextCursor(cursor); +} + +QColor QTermScreen::GetColor(int col) +{ + QColor color[8] = + { + "black", "red", "green", "yellow", + "blue", "magenta", "cyan", "white" + }; + + if (col >= 0 && col <= 7) + { + return color[col]; + } + else + { + return color[0]; + } +} + +void QTermScreen::DisplayReset() +{ + QColor color; + + color = GetColor(0); + DisplayBackground(color); + color = GetColor(7); + DisplayForeground(color); +} + +void QTermScreen::EraseEndOfLine() +{ + QTextCursor tc = textCursor(); + tc.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor); + tc.removeSelectedText(); + setTextCursor(tc); +} + +void QTermScreen::EraseStartOfLine() +{ + QTextCursor tc = textCursor(); + tc.movePosition(QTextCursor::StartOfBlock, QTextCursor::KeepAnchor); + tc.removeSelectedText(); + setTextCursor(tc); +} + +void QTermScreen::EraseEntireLine() +{ + QTextCursor tc = textCursor(); + tc.select(QTextCursor::BlockUnderCursor); + tc.removeSelectedText(); +} + +void QTermScreen::EraseDown() +{ + QTextCursor tc = textCursor(); + + tc.movePosition(QTextCursor::End, QTextCursor::KeepAnchor); + tc.removeSelectedText(); +} + +void QTermScreen::EraseUp() +{ + QTextCursor tc = textCursor(); + + tc.movePosition(QTextCursor::Start, QTextCursor::KeepAnchor); + tc.removeSelectedText(); +} + +void QTermScreen::EraseScreen() +{ + EraseDown(); + EraseUp(); +} diff --git a/QTermWidget/QTermScreen.h b/QTermWidget/QTermScreen.h new file mode 100644 index 0000000..d309e9a --- /dev/null +++ b/QTermWidget/QTermScreen.h @@ -0,0 +1,41 @@ +#ifndef QTERMSCREEN_H +#define QTERMSCREEN_H + +#include + +class QTermScreen : public QPlainTextEdit +{ + Q_OBJECT + +public: + QTermScreen(QWidget *parent = Q_NULLPTR); + +public: + void CursorStartOfLine(); + void CursorNewLine(); + void CursorUp(int n = 1); + void CursorDown(int n = 1); + void CursorLeft(int n = 1); + void CursorRight(int n = 1); + void CursorHome(int row = 0, int column = 0); + +public: + QColor GetColor(int c); + + void DisplayReset(); + void DisplayForeground(QColor &color); + void DisplayBackground(QColor &color); + +public: + void EraseEndOfLine(); + void EraseStartOfLine(); + void EraseEntireLine(); + void EraseDown(); + void EraseUp(); + void EraseScreen(); + +public: + void SelectRight(int n = 1); +}; + +#endif // QTERMSCREEN_H diff --git a/QTermWidget/QTermWidget.cpp b/QTermWidget/QTermWidget.cpp new file mode 100644 index 0000000..98a4609 --- /dev/null +++ b/QTermWidget/QTermWidget.cpp @@ -0,0 +1,295 @@ +#include "QTermWidget.h" + +QTermWidget::QTermWidget(QWidget *parent): + QTermScreen(parent) +{ + m_Mode = 0; + setAcceptDrops(false); +} + +void QTermWidget::putData(const QByteArray &data) +{ + if (data.size() == 0) + return; + + for (int i = 0; i < data.size(); i ++) + { + recvChar(data[i]); + } +} + +void QTermWidget::recvChar(char ch) +{ + switch (m_Mode) + { + case 2: + { + switch (ch) + { + case 'A': + case 'B': + case 'C': + case 'D': + case 'H': + { + m_Mode = 0; + moveCursor(ch); + }break; + case 'J': + case 'K': + { + m_Mode = 0; + + eraseText(ch); + }break; + case 'm': + { + m_Mode = 0; + setDisplay(); + }break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case ';': + { + m_Param.push_back(ch); + }break; + default: + { + m_Mode = 0; + }break; + } + }break; + case 1: + { + switch (ch) + { + case '[': + { + m_Mode = 2; + }break; + default: + { + m_Mode = 0; + }break; + } + }break; + default: + { + switch (ch) + { + case '\e': + { + m_Mode = 1; + m_Param.clear(); + }break; + case 0x0D: + { + CursorStartOfLine(); + }break; + case 0x0A: + { + CursorNewLine(); + }break; + case 0x08: + { + CursorLeft(); + }break; + case 0x07: + { + + }break; + default: + { + QByteArray t; + t[0]=ch; + SelectRight(); + insertPlainText(t); + }break; + } + }break; + } +} + +void QTermWidget::mousePressEvent(QMouseEvent *e) +{ + Q_UNUSED(e) + setFocus(); +} + +void QTermWidget::keyPressEvent(QKeyEvent *e) +{ + QByteArray byte; + + lastkey = e->key(); + + switch (lastkey) + { + case Qt::Key_Backspace: + byte[0] = 0x08; + break; + case Qt::Key_Left: + byte[0] = 0x1B; byte[1] = 0x5B, byte[2] = 0x44; + break; + case Qt::Key_Right: + byte[0] = 0x1B; byte[1] = 0x5B, byte[2] = 0x43; + break; + case Qt::Key_Up: + byte[0] = 0x1B; byte[1] = 0x5B, byte[2] = 0x41; + break; + case Qt::Key_Down: + byte[0] = 0x1B; byte[1] = 0x5B, byte[2] = 0x42; + break; + case Qt::Key_Enter: + break; + case Qt::Key_Return: + default: + byte = e->text().toLocal8Bit(); + break; + } + + if (byte.size() != 0) + { + emit outData(byte); + } +} + +void QTermWidget::parseParam(QVector ¶m, int np, int defval) +{ + if (m_Param.isEmpty()) + { + for (int i = 0; i < np; i ++) + { + param.push_back(defval); + } + return; + } + + m_Param.append(';'); + + QString tmp; + for (int i = 0; i < m_Param.count(); i ++) + { + QChar ch = m_Param.at(i); + + if (ch == ';') + { + int v; + + v = tmp.toInt(); + param.push_back(v); + tmp.clear(); + continue; + } + tmp.push_back(ch); + } +} + +void QTermWidget::eraseText(char ch) +{ + QVector p; + + parseParam(p); + if (ch == 'J') + { + switch (p[0]) + { + case 0: + { + EraseDown(); + }break; + case 1: + { + EraseUp(); + }break; + case 2: + { + EraseScreen(); + }break; + } + } + else + { + switch (p[0]) + { + case 0: + { + EraseEndOfLine(); + }break; + case 1: + { + EraseStartOfLine(); + }break; + case 2: + { + EraseEntireLine(); + }break; + } + } +} + +void QTermWidget::setDisplay() +{ + QVector p; + + parseParam(p); + + for(int i = 0; i < p.count(); i ++) + { + int v = p[i]; + + switch (v) + { + case 0: + { + DisplayReset(); + }break; + default: + { + if (v >= 30 && v <= 37) + { + QColor c = GetColor(v - 30); + DisplayForeground(c); + } + if (v >= 40 && v <= 47) + { + QColor c = GetColor(v - 40); + DisplayBackground(c); + } + }break; + } + } +} + +void QTermWidget::moveCursor(char ch) +{ + QVector p; + + parseParam(p, 2, 1); + + switch (ch) + { + case 'A': + CursorUp(p[0]); + break; + case 'B': + CursorDown(p[0]); + break; + case 'C': + CursorLeft(p[0]); + break; + case 'D': + CursorRight(p[0]); + break; + case 'H': + CursorHome(p[0], p[1]); + break; + } +} diff --git a/QTermWidget/QTermWidget.h b/QTermWidget/QTermWidget.h new file mode 100644 index 0000000..7e9c934 --- /dev/null +++ b/QTermWidget/QTermWidget.h @@ -0,0 +1,37 @@ +#ifndef QTERMWIDGET_H +#define QTERMWIDGET_H + +#include "QTermScreen.h" + +class QTermWidget : public QTermScreen +{ + Q_OBJECT + +public: + QTermWidget(QWidget *parent = Q_NULLPTR); + +public slots: + void putData(const QByteArray &data); + +signals: + void outData(const QByteArray &data); + +protected: + virtual void mousePressEvent(QMouseEvent *e); + virtual void keyPressEvent(QKeyEvent *e); + +private: + void recvChar(char ch); + void parseParam(QVector ¶m, int np = 1, int defval = 0); + void eraseText(char ch); + void moveCursor(char ch); + void setDisplay(); + +private: + int m_Mode; + QString m_Param; + bool m_sel; + int lastkey; +}; + +#endif // QTERMWIDGET_H diff --git a/SendSave/SSWorker.cpp b/SendSave/SSWorker.cpp new file mode 100644 index 0000000..3beaafa --- /dev/null +++ b/SendSave/SSWorker.cpp @@ -0,0 +1,138 @@ +#include "SSWorker.h" + +#include +#include +#include +#include "SendSave.h" + +#include +#include +#include +using namespace std; + +SSWorker::SSWorker(SendSave *parent) +{ + ui = parent; +} + +SSWorker::~SSWorker() +{ + delete dbSS; + delete query; +} + +bool SSWorker::dbInit() +{ + dbSS = new QSqlDatabase; + + *dbSS = QSqlDatabase::addDatabase("QSQLITE"); + dbSS->setDatabaseName("save.dblite"); + if (dbSS->open()) + { + query = new QSqlQuery(*dbSS); + return true; + } + + return false; +} + +void SSWorker::dbNewTable() +{ + QString str = "CREATE TABLE sendlist" + "(" + "sn VARCHAR," + "name VARCHAR," + "type VARCHAR," + "value VARCHAR," + "endline VARCHAR" + ");"; + + query->exec(str); +} + +void SSWorker::dbAddRow(QString &sn, QString &name, QString &type, QString &value, QString &endline) +{ + QString str; + ostringstream tmp; + + tmp << "INSERT INTO sendlist VALUES(" + << "'" << sn.toStdString() << "'," + << "'" << name.toStdString() << "'," + << "'" << type.toStdString() << "'," + << "'" << value.toStdString() << "'," + << "'" << endline.toStdString() << "'" + << ");"; + + str = QString::fromStdString(tmp.str()); + + query->exec(str); +} + +void SSWorker::dbUpdateRow(QString &sn, int col, QString &val) +{ + string head = "UPDATE sendlist SET "; + QString str; + ostringstream temp; + + temp << head; + + switch (col) + { + case 0: + temp << "name = '" << val.toStdString() << "'"; + break; + case 1: + temp << "value = '" << val.toStdString() << "'"; + break; + case 2: + temp << "endline = '" << val.toStdString() << "'"; + break; + default: + break; + } + + temp << " WHERE sn = '" << sn.toStdString() << "'"; + + str = str.fromStdString(temp.str()); + query->prepare(str); + + query->exec(); +} + +void SSWorker::dbDelAll() +{ + string head = "DELETE FROM sendlist;"; + QString str; + ostringstream temp; + + temp << head; + str = str.fromStdString(temp.str()); + query->exec(str); +} + +void SSWorker::dbQuery() +{ + query->exec("SELECT * FROM sendlist;"); + + while(query->next()) + { + QString name; + QString type; + QString value; + QString endline; + + name = query->value(1).toString(); + type = query->value(2).toString(); + value = query->value(3).toString(); + endline = query->value(4).toString(); + + ui->tableAddRow(name, type, value, endline); + } +} + +void SSWorker::run() +{ + dbInit(); + dbNewTable(); + dbQuery(); +} diff --git a/SendSave/SSWorker.h b/SendSave/SSWorker.h new file mode 100644 index 0000000..6efcf8a --- /dev/null +++ b/SendSave/SSWorker.h @@ -0,0 +1,34 @@ +#ifndef SSWORKER_H +#define SSWORKER_H + +#include + +class QSqlDatabase; +class SendSave; +class QSqlQuery; + +class SSWorker : public QThread +{ +public: + SSWorker(SendSave *parent); + ~SSWorker(); + +private: + void run(); + + bool dbInit(); + void dbNewTable(); + void dbQuery(); + +public: + void dbAddRow(QString &sn, QString &name, QString &type, QString &value, QString &endline); + void dbUpdateRow(QString &sn, int col, QString &val); + void dbDelAll(); + +private: + QSqlDatabase *dbSS; + SendSave *ui; + QSqlQuery *query; +}; + +#endif // SSWORKER_H diff --git a/SendSave/SendSave.cpp b/SendSave/SendSave.cpp new file mode 100644 index 0000000..9a2b744 --- /dev/null +++ b/SendSave/SendSave.cpp @@ -0,0 +1,214 @@ +#include "SendSave.h" +#include "ui_SendSave.h" +#include "SSWorker.h" + +#include + +#include +#include +using namespace std; + +SendSave::SendSave(QWidget *parent) : + QDialog(parent), + ui(new Ui::SendSave) +{ + ui->setupUi(this); + + tableInit(); + + worker = new SSWorker(this); + worker->start(); +} + +SendSave::~SendSave() +{ + delete ui; + delete worker; +} + +void SendSave::tableInit() +{ + ui->tbSave->setColumnCount(3); + + QStringList header; + + header << "名称" << "内容" << "换行符"; + ui->tbSave->setHorizontalHeaderLabels(header); + + ui->tbSave->setSelectionBehavior(QAbstractItemView::SelectRows); + ui->tbSave->horizontalHeader()->resizeSection(0, 60); + ui->tbSave->horizontalHeader()->resizeSection(1, 160); + + QHeaderView *vh; + + vh = ui->tbSave->verticalHeader(); + connect(vh, SIGNAL(sectionClicked(int)), this, SLOT(VHeaderClicked(int))); +} + +void SendSave::VHeaderClicked(int index) +{ + QByteArray buf; + QString value; + QString endline; + QTableWidgetItem *item; + + if (index >= ui->tbSave->rowCount() || index < 0) + return; + + item = ui->tbSave->item(index, 1); + value = item->text(); + item = ui->tbSave->item(index, 2); + endline = item->text(); + + dataMake(buf, value, endline); + if (buf.size()) + { + emit outData(buf); + } +} + +void SendSave::tableAddRow(QString &name, QString &type, QString &value, QString &endline) +{ + int row; + + row = ui->tbSave->rowCount(); + ui->tbSave->insertRow(row); + QTableWidgetItem *item = new QTableWidgetItem[3]; + + item->setText(name); + ui->tbSave->setItem(row, 0, item); + item ++; + + item->setText(value); + ui->tbSave->setItem(row, 1, item); + item ++; + + item->setText(endline); + ui->tbSave->setItem(row, 2, item); + + setBtName(row, name); +} + +void SendSave::on_send1_clicked() +{ + VHeaderClicked(0); + hide(); +} + +void SendSave::on_send2_clicked() +{ + VHeaderClicked(1); + hide(); +} + +void SendSave::on_send3_clicked() +{ + VHeaderClicked(2); + hide(); +} + +void SendSave::on_add_clicked() +{ + QString sn; + QString type = "ascii"; + QString endline = "\\n"; + QString value = "test"; + + sn.sprintf("%d", ui->tbSave->rowCount() + 1); + tableAddRow(sn, type, value, endline); + worker->dbAddRow(sn, sn, type, value, endline); +} + +void SendSave::on_tbSave_itemChanged(QTableWidgetItem *item) +{ + QString sn; + QString val; + + sn.sprintf("%d", item->row() + 1); + val = item->text(); + worker->dbUpdateRow(sn, item->column(), val); + + if (item->column() == 0) + { + setBtName(item->row(), val); + } +} + +void SendSave::setBtName(int row, QString name) +{ + if (row > 2) + return; + + switch (row) + { + case 0: + ui->send1->setText(name); + break; + case 1: + ui->send2->setText(name); + break; + case 2: + ui->send3->setText(name); + break; + } +} + +void SendSave::on_clear_clicked() +{ + int row = ui->tbSave->rowCount(); + + worker->dbDelAll(); + + for (int i = 0; i < row; i ++) + { + ui->tbSave->removeRow(0); + } +} + +void SendSave::dataMake(QByteArray &buf, QString &value, QString &endline) +{ + if (value.isEmpty()) + return; + + buf = value.toStdString().c_str(); + int r, n; + r = endline.count("\\r"); + n = endline.count("\\n"); + + for (int i = 0; i < r; i++) + { + buf.append('\r'); + } + + for (int i = 0; i < n; i++) + { + buf.append('\n'); + } +} + +void SendSave::on_send_clicked() +{ + int sel = ui->tbSave->currentRow(); + + VHeaderClicked(sel); +} + +QToolButton* SendSave::toolButton(int index) +{ + QToolButton* bt = NULL; + + switch (index) + { + case 0: + bt = ui->send1; + break; + case 1: + bt = ui->send2; + break; + case 2: + bt = ui->send3; + break; + } + + return bt; +} diff --git a/SendSave/SendSave.h b/SendSave/SendSave.h new file mode 100644 index 0000000..a38bb49 --- /dev/null +++ b/SendSave/SendSave.h @@ -0,0 +1,55 @@ +#ifndef SENDSAVE_H +#define SENDSAVE_H + +#include + +namespace Ui { +class SendSave; +} + +class SSWorker; +class QTableWidgetItem; +class QToolButton; + +class SendSave : public QDialog +{ + Q_OBJECT + +public: + explicit SendSave(QWidget *parent = 0); + ~SendSave(); + + void tableAddRow(QString &name, QString &type, QString &value, QString &endline); + QToolButton* toolButton(int index = 0); + +signals: + void outData(const QByteArray &data); + +private: + void tableInit(); + void dataMake(QByteArray &buf, QString &value, QString &endline); + void setBtName(int row, QString name); + +private slots: + void on_send1_clicked(); + + void on_send2_clicked(); + + void on_send3_clicked(); + + void on_add_clicked(); + + void on_tbSave_itemChanged(QTableWidgetItem *item); + + void on_clear_clicked(); + + void VHeaderClicked(int index); + + void on_send_clicked(); + +private: + Ui::SendSave *ui; + SSWorker *worker; +}; + +#endif // SENDSAVE_H diff --git a/SendSave/SendSave.ui b/SendSave/SendSave.ui new file mode 100644 index 0000000..7b4938c --- /dev/null +++ b/SendSave/SendSave.ui @@ -0,0 +1,66 @@ + + + SendSave + + + + 0 + 0 + 356 + 244 + + + + 发送列表 + + + + + + + + + 1 + + + + + + + 2 + + + + + + + 3 + + + + + + + 发送 + + + + + + + + + + + + + + + 清空 + + + + + + + + diff --git a/images/application-exit.png b/images/application-exit.png new file mode 100644 index 0000000000000000000000000000000000000000..32be6b3f1606ed0ed83fab77687d23b34643b380 GIT binary patch literal 11200 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 literal 0 HcmV?d00001 diff --git a/images/disconnect.png b/images/disconnect.png new file mode 100644 index 0000000000000000000000000000000000000000..fd58f7a480228b072708e3ea3626f27b38fb9294 GIT binary patch literal 15092 zcmX9_1z3~c+kQ6&3>YvF=@=m(T>>KA9ZD)GEsb=J?hvFzNdW=rk_JJ#yF(hJyTASZ z-*#>9^)604=YHaTo^vA9RpoH8$*}pOGaTW&d*7q7;|gjBFp(O91#yay%Pl5INhwTN z`@NG{e0#OriUyP?21;3gAtcE6p9S49vq(9H&_K-j*yxhTaEz>;D7+{jJP-J#5CAd+ zoG5`;ieHT|+VDuWHh>u%PYp?=6~KIgMZk>()SzWG${r5j0dB`%SgeH>(G>(-s0nVXGsb#Zoca{YLD zbVe?)gIk^>;=N{COT^+KCNnV;2Q}!@laHFbYyJAHyV;mgl_z2Hf6ok z24=h7G4p<%pZZA|6^y(#`wiz5{1y&4> z6H}9uV%An3b2C>z3wN%gNCr8LTD21-PIh7j2_$C zy45np{K+Wqg!uUG_S2yZNgTAjpA(WlCF!VZYHPfio*3KQ-rb=fB~MRv9von2EBR?> zyWb^rN6}VIfSjcV?hb(nF@X3H&~5&2VmG)HRRVSoG6?^}7JoP?2#ymDI=I7K8C~n1 zd{I;5dj1F&rFHHn4DTFUe2joFgvd7{fad#d%q6<5WM zDz_13KS$<6VUiuI*vvwV|nNnuhYHjU1KlN}>c1cAe z)Y#zdb$f#;>i58N57=wVDe3$4e@CjQeW zgjQ-69Kb>X=q~b-7=0rF&;N1Z1+36N-K)SS(TE`Sp`XC=elPS%+4fmvL_w#(81n2H zNy2Tf;t9^5U_!}R@p^@w0_Q@4iROs{KCQQKJAsmjQn|Q%qpTq4uiHn%qJyX%g&f-wzbUb*K*a_p0JBMv+> zR@?7qtX)O~dxGs=fA}IgXLH7N*)WU!5g+G^Ox{K<_s5^nKfOvE=GJFI=V#}t{u#wJ z|1F>Wbh!Ta=f>S&-t2kkJm?Y@CqMk$aB59(&Ac1m(_QUHria7We($q4_P3v}wQodj zN`_jkcN>vq0nXzw7J``Q7&wxZmKkYUyzls?-!Ca3g(cRDe zZar8SM0}Lc7o7b3_RR)yiHa$kUW<$4bzjne);nGoZKJ-k-VcU#WI^y^WUsf?ni%kcn!VkugQL-ksU7z%pA^`%ZXMvxgio*L$?0((yGQoe*J%AaD8ij7Ntq+<_1}-( zbqi%Dbu5o_g@d4P0i7-(WEc_`?GLo0yyKQssRlv?HW{)%ymei!I1EKHpY9MQ5&Mb| zpLTa|bhgU#k$mU0lCLG)%s(p#Wot;XXfS)^3E7xsO)IYCVJ_A&udFYF(TddOasqo&OL>L~owfAg%q_H-fYc`J$0gY)F|&2D;} zz0D#nD=Q04(V2I=(Rub@CtNn>5LGC}h7SLWxR z=@bPfBU!#bNaT*(S$S4py6D{SdxdpEB649u^vN3RJ1m}|g&hm+3FhWX3QBg#+6kha zwH7J81D~$h8V!b2OBF zcQKPc|7U)f`qgavlE$z?SK43U?Xms2zf&uQYZ?c7e_d_#cdlL^R${p`E`8H{S#P13 zsMhGvn%R^2QtMfp*_U_5BWJ;6Hh~uyMp)KgUkbmojGbR7_teV{F;Nkg4Q9`zsb?)> z2_<;t=O?SL(8N5d72mSrR~g8~m&cr>PxR|*ozU63r!tClLOZ|YL(h@EFUK2toAbGY z^CsNM*`;**O~FmW+1>K}UA*aUd5xC;c6yf^hFu61pWZaeirp796grq})^c}scb(?YQtcnihYnC?M=1N$V?TD(Y zo0||JR+o8I`!j`?!)-^YYAfiO488R(O@~S6d(${uxhE-Bo0{YR@+nkaO)U-$$(sJm zUSq)1*tEGvq2*J>FR={8TQahj8ODeE0tT-Nhrw5AXad-Lhosbt<3%{;Jb_XZ~K0{2PY?GA~~eLacHwV z17SD&4`+zjYH3CBQo1$T*fiS%hOVbur=E{1N571c3E&>0sr?&4JnFR2>k&s6J_boV zspQP7es{zBNxibGbuvAVl-^|aN3xAxz50NVF5itL9l^Nnwd#4+1xn1g;jLp$O%Gqd z2LJ^JMs2&R%*LqUkZGlUgBn>Hr*oL(~Z< zlr)cgI685z)fv-q`xA(g8D<6eGw7Pyq>?vGv0h#e^#*1^I;^|C3?~v!k7{BA+TEMY zCu{5&7)k)pcCkNC1>VAM-2YjS-7?i*5k1{LcOM;dKEH2^Ac8_{`T3z)l8chZ(n6ny z%sLX7L)Icb%V9vxjBSM@l=*RbPIL{Empo5nbZA)hXzABVNbMiuY-h@8%k39&b*_9$ zuldt`MS*fQfjGE@D8Lo%9K{^Dn3Kxc$&%1}W|Naj@yf;Ou_Dg@Zf#9HwZx8Vf9G+T zN1Pc80A^Zz8#8BuqNv=4bab9xRJo-ucR7L@$EgvJ_CCj~-(60$0P2YawH)_hP8<4V zt|u#;At{;}216un5~$@P`cG0}KCAh4WxnO=5hu#ie!O$XL*KkfN)4M&_`^D``D3d} z-s`z7RU2k_{bY{ZVtDQ^oIZGPct|r-=jGbZO-kml1TxXXMpeb? z%@@Zui#0H1H@X3bH#)6t&!KDBg1wwP_;);<5rSvfL!YKBkzme1`25HRj|ZSl>A3PB zj5n9haT!)Q)W75{p`K_7-^qM9OCeeE+Vfs&dH*NjosCVgb_JVd@6jyZk&K3L_vx%% zb8~Z?x^eswG1~876q%)Zg%@fTt2bQnU5FA-`vGYQ;7!ce96f8gwWkg4j=A-uJp*hde z;i}-DP#uUAbsTSJxpL2zQy7*Mi@qx6)_FrszWQ64gip%P5%QKE(cOuCFBQ5xgmE5ms{Z!p5V-rHXz=E6)&?>!&|byY}vl(b$@On+F~rR*7-D3 zHZB9#aozcMg#T?FP3t4Y@>LhJ3!GH5BOMF!28@(#%=Ev;MEsiHcf0PO6+@GRA9=S` zu<0g;3~yn8G}e(nt5sFuZ8$I2Khnu||LwtwG>BI=;}R4MZ1}?RQ`gDU34J*6S7sl3 zU|Xx-vhSnUJigyi5CP!HTxO+?<3J2o#zBG1X1IU(J=x|we>(1JISq{V$V=F<;opib z21mgYw(cK*x_W&7)~EgrS_Q%!dQ(y|>TY?yP}e=}-+Qhpo?l!$Z+wus6C0>59=;v= zoL<}>wi=;HeB$%qMHvU2^e7Y*~_tbX3!`%q&Wk!2SYMGfHv1Hiwf+rAiB zJ8`Ia;A$kG8yhmdD$TV5qJ`jv;ZpXGJ`fsu?Yx+noXpN=wEa0yovMBCZy`vE==7Mx zaOPvTU0w4fDUx!@43WwW0LEKO9XgJ%#xRrAtmVUy4*Q^S6Dh83G%3=VeSSwu%pM** z8qt9KeA!^hhu2z_5ANFS8bUEnLZLa#n`@13`@c@5Xd2eEm=loNSR^l?7AUNiyB;nX zBaioKgY{QWy;rK|+h=t(Ls;9mAZ-1AuPOvaCMJ-^BRVawpQSk-2mUv%#YXqH0Z2TA zflEc89vVO!EPQh|-u~8TpE%iMY1Kt&FFJNP&PMV^6itnjqf!`FZ)go#r>CX$ksd^ZP@;OZIdDySYjXq>9~> z5?^eoc@iNxr#Zqlh8r^1wTVF#g70prY^FrHBy)CgVQg#+ z2*qNN$~x`qCQGTPuz&dQf$h(u*&b`;d!MZ#OR=+xINvK~0Oa*`B_|#-4o0G%Vli(T zxi7vd61|<}+;6y;dl8eZ-sW>il&N*`@ADyn3;`P2eYiA6{IC*2x-c>Mg)~%MPcP)= z219Fo*hgQ2=+9Mq5a;6g!)2=tXLRT)I%c~vAWq?nUV-0seVQqDmh>lz^HEI4pvJjq zL-qKk(lY8HLN?6tK%eBqvtncqYxGT*PYbX)XcO3PPMMOG`6LF9mM~(avY)tP|3bO2 z#m>&M)p1gbl_rz`vN_s$iVd+Z643qbKn9e%?a#BavKAkHu=?@5uv>|{biw;wK(0P7 zK~840Y}m;BJWd7)wuoUjr*Yk@a^rTotNSSsi=K809uvL8V%<#fPx%u?@_06q zy|zAQqcfhc5{NVwB5Z>nf&Sdx9^77cQ{q|rBGf-*$raQCawtfam642Y#|NyQIhmV3 z`|@S;dC(i;&x0r1`}=>6949nMWIi}j+MM8-^*CFbc9K`Ojpe00uA`d&bul*?r(Phv zPx!;FT|dY;p9Sf5Z+m`@d&n4!fFDti3Tv&|7p=f$2t-6gPymYTY;yZ|d0Xzdnj^{8 zeFxJZB8Y(f2iP2DVo(@7(D4`H>Eak4rbe^K=uXy(vEruYSE}4bKS=!6E5b8x@W?S^0H$0)|q*_?G@S?rtDML>8GUg~$1MLx81>l$6x=-gql)KO&35#c<-}zq`pXizHZKgRM@psR@1{$*v zB2)Q;uzwU4=}AE)c+#ZlJ|TyN##NQZ3Y+3wBNXXJ} zvyrhH&iYfWg_@(IuF0429p>w?w+tF@$YhTI)Dp(ToUXbxDke)z4@YjhVPORA&ClJG zdpLD$8JoGazd{5LDE4?TIsrjU;!tP@Tt4B1XMcau%H9x?(vPe08CzhlA6Jh^nfu`C zn0|!pk2>!6?d=938M$}EOB$Wk7PIMOYS4h;E#82FU+kt88ZPec#nIExVi{k2RmI#K zHnQR(R7%QgU0a_pM!@y`FKlU&*{rX=x?!DN0qtysfjjbmox>5tOpmUQtLTI$0a9J# znww{iMH(t4S{8&f`MLKqpkviBtKReFP=*MO!WdbP6D7(jsT&wbExu7y-qK)uL6rZ3 zpP!cWXI4gG3z50Q+4*@ZXSqR+<3rdWk*c zM@|qMAu-?PI0C`)l;brLv3j;srCV!@C8&xSz>v*gy>fJWuiJcsU0s(c!;>a%@a?z9 zv-(bbxwp!vEFCy6Nr2DC_cu3QnmW^;&EBF6Pi}5Wn@3eiHd(}5C|PPFX_)K|Un`*a zT`SOO-BJ5{n@fy1@=G5sa+G2~FpI9AM>W%4N~xa^74{r8y`M<_KtWCk>v-RrU;zyT z8W-_GK?7Mj`VDSSNp0VjJ2aWBR_4C@4}3MKY#9DiZQ#HeVJX?6@aM@xB@ z5oP!|lVo^WkYKjqMPV_eqCe+K6hziC0Z>{>B6{^kX-qIX79}2@aAxLb_N-5vO5-c? z3`E@Dd7{jXMl=yiFEdsHH;5UBH6sUw$}9e zYKPdGW(=fQy5RilXi0DC1R|GxjL<$&A6l{?YOAkWZgh!MuX16@60Kip*pAS1lF)(+b*@wd_4N zDq*(%?1qMX;}wO3pL3Mj=U=|=jvZF`k)_A{VJ-XGmap=PTSzEBIeVRH7Xja~eJ>?@ zY&)X-3|Y#JOPD1Lm)2lGw(Ysnr46WR`UL1E{|Q7-X+OlzD%hedDvpVuvgkFbei})J z&rG}C&~TIUZ+UE~Z=kK+36?WTfSMajHNzGS__fF(p8yT(2rwSOk%YdlfP{wK<}e>z zZ!Kal%YE6&Qv^alPe2k03@CyS`jj=9j?~4}md$kZjUs+tsASTIIDy4%Vp^#1-28mJ zau<89doa*$3`ZFn<3hT&f5Q;V) z3G)$+J3eOc5XB6oC8dhVZEhx6bp*2Bv!|lL2QUCLAO!0vp9%(4czEq>=K_q9#e4W? zi&(#(F=OYKm#L|xDcNKm8w_ESdnzQ9Fe&4@s_^b7SA!0Qho0 zHCk#l$poUwtimxX7b1QN-mT2lwC6yzMIxE(X?9eQ1WKTA50pRy%=?M6E60L#+03C- zYO-}FKE(9&qdD7DK>1JH7cXA?D47iPRC4kzD%H1koY|)&#h7Wl4=*06=L!Hk++r~*&p83C6`YKUtQ(q^Bl^_W(ja=#Vw2BlZdAp>0+x^iTN@_ zb{a7I2IR~~zW<}N)KCJySM@10ur0Ty22Cl2Ly~!&8=2)JF7vXt5|+$i*VQI2=KPYn zrUv&JZd31%r^S|^1#v=Y9UJ$1EO@UL4{J@9TXX;*0Ew@Z!cvc2p-UMQL=O+9{IXZ& z_zMe64=ydW?>?SE(SLh1V1i63k5r7+IF${s!W+wd9rEjJW@bD7y;(2DW(pcug&Yo{ z)d3^~|H%kv$O^Ilm{u+&+08NZ&Yu){!+W5J7Sy(18(@xwM=2P%{P++jpEtH5h|=$r z?8-`TAR?>L8z|JofJq@DU(V=~;fbN8M+La3wiQM4S2JyyD#HTcf5cJ)o^Qk!FNt1# z>|frfY~QHNuByUsyf-M!sW5&iiOvH?Xv^p?~+iy?{l>bvFdHM_#{;H)9m<*?J0R zGbjo|BoX#;?cbq1tMTfwAOZ@e_B5loIPfJ4jF%Fmlve7egMcH~cel4CI8reCcc*+g zQqcOHizpcY5G4oRMF?9$GkCLq|7hdM_Fv8pSc*d`kqQ*hORmwNu7a{~(d;r$w)lEy z)UUUEP%Ycf?6bYEQ0{FQdLbwn8!%l=YQx#=LP;TDFUQ7@ zmmbs6mz1(r*-zen3Z9$u;^xnXX^#%1n3JL6YHfsu4FCs+!Ux_FbQ*0Y%<*}0q zq<2*IiS((!q*l&_Pe*0~9RUNxY(KhpCsR7?>$1xnpj$&S2n9mjY z!Em#xSulbxe=k52oCY7T!2|Ok@S|7g_kP7yXSg*JkqufYrRbuEK)LQa;}uHq)k(Kr zFa`=``N*l?CZyu`$$h_z6RExa$j)9|ig|`Hl^h5%lTWy4tzA$&ezEzuXfHs_{vmL% zi}u?lK~$d7q-&9#Iw55++(X#ymp(r#$u(cQ-+r-wOACGJA#AVzaS&TmM+cGAms3+i zQK;DvzT+ZHm!>Ro+kS4la0R=$%`Kl5`PKYMAD(W z(dhguEAn<;2N;C*U1~sQ1)+UQ(Gyk7Vx$^+bjJ`ak&9#^Ha*&NmrOEQ{z0Zp(W|b- zcbAzWU@8C{9i^s%19<}ive3l)Uvn#gR0yvC4*`3hzlYkTO@BYsyyiMAK!aosM}^>| zw{d+3zzQsWVTIV>{ipkG#D-;0Q0@U`meqtSp4?uxVgptmirVLD`d3fuQQkH-k!hYb z^0fv;Ed_v0-kulkit1nsltv&&ze7^N(A}cN5kMR&!NRdJu_}7BV_R9`uztI|d^IKu zkO9JqWqdr=aAmKERKgE!c<6uy+)DfiC4n%tvbQxB-<|^1_nHAKr=Hjdc(8EJXfQgo zFD!Pjvl0ddF`;spkbv$Ck+2HaJ!%F*h5y#m_uQH9#bhDtH+KYa6h&tf9?%6qLTOh# zsfEkdMc|Sv?s-SGtQc)@S#~tUZhc=l4u5QR_bl!AQ^Akl@gt7;-VV#OUIfL2(S(2j z>OVG5)UY7qAOIKq$^~wfnS9;`gfiv0*ezp$7+kK-=!5(&(5o*;mz9|E=ce=o^N=NltKs!}L4} zVQL*Y%A{pcOKgXClbDj#8D&d2h?S3RYZZkx3?aLbo>)wV!eO1d*cE0L{bKjd!z9KK zQ1eZy#dkGmU?EuCM#ReyfeRLJNf%FqQUe(%)cJon-({WFh9%19d6W!= z+|1juv9K&S3K9{_&%Z)0Ke(|Decb^7l#c#R(%}dh-)U^5;Jq@%3JiT)7O=WIxa;uj2alsYgvsb27yS>cz!{Pu7SZDqovDC za`@c?VsmhO3%BdN0v9Y`xPa?T0t2O(9|pe284(7i+ywl9u)3NJEFMoOiha3V91p=- ztp7urVxy#c^F#^IAmSFy0Qz?aHm(H|dL_bhcp9uNdG2Ct6gVhdyd#M50f{z;u;o2A zi-r(Q`L5Rg@vV= zf>1tBL~w`6#MIbxqrMn4G!5IiYBWrIYD|ESPt$bpi#ZhHBhT=98|BM`9$~}+`2==K zXr5T~uSG(8HW&eA2GHkIuS2Hz@05fQAhW85#<|JK>yr$T;Y0izy8+I_`=jm;Mc@~2 z>zrTN{eacuDD(3gT&rM<@jj=PdvGB)rN=tMi&^$Q45e=k)F31ZFqQ>I_9U&Ii(^Lo zgh`P^K9ksZq9$BY6x1t?#)KK{D5wd5hED}uwgstu99b*%^0rFe)lo(fzX*h96&KSS zdtmpPVNY`&O1Yxr>m_iHCCZ9^>Ja?E{riO!W37TBfKkcz6eEZ*LgM@Dju7-8?<+=2 zNsNTceT}mm#BI8f@A1UE003V5-(G;>&6|g5F=xyEzmB|6<2BNW$*Vpd1Ci`w(6v4_ zraP6KEknSuw8g28zxOsxwmg`1O+6GlaQ4m+up?E~CiM7VW7Cl?Am*#4m6VTXfeEFw z>~8TbTj}|fNJ{Z$6W^IKthn?;`^iQWL4@?^WS(++-Zg*D|1!@_>PGDJ_<`GZ7tHD=? zO&5`#({M?3qdoM3{0cqph0dVg*SVN#xwkQX4C85w6qilMaozK#7WnNeR0Eq#rdF4Z zbR?r$c&H(>3OM-PQ2${})#n?zA;0(0(GjEevU-;tOC3jnLo&5yaM(az*18m$o&5=n z_9TKqTBGy4A5+~R8!Yb09o{R9G53BYg2ZW>eBDms$#@Jus?sXweT((|HFwPrkI<`m zHyXk6M|27JU#<&jb-OOmO3P8&yQTB?OSZ<6hB361M5&kgOx@#x)1sfP^{Phxs3Tr} zY(guDt)>6e`|oD!QP_v1FPdsVu`A%Glhic#9G4R{_K8OgAsKTJnKkg-RY{KS(<-WH zHkAp(#l6^TNDlDRw++@a%H@?RApz`Mhq3iN&3O&I_iB?eGPPyL`kk18oZaFSW^`B)lGi>xx?yLdq@IjH>GI9UG2yRUQIg^zEH_r}mC!E^z3yR!I9XsLfbKf!@pafD-l+aZBOY~t`3*_(b|&l7Q!U2zeD z!){O&J%Xrbn)%OR17&}EM6irt34In(#P$P&c^SI*T=;%cqfQ-+D*lfI7wTSGrCR56uo`K2zzo* zJ`Q^3kECT@CpelFMm*WHojyVUX!2k2UfF&v2>>HK?P<}F1LOdkI3isf(Z-C$!*ZI) z2SwG1FK%;3S4BlyM12ufot13=+$e^=Q8065nCrPRmhziN1rUAbH03a_3*36Z5P)0VgjS=csfQP z__Sufkcw$&;byw^&e}zANsZTFZ$p~Khc3STOJ2yJNmxhMfWFUXcJ|2AFepf>sSS@}(%kLi8+xo>7i235Hf*KK_}4!s z0tuh2yw>~P`Q0_sGkF>D^B8!_H7h-B%L4kb6q_R)L8@pdQQBr>;7L3gwOTMG;_K4Hy-)cRc-|d+Zm#y3IOz~gh1d0~m&fEE) zw)xxnKTOb^%WzGEoA?539V`2L#71)AIds_P+Ve24&r>kKxK;~L(fs8^*TJP?_qOj1 z3t>d`f>Sp;3$z;kZvvSW;bu2}&X0+zBQ+S!Pw;cs5VYY`9?t?_rUyEjo=yI6Hv2|X zpMOGS$kdVS!GybT=0JY7scMWKIAL%p+;A-rciX~0Kb?0S~C%=^nGVU#sWizUSn`AZ~EVX)1i`b-SPHG%}4$FCYV-;GnK*~;YM13C z=a0@^iev=bF+}wKf%RdBGkKc-_xAO=qAj?F(-J{3@#oDewOYTc1I!iyB9$|;)Y6)e z53JGBUF}h0E7Esqw`rKn2@sb}>_zpr4qT1lh4Ku9TH;(2I&2572-3%CZ+`a8Dm_po zEb)W!igUOz&L~lH%99rW>!$g=Kg$$~a;Qi_by{s+E>MTvIxJP)$ho&VT&tAJr*l@N z1*n^m1e_!r3e9~y*o=GlC%4#KQH7pQqHmNJ`(9OA__<4)RXmBz{Sa!qfs$($s0|yk zi6M_Nt=lDaOSZ2uwZ)O`fEg)9d)D;Gt+lun0uW^2;Rw2g=AcRTC}8A|gUCYRkU+zx z{bn6H_=(AWWhkeg$^DnO-+ftGUm#QB%ufY*<%3$XrCs*fFPGf^nGH+ua-F^;IIbWv z6rOEFBd!meI31<|kO}-CT;L-Z`o3GUa_ON{&ARk4z~w@Rm$T*OnS|Mu~rniB^23&=r47iy+M)GcL~LwQ;yGeFo#DB*4`T<(xlgDG%7TRnEC~i~AHg%`c!m5L?2g#7y~M;3 z(8_1ySW-&p;Kq(n_XV$~o9v}hf-BAEx9&!lr~MT7Cfg6cW}l7E{bnP30Z+{>km*`Q z=}XhbC_Cr997m>F#I>-Y=!Dzf7P(8`CwaWV*t^Es7(w|`oio3+(d6ekp9sxXb4AXK zjHq7^@V;u%)YBU`rsNvyn!i0-e!O=9L3H#hh-Pt$p$+h|&XpuIQ1Ok2 zu+wf^0NT8)T$n828b~L8a6>a)dbb`{dRyLM@{tg%8M<)fb#{pLj@#fdPk+R>mL|-| z16PlB+W+W0W7vLf+LcHap6Z>i9X!Rz2i=a%wsYGh|6xADk0SPC^nr91Hi90EnqbJ= zoaZ9wF+<#JFeU+1;46CjZ-`R|bpT~Vfp&2b&Esm-4iPL4myPfLlLdRahv?nfn5i%g z6R1T!BoAYFv`7v%-Pyi9mU!Dgvh`P%B9~248_fSWZU}&U>4dl<#r8eWcU%~c|3Xx{H6a*I~B&B9M^lHD(M66GujJB{{M4xJOri8}Ietr{gKcR|D>Lx5Lbf?)$&lTM%OqrZfdY4U&hS%3mU50CUl6Tar7;j3P!8nLt%mDBi zU82zVlLR@c42?72_{-SaZYJ!^~$^3KuNhG%5_L9S03$Akgt(P}2A(#8(0u5Wd|&R4@0TC8AB&(Kl6|Jo59z-b0sKB#@`s@hYz%=KSJHa>S@%3{ddP-#a(<*YWbeE zXD4;g;z^xZsDkaEWY@_vfXP8eKN8tD1F5F{PA0xtf%b9k={Kiby}s%iHeFkC({0+f zQi8Sa2C_4N!XZ{>L@<-`Q!9sgw(1D28-q~`r{(~T4*;-B zLHcIEs$+yj)tLGvZ-McsJ>fpn_*mONzhufRaQyB*O%&?L*X84f?u_ZWv^0D9F$5%s zVb}xRu}B5GJFmy5IgP^$urm8(DZl4i9E|;uC0Q#@pctr1G8nq$LSwIgI4}~qXpFh_ zd$&$q)XE#d*7fGM4Kug@qvVK;H6)p?4+@TsDcHGk=198kh?*10OL%4#q?i*sh~Q)J zy*qReu00(YjU6&txb5|Ko3XPO5r)C!-U|v4Evp%*wFX7w0O10tQ+ssq5&qw13v^$u zk)tmvKe+6h4kGEW24K8F5|H@i^6QN^{IT~bhkn-`kLr3mnfT0WAgNL5Zq8atPx2|H z`p?DPUq@y3(Ld8Ch8f4^z56pcXw1#`OHWR9CBVvte)N=ju%mh*K_=)@~wEErs(I&El*@Rk+p6x3Eso*r9HD^C3y%ej!lbx7khzl5}dDO)46khUD zTMaXZv8HL>b~Jqmqnu94te9E#wa=hSpFon5+%qNIsgZ2MePNMKJL){vA9<&Ld{)@O zz?aRUV@1+T3pHu9fG!^)EX}=D&v7@2#r8T%U^9VjLESF;Hyip17o+b7X|-tXxG@8` z!Ci7WXhX%_1z#nZ(fT-euD7{aCs~2w2Dym;TUF z1~a?K>}>xWdxH`*MtQB}`pz+KvS hIwQ>r;UMn4e|H<=T7u0W0<|q4P>@lTE|oM6{6GB6XH)m*1v3-?-%gK^3oE3xBrg(&axx`Kxj%!KiyKHW@y7ldH@+u0{7VUYq=4Ns~D`NwSY9&~Tc5FUiBEEv!63NRpk95}VdC1xGyJ7*8McYs&A zAmaCx%HL;2c*}sX6AJIyJPPzhRz)c079uy)OsTq=AnN!?!JvWZ3ly|;yTTAVysgb$ zG}+Cjd6E*SG_oe-a(E!`BZ2wWbJYd^0NTZ&9P>GoIrnb-W@-PS?Ly;VGet3wqLVDo zFo&~++v+AwyzJ@m$YPT}(NRW*RrcB1>G8xf1M=ky87`1(A63>REGKb3vUwlKoL`%rW>kLTop!U3h zpiJ4sw@Sr4r7P6L^P3KeAQBPS zaakOUB-G=dHD}j4N|t?35`dT(CUE;JNO;Bpe$i$;$B7DuFFJ@}TEGw14387q9tCoE zC%Qu!S3MXtlK`!uiy58D)LMa(JX<=XVwvFQq==gd;w2QPSkH!(5lWQt2 zY3m=9mU$PA;^+D4&;Wc!({5koJ?;H;7DcG1Y-F|qfFY56`z$)`iWt{Fj(wRgH*d+i z^D(zNgn_+@x0iPzM>jB5vhqE2*9IEn=i&;oB_2A}maNv7`!BRDKR|XY{lvlxW?c0b zKa7$(7TE>5-E3ESdG`ofa+kfU|AN5?hR-f#uO~?HlLtiPQ8s~TBItQMBBn{`yR#3A zOF0PLwTVNMV!5@ecX@S1`%(8MzbL|$4Yy&3QxP%iZvX+^b8m7LsPy;ZDzpUllPKsIOCBN-zai1WqTw(4Ax5u{&d)B}8#U*Y$L4+v zV)o@x*_jjxYb4IJQMh2g>#bRt6AM_LxEJtAz|x+9rzXZnnTXdi>(#AIH~DHbf(Zj_^|JU;>n_5NZV?WEGdLY@@&vbkZSKh0DFR>B5%~Ya_{6nlw|T~h zcqo61{Q}kt1uVBpK0kI2jte6$<^X0;cJV2}KXK0IoX?zGymve@p;*1_vHxo12b#5% zUftXepUpx6L(I+~+s%MfyXBN|V{TGocNEG`ldKNt4{Uj}H?P9WQYp7PX-{2*h6MwsQ&Z%q#Pf)&6g2+B_2m21KeaEpz!K_7ucU+*JJpRRIE z%x`zLe8Osuvnkzg28F%>{6m32c9*4CR|?3fZEC{_uWmbil41sVk5TKrW__&RbdVo| z8F9rM(29h>+^h_N4N>EZWx zO#WAVy+w2}ki$Be*j3zGub~IXfW_wN#s?X?`1MrKSo2KK^vSZ7&yhWjR6uiB2^;u@ z`*;ubzDsfl&hf-a0)T=nsAkLnDRb{eIzIZ{kjo=L>rE zoQil&aDC$qL6-oxVgvUWRlf9+6_GaI)H<#3^>5JOzp>Q$d)0J}t}w#{+}w)0!$~lT zrz6B8VPah{=PeVO3NisJ&jpO;fT@g#o1=ajl=8A71MY}haoPK+Hax>*6#WJ@ zj8Ad@WMO$rs30pbmTl*?i&ehKMSE!k)TI?kUr=YSpvZe7gS0r zIEoZ63CoKHn@-r0iso`2fSR`)(+dG6qgu~fJg<4lsnE+JJbv`_Q<_%%26+8}sUU2Y z-7!4V)3cuE7KmfSfJNaS{RL1qaDkdV^p@N^2A@3Tea6y``jg%NY#P6J&dx;(#EcBe zf0z$shGC(xoiJ$Y$s^8i9*zeT=o-l`LG(1K>%c$lY^~Ppo2>hUbG}tHzWg~s5M-2~ zhaaIuz@!5Am0jH+Qpmu`kG-9UjGTZa(*n`cvF$}1J4mZmXI~0E;jtP?No5H=bM(3q zgeRDku7k@im#bj*mllLX!6$UQKw~iAbQD{%3h0+^IP&qgyvpEeH>%}sXT?v|g0tDj zxW7sjV(tb4=O`#ll8zxwHZ-=FqQFlsimsb!jjNs~QndJzJotko=S8~ZmwdCS zP~1e*V3tHAJmbv-JteRrHqiXOdf;)8qlM=iMLMx1V@cMg0mw4hxkCQmTIMyJwaS=Y zT>5&cT{m0(xjD08cl74-POB#BO#NX9n)lWH8l+Kx7x)Z3Psu;P%EG9-5vbGJKE;8L zET*!EzHZ0~R)XO7&VVUX(_U(nESy2OP^_&l(`fDof0zvLBQ|qKN*_KqM)d0s-P^DQ zO@Z{7ELij{q$ly}u73+g0<+LbFaNeG$kulATn}sNDT=i zBOo;-0kDJ{1j{Q8JB$Fr)$i`B+UOXvUu&K0^ixnTknMK8sY2S4-*Tf_!{Ecnds}*- zOa)FDea52rZi9z{sPOeKS?B0`ED)u}-+{|Q_ErXy)lSztE>9qL zrh(~`2~Y9~`z>bHx69ALCk}(JI?^N>HNLxM(f#O-gES#WrvaTyicD1gVwE&0` zjPHD}@R1G}Rz%SCeJQN;(RIVj52V-LR|No0;Op(9uZwBTI@$5SwZ$99yBGnLz-D}o z&y@1(KACnaPC=FO!efmA{bTv~j>>x-^YY+d!tD#4QqK{Rq|djwNcYQ&U=QUf*%lNKr3A0(aS2*z9dd-n=1Fsp)A>|^t#Oaucc@9LO+1z?LsLD?Xi z@95C9zb*xO;3Ffm3sg*s9ZNDgh_R#5g-_p1I?CNlc*AC*Gpb195GUl#clUUJ zkn)8PGi9)_$@B}<4g65KJlO8>BN6Z$h8e>YEjKcj6bn5!(5+5z#o!wI{iXY{&KH+< z?t*0mzHmR#nHlyrK0%SsHNV=gBSbrAJz;&)!WicD4wR%VxjMr@R)@XA&QhGFj4fd_ zaj_PA+pT;zRG}pLXaVsn}=1W2^Z5#}cP!F*o3wBrhnP=mB4{8SnqIfbeN z#1^9KQP{TlARCX?z6gEEGKh+h^ChoIRL>Y^s{@yJ)8x8(+H6 zuSenY86bu*Jpub4Hq~KICm%X&@?Rdt*eEh%vO9MJ{0}&dIMzl(De8ZPFv*P>%KM;~ z%eUw6>Z@2kToK7>D3auT`8o3ief8^ABR2OZ3$dcu>0EgR_(0G;?k0% z(3#ozAO#v2<34|>4`lhfVvl>BMYQ))THvK=(++s$PYeHzx!?!eb-qICPS__$4e(vG z)dKm7C-EVzpb?}=#cmQDvf~K>3R%wm=tEyW-oOuQYt*_t;M9#>qW}6_9fCOX$kOI{ zct;>)5~Tn*F5)0+Ry+96v!0Vu(Tsv1;HYTT&S;%~!`0c}e}-LDs*iF%`q^#~rUP~A zf=9TFpdDPb0o=b}ZU;`bg7V;2dwJgtRejMi+}N}T5l{AOyLNaN!DCGEZ>X|rxC*Hi zgI?#x&i*wK8;*w*y|%{3bk`%!5T4`(x-Su=s@0|STJwXUj8AP(pTd9rvunV#@xfaI zyJ{h@MO!GW#sD4Ei!_3)w6$v{;*7^S@UxH}HDIPv+{l2^iYzVVmV(GZ0fevqN2lVX zKxZ&u3C|t37h-o%mVqV#e9!asLw%x7j@OI*&=B|1D!tL)kP2bRTPr++bU)QQp_wf( zf3(f6H*n_~FFeZsHiJ&@b?5v~^B;h+ov7)+giGrd$Uccnq`X9+zp4o1fIjsJ@%UG? zrWYX|4rh8Aek2hp9hi=xqXP8IL)z=RQ1MS1karb1?=WKN{Djx@6(p+ zt6NbFUg?7S&K7Y1NgBxy)|w;u*?I)Yu+s*R@64%*E!Ce~6WiI`9Y(KeN)%~dGNPo(UZjstUxp~+ zv<&_#x)UKf9;<&u(Buqb)=3)d2u{*|UZ6U@&!BY2H@G`~K)HP$I6EFV>#pAncnf~H z2&ZiBDMK`9HcSE}WiQmRDOL32-J+|@ed9{eQTNUe1Vi^;0@=S&f$iGQrHpBmO|NT0 zq6+?wg5uDL&EX$b{N;fDX1xa7(`vsy1;O0>j{cXNq`2a)95RUws>^mh1gdNGpsxBR z!d>(DGX5N?7Ak6stq}={$@R^-LHz!g`0mmV>`l7?U8zdoH-b z!N{w5ilY#O4{`)$W&RDecI^)tg5fYrbvEAqIC={> zY2Z1UfSm=i+*BY2?HdpK-Kfltg$kSM2wLRZuEa%K`?Lw-PW@eu!%3)?l3l5Xb| zdkgsfhJ=FyWz%7z(CzrSq{2MQQ+pMQ5%y%n0U!vD5dECrO@ULr;689o9Q3LHA|1Zk zLSpE-gPX|#1blCc0?c4jG*$mHC+H9aO6YpPw0T;T8!Mi0+24Njz1sDo^D?1vA05ue zUuVrp84Y*C)UB;GNp%3-#Qmc&A;Aqysi(E`p@uv$(pKh4!6m<56Y5OwKjaQSaC!9@ zCd`#}`^ir}L?TWn9G=~iI$nlT8FagyB_LT&-;;H(Q5LN*iw4|F%-n9`+ARheq&CUZ zz8*MCzC&7z14lbg$$)c2e*OrLQ=#n%3TCQVp~WLa){JaX`JvFeUll*;_>K(|Vd7wa z+TO02Xbe>gcJFhvWsF1N(XH3Y>#6KBA($wNB4O5MR_)mteM@iDp4*m>Ce5VN8mP5> zh&^v5S71};bG>0#-9)@*T8=w3wigb1p4S37iL}eI>$U2Ug2sGm1&2CBD94!HhMQvu zMbANpag`Up9gp$$2fF|j=J1L1$o-Yr5C#>nv>^;fG*n>&OmpywH!cXE1_t0*zML6b zH9wRvffHg4I|B2>(&teZ{um*au{$|zvLbY=iI+a?Ze=;hYOWO5-mPb^wPUU^21aJ) zgijto$j_?M>(PVhNP_P4CAO4H-8wivD0d~%dMAQv0 zL1BFGg2kX&DDnfKl;6Kc*WUPmphj-}f60!NNLdrLJN!h-=%iMP3SDh!Y zmi{O~-sATpC!7r*BtC1zSED-qadkBT8zRCM_2prulEX_;gf>~jSu`8@QP3lvnyxEx z9rEwh?wr*A11o``*VQY0GXdAhQFmyAf*?Xp!wWwk0PPB$AO+mmff{>RSd)_tXsSxb z_&d7WJ#C#J!qKp%BC1W=U3f98HFTDl94sh4Bw!YUr+gRGm7`I)kwaj;jq#|qbMHt&qqXH)nCHA^~y#Qt*-U9;u zWkG#mb~6NR`n?$5v!0EL_CI;0?PmUtAh<5}-sRhElzsBr5iQWgpLvEakIg`y?~2iC z#~3O16_;1Qr|g;Q!pV-?+5gk++b0+v`+IV6h_^8Xwkd{uD=2=bHgyie8oi@uLK2z9tHj!DKIX(A z)jx}mVfj0S1^pbZG}`ZChcJZ#bcH3e0Ub4wY-!6^Q52C(a@JXr%RXS@*CqBvHta2e zSQ!BzsM~YYNM^K2(}g0|0UdKH-uL3fqbjQXkH@2X-^o+;Sk8l@oTen}sgd1BjIrQ7 zZvLEXN8L^i)6P1`2j{KCTfM4A5 zWkEpW{X1e>v1GX;V1=2mlHOdRgIhvEcWtPKf>)Ns9Dl{2G?J(M#6zx;R8%C*BZcOK zx7QqD5Q=PoD2UfMOn(cXA+5u5`H{55YH#lb1`{~NH|6$h>=|xu*ohMs>@Nk>=hUJ& zch1Ri@c-1JE%aez*JR}{4rp!IOHkMC?A_J0&Uv*r88 zX(n2`^iMuESBNPlcv$quCL*;};TlQ>r$9)N zvMkTp493k_`1KVH`gYs3y1XyLa9xTdHm@p?;ps;+Xp|+<%ae$If?JZr1#_K03Vkwt zktIsdmCnpGa95_l1gHh4f81_-b%ztLJ(bmBE=`WXAhKof<;8ubVqAG6>^4Ik7FywWRg7!ZLKbG(vd5<#0D&& zM8{d>*a$h_CQ2*GFCK~Vl$_LwmvLW|wppWpF)g44lqMrAdwozVai}qUm1KNc;gOO1 zmS1|F@YB&0X&T}nU#NCt;{INzu*yGKB8s(nZAEcp=}F8T>Fh-UQ<)@L8c@)QhQtD# zW>PPd$~$3<+wOhJnM_j*{(w-^k0(sl=YH4+s~^5C|xTy4?%(7&(IhqQ+d3~LFq zveTMu!6LX1wAx3F{#sxkUkRlV14&&P@3 zX*WWe@?fWGllFOU;0hmx*1Eh+ZDo%A_d0k3$I&CqnhdaPe_wqL#`E|(Gh2gdW)zB- zefK(ZTf<)t)9^Vh1#xB+X3DWpEJoKoH}G%lb7_6Bak?^;7O&Qdd=+8g?_B6Ma--~1 zCEFM9h06?m{d|U0vF~juovGD55Ss3O$TJmMgECZu6CWlY~9%7JkX6zb=wFp@S4vWD-0=W-yZhLw=AXs)06F4i~?_u2&we zT-eb;r;%GD*9^bO$WPPx#coDRWeyo8T|_Ir!0%DNzfX5AYP^yf|U zT+}t~!j=8T+|zw1r`3PlP>lmC?m1joKG)Uo4iI z{46|!5dC1N`)4Bb*O0ez-;1)XY`6l!L;RKB&~u?v%@v%EL7#u(X>ewJ;d%rfsk&ihs}NP1^6{;iDZV06J6JpMC}o7G|=6ehLIx&wAg{`2Ed`Koqjnu90*(Y2>ecUyCLF zDlUF>5#DWNaAs-0X7@)M;j=z%aFEFkj91mD3B;|5np6IQ%?~-AAcwKSR zAst@w#)_vQYzwq8;q2CpsZP>U8&8yc`J_-G5x=A&)y6_Fda&(%dNvpFESG|>0X?*| z!LPgUWKv#6EYifWH+YP~L4OcWqnpC*c{E8nkaP0<(!VVMbpX=wJ z+H(Mq`aCr_vJ+6qcg^5DhM8VqY+@<-o+mh$N&=>lU*-3K^&?0xvKq)$*WA0ncUA$~ zz+|h?OGBV|nWc}?uO@Q976;~iJ<*C1t%35YaDMDbT;a(%BVTE;6AReM{_=1@i>uT03gI|-j~9GX1#b13Jw`b}$%CE5^bQm%oFFmG|)HIWNg{~g z!inaHm@+0;YmhoS7g}+72ZkfmRm8Aki+b{F6Td&*S#*HL(}v)WF>JhV`CiREO>?)` zIe(DV{T1!L7mBH;znOn?Nfk^dZ8fx#2R_^>6<>#9R*VA&22<+qzO`U~(T0LMa1dJty^ zTwbvGfVAxr(c_edOvc@*_E_~le;vSgt3Qy@@vGF3wGUq*JL_+N6U5ANbOC)b6m zV>K1XIt1SRp_va|Nq9!r@AG+pSGQP%AG#`+-KV7m8QBgMmrw`#UkWD2nvm;?2ub1>#d|fgvBM zbrPp;p3)}|$2;)9+HHTBx~okAJR<|hW+BYcJm=a$B%Wr`S|MJ#{fyAU6Q zNhg(IhIq4=1-J4?6A+RMLOtFfl*>oq7TIFfuP9@-5VpzapZQ+9H%eAFWd=rEgOl24 z|KO9(GvK++PrJ2uWfKihl*9k&qhQ;y_}C<#bu{&>aovbcfzWQI9V3s&t1W^8cn=%c z;BrT-u_GQjR8j%KkE~`f60!>|sBS#6KJCcBug`LaPW4lr`x;R{$F3*J5*wF(XgDxQ znP0c|J_YE;={j$QGPbysYfS#lLb`|BJ;`d79CPK<`VssuCeE{-isxhpBEssksXHRy zC`)%L_>C6noEEkAZh7su$>xM=^fet3(~O3~IO^9N_s;h(2L@Oq$pCk&H*;K5QR9Int z@hh!O59zfbTmM~(0UUpW>4{s4f0ZL?`|1p>krw^&1I?g2DmH9Sh6bE>%(Mq|SaX}({Y~_DsvuG%MQyBZ62DVDi_?9+ zzl4-c7Bq%u{4ZXKRAAHO8dcZ&qm2yDmfDP|SXibh=>`>+1yO+L<^#^9AH~AmxoCad zRyf@;&uBrk94B_yFB1L^bZo;PKiqV?#Dv9QG2$-iYAoX2l?x*ccj&7{5^v#dY3Qoa z%;dU#-W4GM2wvDPnDw3fi?G)A7NTO;{nasNpoGl0K6v4RWrwuxpk%dJMS$B@WRNb7 zfGV)k0X2&=0FC$^{*ht|KHg)IDvS9uY&h%jOIoFmu9(oR*y_6_S68n%ewe})5+G@E zlEw8r8a|!_>7MG{k$iw&IFIF4d0rgd9`y1&zy5^19A0{;;67ZqJ=1l)ExqE%5lp9NIw6=B$zygn7p%a zrv30J#782u#Sok_3FCW9;$mjg-|-vionhkSz@}Vx&lUJwpHEU-`CE}&(F?W=Y2x_I z7SJF4#|(vc=XI=)V#vU%Pq4gIW2gqC&AKGA0dM=UFKa)4tcC*|#KAmN40oiJ7lxR% zB6CZ`TEfy=e_-Y)(6Fw+7vfrXwYlvGqtVuJxPtQ{|D^-cJGzU3{RMaY^8(u?E5-9d zt{;hSiM3#;0k7j99fh*soY=@{L_7}Qxv5xFm)}`gID%hBNyy_OnfJ{uY-PvXo9r1Z zpBIX}X@#Y5zyHZ7(4_~5r;}E=jV>JTO%!P%EAPv}@@9{s$RwmCMS~x6U&h-ytidh~neWaJDxuK_lj13@V111=F*zV@DGBlxhdpHSyvXt>d zN*pg@mU=-8#dQ!UXOo~kzHV0L$aWLyU!{F-*sg4J(#-pKH==jOqcnX!OU&AMs>HEz zbf3q?t3Nf`28udzgSX7>OEzHN)7?o?6+WwtLe(}=TWYThiOFJHAzx>PQKG)ANTc|E z8oaa^Nc3gsYm^iGK<<^yUi=tU++j#7GDVON5lm4qi* zKDi>G!1k;Oy`puKZ{TDAv=n*;VIIleP4B0*8>NDfiDn5fhT+4A5p5Xd9L*kA32PAo zA59-UETLj|=#K)0WGY^cu9`vN1}BWY{hj0r#kOpHuIViFTC*c}j<;dEF&4@guW-A0 zDoE6g&rgN)_^M<+?N{v^kcomLd7@yedS7p{tvme(4_QZkMmz zne9*45TqRU%yK=IZf$(Y^#$=$7rWY5*>CZaC{&kyroHOv)$sJ{G&I7jn~Cm)u^oJm&D5J1))bR)EH zj+`V8YP@r6kj+}xFqtdh&<{~*SdWOrct=+BRyB`o1Q7h-!03=2vhD}@X4dKwdfy}j z1BC91r4Q7I;5-c3aLK%aS-;s|BC3ZMZ#b(voczHeUlmvJ=rsM6E@>*&ENS?I>!D-& z2nYjR3)}}$?(@m~u1{m`P&^=1j8>r|PHq*;lLbH~HiQEa#B(q|jppWP&NqD}dd}qs zQ8U0oCc_Y9zw2z&481Aa$ITLyd{$od&y!J+mn)z|@_z>uHOUz_`R^rx5DP<}tcHWC z%u7C3`B6tGEc6wSpSRM_!U9KncTEpT3ySr5KF4KE3(ZP*fk?f2Q$N6e0M=0cT;F-i z3^TXFRg^&QY-_W7PMd&Pjm_@7FK^Ukl@B^ER|vO1t8hqSjuUVG^HaRJLJvL1cw3DR zs<%pmQw`&%QU~e)`2CgY8k!stiM+ctvBB4&ea@|2f&F@O-yNtwJCkvfQnGMCv`aTf zzPB|L{;5h4tTqDDe+qKW>A!wjMB+X}PrJSucH*2V zj*FnI4^c*|kjZlG4};NbHLFCJ}Mx&B2?e#IVRC1Dx&_-t99qW05YYJ1+ejJDkvqy z271-QdghTN4hA20++UAfHpvbFrrf)ZmT)nR1+)C>OgpG@f&S&0Ge2*&tgDhn9zxoX_dy`B^uU z);ol~JD%y^yXoe<*yD9&K;v-LVS9=Vw$Q!}H5R7%(A@YDsLG-AIiszs$g%4?=fv#* z&O|(Hi-<$!BEJw0i|EkREAG^XEvEpHE(_RiLY-8Sq+84(a|P-igwpe3(L9~n-%bEg z%-eb1KisePOw1Qg*jEc&bedE!d`aSBdnd2lF#gh6nTQWmxIuqp-9OM8-!eTZIf1s$ z$4LBF?iGbUJ7hXg6X%oIH%5IfiYDSZ!xS9rdBeIxSu5gfBo%8GchVm^7Fs+&7S&QZ z<25zrCr#Q{&=-S|eIk~pGTd02!}S_%hy_6Nx4^Q}-IhW)4r2vZdMUlS?T)<3yoA>R z(eA{xW%+iS(+H5VmPw3XL2gqv1GHCBtbd~-S<=Lt)eTqI`Oa0xl-^y^+p!)sklA37?xX_Q6E5!Tj$aN;!VL5A*6+HVjUN8U?SZzmuT32hy4)yEu>XpD833|DD*KcPGsUw08t;S?cM* zhbdikift+97mxF1`ix=xghybjt&X{h{5b1tUvQnbo4RPX+u+JiG8)%)#ppdQb2w-% zoyh{JzG-V7&Jy4)5a#>z>{ueaaOIBTuKdnrFMW^*{5{RqwJDL-OWS>`MR#JIAupal zH;dNKY&dlK4gHxP_|d8Zv0jU`p4bU0s99F?@Lf0Ef!ZC;&Y~WFfUqHW*-H`#-7|wq z+~Z-^DaHYK+3}GuyY-jXE?kEE$#ARCN@wnk`0p%1UYGShJ6AP|=6xuInnh9;2U-D! zzaZ6*QawD|hzms+PYixeN9LrDk47LE&K=s=b1`E@ca+e7AzFz-3Mlx;ATe&m%Vm^_vAgybqDeuAVQTgi$(V?H>yg1rP z%UN5JVEO$INiD<_Jw$16$1q>2MP2OgOIz?DFm*u%x?IOHz^!|T|7v29VI}JNjtzY- zvGH|o`vMm5)nhiOjvMdLm)K>VQ-9)+MR_zJwc0fBNUV}bY*MYb_kF`SUSE0N^56tsi52GUrf9Zdwo;FI(nt4 z>Vou$qnO!rG#{t@=`M}~TCQRNy^dlXy(Si4x7v{$%}uqM>->?$@;r^yXBuO>9b*Qa z`&j~werq%iAJ=y%z(CwE#tb$y$4i1>aXC0HDygH=|YPlLE~)LMf| z?PxJYrNbd!2D7dR^otth7miz8;0rqu^i+xiL@$0?7kM1Vc7_~bXuB)%^En&+PGbo2H6A-H7Yj#M{)i8oPM+D@ z9E+B{?G_60$d`dhD>`Ud9vGxinCwTH3T<+kdcYiW3t*9q)|N5pTs}VzW_C^FroBl$(GGwhXq9tQz+}+VyP(G)p8va{cR}f-dCmn+B zdRNzG{fxs0T>D0Mg=DH#S^t?db5|y}nI@-)qv?%$QNbP#;JDncoFG=V?DJ*HkXKqB zvT%*_EEL8EEBN?dO9^dkdj(|))3o~gFkGp(xgs()~GXEbBoRm{k_bDBj%|Z z_6s#9VtF6-Gpr-(kAL%=X8O4uNn>4Hp~gZ3WZ{PV80n+(EfWRav<7Hxj7V16wyS|^ zHFNz8RLdmK4@Cff-mG7o+GzRb%=w-iGwaTO+R_8R{dJ$*Gk?Zhl<8fWZ2g= zc9I~+ev5TZu!6B)YoY9{vCdabyV-6f@fA!jKA&Ps2KHBZL$hGX;QFNYOOD(!6h5!O z%Bi(J#y>b%E=-@C`J+A~-h7N%hiG5z=p15?*&OTWbIo=d6E18?+5rV$?HV+vlbo^o zkk0W`5f`F<_do;H1nVyvO>%-BI{diKadHaXVA4x)-FYQ(m3GxoOG$>~cJJo=8D^RS z*rE$~QZ-17=HGis2aG)~VHa7=kGjGX-+SwfcXV_Viwb9~XXG^bgFRts^mSxhV2H54 zQo8UJ3_%I4wuR20NwZs7iW@9B_Jh>h&kI9R(E?ebxX}z2W%mQ5LBsXPM@bx^nF1hj zMnMhR{@?lgkGgZguYmJ9S*m}L+DXI9IQu8uiM;@+C(=>Y^@}Y{Vx)iC7(<1UlfaLq}4h$Q&JPjBfq+p zOuL;eyrwmP$gwSBB4F2t6Nr^51h6SMEOYWcp(!QOnfp}|(cR@HS71_GN*#F^Ug8hp zOsUDz&TdL!*0#y@5qFhvb;ojc)E(-oH9SP~2VVle<*6A%@j<`n8fCPxDN>6&{fc{J zLFob3*~Qu#JB%TZxNVKaGzD}(q4;9*mr5$3vR|BeWmVR!YAGID-EAeJ@mn0uRYW*g zI%+uuFbRb*gImRIQncn83rj%#@CGg3@NUeh%8`DaG~{n+B_sg@2ppYHY+0~pvJ~@4 zO}}_3tGg1)GtE&6NO_4Ys772sB4u}&(_h5fns|S +** Copyright (C) 2012 Laszlo Papp +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtSerialPort module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include + +#include "mainwindow.h" + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + + qRegisterMetaType("string"); + + MainWindow w; + w.show(); + return a.exec(); +} diff --git a/mainwindow.cpp b/mainwindow.cpp new file mode 100644 index 0000000..49de35f --- /dev/null +++ b/mainwindow.cpp @@ -0,0 +1,281 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Denis Shienkov +** Copyright (C) 2012 Laszlo Papp +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtSerialPort module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "mainwindow.h" +#include "ui_mainwindow.h" + +#include "settingsdialog.h" +#include "SendSave/SendSave.h" +#include +#include +#include +#include +#include +#include "Modem/Modem.h" + +MainWindow::MainWindow(QWidget *parent) : + QMainWindow(parent), + ui(new Ui::MainWindow), + m_modemEn(false) +{ + ui->setupUi(this); + + term = new QTermWidget(this); + + setCentralWidget(term); + setAcceptDrops(true); + + serial = new QSerialPort(this); + + settings = new SettingsDialog; + + ui->actionConnect->setEnabled(true); + ui->actionDisconnect->setEnabled(false); + ui->actionQuit->setEnabled(true); + ui->actionConfigure->setEnabled(true); + + status = new QLabel; + ui->statusBar->addWidget(status); + + initActionsConnections(); + + connect(serial, static_cast(&QSerialPort::error), + this, &MainWindow::handleError); + + connect(serial, &QSerialPort::readyRead, this, &MainWindow::readData); + connect(term, &term->outData, this, &MainWindow::writeData); + + dlgSS = new SendSave; + statusBar()->addWidget(ui->toolButton); + + connect(dlgSS, &dlgSS->outData, this, &MainWindow::writeData); + + statusBar()->addWidget(dlgSS->toolButton(0)); + statusBar()->addWidget(dlgSS->toolButton(1)); + statusBar()->addWidget(dlgSS->toolButton(2)); + + modem = new Modem(this); + connect(modem, &modem->outData, this, &MainWindow::writeData); + + modemCheck = new QTimer; + modemCheck->setSingleShot(true); + connect(modemCheck, SIGNAL(timeout()), this, SLOT(startModem())); + + connect(modem, &modem->exitTransfer, this, &this->exitTransfer); +} + +MainWindow::~MainWindow() +{ + delete settings; + delete ui; + delete dlgSS; + delete modem; +} + +void MainWindow::startModem() +{ + QString filename; + + modem->getFile(filename); + + if (filename.isEmpty()) + { + showStatus(string("请拖入文件")); + } + else + { + m_modemEn = true; + modem->startTransfer(); + } +} + +void MainWindow::exitTransfer() +{ + m_modemEn = false; + modem->hide(); +} + +void MainWindow::openSerialPort() +{ + SettingsDialog::Settings p = settings->settings(); + serial->setPortName(p.name); + serial->setBaudRate(p.baudRate); + serial->setDataBits(p.dataBits); + serial->setParity(p.parity); + serial->setStopBits(p.stopBits); + serial->setFlowControl(p.flowControl); + if (serial->open(QIODevice::ReadWrite)) + { + ui->actionConnect->setEnabled(false); + ui->actionDisconnect->setEnabled(true); + ui->actionConfigure->setEnabled(false); + showStatusMessage(tr("连接到 %1") + .arg(p.name)); + } + else + { + showStatus(string("打开出错")); + } +} + +void MainWindow::dropEvent(QDropEvent *event) +{ + QList urls; + + urls = event->mimeData()->urls(); + if (urls.isEmpty()) + return; + + QString filename = urls.first().toLocalFile(); + modem->setFile(filename); + + emit showStatus(filename.toStdString()); +} + +void MainWindow::dragEnterEvent(QDragEnterEvent *event) +{ + //如果为文件,则支持拖放 + if (event->mimeData()->hasFormat("text/uri-list")) + event->acceptProposedAction(); +} + +void MainWindow::showStatus(string s) +{ + QString qs; + + qs = qs.fromStdString(s); + statusBar()->showMessage(qs, 2000); +} + +void MainWindow::closeSerialPort() +{ + if (serial->isOpen()) + serial->close(); + + ui->actionConnect->setEnabled(true); + ui->actionDisconnect->setEnabled(false); + ui->actionConfigure->setEnabled(true); + showStatus(string("已断开")); +} + +void MainWindow::about() +{ + QMessageBox::about(this, tr("版本:1.0.2 作者:heyuanjie"), + tr("将文件拖入窗口,收到'C'后进入Ymodem模式")); +} + +void MainWindow::writeData(const QByteArray &data) +{ + serial->write(data); +} + +void MainWindow::readData() +{ + QByteArray data = serial->readAll(); + + if (m_modemEn) + { + modem->putData(data); + } + else + { + modemCheck->stop(); + if (data.at(data.size()- 1) == 'C') + { + modemCheck->start(20); + } + + term->putData(data); + } + + QString tmp; + + for (int i = 0; i < data.size(); i ++) + { + QString ch; + tmp += ch.sprintf("0x%02X, ", (uint8_t)data[i]); + } + //qDebug(tmp.toStdString().c_str()); +} + +void MainWindow::handleError(QSerialPort::SerialPortError error) +{ + if (error == QSerialPort::ResourceError) + { + QMessageBox::critical(this, tr("Critical Error"), serial->errorString()); + closeSerialPort(); + } +} + +void MainWindow::initActionsConnections() +{ + connect(ui->actionConnect, &QAction::triggered, this, &MainWindow::openSerialPort); + connect(ui->actionDisconnect, &QAction::triggered, this, &MainWindow::closeSerialPort); + connect(ui->actionQuit, &QAction::triggered, this, &MainWindow::close); + connect(ui->actionConfigure, &QAction::triggered, settings, &MainWindow::show); + connect(ui->actionAbout, &QAction::triggered, this, &MainWindow::about); + connect(ui->actionAboutQt, &QAction::triggered, qApp, &QApplication::aboutQt); +} + +void MainWindow::showStatusMessage(const QString &message) +{ + status->setText(message); +} + +void MainWindow::on_toolButton_clicked() +{ + dlgSS->show(); +} + +void MainWindow::on_actionClear_triggered() +{ + term->clear(); +} diff --git a/mainwindow.h b/mainwindow.h new file mode 100644 index 0000000..aec9075 --- /dev/null +++ b/mainwindow.h @@ -0,0 +1,124 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Denis Shienkov +** Copyright (C) 2012 Laszlo Papp +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtSerialPort module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include + +#include +#include +#include +#include + +using namespace std; + +#include "QTermWidget/QTermWidget.h" + +QT_BEGIN_NAMESPACE + +class QLabel; +class SendSave; + +namespace Ui { +class MainWindow; +} + +QT_END_NAMESPACE + +class SettingsDialog; +class Modem; +class QTimer; + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + explicit MainWindow(QWidget *parent = 0); + ~MainWindow(); + +private slots: + void openSerialPort(); + void closeSerialPort(); + void about(); + void writeData(const QByteArray &data); + void readData(); + void startModem(); + void handleError(QSerialPort::SerialPortError error); + void showStatus(string s); + void exitTransfer(); + void on_toolButton_clicked(); + + void on_actionClear_triggered(); + +protected: + virtual void dropEvent(QDropEvent *event); + virtual void dragEnterEvent(QDragEnterEvent *event); + +private: + void initActionsConnections(); + +private: + void showStatusMessage(const QString &message); + + Ui::MainWindow *ui; + QLabel *status; + SettingsDialog *settings; + QSerialPort *serial; + SendSave *dlgSS; + QTermWidget *term; + Modem *modem; + bool m_modemEn; + QTimer *modemCheck; +}; + +#endif // MAINWINDOW_H diff --git a/mainwindow.ui b/mainwindow.ui new file mode 100644 index 0000000..f4fc0ff --- /dev/null +++ b/mainwindow.ui @@ -0,0 +1,185 @@ + + + MainWindow + + + + 0 + 0 + 861 + 693 + + + + + Consolas + 12 + + + + QyTerm + + + + + + + + + + 保存 + + + + + + + + + 0 + 0 + 861 + 29 + + + + + Calls + + + + + + + + + Tools + + + + + + + Help + + + + + + + + + + + TopToolBarArea + + + false + + + + + + + + + + 14 + + + + + + &About + + + About program + + + Alt+A + + + + + About Qt + + + + + + :/images/connect.png:/images/connect.png + + + C&onnect + + + Connect to serial port + + + Ctrl+O + + + + + + :/images/disconnect.png:/images/disconnect.png + + + &Disconnect + + + Disconnect from serial port + + + Ctrl+D + + + + + + :/images/settings.png:/images/settings.png + + + &Configure + + + Configure serial port + + + Alt+C + + + + + + :/images/clear.png:/images/clear.png + + + C&lear + + + Clear data + + + Alt+L + + + + + + :/images/application-exit.png:/images/application-exit.png + + + &Quit + + + Ctrl+Q + + + + + + + + + diff --git a/settingsdialog.cpp b/settingsdialog.cpp new file mode 100644 index 0000000..4ae6f84 --- /dev/null +++ b/settingsdialog.cpp @@ -0,0 +1,209 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Denis Shienkov +** Copyright (C) 2012 Laszlo Papp +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtSerialPort module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "settingsdialog.h" +#include "ui_settingsdialog.h" + +#include +#include +#include + +QT_USE_NAMESPACE + +SettingsDialog::SettingsDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::SettingsDialog) +{ + ui->setupUi(this); + + intValidator = new QIntValidator(0, 4000000, this); + + ui->baudRateBox->setInsertPolicy(QComboBox::NoInsert); + + connect(ui->applyButton, SIGNAL(clicked()), + this, SLOT(apply())); + connect(ui->serialPortInfoListBox, SIGNAL(currentIndexChanged(int)), + this, SLOT(showPortInfo(int))); + connect(ui->baudRateBox, SIGNAL(currentIndexChanged(int)), + this, SLOT(checkCustomBaudRatePolicy(int))); + + fillPortsParameters(); + fillPortsInfo(); + + updateSettings(); +} + +SettingsDialog::~SettingsDialog() +{ + delete ui; +} + +SettingsDialog::Settings SettingsDialog::settings() const +{ + return currentSettings; +} + +void SettingsDialog::showPortInfo(int idx) +{ + if (idx != -1) + { + QStringList list = ui->serialPortInfoListBox->itemData(idx).toStringList(); + ui->descriptionLabel->setText(tr("描述: %1").arg(list.at(1))); + } +} + +void SettingsDialog::apply() +{ + updateSettings(); + hide(); +} + +void SettingsDialog::checkCustomBaudRatePolicy(int idx) +{ + bool isCustomBaudRate = !ui->baudRateBox->itemData(idx).isValid(); + ui->baudRateBox->setEditable(isCustomBaudRate); + if (isCustomBaudRate) + { + ui->baudRateBox->clearEditText(); + QLineEdit *edit = ui->baudRateBox->lineEdit(); + edit->setValidator(intValidator); + } +} + +void SettingsDialog::fillPortsParameters() +{ + ui->baudRateBox->addItem(QStringLiteral("9600"), QSerialPort::Baud9600); + ui->baudRateBox->addItem(QStringLiteral("19200"), QSerialPort::Baud19200); + ui->baudRateBox->addItem(QStringLiteral("38400"), QSerialPort::Baud38400); + ui->baudRateBox->addItem(QStringLiteral("115200"), QSerialPort::Baud115200); + ui->baudRateBox->addItem(QStringLiteral("自定义")); + ui->baudRateBox->setCurrentIndex(3); + + ui->dataBitsBox->addItem(QStringLiteral("5"), QSerialPort::Data5); + ui->dataBitsBox->addItem(QStringLiteral("6"), QSerialPort::Data6); + ui->dataBitsBox->addItem(QStringLiteral("7"), QSerialPort::Data7); + ui->dataBitsBox->addItem(QStringLiteral("8"), QSerialPort::Data8); + ui->dataBitsBox->setCurrentIndex(3); + + ui->parityBox->addItem(QStringLiteral("None"), QSerialPort::NoParity); + ui->parityBox->addItem(QStringLiteral("Even"), QSerialPort::EvenParity); + ui->parityBox->addItem(QStringLiteral("Odd"), QSerialPort::OddParity); + ui->parityBox->addItem(QStringLiteral("Mark"), QSerialPort::MarkParity); + ui->parityBox->addItem(QStringLiteral("Space"), QSerialPort::SpaceParity); + + ui->stopBitsBox->addItem(QStringLiteral("1"), QSerialPort::OneStop); + ui->stopBitsBox->addItem(QStringLiteral("2"), QSerialPort::TwoStop); + + ui->flowControlBox->addItem(QStringLiteral("None"), QSerialPort::NoFlowControl); + ui->flowControlBox->addItem(QStringLiteral("RTS/CTS"), QSerialPort::HardwareControl); + ui->flowControlBox->addItem(QStringLiteral("XON/XOFF"), QSerialPort::SoftwareControl); +} + +void SettingsDialog::fillPortsInfo() +{ + static const QString blankString = QObject::tr("N/A"); + QString description; + QStringList list; + + ui->serialPortInfoListBox->clear(); + + list << QString(tr("刷新")) + << QString(tr("点击更新列表")); + ui->serialPortInfoListBox->addItem(list.first(), list); + + foreach (const QSerialPortInfo &info, QSerialPortInfo::availablePorts()) + { + QStringList list; + description = info.description(); + + list << info.portName() + << (!description.isEmpty() ? description : blankString); + + ui->serialPortInfoListBox->addItem(list.first(), list); + } + + if (ui->serialPortInfoListBox->count() != 1) + { + ui->serialPortInfoListBox->setCurrentIndex(1); + } +} + +void SettingsDialog::updateSettings() +{ + currentSettings.name = ui->serialPortInfoListBox->currentText(); + + if (ui->baudRateBox->currentIndex() == 4) + { + currentSettings.baudRate = ui->baudRateBox->currentText().toInt(); + } + else + { + currentSettings.baudRate = static_cast( + ui->baudRateBox->itemData(ui->baudRateBox->currentIndex()).toInt()); + } + + currentSettings.stringBaudRate = QString::number(currentSettings.baudRate); + + currentSettings.dataBits = static_cast( + ui->dataBitsBox->itemData(ui->dataBitsBox->currentIndex()).toInt()); + currentSettings.stringDataBits = ui->dataBitsBox->currentText(); + + currentSettings.parity = static_cast( + ui->parityBox->itemData(ui->parityBox->currentIndex()).toInt()); + currentSettings.stringParity = ui->parityBox->currentText(); + + currentSettings.stopBits = static_cast( + ui->stopBitsBox->itemData(ui->stopBitsBox->currentIndex()).toInt()); + currentSettings.stringStopBits = ui->stopBitsBox->currentText(); + + currentSettings.flowControl = static_cast( + ui->flowControlBox->itemData(ui->flowControlBox->currentIndex()).toInt()); + currentSettings.stringFlowControl = ui->flowControlBox->currentText(); +} + +void SettingsDialog::on_serialPortInfoListBox_activated(int index) +{ + if (index == 0) + { + fillPortsInfo(); + } +} diff --git a/settingsdialog.h b/settingsdialog.h new file mode 100644 index 0000000..a78284c --- /dev/null +++ b/settingsdialog.h @@ -0,0 +1,105 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Denis Shienkov +** Copyright (C) 2012 Laszlo Papp +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtSerialPort module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef SETTINGSDIALOG_H +#define SETTINGSDIALOG_H + +#include +#include + +QT_USE_NAMESPACE + +QT_BEGIN_NAMESPACE + +namespace Ui { +class SettingsDialog; +} + +class QIntValidator; + +QT_END_NAMESPACE + +class SettingsDialog : public QDialog +{ + Q_OBJECT + +public: + struct Settings + { + QString name; + qint32 baudRate; + QString stringBaudRate; + QSerialPort::DataBits dataBits; + QString stringDataBits; + QSerialPort::Parity parity; + QString stringParity; + QSerialPort::StopBits stopBits; + QString stringStopBits; + QSerialPort::FlowControl flowControl; + QString stringFlowControl; + bool localEchoEnabled; + }; + + explicit SettingsDialog(QWidget *parent = 0); + ~SettingsDialog(); + + Settings settings() const; + +private slots: + void showPortInfo(int idx); + void apply(); + void checkCustomBaudRatePolicy(int idx); + + void on_serialPortInfoListBox_activated(int index); + +private: + void fillPortsParameters(); + void fillPortsInfo(); + void updateSettings(); + +private: + Ui::SettingsDialog *ui; + Settings currentSettings; + QIntValidator *intValidator; +}; + +#endif // SETTINGSDIALOG_H diff --git a/settingsdialog.ui b/settingsdialog.ui new file mode 100644 index 0000000..bcc7a39 --- /dev/null +++ b/settingsdialog.ui @@ -0,0 +1,123 @@ + + + SettingsDialog + + + + 0 + 0 + 327 + 263 + + + + 设置 + + + + + + + + Qt::Horizontal + + + + 96 + 20 + + + + + + + + 应用 + + + + + + + + + 端口选择 + + + + + + + + + 描述: + + + + + + + + + + 参数设置 + + + + + + 波特率 + + + + + + + + + + 数据位 + + + + + + + + + + 校验 + + + + + + + + + + 停止位 + + + + + + + + + + 流控 + + + + + + + + + + + + + + diff --git a/terminal.pro b/terminal.pro new file mode 100644 index 0000000..f7bab2a --- /dev/null +++ b/terminal.pro @@ -0,0 +1,36 @@ +QT += widgets serialport sql + +TARGET = QTerm +TEMPLATE = app + +SOURCES += \ + main.cpp \ + mainwindow.cpp \ + settingsdialog.cpp \ + SendSave/SendSave.cpp \ + SendSave/SSWorker.cpp \ + QTermWidget/QTermScreen.cpp \ + QTermWidget/QTermWidget.cpp \ + Modem/Modem.cpp \ + Modem/Ymodem.cpp \ + Modem/crc16.c + +HEADERS += \ + mainwindow.h \ + settingsdialog.h \ + SendSave/SendSave.h \ + SendSave/SSWorker.h \ + QTermWidget/QTermScreen.h \ + QTermWidget/QTermWidget.h \ + Modem/Modem.h \ + Modem/Ymodem.h \ + Modem/crc.h + +FORMS += \ + mainwindow.ui \ + settingsdialog.ui \ + SendSave/SendSave.ui \ + Modem/modem.ui + +RESOURCES += \ + terminal.qrc diff --git a/terminal.qrc b/terminal.qrc new file mode 100644 index 0000000..7465cf7 --- /dev/null +++ b/terminal.qrc @@ -0,0 +1,9 @@ + + + images/connect.png + images/disconnect.png + images/application-exit.png + images/settings.png + images/clear.png + +