当前位置: 首页 > news >正文

用 Turbo Vision 2 为 Qt 6 控制台应用创建 TUI 字符 MainFrame

Qt是非常好的C++开发框架,虽然对没有GUI的操作系统也提供了platform插件,如 vnc, framebuf等,但是终究不是真正意义上的字符化的界面。以前在字符模式下,Qt只能用curse自己画对话框。但是自己画对话框毕竟不是一种省事的方式。

想到我在1996年似乎使用过Borland Turbo C++提供的视觉库“Turbo-vision”, 印象深刻,通过搜索,这个Turbo Vision已经成为开源项目,正好可以移植到Qt来用!一如既往,本次实验我们还在msys2 Qt ucrt64 环境下来做, 并在Linux环境 (Manjaro)下也做一遍。。

1. 下载并编译 Turbo Vision

 git clone https://github.com/magiblot/tvision.gitcd tvision/cmake . -B ./build/ucrt64 -G "MinGW Makefiles" -DCMAKE_BUILD_TYPE=Release && cmake --build ./build/ucrt64

如果需要参考教材,可以继续克隆教材库:

cd ..
git clone  https://github.com/yym36100/t3_rom.git

编译完毕后,可以直接进入文件夹运行例子。这里不再赘述。教材很厚,非常佩服以前的纸质教材:

下载
编译好后,发现库是静态的!太棒了。

2. 从Editor例子构造Qt应用

我们从TVisition的例子入手,构造Qt应用。下面完整附加所有代码。

2.1 工程文件

Qt控制台程序,6.9.1,文件名为qtvedit.pro:

QT = core concurrent
CONFIG += c++17 cmdlineHEADERS += \editor/tvedit.h
SOURCES += \editor/tvedit1.cpp \editor/tvedit2.cpp \editor/tvedit3.cpp \main.cppTVISION_PATH = c:/msys64/home/user/projects/3rdparty/tvision
INCLUDEPATH += $$TVISION_PATH/include
#Only for this example
INCLUDEPATH += $$TVISION_PATH/include/tvision/compat/borland
LIBS += -L$$TVISION_PATH/build/ucrt64 -ltvision

以tv开始的文件就是Turbo Vision自带的例子改的文件,main.cpp是我们Qt的文件。

2.2 使用独立的线程运行TVision事件循环

如果仔细看TVision的例子,就会发现它的main和Qt的很像,都要有一个全局事件泵(App)来运行。这样的话,原则上和Qt的Application是冲突的。怎么办?当然可以多线程了。让Qt在主线程,TApp在子线程,文件名为main.cpp:

#include <QCoreApplication>
#include <QtConcurrentRun>
int run_tvmain(int argc, char **argv);int main(int argc, char *argv[])
{QCoreApplication a(argc, argv);//子线程运行Turbo Vision,并优雅退出auto fu = QtConcurrent::run([&]() -> int {run_tvmain(argc, argv);return 0;}).then([](int ret) { QCoreApplication::exit(ret); });return a.exec();
}

我们改造 tedit1.cpp里的main函数:

int run_tvmain(int argc, char **argv)
{TEditorApp *editorApp = new TEditorApp(argc, argv);editorApp->run();editorApp->deleteLater();return 0;
}

这样就实现了两套App 运行。

2.3 使用QObject多重派生TApp

我们使用QObject作为基类,多重派生TApp,使得它可以支持信号和槽以及Meta,文件名为tvedit.h:

#if !defined(__TVEDIT_H)
#define __TVEDIT_H
#define Uses_TApplication
#define Uses_TEditWindow
#define Uses_TDeskTop
#define Uses_TRect
#define Uses_TEditor
#define Uses_TFileEditor
#define Uses_TFileDialog
#define Uses_TChDirDialog
#define Uses_TDialog
#define Uses_TProgram
#define Uses_TObject
#define Uses_TInputLine
#define Uses_TLabel
#define Uses_THistory
#define Uses_TCheckBoxes
#define Uses_TButton
#define Uses_MsgBox
#define Uses_TSItem
#define Uses_TMenuBar
#define Uses_TSubMenu
#define Uses_TKeys
#define Uses_TMenuItem
#define Uses_TStatusLine
#define Uses_TStatusItem
#define Uses_TStatusDef
#define Uses_TPoint#include <QObject>
#include <iomanip.h>
#include <stdlib.h>
#include <strstrea.h>
#include <tvision/tv.h>class TMenuBar;
class TStatusLine;
class TEditWindow;
class TDialog;const int cmChangeDrct = 102;class TEditorApp : public QObject, public TApplication
{Q_OBJECT
public:TEditorApp(int argc, char **argv, QObject *parent = nullptr);virtual void handleEvent(TEvent &event);static TMenuBar *initMenuBar(TRect);static TStatusLine *initStatusLine(TRect);virtual void outOfMemory();private:TEditWindow *openEditor(const char *fileName, Boolean visible);void fileOpen();void fileNew();void changeDir();
};ushort execDialog(TDialog *d, void *data);
TDialog *createFindDialog();
TDialog *createReplaceDialog();
ushort doEditDialog(int dialog, ...);#endif // __TVEDIT_H

2.4 改造后的 tvedit1.cpp

#include "tvedit.h"
TEditWindow *TEditorApp::openEditor(const char *fileName, Boolean visible)
{TRect r = deskTop->getExtent();TView *p = validView(new TEditWindow(r, fileName, wnNoNumber));if (!visible)p->hide();deskTop->insert(p);return (TEditWindow *) p;
}
TEditorApp::TEditorApp(int argc, char **argv, QObject *parent): QObject(parent), TProgInit(TEditorApp::initStatusLine, TEditorApp::initMenuBar, TEditorApp::initDeskTop), TApplication()
{TCommandSet ts;ts.enableCmd(cmSave);ts.enableCmd(cmSaveAs);ts.enableCmd(cmCut);ts.enableCmd(cmCopy);ts.enableCmd(cmPaste);ts.enableCmd(cmClear);ts.enableCmd(cmUndo);ts.enableCmd(cmFind);ts.enableCmd(cmReplace);ts.enableCmd(cmSearchAgain);disableCommands(ts);TEditor::editorDialog = doEditDialog;while (--argc > 0)			   // Open files specifiedopenEditor(*++argv, True); // on command line.cascade();
}void TEditorApp::fileOpen()
{char fileName[4096];strcpy(fileName, "*.*");if (execDialog(new TFileDialog("*.*", "Open file", "~N~ame", fdOpenButton, 100), fileName)!= cmCancel)openEditor(fileName, True);
}void TEditorApp::fileNew()
{openEditor(0, True);
}void TEditorApp::changeDir()
{execDialog(new TChDirDialog(cdNormal, 0), 0);
}void TEditorApp::handleEvent(TEvent &event)
{TApplication::handleEvent(event);if (event.what != evCommand)return;elseswitch (event.message.command){case cmOpen:fileOpen();break;case cmNew:fileNew();break;case cmChangeDrct:changeDir();break;default:return;}clearEvent(event);
}int run_tvmain(int argc, char **argv)
{TEditorApp *editorApp = new TEditorApp(argc, argv);editorApp->run();editorApp->deleteLater();return 0;
}

2.5 tvedit2.cpp


#include "tvedit.h"ushort execDialog(TDialog *d, void *data)
{TView *p = TProgram::application->validView(d);if (p == 0)return cmCancel;else{if (data != 0)p->setData(data);ushort result = TProgram::deskTop->execView(p);if (result != cmCancel && data != 0)p->getData(data);TObject::destroy(p);return result;}
}TDialog *createFindDialog()
{TDialog *d = new TDialog(TRect(0, 0, 38, 12), "Find");d->options |= ofCentered;TInputLine *control = new TInputLine(TRect(3, 3, 32, 4), 80);d->insert(control);d->insert(new TLabel(TRect(2, 2, 15, 3), "~T~ext to find", control));d->insert(new THistory(TRect(32, 3, 35, 4), control, 10));d->insert(new TCheckBoxes(TRect(3, 5, 35, 7),new TSItem("~C~ase sensitive", new TSItem("~W~hole words only", 0))));d->insert(new TButton(TRect(14, 9, 24, 11), "O~K~", cmOK, bfDefault));d->insert(new TButton(TRect(26, 9, 36, 11), "Cancel", cmCancel, bfNormal));d->selectNext(False);return d;
}TDialog *createReplaceDialog()
{TDialog *d = new TDialog(TRect(0, 0, 40, 16), "Replace");d->options |= ofCentered;TInputLine *control = new TInputLine(TRect(3, 3, 34, 4), 80);d->insert(control);d->insert(new TLabel(TRect(2, 2, 15, 3), "~T~ext to find", control));d->insert(new THistory(TRect(34, 3, 37, 4), control, 10));control = new TInputLine(TRect(3, 6, 34, 7), 80);d->insert(control);d->insert(new TLabel(TRect(2, 5, 12, 6), "~N~ew text", control));d->insert(new THistory(TRect(34, 6, 37, 7), control, 11));d->insert(new TCheckBoxes(TRect(3, 8, 37, 12),new TSItem("~C~ase sensitive",new TSItem("~W~hole words only",new TSItem("~P~rompt on replace",new TSItem("~R~eplace all", 0))))));d->insert(new TButton(TRect(17, 13, 27, 15), "O~K~", cmOK, bfDefault));d->insert(new TButton(TRect(28, 13, 38, 15), "Cancel", cmCancel, bfNormal));d->selectNext(False);return d;
}

2.6 tvedit3.cpp

#include "tvedit.h"
TMenuBar *TEditorApp::initMenuBar(TRect r)
{TSubMenu &sub1 = *new TSubMenu("~F~ile", kbAltF)+ *new TMenuItem("~O~pen", cmOpen, kbF3, hcNoContext, "F3")+ *new TMenuItem("~N~ew", cmNew, kbCtrlN, hcNoContext, "Ctrl-N")+ *new TMenuItem("~S~ave", cmSave, kbF2, hcNoContext, "F2")+ *new TMenuItem("S~a~ve as...", cmSaveAs, kbNoKey) + newLine()+ *new TMenuItem("~C~hange dir...", cmChangeDrct, kbNoKey)+ *new TMenuItem("~D~OS shell", cmDosShell, kbNoKey)+ *new TMenuItem("E~x~it", cmQuit, kbCtrlQ, hcNoContext, "Ctrl-Q");TSubMenu &sub2 = *new TSubMenu("~E~dit", kbAltE)+ *new TMenuItem("~U~ndo", cmUndo, kbCtrlU, hcNoContext, "Ctrl-U") + newLine()+ *new TMenuItem("Cu~t~", cmCut, kbShiftDel, hcNoContext, "Shift-Del")+ *new TMenuItem("~C~opy", cmCopy, kbCtrlIns, hcNoContext, "Ctrl-Ins")+ *new TMenuItem("~P~aste", cmPaste, kbShiftIns, hcNoContext, "Shift-Ins")+ newLine()+ *new TMenuItem("~C~lear", cmClear, kbCtrlDel, hcNoContext, "Ctrl-Del");TSubMenu &sub3 = *new TSubMenu("~S~earch", kbAltS)+ *new TMenuItem("~F~ind...", cmFind, kbNoKey)+ *new TMenuItem("~R~eplace...", cmReplace, kbNoKey)+ *new TMenuItem("~S~earch again", cmSearchAgain, kbNoKey);TSubMenu &sub4 = *new TSubMenu("~W~indows", kbAltW)+ *new TMenuItem("~S~ize/move", cmResize, kbCtrlF5, hcNoContext, "Ctrl-F5")+ *new TMenuItem("~Z~oom", cmZoom, kbF5, hcNoContext, "F5")+ *new TMenuItem("~T~ile", cmTile, kbNoKey)+ *new TMenuItem("C~a~scade", cmCascade, kbNoKey)+ *new TMenuItem("~N~ext", cmNext, kbF6, hcNoContext, "F6")+ *new TMenuItem("~P~revious", cmPrev, kbShiftF6, hcNoContext, "Shift-F6")+ *new TMenuItem("~C~lose", cmClose, kbCtrlW, hcNoContext, "Ctrl+W");r.b.y = r.a.y + 1;return new TMenuBar(r, sub1 + sub2 + sub3 + sub4);
}TStatusLine *TEditorApp::initStatusLine(TRect r)
{r.a.y = r.b.y - 1;return new TStatusLine(r,*new TStatusDef(0, 0xFFFF) + *new TStatusItem(0, kbAltX, cmQuit)+ *new TStatusItem("~F2~ Save", kbF2, cmSave)+ *new TStatusItem("~F3~ Open", kbF3, cmOpen)+ *new TStatusItem("~Ctrl-W~ Close", kbAltF3, cmClose)+ *new TStatusItem("~F5~ Zoom", kbF5, cmZoom)+ *new TStatusItem("~F6~ Next", kbF6, cmNext)+ *new TStatusItem("~F10~ Menu", kbF10, cmMenu)+ *new TStatusItem(0, kbShiftDel, cmCut)+ *new TStatusItem(0, kbCtrlIns, cmCopy)+ *new TStatusItem(0, kbShiftIns, cmPaste)+ *new TStatusItem(0, kbCtrlF5, cmResize));
}void TEditorApp::outOfMemory()
{messageBox("Not enough memory for this operation.", mfError | mfOKButton);
}typedef char *_charPtr;
typedef TPoint *PPoint;//#pragma warn - rvlushort doEditDialog(int dialog, ...)
{va_list arg;char buf[256] = {0};ostrstream os(buf, sizeof(buf) - 1);switch (dialog){case edOutOfMemory:return messageBox("Not enough memory for this operation", mfError | mfOKButton);case edReadError:{va_start(arg, dialog);os << "Error reading file " << va_arg(arg, _charPtr) << "." << ends;va_end(arg);return messageBox(buf, mfError | mfOKButton);}case edWriteError:{va_start(arg, dialog);os << "Error writing file " << va_arg(arg, _charPtr) << "." << ends;va_end(arg);return messageBox(buf, mfError | mfOKButton);}case edCreateError:{va_start(arg, dialog);os << "Error creating file " << va_arg(arg, _charPtr) << "." << ends;va_end(arg);return messageBox(buf, mfError | mfOKButton);}case edSaveModify:{va_start(arg, dialog);os << va_arg(arg, _charPtr) << " has been modified. Save?" << ends;va_end(arg);return messageBox(buf, mfInformation | mfYesNoCancel);}case edSaveUntitled:return messageBox("Save untitled file?", mfInformation | mfYesNoCancel);case edSaveAs:{va_start(arg, dialog);return execDialog(new TFileDialog("*.*", "Save file as", "~N~ame", fdOKButton, 101),va_arg(arg, _charPtr));}case edFind:{va_start(arg, dialog);return execDialog(createFindDialog(), va_arg(arg, _charPtr));}case edSearchFailed:return messageBox("Search string not found.", mfError | mfOKButton);case edReplace:{va_start(arg, dialog);return execDialog(createReplaceDialog(), va_arg(arg, _charPtr));}case edReplacePrompt://  Avoid placing the dialog on the same line as the cursorTRect r(0, 1, 40, 8);r.move((TProgram::deskTop->size.x - r.b.x) / 2, 0);TPoint t = TProgram::deskTop->makeGlobal(r.b);t.y++;va_start(arg, dialog);TPoint *pt = va_arg(arg, PPoint);if (pt->y <= t.y)r.move(0, TProgram::deskTop->size.y - r.b.y - 2);va_end(arg);return messageBoxRect(r, "Replace this occurence?", mfYesNoCancel | mfInformation);}return cmCancel;
}//#pragma warn.rvl

3. 编译运行

编译运行:

Ok
哇!Qt 6.9 也有字符界面啦!

4. 在 Linux 下构造

Turbo Vision 是一个古老的库,但是由于已经被其作者进行了现代化的改造,使得在现代编译器上也运行的很好。有了它,后面控制台程序也能实现比较复杂的界面,且同时使用最新的Qt版本的各种特性。上述代码在 msys2 ucrt64 Qt 6.9.1 下编译通过。下面,在Linux下简单重复上述步骤:

git clone https://github.com/magiblot/tvision.git
cd tvision/
mkdir build
cd build/
mkdir linux64
cd linux64
cmake ../..
make -j 8

Linux下,tvision依赖 curses 和 gpm库,所以工程文件稍微改一下:

QT = core concurrent
CONFIG += c++17 cmdlineHEADERS += \editor/tvedit.h
SOURCES += \editor/tvedit1.cpp \editor/tvedit2.cpp \editor/tvedit3.cpp \main.cpp#这是git clone 的文件夹路径
TVISION_PATH = /home/user/projects/tvision
#一般构建 tvision 的位置是在路径下的 build/,如果考虑多个平台,则外加平台名称。
#在windows下,我们的msys2 ucrt64环境下,构建位置是在 $$TVISION_PATH/build/ucrt64INCLUDEPATH += $$TVISION_PATH/include
#Only for this example
INCLUDEPATH += $$TVISION_PATH/include/tvision/compat/borlandLIBS += -L$$TVISION_PATH/build/linux64 -ltvision
linux: LIBS +=-lcurses -lgpm

编译完毕后,使用ssh登入,在没有X的情况下,也能运行字符窗口界面(TUI)了:

Linux

5. 关于Turbo和Borland的回忆

1996年,是面向对象编程的活跃时代。对于上了年纪的人,非常怀念老式卡带、邓丽君、毛阿敏,还有刚买了电视机就观看的电视剧《渴望》。Borland在1990年代推出的Turbo系列工具,包括 Turbo C\C++、Turbo Pascal、Turbo Debugger、Turbo ASM、 Turbo BASIC,编织了属于软件个人英雄时代的传奇。记得有一本书叫做《Borland传奇》,写的不错。

在Linux下的自绘TUI还有类似MC,VIM,Nano等环境,包括 raspi-config,但是这些对于GUI行为的模仿要么不是很贴合,风格也不是很一致,要么就是配置很复杂。 在1990年代,TUI对于构造傻瓜化的IDE非常重要。现如今,图形化已经不再是成本和性能的瓶颈,但是基于TUI的APP本身还是为一些场景提供了更多的选择。与 Turbo Vision 同风格的是微软的TUI界面,大量在 EDITOR、Quick BASIC、Programmers’ Working Bench(PWB)、Viusal BASIC for DOS下使用。

http://www.dtcms.com/a/267710.html

相关文章:

  • Redis性能优化
  • 五、Python新特性指定类型用法
  • AI大模型(六)Langchain核心模块与实战(一)
  • 使用LVM和扩展文件系统增加root分区存储容量
  • 信号和槽(4)
  • 3dmax物理材质转换标准材质,物理材质转VR材质,VR材质转标准材质3dmax物理材质转标准材质插件
  • 98.验证二叉搜索树
  • python实现简单的地图绘制与标记20250705
  • 【每天一个知识点】子空间聚类(Subspace Clustering)
  • 零基础保姆级本地化部署文心大模型4.5开源系列
  • Unity文件夹标签 —— FolderTag
  • SpinLock (TTAS) C-A-S 自旋锁实现原理
  • 人工智能学习60-Yolo
  • TCP协议概念和特性
  • cmd 的sftp传输;Conda出现环境问题: error: invalid value for --gpu-architecture (-arch)
  • Kotlin 协程:Channel 与 Flow 深度对比及 Channel 使用指南
  • 《自然语言处理:基于预训练模型的方法》-笔记
  • Java教程——深入学习guava之并发编程
  • 基于matlab的二连杆机械臂PD控制的仿真
  • 2025年5月-植物水分吸收和运输优化算法water uptake and transport-附Matlab免费代码
  • 数字地和模拟地
  • 使用 C++/OpenCV 和 MFCC 构建双重认证智能门禁系统
  • 白皮解读——173页数据安全治理白皮书5.0【附全文阅读】
  • 灵活应对市场波动的智能运营中枢​
  • leetcode918.环形子数组的最大和
  • Spark SQL架构及高级用法
  • Java Go SDK 管理工具与最佳实践
  • 【汇编逆向系列】九、函数传参之结构体 - SHL、SHR指令,小型结构体参数和返回值
  • 《深度剖析:5G网络切片如何精准保障不同业务QoS需求》
  • PHP语法基础篇(九):正则表达式