Turbo Vision 2.0的现代化港口

2020-12-19 10:04:57

Turbo Vision 2.0的现代版本,这是基于文本的用户界面的经典框架。现在跨平台并支持Unicode。

我在2018年底开始了这个个人项目。到2020年5月,我认为它与原版非常接近,并决定将其开放。

通过尽可能少地更改旧代码库,使Turbo Vision在Linux上工作。

在源代码级别上尽可能与旧的Turbo Vision应用程序兼容。这使我实现了一些Borland C ++ RTL函数,如下所述。

在某一时刻,我认为我已经做了足够的工作,并且任何尝试修改库并克服其原始限制的尝试都将需要扩展API或打破向后兼容性,并且最有可能需要进行重大重写。

但是,在2020年7月至2020年8月之间,我找到了将成熟的Unicode支持集成到现有设备中的方法,编写了Turbo文本编辑器,并在Windows上提供了新功能。因此,我相信Turbo Vision现在可以满足现代用户和程序员的许多期望。

自Borland在90年代初期创建Turbo Vision以来,发生了许多变化。如今,许多GUI工具将外观规范与行为规范分开,使用更安全或动态的语言,这些语言不会对错误进行细分,并支持并发或异步编程,或同时支持两者。

Turbo Vision并没有做任何事情,但是它确实克服了程序员在编写终端应用程序时仍然面临的许多问题:

忘记终端功能和直接终端I / O。在编写Turbo Vision应用程序时,您只需要关心应用程序的行为和外观即可-无需在代码中添加变通办法。 Turbo Vision尽力在所有环境下产生相同的结果。例如:为了在Linux控制台上获得明亮的背景色,必须设置眨眼属性。 Turbo Vision为您做到了。

重用已经完成的工作。 Turbo Vision提供了许多小部件类(也称为视图),包括可调整大小的窗口,重叠的窗口,下拉菜单,对话框,按钮,滚动条,输入框,复选框和单选按钮。您可以使用和扩展它们;但是,即使您更喜欢创建自己的设备,Turbo Vision也已经可以处理事件调度,全角Unicode字符显示等:您无需浪费时间来重写任何内容。

您是否可以想象一下开箱即用地在没有#ifdef的情况下编写可在Linux和Windows(因此是跨平台)上运行的基于文本的界面? Turbo Vision使这成为可能。首先:Turbo Vision继续使用char数组,而不是依赖于实现定义的和平台相关的wchar_t或TCHAR。第二:由于Microsoft RTL的最新版本在setlocale中支持UTF-8,因此以下代码将按预期工作:

std :: ifstream f("コンピュータ.txt"); //在Windows上,RTL即时将其转换为系统编码。

您可以开始阅读《 Turbo Vision For C ++用户指南》,并查看示例应用程序hello,tvdemo和tvedit。一旦您掌握了基础知识,我建议您看一下《 Turbo Vision 2.0编程指南》,我认为,尽管使用了Pascal,它还是更直观,更易于理解。到那时,您可能会对调色板示例感兴趣,该示例包含有关如何使用调色板的详细说明。

cmake。 -B ./build -DCMAKE_BUILD_TYPE =发布&& #也可以是' Debug',MinSizeRel'或' RelWithDebInfo' .cmake --build ./build#或`cd ./build&&制造

演示应用程序hello,tvdemo,tvedit,tvdir与原始Turbo Vision捆绑在一起(尽管其中一些改进)。

从此项目的根目录构建Turbo Vision应用程序(例如,带有GCC的hello.cpp)所需的最小命令行为:

-Iinclude / tvision(如果您的应用程序使用Turbo Vision 1.x包含)(#include< tv.h>代替#include< tvision / tv.h>)。

在Gentoo(可能还有其他)上:-ltinfow(如果libtinfo.so和libtinfow.so在系统中均可用)。否则,在运行Turbo Vision应用程序时可能会出现分段错误(#11)。请注意,tinfo与ncurses捆绑在一起。

include / tvision / compat中的向后兼容标头可模拟Borland C ++ RTL。 Turbo Vision的源代码仍然取决于它们,如果移植旧的应用程序,它们可能会很有用。这也意味着包括tvision / tv.h会将几个std名称带到全局名称空间。

MSVC的构建过程稍微复杂一些,因为有更多选项可供选择。请注意,对于不同的目标体系结构,您将需要不同的构建目录。例如,要生成优化的二进制文件:

cmake。 -B ./build&& #添加-A x64' (64位)或-A Win32' (32位)覆盖默认平台。cmake --build ./build --config Release#也可以是' Debug&#39 ;、' MinSizeRel'。或' RelWithDebInfo'。

如果您希望与Microsoft的运行时库(/ MT代替/ MD)静态链接Turbo Vision,请启用TV_USE_STATIC_RTL选项(调用cmake时-DTV_USE_STATIC_RTL = ON)。

如果您希望将应用程序链接到Turbo Vision,请注意,MSVC不允许您将/ MT与/ MD混合使用,也不能与非调试二进制文件进行调试。所有组件都必须以相同的方式链接到RTL。

注意:Turbo Vision使用setlocale在UTF-8模式下设置RTL功能。如果您使用旧版本的RTL,则将无法使用。

通过静态链接RTL,并且如果setlocale支持UTF-8,Turbo Vision应用程序是可移植的,并且默认情况下在Windows Vista和更高版本上可以运行。

一旦正确设置了MinGW环境,就可以通过与Linux类似的方式进行构建:

在上面的示例中,如果TV_BUILD_EXAMPLES选项设置为ON(默认值),则libtvision.a和所有示例都位于./build中。

如果您希望针对Turbo Vision链接应用程序,只需将-L./build/lib -ltvision添加到链接器中,并将-I./include添加到编译器中

Turbo Vision仍然可以使用Borland C ++构建为DOS或Windows库。显然,这里没有Unicode支持。

您可能会遇到不同的问题,具体取决于您的构建环境。例如,Turbo Assembler需要一个补丁才能在Windows 95下工作。在Windows XP上,一切似乎都可以正常工作。在Windows 10上,MAKE可能会发出错误致命:命令参数过长,可以通过将MAKE升级到与Borland C ++ 5.x捆绑在一起的命令来解决。

是的,它可以在64位Windows 10上使用。Borland C ++安装程序是16位应用程序,不能使用。您将不得不在其他环境上运行它,或者与winevdm一起试试运气。

可以在项目目录中找到Borland Makefile。可以通过以下方式完成构建:

-DWIN32用于32位本机Win32应用程序(TVDEMO无法使用,它依赖于farcoreleft()和其他上古物)。

-DOVERLAY,-​​DALIGNMENT = {2,4},-DEXCEPTION,-DNO_STREAMABLE,-DNOTASM用于我从未使用过但从未出现在原始makefile中的东西。

这会将库编译到项目旁边的LIB目录中,并将在各自的example / *目录中编译演示应用程序的可执行文件。

对不起,根makefile假定它是从项目目录执行的。如果要使用其他设置,您仍然可以直接运行原始makefile(在source / tvision和examples / *中)。

从此项目的根目录构建Turbo Vision应用程序(例如hello.cpp)所需的最小命令行为:

#16位实模式DOS.BCC.EXE -ml -Iinclude hello.cpp lib / tv.lib#32位本机Win32应用程序.BCC32.EXE -WC -Iinclude hello.cpp lib / tv32.lib#32位,受保护的模式DOS(DPMI32).BCC32.EXE -WX -Iinclude hello.cpp lib / tv32.lib import32.lib

使用-WX时,请注意额外的import32.lib。该文件由Borland C ++提供,因此只要正确配置了编译器即可找到该文件(即默认情况下在$(BCROOT)\ LIB和$(BCROOT)\ INCLUDE中查找库和头文件)。如果您不链接此文件,则会出现以下错误:

此外,在实际的DOS环境上运行DPMI32应用程序时,您可能会收到类似这样的警告:

32loader错误:' C:\ HELLO.EXE'无效的导入引用:-模块' kernel32.dll'入口点' SetConsoleActiveScreenBuffer'您是否要尝试运行该程序(是/否)?

如果您为应用程序选择CMake构建系统,并将Turbo Vision作为子模块放置在存储库中,则可以轻松地配置应用程序以链接到Turbo Vision:

< tvision / tv.h>在编译过程中将在您的应用程序的include路径中提供。此外,您的应用程序将自动链接到必要的库(Ncurses,GPM ...)。

UTF-8支持终端I / O和API。您可以在tvedit和tvdemo应用程序中尝试Unicode支持。

一些Borland C ++ RTL函数的实现:findfirst,findnext,fnsplit,_dos_findfirst,_dos_findnext,getdisk,setdisk,getcurdir,filelength。

有一些环境变量会影响所有Turbo Vision应用程序的行为:

TVISION_DISPLAY:绘制到屏幕的策略。有效值为ncurses和ansi。默认值为ansi,这是一种自定义策略,可避免冗余缓冲和UTF-8到宽字符转换。如果遇到问题,可以尝试使用ncurses。

TVISION_MAX_FPS:向终端绘制屏幕更改的次数限制,默认为60。这有助于保持合理的绘制性能。此选项的特殊值是0(禁用刷新速率限制)和-1(实际上在每次调用THardwareInfo :: screenWrite时吸引到终端(用于调试))。

TVISION_ESCDELAY:收到ESC键后应等待的延迟时间(以毫秒为单位)。如果在此延迟期间按下另一个键,它将被解释为Alt + Key组合键。

TVISION_CODEPAGE:Turbo Vision内部使用的字符集,用于将扩展的ASCII转换为Unicode。目前仅支持437和850,尽管增加的成本与在source / platform / tables.cpp中添加一系列转换的成本一样低。

TVISION_USE_STDIO:如果已定义,则通过stdin / stdout执行终端I / O,以便可以从Shell重定向它。默认情况下,Turbo Vision通过/ dev / tty执行终端I / O,使用户可以根据需要重定向stdin,stdout和stderr,而不会影响应用程序的稳定性。

尽管以下内容会将所有转义序列和应用程序打印的文本转储到out.txt中:

应用程序适合控制台窗口大小而不是缓冲区大小(没有滚动条可见)。

没有繁忙的事件轮询(即空闲时没有100%的CPU消耗,这是原来的样子)。

Microsoft的C运行时功能自动设置为UTF-8模式,因此您无需使用wchar_t变体。

注意:Turbo Vision将UTF-8文本直接写入Windows控制台。如果将控制台设置为旧版模式,并且使用了位图字体,则Unicode字符将无法正确显示(照片)。

支持kbCtrlUp和kbCtrlDown键代码(在16位DOS上不起作用,请不要问我为什么)。它们可用于通过键盘更快地移动窗口(就像kbCtrlLeft和kbCtrlRight一样)。

改进了菜单的可用性:可以通过再次单击它们,甚至子菜单来关闭它们。

改进的滚动条可用性:拖动滚动条也可以滚动页面。单击滚动条的空白区域将拇指移到光标下方。默认情况下,它们对鼠标滚轮事件作出响应。

TInputLines不再在焦点/未聚焦时滚动文本显示,从而使相关文本保持可见。

在tvdemo和tvedit中支持LF行结尾。 tvedit保留以文件保存结尾的行,但是默认情况下,所有新创建的文件都使用CRLF。

tvedit:使用kbCtrlBack和kbCtrlDel删除整个单词(Linux用户注意:它们可能无法在终端模拟器上运行,但可以在控制台上运行)。

屏幕写操作被缓冲,并且通常在活动事件循环的每次迭代中都发送到终端一次(另请参见TVISION_MAX_FPS)。如果需要在繁忙循环中更新屏幕,则可以使用TScreen :: flushScreen()。

TDrawBuffer不再是固定长度的数组。 .length()方法等效于sizeof(TDrawBuffer)/ sizeof(ushort)。

TTextDevice现在已被缓冲,因此,如果您使用的是otstream,则可能必须通过它发送std :: flush或std :: endl才能调用do_sputn。

TApplication现在提供dosShell(),cascade()和tile(),并且默认情况下处理cmDosShell,cmCascade和cmTile。可以通过重写getTileRect()和writeShellMsg()来自定义这些函数。这与Pascal版本中的行为相同。

evMouseUp事件中的按钮字段不再为空。现在,它指示释放了哪个按钮。

TRect方法移动,增长,相交,并合并现在返回TRect&而不是虚无,以便可以将它们链接起来。

新函数ushort popupMenu(TPoint where,TMenuItem& aMenu,TGroup * receiver = 0)在桌面上产生一个TMenuPopup。参见source / tvision / popupmnu.cpp。

新的虚拟方法TMenuItem& TEditor :: initContextMenu(TPoint p)确定TEditor中右键单击上下文菜单的条目。

新类TStringView,它是std :: string_view的副本。除非您是在没有std :: string_view的Borland C ++中进行编程,否则您不需要它。

许多最初具有以空值终止的字符串参数的方法现在改为接收TStringView。 TStringView与std :: string_view,std :: string和const char *(甚至nullptr)兼容。

我不希望扩展名不予记录,因此我将其写下来只是为了记录。

TEvent :: getKeyEvent()现在接受一个默认为True的阻塞参数,从而可以以非阻塞方式查询输入事件。

如果您知道任何未丢失源代码的Turbo Vision应用程序,并且可以从中受益,请告诉我。

如果您的应用程序基于该项目,并且希望将其显示在以下列表中,请告诉我。

Turbo Vision API已扩展,可以接收Unicode输入并显示Unicode文本。由于多种原因,支持的编码为UTF-8:

它与已经存在的数据类型(char *)兼容,因此不需要对现有代码进行侵入式修改。

请注意,使用Borland C ++构建时,Turbo Vision不支持Unicode。但是,这并不影响Turbo Vision应用程序的编写方式,因为API扩展旨在允许与编码无关的代码。

//' ev'是TEvent,而&ev39.what'等于' evKeyDown'。 switch(ev.keyDown.keyCode){//通常首先检查快捷键。 // ...默认值:{//字符被编码在当前代码页中//(默认为CP437)。 char c = ev。 keyDown。 charScan。 charCode; // ...}}

现有的一些处理文本输入的Turbo Vision类仍然依赖于此方法,并且这种方法没有改变。单字节字符在当前代码页中可表示时,在ev.keyDown.charScan.charCode中继续可用。

char text [4],可能包含从终端读取的内容:通常是UTF-8序列,但也可能是任何原始数据。

uchar textLength,它是文本中可用数据的字节数,从0到4。

请注意,文本字段可能包含从位置textLength开始的垃圾数据或未初始化的数据。

让我们从另一个角度来看它。如果用户键入ñ,则使用以下keyDown结构生成一个TEvent:

KeyDownEvent {union {。 keyCode = 0xA4,。 charScan = CharScanType {。 charCode = 164('ñ'),//在CP437中。 scanCode = 0}},。 controlKeyState = 0x200(kbInsState),。文字= {' \ xC3',' \ xB1&#39 ;,' \ x00&#39 ;,' \ x00'},//在UTF-8中。 textLength = 2}

KeyDownEvent {union {。 keyCode = 0x0(kbNoKey),//'€'不是CP437的一部分。 charScan = CharScanType {。 charCode = 0,。 scanCode = 0}},。 controlKeyState = 0x200(kbInsState),。文字= {' \ xE2',' \ x82',' \ xAC',' \ x00'},//在UTF-8中。 textLength = 3}

KeyDownEvent {union {。 keyCode = 0xB(kbCtrlK),。 charScan = CharScanType {。 charCode = 11('♂'),。 scanCode = 0}},。 controlKeyState = 0x20C(kbCtrlShift | kbInsState),。文字= {' \ x00&#39 ;,' \ x00&#39 ;,' \ x00&#39 ;,' \ x00'} ,。 textLength = 0}

因此,简而言之:在设计时不考虑Unicode输入的视图将继续按照以前的方式工作,而希望具有Unicode意识的视图将没有任何问题。

Turbo Vision的原始设计使用16位代表一个屏幕单元,其中8位用于字符,8位用于BIOS颜色属性。

在< tvision / scrncell.h>中定义了新的TScreenCell类型。除了扩展属性(粗体,下划线,斜体...)外,它还可以容纳有限数量的UTF-8代码点。但是,您不应直接将文本写入TScreenCell,而应使用支持Unicode的API函数。

提供给处理显示文本的任何Turbo Vision API函数的参数的字符解释如下:

0x00到0xFF范围内的不可打印字符在活动代码页中被解释为字符。例如,如果使用CP437,则0x7F显示为⌂,0xF0显示为≡。作为例外,0x00始终显示为常规空间。这些字符都是一栏宽。

与上述情况一样,无效的UTF-8字符序列在当前代码页中被解释为字符序列。

显示宽度不是一个的有效UTF-8序列将以特殊方式处理,请参见下文。

例如,字符串"╔[\ xFE]╗"可能会显示为╔[■]╗。这意味着通常可以将绘图字符与UTF-8混合使用,这对于向后兼容很有用。但是,如果您依靠这种行为,则可能会得到意想不到的结果:例如," \ xC4 \ xBF"是有效的UTF-8序列,显示为Ŀ而不是─┐。

Unicode支持的问题之一是存在多角字符和组合字符。这与Turbo Vision最初的假设是矛盾的,即屏幕是由单个字符占据的单元格网格。但是,可以通过以下方式处理这些情况:

可以在屏幕上的任意位置绘制多角字符,如果它们与其他字符部分重叠,也不会发生任何不良情况。

零宽度字符覆盖前一个字符。 例如,序列of由单角字符म和组合字符े和में组成。 在这种情况下,三个Unicode代码点可放入同一单元格中。 零宽度JOINER(U + 200D)总是被忽略,因为它使事情变得非常复杂。 例如,它可以变成"👩👦"这样的字符串。 (宽4列)放入" 👩‍👦" (2列宽)。 并非所有的终端仿真器都尊重ZWJ,因此,为了产生可预测的结果,Turbo Vision将同时打印"👩👦"。 和" 👩‍👦" 如👩👦。 只要您的终端仿真器遵守由wcwidth度量的字符宽度,就不会发生明显的图形故障。 写入屏幕的通常方法是使用TDrawBuffer。 几种方法 ......