From 7d123b2c0616cda20581a1e19fcf3c5b63f2d9a5 Mon Sep 17 00:00:00 2001 From: taynpg Date: Sun, 15 Jun 2025 14:31:54 +0800 Subject: [PATCH] gui: add basic gui code. --- .vscode/settings.json | 3 +- CMakeLists.txt | 2 + ClientCore/CMakeLists.txt | 4 + ClientCore/ClientCore.cpp | 21 +++ ClientCore/ClientCore.h | 13 +- ClientCore/RemoteFile.cpp | 31 ++++ ClientCore/RemoteFile.h | 26 ++++ Gui/CMakeLists.txt | 83 +++++++++++ Gui/Control/CompareControl.cpp | 13 ++ Gui/Control/CompareControl.h | 22 +++ Gui/Control/CompareControl.ui | 19 +++ Gui/Control/ConnectControl.cpp | 155 +++++++++++++++++++ Gui/Control/ConnectControl.h | 61 ++++++++ Gui/Control/ConnectControl.ui | 137 +++++++++++++++++ Gui/Control/FileControl.cpp | 222 ++++++++++++++++++++++++++++ Gui/Control/FileControl.h | 44 ++++++ Gui/Control/FileControl.ui | 66 +++++++++ Gui/Control/LogControl.cpp | 63 ++++++++ Gui/Control/LogControl.h | 38 +++++ Gui/Control/LogControl.ui | 24 +++ Gui/GuiUtil/Public.cpp | 42 ++++++ Gui/GuiUtil/Public.h | 16 ++ Gui/frelayGUI.cpp | 73 +++++++++ Gui/frelayGUI.h | 45 ++++++ Gui/frelayGUI.ui | 31 ++++ Gui/main.cpp | 19 +++ Protocol/CMakeLists.txt | 5 +- Struct/CMakeLists.txt | 27 ++++ {Protocol => Struct}/InfoClient.cpp | 0 {Protocol => Struct}/InfoClient.h | 6 +- Struct/InfoDirFile.cpp | 13 ++ Struct/InfoDirFile.h | 55 +++++++ Struct/InfoMsg.h | 28 ++++ {Protocol => Struct}/InfoPack.hpp | 0 Struct/infoMsg.cpp | 13 ++ Util/CMakeLists.txt | 4 +- Util/LocalFile.cpp | 16 ++ Util/LocalFile.h | 20 +++ Util/Util.cpp | 13 ++ Util/Util.h | 22 +++ 40 files changed, 1483 insertions(+), 12 deletions(-) create mode 100644 ClientCore/RemoteFile.cpp create mode 100644 ClientCore/RemoteFile.h create mode 100644 Gui/CMakeLists.txt create mode 100644 Gui/Control/CompareControl.cpp create mode 100644 Gui/Control/CompareControl.h create mode 100644 Gui/Control/CompareControl.ui create mode 100644 Gui/Control/ConnectControl.cpp create mode 100644 Gui/Control/ConnectControl.h create mode 100644 Gui/Control/ConnectControl.ui create mode 100644 Gui/Control/FileControl.cpp create mode 100644 Gui/Control/FileControl.h create mode 100644 Gui/Control/FileControl.ui create mode 100644 Gui/Control/LogControl.cpp create mode 100644 Gui/Control/LogControl.h create mode 100644 Gui/Control/LogControl.ui create mode 100644 Gui/GuiUtil/Public.cpp create mode 100644 Gui/GuiUtil/Public.h create mode 100644 Gui/frelayGUI.cpp create mode 100644 Gui/frelayGUI.h create mode 100644 Gui/frelayGUI.ui create mode 100644 Gui/main.cpp create mode 100644 Struct/CMakeLists.txt rename {Protocol => Struct}/InfoClient.cpp (100%) rename {Protocol => Struct}/InfoClient.h (92%) create mode 100644 Struct/InfoDirFile.cpp create mode 100644 Struct/InfoDirFile.h create mode 100644 Struct/InfoMsg.h rename {Protocol => Struct}/InfoPack.hpp (100%) create mode 100644 Struct/infoMsg.cpp create mode 100644 Util/LocalFile.cpp create mode 100644 Util/LocalFile.h diff --git a/.vscode/settings.json b/.vscode/settings.json index ebb2564..64e9308 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -157,6 +157,7 @@ "text_encoding": "cpp", "hash_map": "cpp", "qreadwritelock": "cpp", - "qdatastream": "cpp" + "qdatastream": "cpp", + "qhostaddress": "cpp" } } \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 18d5cef..6a50d65 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -31,4 +31,6 @@ add_subdirectory(Protocol) add_subdirectory(Server) add_subdirectory(ClientCore) add_subdirectory(Util) +add_subdirectory(Gui) +add_subdirectory(Struct) add_subdirectory(Test) diff --git a/ClientCore/CMakeLists.txt b/ClientCore/CMakeLists.txt index eefccab..c44d2fd 100644 --- a/ClientCore/CMakeLists.txt +++ b/ClientCore/CMakeLists.txt @@ -15,11 +15,15 @@ find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core Network) set(SOURCES ClientCore.cpp ClientCore.h +RemoteFile.h +RemoteFile.cpp ) add_library(ClientCore STATIC ${SOURCES}) target_link_libraries(ClientCore PRIVATE Protocol Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Network + Struct + Util ) target_include_directories(ClientCore PUBLIC ${CMAKE_CURRENT_LIST_DIR}) diff --git a/ClientCore/ClientCore.cpp b/ClientCore/ClientCore.cpp index 42b1442..f98c037 100644 --- a/ClientCore/ClientCore.cpp +++ b/ClientCore/ClientCore.cpp @@ -76,3 +76,24 @@ bool ClientCore::Send(const char* data, qint64 len) } return true; } + +void ClientCore::SetClientsCall(const std::function& call) +{ +} + +void ClientCore::SetPathCall(const std::function& call) +{ +} + +void ClientCore::SetFileCall(const std::function& call) +{ +} + +void ClientCore::SetRemoteID(const QString& id) +{ +} + +QString ClientCore::GetRemoteID() +{ + return remoteID_; +} diff --git a/ClientCore/ClientCore.h b/ClientCore/ClientCore.h index e6401bd..1e53958 100644 --- a/ClientCore/ClientCore.h +++ b/ClientCore/ClientCore.h @@ -1,6 +1,8 @@ #ifndef CLIENTCORE_H #define CLIENTCORE_H +#include +#include #include #include #include @@ -20,6 +22,8 @@ public: public: bool Connect(const QString& ip, quint16 port); void Disconnect(); + bool Send(QSharedPointer frame); + bool Send(const char* data, qint64 len); private: void onReadyRead(); @@ -27,8 +31,13 @@ private: private: void UseFrame(QSharedPointer frame); - bool Send(QSharedPointer frame); - bool Send(const char* data, qint64 len); + +public: + void SetClientsCall(const std::function& call); + void SetPathCall(const std::function& call); + void SetFileCall(const std::function& call); + void SetRemoteID(const QString& id); + QString GetRemoteID(); public: QMutex conMutex_; diff --git a/ClientCore/RemoteFile.cpp b/ClientCore/RemoteFile.cpp new file mode 100644 index 0000000..af61a00 --- /dev/null +++ b/ClientCore/RemoteFile.cpp @@ -0,0 +1,31 @@ +#include "RemoteFile.h" + +#include +#include "LocalFile.h" + +void RemoteFile::setClientCore(ClientCore* cliCore) +{ + cliCore_ = cliCore; + cliCore_->SetPathCall(pathCall_); + cliCore_->SetFileCall(fileCall_); +} + +bool RemoteFile::GetHome() +{ + InfoMsg info; + auto frame = QSharedPointer::create(); + frame->data = infoPack(info); + frame->type = FBT_CLI_ASK_HOME; + frame->tid = cliCore_->GetRemoteID(); + return cliCore_->Send(frame); +} + +bool RemoteFile::GetDirFile(const QString& dir) +{ + InfoMsg info; + auto frame = QSharedPointer::create(); + frame->data = infoPack(info); + frame->type = FBT_CLI_ASK_DIRFILE; + frame->tid = cliCore_->GetRemoteID(); + return cliCore_->Send(frame); +} \ No newline at end of file diff --git a/ClientCore/RemoteFile.h b/ClientCore/RemoteFile.h new file mode 100644 index 0000000..3f58c84 --- /dev/null +++ b/ClientCore/RemoteFile.h @@ -0,0 +1,26 @@ +#ifndef REMOTE_FILE_H +#define REMOTE_FILE_H + +#include +#include + +#include "ClientCore.h" + +class RemoteFile : public DirFileHelper +{ +public: + RemoteFile() = default; + ~RemoteFile() override = default; + +public: + void setClientCore(ClientCore* cliCore); + +public: + bool GetHome() override; + bool GetDirFile(const QString& dir) override; + +private: + ClientCore* cliCore_; +}; + +#endif // REMOTE_FILE_H \ No newline at end of file diff --git a/Gui/CMakeLists.txt b/Gui/CMakeLists.txt new file mode 100644 index 0000000..153f925 --- /dev/null +++ b/Gui/CMakeLists.txt @@ -0,0 +1,83 @@ +cmake_minimum_required(VERSION 3.16) + +project(frelayGUI VERSION ${PROJECT_VERSION} LANGUAGES CXX) + +set(CMAKE_AUTOUIC ON) +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) + +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +add_definitions(-DUSE_QT_GUI) +find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets Network) +find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets Network) + +include_directories(${CMAKE_CURRENT_SOURCE_DIR}) +set(PROJECT_SOURCES main.cpp +frelayGUI.cpp frelayGUI.h frelayGUI.ui +Control/LogControl.h Control/LogControl.cpp Control/LogControl.ui +Control/FileControl.h Control/FileControl.cpp Control/FileControl.ui +Control/ConnectControl.h Control/ConnectControl.cpp Control/ConnectControl.ui +Control/CompareControl.h Control/CompareControl.cpp Control/CompareControl.ui +GuiUtil/Public.h GuiUtil/Public.cpp +) + +if(${QT_VERSION_MAJOR} GREATER_EQUAL 6) + qt_add_executable(frelayGUI + MANUAL_FINALIZATION + ${PROJECT_SOURCES} + ) +# Define target properties for Android with Qt 6 as: +# set_property(TARGET frelayGUI APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR +# ${CMAKE_CURRENT_SOURCE_DIR}/android) +# For more information, see https://doc.qt.io/qt-6/qt-add-executable.html#target-creation +else() + if(ANDROID) + add_library(frelayGUI SHARED + ${PROJECT_SOURCES} + ) +# Define properties for Android with Qt 5 after find_package() calls as: +# set(ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android") + else() + add_executable(frelayGUI + ${PROJECT_SOURCES} + ) + endif() +endif() + +target_link_libraries(frelayGUI PRIVATE + Qt${QT_VERSION_MAJOR}::Widgets + Qt${QT_VERSION_MAJOR}::Network + ClientCore Protocol + Util + Struct +) +if (CMAKE_CXX_COMPILER_ID MATCHES "GNU" AND CMAKE_SYSTEM_NAME MATCHES "Windows") + target_link_libraries(frelayGUI PRIVATE ws2_32 wsock32) +endif() + +# Qt for iOS sets MACOSX_BUNDLE_GUI_IDENTIFIER automatically since Qt 6.1. +# If you are developing for iOS or macOS you should consider setting an +# explicit, fixed bundle identifier manually though. +if(${QT_VERSION} VERSION_LESS 6.1.0) + set(BUNDLE_ID_OPTION MACOSX_BUNDLE_GUI_IDENTIFIER com.example.frelayGUI) +endif() +set_target_properties(frelayGUI PROPERTIES + ${BUNDLE_ID_OPTION} + MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION} + MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR} + MACOSX_BUNDLE TRUE + WIN32_EXECUTABLE TRUE +) + +include(GNUInstallDirs) +install(TARGETS frelayGUI + BUNDLE DESTINATION . + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} +) + +if(QT_VERSION_MAJOR EQUAL 6) + qt_finalize_executable(frelayGUI) +endif() diff --git a/Gui/Control/CompareControl.cpp b/Gui/Control/CompareControl.cpp new file mode 100644 index 0000000..1662085 --- /dev/null +++ b/Gui/Control/CompareControl.cpp @@ -0,0 +1,13 @@ +#include "CompareControl.h" + +#include "ui_CompareControl.h" + +Compare::Compare(QWidget* parent) : QWidget(parent), ui(new Ui::Compare) +{ + ui->setupUi(this); +} + +Compare::~Compare() +{ + delete ui; +} diff --git a/Gui/Control/CompareControl.h b/Gui/Control/CompareControl.h new file mode 100644 index 0000000..3b4def3 --- /dev/null +++ b/Gui/Control/CompareControl.h @@ -0,0 +1,22 @@ +#ifndef COMPARECONTROL_H +#define COMPARECONTROL_H + +#include + +namespace Ui { +class Compare; +} + +class Compare : public QWidget +{ + Q_OBJECT + +public: + explicit Compare(QWidget *parent = nullptr); + ~Compare(); + +private: + Ui::Compare *ui; +}; + +#endif // COMPARECONTROL_H diff --git a/Gui/Control/CompareControl.ui b/Gui/Control/CompareControl.ui new file mode 100644 index 0000000..0d5be94 --- /dev/null +++ b/Gui/Control/CompareControl.ui @@ -0,0 +1,19 @@ + + + Compare + + + + 0 + 0 + 400 + 300 + + + + Form + + + + + diff --git a/Gui/Control/ConnectControl.cpp b/Gui/Control/ConnectControl.cpp new file mode 100644 index 0000000..fa8afb5 --- /dev/null +++ b/Gui/Control/ConnectControl.cpp @@ -0,0 +1,155 @@ +#include "ConnectControl.h" + +#include +#include +#include + +#include "Control/LogControl.h" +#include "GuiUtil/Public.h" +#include "ui_ConnectControl.h" + +Connecter::Connecter(QWidget* parent) : QWidget(parent), ui(new Ui::Connecter) +{ + ui->setupUi(this); + InitControl(); +} + +Connecter::~Connecter() +{ + if (thConnect_.joinable()) { + thConnect_.join(); + } + delete ui; +} + +void Connecter::SetClientCore(ClientCore* clientCore) +{ + clientCore_ = clientCore; + clientCore_->SetClientsCall([this](const InfoClientVec& clients) { HandleClients(clients); }); +} + +void Connecter::SetLogPrint(LogPrint* log) +{ + log_ = log; +} + +void Connecter::SetRemoteCall(const std::function& call) +{ + remoteCall_ = call; +} + +void Connecter::HandleClients(const InfoClientVec& clients) +{ + model_->removeRows(0, ui->listView->model()->rowCount()); + for (const auto& client : clients.vec) { + auto* item = new QStandardItem(client.name); + model_->appendRow(item); + } +} + +void Connecter::Connect() +{ + auto ip = ui->edIP->text().trimmed(); + auto port = ui->edPort->text().trimmed(); + if (ip.isEmpty() || port.isEmpty()) { + FTCommon::msg(this, tr("IP or Port is empty.")); + return; + } + auto task = [this, ip, port]() { + emit sendConnect(ConnectState::CS_CONNECTING); + connceted_ = clientCore_->Connect(ip, port.toInt()); + if (connceted_) { + emit sendConnect(ConnectState::CS_CONNECTED); + } else { + emit sendConnect(ConnectState::CS_DISCONNECT); + } + }; + if (thConnect_.joinable()) { + thConnect_.join(); + } + thConnect_ = std::thread(task); +} + +void Connecter::setState(ConnectState cs) +{ + switch (cs) { + case CS_CONNECTING: + ui->btnConnect->setEnabled(false); + ui->btnDisconnect->setEnabled(false); + log_->Info(tr("Connecting...")); + break; + case CS_CONNECTED: + ui->btnConnect->setEnabled(false); + ui->btnDisconnect->setEnabled(true); + break; + case CS_DISCONNECT: + ui->btnConnect->setEnabled(true); + ui->btnDisconnect->setEnabled(false); + break; + default: + break; + } +} + +void Connecter::RefreshClient() +{ + InfoMsg info; + auto frame = QSharedPointer::create(); + frame->data = infoPack(info); + frame->type = FBT_SER_MSG_ASKCLIENTS; + if (!clientCore_->Send(frame)) { + qCritical() << QString(tr("send ask client list failed.")); + return; + } + qInfo() << QString(tr("ask client list...")); +} + +void Connecter::ShowContextMenu(const QPoint& pos) +{ + auto index = ui->listView->indexAt(pos); + + if (!index.isValid()) { + return; + } + menu_->exec(QCursor::pos()); +} + +std::string Connecter::getCurClient() +{ + return ui->elbClient->text().toStdString(); +} + +void Connecter::InitControl() +{ + ui->btnDisconnect->setEnabled(false); + ui->edIP->setText("127.0.0.1"); + ui->edPort->setText("9009"); + connect(ui->btnConnect, &QPushButton::clicked, this, &Connecter::Connect); + connect(ui->btnRefresh, &QPushButton::clicked, this, &Connecter::RefreshClient); + connect(this, &Connecter::sendConnect, this, &Connecter::setState); + + model_ = new QStandardItemModel(this); + ui->listView->setModel(model_); + ui->listView->setContextMenuPolicy(Qt::CustomContextMenu); + + connect(ui->listView, &QListView::customContextMenuRequested, this, &Connecter::ShowContextMenu); + + menu_ = new QMenu(this); + QAction* acUseThis = menu_->addAction(tr("UseThis")); + connect(acUseThis, &QAction::triggered, this, [this]() { + auto index = ui->listView->currentIndex(); + if (!index.isValid()) { + return; + } + auto* item = model_->itemFromIndex(index); + if (!item) { + return; + } + auto name = item->text(); + ui->elbClient->setText(name); + ui->elbClient->setStyleSheet("color: blue;"); + remoteCall_(name); + }); + + setMaximumSize(300, 300); +} diff --git a/Gui/Control/ConnectControl.h b/Gui/Control/ConnectControl.h new file mode 100644 index 0000000..0ee0a27 --- /dev/null +++ b/Gui/Control/ConnectControl.h @@ -0,0 +1,61 @@ +#ifndef CONNECTCONTROL_H +#define CONNECTCONTROL_H + +#include +#include +#include +#include +#include + +namespace Ui { +class Connecter; +} + +enum ConnectState { + CS_DISCONNECT, + CS_CONNECTING, + CS_CONNECTED, +}; + +class LogPrint; +class Connecter : public QWidget +{ + Q_OBJECT + +public: + explicit Connecter(QWidget* parent = nullptr); + ~Connecter(); + +public: + void SetClientCore(ClientCore* clientCore); + void SetLogPrint(LogPrint* log); + void SetRemoteCall(const std::function& call); + void HandleClients(const InfoClientVec& clients); + +signals: + void sendConnect(ConnectState cs); + +private: + void InitControl(); + void Connect(); + void setState(ConnectState cs); + void RefreshClient(); + void ShowContextMenu(const QPoint& pos); + std::string getCurClient(); + +private: + std::thread thConnect_; + Ui::Connecter* ui; + LogPrint* log_; + bool thRun_{false}; + bool connceted_{false}; + std::thread thContext_; + std::function remoteCall_; + ClientCore* clientCore_; + +private: + QMenu* menu_; + QStandardItemModel* model_; +}; + +#endif // CONNECTCONTROL_H diff --git a/Gui/Control/ConnectControl.ui b/Gui/Control/ConnectControl.ui new file mode 100644 index 0000000..9486a34 --- /dev/null +++ b/Gui/Control/ConnectControl.ui @@ -0,0 +1,137 @@ + + + Connecter + + + + 0 + 0 + 350 + 276 + + + + Form + + + + + + + + Server IP: + + + + + + + + + + Port: + + + + + + + + 60 + 16777215 + + + + + + + + + + + + Client: + + + + + + + None + + + + + + + Qt::Orientation::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + + Disconnect + + + + + + + Qt::Orientation::Horizontal + + + + 40 + 20 + + + + + + + + Refresh + + + + + + + Qt::Orientation::Horizontal + + + + 40 + 20 + + + + + + + + Connect + + + + + + + + + + diff --git a/Gui/Control/FileControl.cpp b/Gui/Control/FileControl.cpp new file mode 100644 index 0000000..b4635a0 --- /dev/null +++ b/Gui/Control/FileControl.cpp @@ -0,0 +1,222 @@ +#include "FileControl.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "LogControl.h" +#include "ui_FileControl.h" + +FileManager::FileManager(QWidget* parent) : QWidget(parent), ui(new Ui::FileManager) +{ + ui->setupUi(this); + InitControl(); +} + +FileManager::~FileManager() +{ + delete ui; +} + +void FileManager::SetModeStr(const QString& modeStr, int type, ClientCore* clientCore) +{ + ui->lbMode->setText(modeStr); + if (type == 0) { + fileHelper_ = std::make_shared(); + fileHelper_->registerPathCall([this](const QString& path) { ShowPath(path); }); + fileHelper_->registerFileCall([this](const DirFileInfoVec& info) { ShowFile(info); }); + } else { + auto remotePtr = std::make_shared(); + remotePtr->registerPathCall([this](const QString& path) { ShowPath(path); }); + remotePtr->registerFileCall([this](const DirFileInfoVec& info) { ShowFile(info); }); + remotePtr->setClientCore(clientCore); + fileHelper_ = remotePtr; + } +} + +void FileManager::SetLogPrint(LogPrint* log) +{ + log_ = log; +} + +void FileManager::InitControl() +{ + QStringList headers; + headers << tr("") << tr("FileName") << tr("ModifyTime") << tr("Type") << tr("Size"); + ui->tableWidget->setColumnCount(headers.size()); + ui->tableWidget->setHorizontalHeaderLabels(headers); + ui->tableWidget->setSelectionBehavior(QAbstractItemView::SelectRows); + + ui->comboBox->setEditable(true); + // ui->tableWidget->setColumnWidth(0, 50); + ui->tableWidget->setColumnWidth(1, 300); + ui->tableWidget->setColumnWidth(2, 150); + ui->tableWidget->setColumnWidth(3, 70); + ui->tableWidget->setColumnWidth(4, 90); + ui->tableWidget->setSelectionBehavior(QAbstractItemView::SelectRows); + ui->tableWidget->verticalHeader()->setSectionResizeMode(QHeaderView::Fixed); + ui->tableWidget->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Fixed); + ui->tableWidget->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch); + // ui->tableWidget->setStyleSheet("QTableWidget::item:hover { background-color: transparent; }"); + + connect(ui->btnHome, &QPushButton::clicked, this, &FileManager::evtHome); + connect(ui->btnVisit, &QPushButton::clicked, this, &FileManager::evtFile); + connect(ui->tableWidget, &QTableWidget::cellDoubleClicked, this, &FileManager::doubleClick); + connect(ui->btnUp, &QPushButton::clicked, this, &FileManager::evtUp); +} + +void FileManager::ShowPath(const QString& path) +{ + int existingIndex = ui->comboBox->findText(path); + if (existingIndex != -1) { + ui->comboBox->removeItem(existingIndex); + } else if (ui->comboBox->count() >= 20) { + ui->comboBox->removeItem(ui->comboBox->count() - 1); + } + ui->comboBox->insertItem(0, path); + ui->comboBox->setCurrentIndex(0); +} + +void FileManager::ShowFile(const DirFileInfoVec& info) +{ + QAbstractItemModel* const mdl = ui->tableWidget->model(); + mdl->removeRows(0, mdl->rowCount()); + ui->tableWidget->setRowCount(info.vec.size()); + + for (int i = 0; i < info.vec.size(); ++i) { + const DirFileInfo& fileInfo = info.vec[i]; + + // *********************************************************************************** + auto* iconItem = new QTableWidgetItem(""); + iconItem->setTextAlignment(Qt::AlignHCenter | Qt::AlignVCenter); + iconItem->setFlags(iconItem->flags() & ~Qt::ItemIsEditable); + ui->tableWidget->setItem(i, 0, iconItem); + + // *********************************************************************************** + auto* fileItem = new QTableWidgetItem(fileInfo.name); + fileItem->setFlags(fileItem->flags() & ~Qt::ItemIsEditable); + ui->tableWidget->setItem(i, 1, fileItem); + QDateTime modifyTime = QDateTime::fromMSecsSinceEpoch(fileInfo.lastModified); + + // *********************************************************************************** + QString timeStr = modifyTime.toString("yyyy-MM-dd hh:mm:ss"); + auto* timeItem = new QTableWidgetItem(timeStr); + timeItem->setFlags(timeItem->flags() & ~Qt::ItemIsEditable); + ui->tableWidget->setItem(i, 2, timeItem); + + // *********************************************************************************** + QString typeStr; + switch (fileInfo.type) { + case File: + typeStr = "File"; + iconItem->setIcon(QApplication::style()->standardIcon(QStyle::SP_FileIcon)); + break; + case Dir: + typeStr = "Dir"; + iconItem->setIcon(QApplication::style()->standardIcon(QStyle::SP_DirIcon)); + break; + case Link: + typeStr = "Link"; + break; + case Other: + typeStr = "Other"; + break; + default: + typeStr = "Unknown"; + break; + } + + // *********************************************************************************** + auto* typeItem = new QTableWidgetItem(typeStr); + typeItem->setFlags(typeItem->flags() & ~Qt::ItemIsEditable); + ui->tableWidget->setItem(i, 3, typeItem); + + // *********************************************************************************** + QString sizeStr; + if (fileInfo.size < 1024) { + sizeStr = QString::number(fileInfo.size) + " B"; + } else if (fileInfo.size < 1024 * 1024) { + sizeStr = QString::number(fileInfo.size / 1024.0, 'f', 2) + " KB"; + } else { + sizeStr = QString::number(fileInfo.size / (1024.0 * 1024.0), 'f', 2) + " MB"; + } + QTableWidgetItem* item = nullptr; + if (fileInfo.type == File) { + item = new QTableWidgetItem(sizeStr); + } else { + item = new QTableWidgetItem(""); + } + item->setFlags(item->flags() & ~Qt::ItemIsEditable); + ui->tableWidget->setItem(i, 4, item); + } + ui->tableWidget->resizeColumnToContents(0); + + if (info.vec.empty()) { + return; + } + + QString fp = info.vec[0].fullPath; + QDir dir(fp); + dir.cdUp(); + fp = dir.path(); + curRoot_ = fp; + + ShowPath(curRoot_); +} + +void FileManager::evtHome() +{ + auto r = fileHelper_->GetHome(); + auto curPath = ui->comboBox->currentText(); + curRoot_ = curPath; + qDebug() << QString(tr("%1 get home ret:%2").arg(__FUNCTION__).arg(r)); +} + +void FileManager::evtFile() +{ + auto curPath = ui->comboBox->currentText(); + auto r = fileHelper_->GetDirFile(curPath); + curRoot_ = curPath; + qDebug() << QString(tr("%1 get files ret:%2").arg(__FUNCTION__).arg(r)); +} + +void FileManager::evtUp() +{ + QString path(curRoot_); + QDir dir(path); + dir.cdUp(); + path = dir.path(); + + if (path.isEmpty()) { + return; + } + + auto r = fileHelper_->GetDirFile(path); + if (r) { + curRoot_ = path; + ShowPath(curRoot_); + } +} + +void FileManager::doubleClick(int row, int column) +{ + Q_UNUSED(column) + + auto* item = ui->tableWidget->item(row, 1); + if (item == nullptr) { + return; + } + + auto type = ui->tableWidget->item(row, 3)->text(); + if (type != "Dir") { + return; + } + + QDir dir(curRoot_); + QString np = dir.filePath(item->text()); + fileHelper_->GetDirFile(np); +} diff --git a/Gui/Control/FileControl.h b/Gui/Control/FileControl.h new file mode 100644 index 0000000..8bc72f6 --- /dev/null +++ b/Gui/Control/FileControl.h @@ -0,0 +1,44 @@ +#ifndef FILECONTROL_H +#define FILECONTROL_H + +#include +#include +#include +#include + +namespace Ui { +class FileManager; +} + +class LogPrint; +class FileManager : public QWidget +{ + Q_OBJECT + +public: + explicit FileManager(QWidget* parent = nullptr); + ~FileManager(); + +public: + void SetModeStr(const QString& modeStr, int type = 0, ClientCore* clientCore = nullptr); + void SetLogPrint(LogPrint* log); + +private: + void InitControl(); + void ShowPath(const QString& path); + void ShowFile(const DirFileInfoVec& info); + void doubleClick(int row, int column); + +private: + void evtHome(); + void evtFile(); + void evtUp(); + +private: + Ui::FileManager* ui; + LogPrint* log_; + QString curRoot_; + std::shared_ptr fileHelper_; +}; + +#endif // FILECONTROL_H diff --git a/Gui/Control/FileControl.ui b/Gui/Control/FileControl.ui new file mode 100644 index 0000000..af69e45 --- /dev/null +++ b/Gui/Control/FileControl.ui @@ -0,0 +1,66 @@ + + + FileManager + + + + 0 + 0 + 719 + 339 + + + + Form + + + + + + + + Mode: + + + + + + + + 0 + 0 + + + + + + + + Go + + + + + + + Home + + + + + + + Up + + + + + + + + + + + + + diff --git a/Gui/Control/LogControl.cpp b/Gui/Control/LogControl.cpp new file mode 100644 index 0000000..317bf32 --- /dev/null +++ b/Gui/Control/LogControl.cpp @@ -0,0 +1,63 @@ +#include "LogControl.h" + +#include +#include +#include +#include + +#include "ui_LogControl.h" + +LogPrint::LogPrint(QWidget* parent) : QWidget(parent), ui(new Ui::LogPrint) +{ + ui->setupUi(this); + InitControl(); +} + +void LogPrint::InitControl() +{ + model_ = new QStandardItemModel(this); + ui->listView->setModel(model_); +} + +std::string LogPrint::now_str() +{ + auto now = std::chrono::system_clock::now(); + auto time_t_now = std::chrono::system_clock::to_time_t(now); + auto milliseconds = std::chrono::duration_cast(now.time_since_epoch()) % 1000; + + std::ostringstream timestamp; + timestamp << std::put_time(std::localtime(&time_t_now), "%H:%M:%S") << "." << std::setfill('0') << std::setw(3) + << milliseconds.count() << " "; + + return timestamp.str(); +} + +LogPrint::~LogPrint() +{ + delete ui; +} + +void LogPrint::Info(const QString& message) +{ + Print(message, Qt::black); +} +void LogPrint::Warn(const QString& message) +{ + Print(message, Qt::yellow); +} +void LogPrint::Error(const QString& message) +{ + Print(message, Qt::red); +} +void LogPrint::Debug(const QString& message) +{ + Print(message, Qt::blue); +} +void LogPrint::Print(const QString& message, const QBrush& color) +{ + auto timeStr = QString("%1%2").arg(QString::fromStdString(now_str())).arg(message); + auto* item = new QStandardItem(timeStr); + item->setForeground(color); + model_->appendRow(item); + ui->listView->scrollToBottom(); +} diff --git a/Gui/Control/LogControl.h b/Gui/Control/LogControl.h new file mode 100644 index 0000000..5feeae2 --- /dev/null +++ b/Gui/Control/LogControl.h @@ -0,0 +1,38 @@ +#ifndef LOGCONTROL_H +#define LOGCONTROL_H + +#include +#include +#include + +namespace Ui { +class LogPrint; +} + +class LogPrint : public QWidget +{ + Q_OBJECT + +public: + explicit LogPrint(QWidget* parent = nullptr); + ~LogPrint(); + +public: + void Info(const QString& message); + void Warn(const QString& message); + void Error(const QString& message); + void Debug(const QString& message); + +public: + std::string now_str(); + +private: + void InitControl(); + void Print(const QString& message, const QBrush& color); + +private: + Ui::LogPrint* ui; + QStandardItemModel* model_; +}; + +#endif // LOGCONTROL_H diff --git a/Gui/Control/LogControl.ui b/Gui/Control/LogControl.ui new file mode 100644 index 0000000..3b8a103 --- /dev/null +++ b/Gui/Control/LogControl.ui @@ -0,0 +1,24 @@ + + + LogPrint + + + + 0 + 0 + 767 + 291 + + + + Form + + + + + + + + + + diff --git a/Gui/GuiUtil/Public.cpp b/Gui/GuiUtil/Public.cpp new file mode 100644 index 0000000..73eb049 --- /dev/null +++ b/Gui/GuiUtil/Public.cpp @@ -0,0 +1,42 @@ +#include "Public.h" + +#include +#include + +void FTCommon::msg(QWidget* parent, const QString& content) +{ + QMessageBox::information(parent, QObject::tr("prompt"), content); +} + +bool FTCommon::affirm(QWidget* parent, const QString& titile, const QString& content) +{ + QMessageBox questionBox(parent); + questionBox.setText(content); + questionBox.setWindowTitle(titile); + questionBox.setIcon(QMessageBox::Question); + questionBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); + int result = questionBox.exec(); + if (result != QMessageBox::Yes) { + return false; + } else { + return true; + } +} + +QString FTCommon::select_file(QWidget* parent, const QString& info, const QString& filter) +{ + QString filePath = QFileDialog::getOpenFileName(parent, info, QDir::homePath(), filter); + return filePath; +} + +QString FTCommon::GetAppPath() +{ + QString home = QDir::homePath(); + QDir dir(home); + auto ret_path = dir.absoluteFilePath(".config/frelayGUI"); + QDir d(ret_path); + if (!d.exists()) { + d.mkpath(ret_path); + } + return ret_path; +} diff --git a/Gui/GuiUtil/Public.h b/Gui/GuiUtil/Public.h new file mode 100644 index 0000000..ea7f690 --- /dev/null +++ b/Gui/GuiUtil/Public.h @@ -0,0 +1,16 @@ +#ifndef PUBLIC_H +#define PUBLIC_H + +#include +#include + +class FTCommon +{ +public: + static void msg(QWidget* parent, const QString& content); + static bool affirm(QWidget* parent, const QString& titile, const QString& content); + static QString select_file(QWidget* parent, const QString& info, const QString& filter); + static QString GetAppPath(); +}; + +#endif // PUBLIC_H \ No newline at end of file diff --git a/Gui/frelayGUI.cpp b/Gui/frelayGUI.cpp new file mode 100644 index 0000000..3614fb2 --- /dev/null +++ b/Gui/frelayGUI.cpp @@ -0,0 +1,73 @@ +#include "frelayGUI.h" + +#include + +#include "./ui_frelayGUI.h" + +frelayGUI::frelayGUI(QWidget* parent) : QMainWindow(parent), ui(new Ui::frelayGUI) +{ + ui->setupUi(this); + InitControl(); + ControlSignal(); + ControlLayout(); + resize(1500, 800); +} + +frelayGUI::~frelayGUI() +{ + delete ui; +} + +void frelayGUI::InitControl() +{ + log_ = new LogPrint(this); + + clientCore_ = new ClientCore(this); + + connecter_ = new Connecter(this); + connecter_->SetClientCore(clientCore_); + connecter_->SetLogPrint(log_); + connecter_->SetRemoteCall([this](const QString& id) { clientCore_->SetRemoteID(id); }); + + localFile_ = new FileManager(this); + remoteFile_ = new FileManager(this); + localFile_->SetModeStr(tr("Local:")); + remoteFile_->SetModeStr(tr("Remote:"), 1, clientCore_); + localFile_->SetLogPrint(log_); + remoteFile_->SetLogPrint(log_); + + tabWidget_ = new QTabWidget(this); +} + +void frelayGUI::ControlSignal() +{ +} + +void frelayGUI::ControlLayout() +{ + auto* splitter = new QSplitter(Qt::Vertical); + splitter->setHandleWidth(1); + auto* sTop = new QSplitter(Qt::Horizontal); + auto* sConnect = new QSplitter(Qt::Vertical); + auto* sFile = new QSplitter(Qt::Horizontal); + + sTop->setHandleWidth(1); + sConnect->setHandleWidth(1); + sFile->setHandleWidth(1); + + sTop->addWidget(tabWidget_); + sTop->addWidget(connecter_); + tabWidget_->addTab(log_, tr("Log")); + + sFile->addWidget(localFile_); + sFile->addWidget(remoteFile_); + + splitter->addWidget(sTop); + splitter->addWidget(sFile); + setCentralWidget(splitter); +} + +void frelayGUI::closeEvent(QCloseEvent* event) +{ + QMainWindow::closeEvent(event); +} diff --git a/Gui/frelayGUI.h b/Gui/frelayGUI.h new file mode 100644 index 0000000..d8b7f96 --- /dev/null +++ b/Gui/frelayGUI.h @@ -0,0 +1,45 @@ +#ifndef FRELAYGUI_H +#define FRELAYGUI_H + +#include +#include +#include +#include +#include + +#include "Control/ConnectControl.h" +#include "Control/FileControl.h" +#include "Control/LogControl.h" + +QT_BEGIN_NAMESPACE +namespace Ui { +class frelayGUI; +} +QT_END_NAMESPACE + +class frelayGUI : public QMainWindow +{ + Q_OBJECT + +public: + frelayGUI(QWidget* parent = nullptr); + ~frelayGUI(); + +private: + void InitControl(); + void ControlSignal(); + void ControlLayout(); + +protected: + void closeEvent(QCloseEvent* event) override; + +private: + Ui::frelayGUI* ui; + LogPrint* log_; + QTabWidget* tabWidget_; + Connecter* connecter_; + FileManager* localFile_; + FileManager* remoteFile_; + ClientCore* clientCore_; +}; +#endif // FRELAYGUI_H diff --git a/Gui/frelayGUI.ui b/Gui/frelayGUI.ui new file mode 100644 index 0000000..99986ec --- /dev/null +++ b/Gui/frelayGUI.ui @@ -0,0 +1,31 @@ + + + frelayGUI + + + + 0 + 0 + 800 + 600 + + + + frelayGUI + + + + + + 0 + 0 + 800 + 25 + + + + + + + + diff --git a/Gui/main.cpp b/Gui/main.cpp new file mode 100644 index 0000000..b18d4fa --- /dev/null +++ b/Gui/main.cpp @@ -0,0 +1,19 @@ +#include +#include + +#include "frelayGUI.h" + +int main(int argc, char* argv[]) +{ + QApplication a(argc, argv); + +#ifdef _WIN32 + QFont font("Microsoft YaHei", 9); + a.setFont(font); + a.setStyle("Windows"); +#endif + + frelayGUI w; + w.show(); + return a.exec(); +} diff --git a/Protocol/CMakeLists.txt b/Protocol/CMakeLists.txt index 20266dd..206f62f 100644 --- a/Protocol/CMakeLists.txt +++ b/Protocol/CMakeLists.txt @@ -15,11 +15,8 @@ find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core) set(SOURCES Protocol.cxx Protocol.h -InfoClient.h -InfoClient.cpp -InfoPack.hpp ) add_library(Protocol STATIC ${SOURCES}) -target_link_libraries(Protocol Qt${QT_VERSION_MAJOR}::Core) +target_link_libraries(Protocol Qt${QT_VERSION_MAJOR}::Core Struct) target_include_directories(Protocol PUBLIC ${CMAKE_CURRENT_LIST_DIR}) diff --git a/Struct/CMakeLists.txt b/Struct/CMakeLists.txt new file mode 100644 index 0000000..a517f2e --- /dev/null +++ b/Struct/CMakeLists.txt @@ -0,0 +1,27 @@ +cmake_minimum_required(VERSION 3.16) + +project(Struct LANGUAGES CXX) + +set(CMAKE_AUTOUIC ON) +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) + +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Core) +find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core) + +set(SOURCES +InfoClient.h +InfoClient.cpp +InfoPack.hpp +InfoDirFile.h +InfoDirFile.cpp +InfoMsg.h +InfoMsg.cpp +) + +add_library(Struct STATIC ${SOURCES}) +target_link_libraries(Struct Qt${QT_VERSION_MAJOR}::Core) +target_include_directories(Struct PUBLIC ${CMAKE_CURRENT_LIST_DIR}) diff --git a/Protocol/InfoClient.cpp b/Struct/InfoClient.cpp similarity index 100% rename from Protocol/InfoClient.cpp rename to Struct/InfoClient.cpp diff --git a/Protocol/InfoClient.h b/Struct/InfoClient.h similarity index 92% rename from Protocol/InfoClient.h rename to Struct/InfoClient.h index 0072c65..272d42f 100644 --- a/Protocol/InfoClient.h +++ b/Struct/InfoClient.h @@ -1,5 +1,5 @@ -#ifndef COMSTRUCT_H -#define COMSTRUCT_H +#ifndef INFO_CLIENT_H +#define INFO_CLIENT_H #include #include @@ -46,4 +46,4 @@ struct InfoClientVec { } }; -#endif // COMSTRUCT_H \ No newline at end of file +#endif // INFO_CLIENT_H \ No newline at end of file diff --git a/Struct/InfoDirFile.cpp b/Struct/InfoDirFile.cpp new file mode 100644 index 0000000..2951532 --- /dev/null +++ b/Struct/InfoDirFile.cpp @@ -0,0 +1,13 @@ +#include "InfoDirFile.h" + +QDataStream& operator<<(QDataStream& data, const DirFileInfo& info) +{ + info.serialize(data); + return data; +} + +QDataStream& operator>>(QDataStream& data, DirFileInfo& info) +{ + info.deserialize(data); + return data; +} diff --git a/Struct/InfoDirFile.h b/Struct/InfoDirFile.h new file mode 100644 index 0000000..c78468e --- /dev/null +++ b/Struct/InfoDirFile.h @@ -0,0 +1,55 @@ +#ifndef INFO_DIR_FILE_H +#define INFO_DIR_FILE_H + +#include +#include +#include +#include +#include + +enum FileType : uint32_t { None = 0, File, Dir, Link, Other }; + +struct DirFileInfo { + QString name; + quint64 size{}; + FileType type = None; + QString fullPath; + quint16 permission{}; + quint64 lastModified{}; + + void serialize(QDataStream& data) const + { + data << name << size << type << fullPath << permission << lastModified; + } + + void deserialize(QDataStream& data) + { + data >> name >> size >> type >> fullPath >> permission >> lastModified; + } +}; + +QDataStream& operator<<(QDataStream& data, const DirFileInfo& info); +QDataStream& operator>>(QDataStream& data, DirFileInfo& info); + +struct DirFileInfoVec { + QVector vec; + + void serialize(QDataStream& data) const + { + data << vec.size(); + for (const auto& info : vec) { + data << info; + } + } + void deserialize(QDataStream& data) + { + qsizetype size; + data >> size; + vec.resize(size); + for (quint32 i = 0; i < size; ++i) { + data >> vec[i]; + } + } +}; + +#endif // INFO_DIR_FILE_H \ No newline at end of file diff --git a/Struct/InfoMsg.h b/Struct/InfoMsg.h new file mode 100644 index 0000000..97fc119 --- /dev/null +++ b/Struct/InfoMsg.h @@ -0,0 +1,28 @@ +#ifndef INFO_MSG_H +#define INFO_MSG_H + +#include +#include +#include +#include +#include + +struct InfoMsg { + qint32 mark{}; + QString msg; + + void serialize(QDataStream& data) const + { + data << mark << msg; + } + + void deserialize(QDataStream& data) + { + data >> mark >> msg; + } +}; + +QDataStream& operator<<(QDataStream& data, const InfoMsg& info); +QDataStream& operator>>(QDataStream& data, InfoMsg& info); + +#endif // INFO_MSG_H \ No newline at end of file diff --git a/Protocol/InfoPack.hpp b/Struct/InfoPack.hpp similarity index 100% rename from Protocol/InfoPack.hpp rename to Struct/InfoPack.hpp diff --git a/Struct/infoMsg.cpp b/Struct/infoMsg.cpp new file mode 100644 index 0000000..fec12f2 --- /dev/null +++ b/Struct/infoMsg.cpp @@ -0,0 +1,13 @@ +#include "InfoMsg.h" + +QDataStream& operator<<(QDataStream& data, const InfoMsg& info) +{ + info.serialize(data); + return data; +} + +QDataStream& operator>>(QDataStream& data, InfoMsg& info) +{ + info.deserialize(data); + return data; +} diff --git a/Util/CMakeLists.txt b/Util/CMakeLists.txt index 4b15138..5d8717a 100644 --- a/Util/CMakeLists.txt +++ b/Util/CMakeLists.txt @@ -12,6 +12,6 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Core) find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core) -add_library(Util STATIC Util.h Util.cpp) -target_link_libraries(Util PRIVATE Qt${QT_VERSION_MAJOR}::Core) +add_library(Util STATIC Util.h Util.cpp LocalFile.h LocalFile.cpp) +target_link_libraries(Util PRIVATE Qt${QT_VERSION_MAJOR}::Core Struct) target_include_directories(Util PUBLIC ${CMAKE_CURRENT_LIST_DIR}) diff --git a/Util/LocalFile.cpp b/Util/LocalFile.cpp new file mode 100644 index 0000000..dd2fc8d --- /dev/null +++ b/Util/LocalFile.cpp @@ -0,0 +1,16 @@ +#include "LocalFile.h" + +bool LocalFile::GetHome() +{ + return false; +} + +bool LocalFile::GetDirFile(const QString& dir) +{ + return false; +} + +bool LocalFile::GetDirFile(const QString& dir, DirFileInfoVec& vec) +{ + return false; +} \ No newline at end of file diff --git a/Util/LocalFile.h b/Util/LocalFile.h new file mode 100644 index 0000000..2c4ffde --- /dev/null +++ b/Util/LocalFile.h @@ -0,0 +1,20 @@ +#ifndef LOCALFILE_H +#define LOCALFILE_H + +#include + +#include "Util.h" + +class LocalFile : public DirFileHelper +{ +public: + LocalFile() = default; + ~LocalFile() override = default; + +public: + bool GetHome() override; + bool GetDirFile(const QString& dir) override; + bool GetDirFile(const QString& dir, DirFileInfoVec& vec); +}; + +#endif // LOCALFILE_H \ No newline at end of file diff --git a/Util/Util.cpp b/Util/Util.cpp index c7c270a..92d4e2b 100644 --- a/Util/Util.cpp +++ b/Util/Util.cpp @@ -58,3 +58,16 @@ void Util::ConsoleMsgHander(QtMsgType type, const QMessageLogContext& context, c break; } } + +QString DirFileHelper::GetErr() const +{ + return QString(); +} + +void DirFileHelper::registerPathCall(const std::function& call) +{ +} + +void DirFileHelper::registerFileCall(const std::function& call) +{ +} diff --git a/Util/Util.h b/Util/Util.h index 18f3bf7..f463700 100644 --- a/Util/Util.h +++ b/Util/Util.h @@ -2,6 +2,7 @@ #define UTIL_H #include +#include class Util : public QObject { @@ -14,4 +15,25 @@ public: static void ConsoleMsgHander(QtMsgType type, const QMessageLogContext& context, const QString& msg); }; +class DirFileHelper +{ +public: + DirFileHelper() = default; + virtual ~DirFileHelper() = default; + +public: + QString GetErr() const; + void registerPathCall(const std::function& call); + void registerFileCall(const std::function& call); + +protected: + QString err_; + std::function pathCall_; + std::function fileCall_; + +public: + virtual bool GetHome() = 0; + virtual bool GetDirFile(const QString& dir) = 0; +}; + #endif // UTIL_H \ No newline at end of file