diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..dfe0770 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..259148f --- /dev/null +++ b/.gitignore @@ -0,0 +1,32 @@ +# Prerequisites +*.d + +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod +*.smod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..e15f929 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,38 @@ +cmake_minimum_required(VERSION 3.5) + +message(${PROJECT_SOURCE_DIR}/../MINGW64_MINSIZERELEASE) + +project(ConsoleInQt LANGUAGES CXX) + +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/../MINGW64_MINSIZERELEASE) +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/../MINGW64_MINSIZERELEASE) +set(CMAKE_BINARY_DIR ${PROJECT_SOURCE_DIR}/../MINGW64_MINSIZERELEASE) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/../MINGW64_MINSIZERELEASE) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) +set(CMAKE_AUTOUIC ON) +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_PREFIX_PATH "F:/Software/Qt5/Qt5.14.2/5.14.2/mingw73_64") + + +find_package(Qt5 COMPONENTS Widgets REQUIRED) + +add_library(ConsoleInQt SHARED + ConsoleInQt_global.h + Console.cpp + Console.h +) + +target_link_libraries(ConsoleInQt PRIVATE Qt5::Widgets) + +target_compile_definitions(ConsoleInQt PRIVATE CONSOLEINQT_LIBRARY) +set(MY_CUSTOM_COMMAND "${CMAKE_BINARY_DIR}/copy.bat") +message("Executing COPYING: ${MY_CUSTOM_COMMAND}") +## 添加自定义命令,在编译完成后执行 +add_custom_command( + TARGET ConsoleInQt POST_BUILD + COMMAND ${MY_CUSTOM_COMMAND} + COMMENT "Copying include files after build") diff --git a/Console.cpp b/Console.cpp new file mode 100644 index 0000000..1dc381a --- /dev/null +++ b/Console.cpp @@ -0,0 +1,10 @@ +#include "Console.h" +using namespace ConsoleInQt; +Console::Console(QWidget* parent) : QWidget(parent) +{ + CIQ_consoleWidget.setFocusPolicy(Qt::StrongFocus); + connect(CIQ_scrollArea.verticalScrollBar(),&QAbstractSlider::rangeChanged, [=]() { + CIQ_scrollArea.verticalScrollBar()->setValue(CIQ_scrollArea.verticalScrollBar()->maximum()); + }); + installEventFilter(this); +} diff --git a/Console.h b/Console.h new file mode 100644 index 0000000..6fab56d --- /dev/null +++ b/Console.h @@ -0,0 +1,280 @@ +#ifndef CONSOLE_H +#define CONSOLE_H + +#include "ConsoleInQt_global.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include // 添加定时器用于焦点延迟设置 + +#define _CIQ_HEIGHT_MAX_ 16777215 + +namespace ConsoleInQt { + + //控制台模式枚举 + enum class ConsoleModeEnum { + Shell=1, //Shell模式 + Text=2 //普通文本模式 + }; + +class CONSOLEINQT_EXPORT Config : public QObject{ + Q_OBJECT +public: + explicit Config(QObject* parent=nullptr){ + Q_UNUSED(parent); + }; + ~Config(){}; + +protected: + + //=========^全局设置^=========// + size_t ConsoleMode=static_cast(ConsoleModeEnum::Shell); //控制台模式 + QString WelcomeMessage=""; //欢迎消息 + QString BackgroundColor="black"; //背景颜色 + QString TextCursorColor="white"; //光标颜色 + QString TextColor="white"; //文本颜色 + QString TextSize="20"; //字体大小 + +public slots: + + //=========^全局设置方法^=========// + inline void setWelcomeMessage(const QString& WelcomeMessageSc){WelcomeMessage=WelcomeMessageSc;} + inline QString getWelcomeMessage(void) const{return WelcomeMessage;}; + + inline void setBackgroundColor(const QString& BackgroundColorSc){BackgroundColor=BackgroundColorSc;} + inline QString getBackgroundColor(void) const{return BackgroundColor;}; + + inline void setTextColor(const QString& TextColorSc){TextColor=TextColorSc;} + inline QString getTextColor(void) const{return TextColor;} + + inline void setTextSize(const QString& TextSizeSc){TextSize=TextSizeSc;} + inline QString getTextSize(void) const{return TextSize;} + + inline size_t TestingOutput() const{ + return ConsoleMode; + } //测试输出 + + inline QString getCurrentConfig(void) const { + return QString("background: %1; color: %2; font-size: %3px; border-width: 0; border-style: outset;") + .arg(BackgroundColor, TextColor, TextSize); + } //获取当前全局设置 +}; + + +class Console; +class CONSOLEINQT_EXPORT ConsoleBufferStream { +private: + Console& console; + std::string buffer; +public: + explicit ConsoleBufferStream(Console& console):console(console){} + ConsoleBufferStream(const ConsoleBufferStream&)=delete; + ConsoleBufferStream& operator=(const ConsoleBufferStream&)=delete; + ConsoleBufferStream(ConsoleBufferStream&& other) noexcept + : console(other.console), buffer(std::move(other.buffer)) { + other.buffer.clear(); // 清空源对象缓冲区 + }; + inline ~ConsoleBufferStream(); + template + ConsoleBufferStream& operator<<(const BufferT& data) { + std::ostringstream oss; + oss<count() > 0) { + if (QWidget* lastWidget = CIQ_consoleLayout->itemAt(CIQ_consoleLayout->count()-1)->widget()) { + // 使用定时器确保在事件循环结束后设置焦点 + QTimer::singleShot(5, [lastWidget] { + lastWidget->setFocus(); + if (auto* input = qobject_cast(lastWidget)) { + input->setCursorPosition(input->text().length()); + } + }); + //QThread::sleep(50); + /*lastWidget->setFocus(); + if (auto* input = qobject_cast(lastWidget)) { + input->setCursorPosition(input->text().length()); + }*/ + } + } + } + + void CIQ_Shell_createNewLine(const QString& content="",const bool userInput=true) { + QLineEdit* currentInput = nullptr; + if (CIQ_consoleLayout->count()>0) { + if ( (currentInput = qobject_cast(CIQ_consoleLayout->itemAt(CIQ_consoleLayout->count()-1)->widget()))) { + currentInput->setReadOnly(true); + } + } + auto* newInput = new QLineEdit(&CIQ_consoleWidget); + newInput->setAlignment(Qt::AlignLeft|Qt::AlignVCenter); + CIQ_currentLineEdit=newInput; + CIQ_consoleLayout->addWidget(newInput); + if (userInput) { + newInput->setText(">"+content); + connect(CIQ_currentLineEdit, &QLineEdit::textChanged, [this](){ + if (CIQ_currentLineEdit->text()[0]!=">") { + CIQ_currentLineEdit->setText(">"+CIQ_currentLineEdit->text()); + }}); + setFocusToCurrentInput(); + } + else { + newInput->setText(content); + newInput->setReadOnly(true); + } + } + + //初始化主布局和控制台窗口布局 + void initLayouts(void) { + CIQ_consoleWidget.setLayout(CIQ_consoleLayout); + CIQ_mainLayout->setMargin(0); + CIQ_mainLayout->setSpacing(0); + CIQ_mainLayout->setContentsMargins(0,0,0,0); + CIQ_mainLayout->setAlignment(Qt::AlignCenter); + CIQ_consoleLayout->setMargin(0); + CIQ_consoleLayout->setSpacing(0); + CIQ_consoleLayout->setContentsMargins(0,0,0,0); + CIQ_consoleLayout->setAlignment(Qt::AlignTop|Qt::AlignLeft); + } + + bool eventFilter(QObject *watched, QEvent *event) override { + if (static_cast(event)->key()&&CIQ_currentLineEdit!=nullptr&&CIQ_currentLineEdit->cursorPosition()<=1) { + CIQ_currentLineEdit->setCursorPosition(1); + } + return QWidget::eventFilter(watched, event); + } + + void keyPressEvent(QKeyEvent *event) override { + //按键逻辑分支 + if (event->key() == Qt::Key_Return) { + emit commandSend(std::string_view(std::string(std::string(CIQ_currentLineEdit->text().toStdString()).begin()+1,std::string(CIQ_currentLineEdit->text().toStdString()).end()))); + QString command=""; + for (int i=1;itext().length();i++) { + command+=CIQ_currentLineEdit->text()[i]; + } + emit commandSend(command); + CIQ_Shell_createNewLine(); + return; + }//针对按下回车键之后的事件处理机制 + if (event->key()&&CIQ_currentLineEdit!=nullptr&&CIQ_currentLineEdit->cursorPosition()<=1) { + CIQ_currentLineEdit->setCursorPosition(1); + } + } +protected: + void processBuffer(const std::string& buffer) { + std::istringstream iss(buffer); + std::string line; + while (std::getline(iss, line)) { + CIQ_Shell_createNewLine(QString::fromStdString(line), false); + } + CIQ_Shell_createNewLine(); + } + +//公共方法 +public: + explicit Console(QWidget* parent = nullptr); + ~Console() { + } + + template // 默认以string_view构建 + ConsoleBufferStream operator<<(OutputT&& content) { + ConsoleBufferStream bufferStream(*this); + + if constexpr(std::is_same_v, QString>) { + bufferStream << content.toStdString(); + } + else if constexpr(std::is_convertible_v) { + // 处理所有可转换为string_view的类型(包括string、char数组等) + bufferStream << std::string_view(content); + } + else { + // 回退到ostream处理(如数值类型) + std::ostringstream oss; + oss << content; + bufferStream << oss.str(); + } + + return bufferStream; + } + + void refreshAllStyleSheet(const Config& config) { + this->setStyleSheet(config.getCurrentConfig()+"QLineEdit{"+config.getCurrentConfig()+"};"); + CIQ_consoleWidget.setStyleSheet(config.getCurrentConfig()+"QLineEdit{"+config.getCurrentConfig()+"};"); + } + + void init(const Config& defaultConfig=Config(),const QSize& defaultSize=QSize()) { + initLayouts(); + // 大小调整 + if (!defaultSize.isNull()) { + this->resize(defaultSize); + CIQ_scrollArea.resize(this->size()); + CIQ_consoleWidget.resize(this->size()); + } + refreshAllStyleSheet(defaultConfig); + + // 添加初始输入框 + if (!defaultConfig.getWelcomeMessage().isEmpty()) { + *this<addWidget(&CIQ_scrollArea); + CIQ_scrollArea.show(); + CIQ_consoleWidget.show(); + this->show(); + } + + inline void resizeEvent(QResizeEvent*) override{ + CIQ_consoleWidget.resize(this->size()); + } + +public slots: + // Shell Mode +}; + +inline ConsoleBufferStream::~ConsoleBufferStream() { + console.processBuffer(buffer); +} + + +} + // namespace ConsoleInQt +#endif // CONSOLE_H \ No newline at end of file diff --git a/ConsoleInQt_global.h b/ConsoleInQt_global.h new file mode 100644 index 0000000..69d72d0 --- /dev/null +++ b/ConsoleInQt_global.h @@ -0,0 +1,12 @@ +#ifndef CONSOLEINQT_GLOBAL_H +#define CONSOLEINQT_GLOBAL_H + +#include + +#if defined(CONSOLEINQT_LIBRARY) +# define CONSOLEINQT_EXPORT Q_DECL_EXPORT +#else +# define CONSOLEINQT_EXPORT Q_DECL_IMPORT +#endif + +#endif // CONSOLEINQT_GLOBAL_H