Un puerto moderno de Turbo Vision 2.0, el marco clásico para interfaces de usuario basadas en texto. Ahora multiplataforma y con soporte de Unicode.

Comencé esto como un proyecto personal a fines de 2018. En mayo de 2020 consideré que estaba muy cerca de presentar una paridad con el original, y decidí abrirlo.
Los objetivos originales de este proyecto fueron:
En un momento, consideré que había hecho lo suficiente, y que cualquier intento de renovar la biblioteca y superar sus limitaciones originales requeriría extender la API o romper la compatibilidad hacia atrás, y que es muy necesaria una reescritura importante.
Sin embargo, entre julio y agosto de 2020 encontré la manera de integrar el soporte de Unicode completo en la arquitectura existente, escribí el editor de texto Turbo y también puse las nuevas funciones disponibles en Windows. Así que estoy seguro de que Turbo Vision ahora puede cumplir con muchas de las expectativas de los usuarios y programadores modernos.
La ubicación original de este proyecto es https://github.com/magiblot/tvision.
Mucho ha cambiado desde que Borland creó Turbo Vision a principios de los 90. Muchas herramientas de GUI hoy separan la especificación de apariencia de la especificación de comportamiento, usan idiomas más seguros o dinámicos que no segultados de error, y admiten programación paralela o asincrónica, o ambos.
Turbo Vision no se destaca en ninguno de esos, pero ciertamente supera que muchos de los problemas que los programadores aún se enfrentan hoy al escribir aplicaciones terminales:
Olvídate de las capacidades terminales y la E/S del terminal directa. Al escribir una aplicación de visión turbo, todo lo que tiene que preocuparse es cómo desea que su aplicación se comporte y se vea: no hay necesidad de agregar soluciones en su código. Turbo Vision hace todo lo posible para producir los mismos resultados en todos los entornos. Por ejemplo: para obtener un color de fondo brillante en la consola Linux, se debe establecer el atributo Blink . Turbo Vision hace esto por ti.
Reutilice lo que ya se ha hecho. Turbo Vision proporciona muchas clases de widgets (también conocidas como vistas ), que incluyen ventanas reestrizables y superpuestas, menús desplegables, cuadros de diálogo, botones, barras de desplazamiento, cuadros de entrada, casillas de verificación y botones de radio. Puede usarlos y extenderlos; Pero incluso si prefiere crear su propia visión turbo ya maneja el envío de eventos, la visualización de personajes de unicode de ancho completo, etc.: No necesita perder el tiempo reescribiendo nada de eso.
¿Te imaginas escribir una interfaz basada en texto que funcione tanto en Linux como en Windows (y, por lo tanto, está fuera de la plataforma) fuera de la caja, sin #ifdef S? Turbo Vision hace que esto sea posible. Primero: Turbo Vision sigue utilizando matrices char en lugar de confiar en el wchar_t o TCHAR definido por la implementación y dependiente de la plataforma. Segundo: gracias al soporte UTF-8 en setlocale en versiones recientes de RTL de Microsoft, el código como el siguiente funcionará según lo previsto:
std::ifstream f ( "コンピュータ.txt " ); // On Windows, the RTL converts this to the system encoding on-the-fly. Puede comenzar con la Guía del usuario de Turbo Vision para C ++ y mirar las aplicaciones de muestra hello , tvdemo y tvedit . Una vez que comprende los conceptos básicos, le sugiero que eche un vistazo a la Guía de programación Turbo Vision 2.0, que es, en mi opinión, más intuitiva y más fácil de entender, a pesar de usar Pascal. Para entonces, probablemente estará interesado en el ejemplo palette , que contiene una descripción detallada de cómo se utilizan las paletas.
No olvide consultar las secciones de características y cambios de API también.
Este proyecto no tiene lanzamientos estables por el momento. Si es un desarrollador, intente cumplir con la última confirmación e informar cualquier problema que encuentre al actualizar.
Si solo desea probar las aplicaciones de demostración:
examples-dos32.zip : ejecutables de 32 bits construidos con Borland C ++. Sin soporte de Unicode.examples-x86.zip : ejecutables de 32 bits construidos con MSVC. Windows Vista o posterior requerido.examples-x64.zip : ejecutables de 64 bits construidos con MSVC. X64 Windows Vista o más tarde requerido. Turbo Vision se puede construir como una biblioteca estática con CMake y GCC/Clang.
cmake . -B ./build -DCMAKE_BUILD_TYPE=Release && # Could also be 'Debug', 'MinSizeRel' or 'RelWithDebInfo'.
cmake --build ./build # or `cd ./build; make` Las versiones de CMake mayores de 3.13 pueden no admitir la opción -B . Puede probar lo siguiente en su lugar:
mkdir -p build ; cd build
cmake .. -DCMAKE_BUILD_TYPE=Release &&
cmake --build .Lo anterior produce los siguientes archivos:
libtvision.a , que es la biblioteca turbo de visión.hello , tvdemo , tvedit , tvdir , que se incluyeron con la visión turbo original (aunque algunas de ellas tienen algunas mejoras).mmenu y palette del soporte técnico de Borland.tvhc , The Turbo Vision Ayuda compilador. La biblioteca y los ejecutables se pueden encontrar en ./build .
Los requisitos de compilación son:
libncursesw (tenga en cuenta el 'w').libgpm para soporte del mouse en la consola Linux (opcional). Si su distribución proporciona paquetes de Devel separados (por ejemplo, libncurses-dev , libgpm-dev en distribuciones con sede en Debian), instale también.
Los requisitos de tiempo de ejecución son:
xsel o xclip para soporte de portapapeles en entornos X11.wl-clipboard para soporte de portapalones en entornos de Wayland. La línea de comando mínima requerida para construir una aplicación de visión turbo (por ejemplo, hello.cpp con GCC) desde la raíz de este proyecto es:
g++ -std=c++14 -o hello hello.cpp ./build/libtvision.a -Iinclude -lncursesw -lgpmEs posible que también necesite:
-Iinclude/tvision Si su aplicación usa turbo visión 1.x incluye ( #include <tv.h> en lugar de #include <tvision/tv.h> ).
-Iinclude/tvision/compat/borland si su aplicación incluye encabezados de Borland ( dir.h , iostream.h , etc.).
En Gentoo (y posiblemente otros): -ltinfow si tanto libtinfo.so como libtinfow.so están disponibles en su sistema. De lo contrario, puede obtener una falla de segmentación al ejecutar aplicaciones de visión turbo (#11). Tenga en cuenta que tinfo está incluido con ncurses .
-lgpm solo es necesario si la visión turbo se construyó con soporte libgpm .
Los encabezados de compatibilidad hacia atrás en include/tvision/compat/borland emulan el Borland C ++ RTL. El código fuente de Turbo Vision aún depende de ellos, y podrían ser útiles si portar aplicaciones antiguas. Esto también significa que incluir tvision/tv.h traerá varios nombres std al espacio de nombres global.
El proceso de compilación con MSVC es un poco más complejo, ya que hay más opciones para elegir. Tenga en cuenta que necesitará diferentes directorios de compilación para diferentes arquitecturas de objetivos. Por ejemplo, para generar binarios optimizados:
cmake . -B ./build && # Add '-A x64' (64-bit) or '-A Win32' (32-bit) to override the default platform.
cmake --build ./build --config Release # Could also be 'Debug', 'MinSizeRel' or 'RelWithDebInfo'. En el ejemplo anterior, tvision.lib y las aplicaciones de ejemplo se colocarán en ./build/Release .
Si desea vincular la visión turbo estáticamente contra la biblioteca de tiempo de ejecución de Microsoft ( /MT en lugar de /MD ), habilite la opción TV_USE_STATIC_RTL ( -DTV_USE_STATIC_RTL=ON cuando llame cmake ).
Si desea vincular una aplicación contra Turbo Vision, tenga en cuenta que MSVC no le permitirá mezclar /MT con /MD o depurar con binarios no debug. Todos los componentes deben estar vinculados con el RTL de la misma manera.
Si desarrolla su propia aplicación Turbo Vision, asegúrese de habilitar las siguientes banderas del compilador, o de lo contrario recibirá errores de compilación cuando incluye <tvision/tv.h> :
/permissive-
/Zc:__cplusplus
Si usa turbo visión como un submódulo Cmake, estos indicadores se habilitarán automáticamente.
Nota: Turbo Vision usa setlocale para establecer las funciones RTL en el modo UTF-8. Esto no funcionará si usa una versión anterior del RTL.
Con el RTL está estáticamente vinculado, y si UTF-8 es compatible con setlocale , las aplicaciones de visión turbo son portátiles y funcionan de forma predeterminada en Windows Vista y más tarde .
Una vez que su entorno MINGW está configurado correctamente, la compilación se realiza de manera similar a Linux:
cmake . -B ./build -G " MinGW Makefiles " -DCMAKE_BUILD_TYPE=Release &&
cmake --build ./build En el ejemplo anterior, libtvision.a y todos los ejemplos están en ./build si la opción TV_BUILD_EXAMPLES está ON (el valor predeterminado).
Si desea vincular una aplicación contra Turbo Vision, simplemente agregue -L./build/lib -ltvision a su enlazador y -I./include a su compilador
Turbo Vision todavía se puede construir como una biblioteca de DOS o Windows con Borland C ++. Obviamente, no hay soporte de Unicode aquí.
Puedo confirmar que el proceso de compilación funciona con:
Puede enfrentar diferentes problemas dependiendo de su entorno de construcción. Por ejemplo, el ensamblador de turbo necesita un parche para funcionar en Windows 95. En Windows XP, todo parece funcionar bien. En Windows 10, May puede emitir el error Fatal: Command arguments too long , que se pueden solucionar actualizando la realización a la que se incluye con Borland C ++ 5.x.
Sí, esto funciona en Windows 10 de 64 bits. Lo que no funcionará es el instalador de Borland C ++, que es una aplicación de 16 bits. Tendrá que ejecutarlo en otro entorno o probar suerte con Winevdm.
Se puede encontrar una marca Borland en el directorio project . La construcción se puede hacer haciendo:
cd project
make.exe < options > Donde <options> puede estar:
-DDOS32 para aplicaciones DPMI de 32 bits (que todavía funcionan en ventanas de 64 bits).-DWIN32 para aplicaciones Win32 nativas de 32 bits (no es posible para TVDemo, que se basa en farcoreleft() y otras antigüedades).-DDEBUG para construir versiones de depuración de la aplicación y la biblioteca.-DTVDEBUG para vincular las aplicaciones con la versión de depuración de la biblioteca.-DOVERLAY , -DALIGNMENT={2,4} , -DEXCEPTION , -DNO_STREAMABLE , -DNOTASM para cosas que nunca he usado pero apareció en los makingfiles originales. Esto compilará la biblioteca en un directorio LIB junto al project , y compilará ejecutables para las aplicaciones de demostración en sus respectivos examples/* directorios.
Lo siento, la raíz Makefile supone que se ejecuta desde el directorio project . Todavía puede ejecutar los makefiles originales directamente (en source/tvision y examples/* ) si desea usar diferentes configuraciones.
Turbo Vision se puede construir e instalar utilizando el Administrador de dependencia de VCPKG:
git clone https://github.com/Microsoft/vcpkg.git
cd vcpkg
./bootstrap-vcpkg.sh
./vcpkg integrate install
./vcpkg install tvision El puerto tvision en VCPKG se mantiene actualizado por los miembros del equipo de Microsoft y los contribuyentes comunitarios. Si encuentra que está desactualizado, cree un problema o extraiga la solicitud en el repositorio de VCPKG.
Si elige el sistema de compilación CMake para su aplicación, hay dos formas principales de vincular contra Turbo Vision:
Instalación de turbo visión e importarla con find_package . La instalación depende del tipo de generador:
Primero, decida un prefijo de instalación. El predeterminado funcionará fuera de la caja, pero generalmente requiere privilegios de administración. En los sistemas UNIX, puede usar $HOME/.local en su lugar. En Windows, puede usar cualquier ruta personalizada que desee, pero tendrá que agregarla a la variable de entorno CMAKE_PREFIX_PATH al crear su aplicación.
Para los generadores mono-config ( Unix Makefiles , Ninja ...), solo tiene que construir e instalar una vez:
cmake . -B ./build # '-DCMAKE_INSTALL_PREFIX=...' to override the install prefix.
cmake --build ./build
cmake --install ./build Para generadores multi-config ( Visual Studio , Ninja Multi-Config ...) debe construir e instalar todas las configuraciones:
cmake . -B ./build # '-DCMAKE_INSTALL_PREFIX=...' to override the install prefix.
cmake --build ./build --config Release
cmake --build ./build --config Debug --target tvision
cmake --build ./build --config RelWithDebInfo --target tvision
cmake --build ./build --config MinSizeRel --target tvision
cmake --install ./build --config Release
cmake --install ./build --config Debug --component library
cmake --install ./build --config RelWithDebInfo --component library
cmake --install ./build --config MinSizeRel --component library Luego, en CMakeLists.txt de su aplicación, puede importarlo así:
find_package (tvision CONFIG)
target_link_libraries (my_application tvision::tvision) Tener turbo visión en un submódulo en su repositorio e importarlo con add_subdirectory :
add_subdirectory (tvision) # Assuming Turbo Vision is in the 'tvision' directory.
target_link_libraries (my_application tvision) En cualquier caso, <tvision/tv.h> estará disponible en la ruta de inclusión de su aplicación durante la compilación, y su aplicación estará vinculada con las bibliotecas necesarias (NCURSES, GPM ...) automáticamente.
tvedit .~/ en $HOME .stdin / stdout / stderr no interfiere con la E / S terminal.Hay algunas variables de entorno que afectan el comportamiento de todas las aplicaciones de visión turbo:
TVISION_MAX_FPS : tasa de actualización máxima, predeterminado 60 . Esto puede ayudar a mantener la suavidad en los emuladores terminales con un manejo ineficiente de los caracteres de arrastre de caja. Los valores especiales para esta opción son 0 , para deshabilitar la limitación de la velocidad de actualización, y -1 , para dibujar realmente al terminal en cada llamada a THardwareInfo::screenWrite (útil al depurar).
TVISION_CODEPAGE : el conjunto de caracteres utilizado internamente por turbo visión para traducir ASCII extendido al unicode. Los valores válidos en este momento son 437 y 850 , siendo 437 el valor predeterminado, aunque agregar más requiere muy poco esfuerzo.
win32-input-mode de Conpty (disponible en WSL).TIOCLINUX ) y el mouse (a través de GPM) en la consola Linux.stderr es un TTY, los mensajes escritos se redirigen a un búfer para evitar que arruinen la pantalla y finalmente se impriman a la consola al salir o suspender la aplicación.stderr fallará una vez que el búfer esté lleno. Si desea preservar todo stderr , simplemente redirigirlo a un archivo desde la línea de comando con 2> .También se tienen en cuenta las siguientes variables de entorno:
TERM : NCurses lo usa para determinar las capacidades terminales. El emulador terminal se establece automáticamente.
COLORTERM : Cuando se establece en truecolor o 24bit , Turbo Vision asumirá que el emulador terminal admite un color de 24 bits. Se establece automáticamente por emuladores terminales que lo respaldan.
ESCDELAY : El número de milisegundos para esperar después de recibir una tecla ESC Presione, predeterminado 10 . Si se presiona otra tecla durante este retraso, se interpretará como una combinación de tecla Alt+. Usar un valor mayor es útil cuando el terminal no admite la tecla ALT.
TVISION_USE_STDIO : cuando no está vacía, la E / S del terminal se realiza a través de stdin / stdout , para que pueda redirigirse de la carcasa. Por defecto, Turbo Vision realiza E/S terminal a través de /dev/tty , lo que permite al usuario redirigir stdin , stdout y stderr para sus necesidades, sin afectar la estabilidad de la aplicación.
Por ejemplo, lo siguiente dejará out.txt .
tvdemo | tee out.txt Mientras que lo siguiente descargará todas las secuencias de escape y texto impreso por la aplicación en out.txt :
TVISION_USE_STDIO=1 tvdemo | tee out.txtLos siguientes no están disponibles al compilar con Borland C ++:
wchar_t . Nota: Turbo Vision escribe texto UTF-8 directamente a la consola de Windows. Si la consola se establece en modo heredado y se está utilizando la fuente de mapa de bits, los caracteres Unicode no se mostrarán correctamente (foto). Para evitar esto, Turbo Vision detecta esta situación e intenta cambiar la fuente de la consola a Console Consolas o Lucida Console .
Las siguientes son nuevas características que no están disponibles en el lanzamiento de Borland de Turbo Vision o en puertos de código abierto anteriores (Sigala, SET):
TInputLine S ya no desplazan la pantalla de texto en Focus/Desfleus, lo que permite que el texto relevante se mantenga visible.TFileViewer ( tvdemo ) y TEditor ( tvedit ). TEditor conserva la línea que finaliza en el archivo Guardar, pero todos los archivos recién creados usan CRLF de forma predeterminada.TEditor : menú contextual al hacer clic derecho.TEditor : arrastre el desplazamiento con el botón central del mouse.TEditor , TInputLine : elimine las palabras completas con kbAltBack , kbCtrlBack y kbCtrlDel .TEditor : la clave de inicio alternar entre el comienzo de la línea y el comienzo del texto con sangría.TEditor : soporte para archivos de más de 64 KIB en compilaciones de 32 bits o 64 bits.tvdemo : APPLE DE VISTO DE EVENTOS ALTA PARA DEBUGACIÓN DE EVENTOS.tvdemo : opción para cambiar el patrón de fondo. Las escrituras de pantalla se amortiguan y generalmente se envían al terminal una vez para cada iteración del bucle de evento activo (ver también TVISION_MAX_FPS ). Si necesita actualizar la pantalla durante un bucle ocupado, puede usar TScreen::flushScreen() .
TDrawBuffer ya no es una matriz de longitud fija y sus métodos evitan los accesos de matriz de extremo más allá. Por lo tanto, el código antiguo que contiene comparaciones contra sizeof(TDrawBuffer)/sizeof(ushort) ya no es válido; Dichos controles deben eliminarse.
TApplication ahora proporciona dosShell() , cascade() y tile() y maneja cmDosShell , cmCascade y cmTile de forma predeterminada. Estas funciones se pueden personalizar anulando getTileRect() y writeShellMsg() . Este es el mismo comportamiento que en la versión Pascal.
Soporte de la rueda del mouse: nuevo evento del mouse evMouseWheel . La dirección de la rueda se especifica en el nuevo event.mouse.wheel de campo. Rouse.wheel, cuyos valores posibles son mwUp , mwDown , mwLeft o mwRight .
Soporte del botón del mouse medio: nueva bandera del botón del mouse mbMiddleButton .
El campo buttons en eventos evMouseUp ya no está vacío. Ahora indica qué botón se lanzó.
Soporte de triple clic: nueva bandera del evento meTripleClick .
Los métodos TRect move , grow , intersect y Union ahora devuelve TRect& en lugar de ser void para que puedan ser encadenados.
TOutlineViewer ahora permite que el nodo raíz tenga hermanos.
Nueva función ushort popupMenu(TPoint where, TMenuItem &aMenu, TGroup *receiver=0) que genera un TMenuPopup en el escritorio. Ver source/tvision/popupmnu.cpp .
Nuevo método virtual TMenuItem& TEditor::initContextMenu(TPoint p) que determina las entradas del menú contextual de clic derecho en TEditor .
fexpand ahora puede tomar un segundo parámetro relativeTo .
Nueva clase TStringView , inspirada en std::string_view .
TStringView en su lugar. TStringView es compatible con std::string_view , std::string y const char * (incluso nullptr ). Nueva clase TSpan<T> , inspirada en std::span .
Nuevas clases TDrawSurface y TSurfaceView , ver <tvision/surface.h> .
Los subsistemas de integración del sistema ( THardwareInfo , TScreen , TEventQueue ...) ahora se inicializan al construir una TApplication por primera vez, en lugar de antes de main . Todavía son destruidos en la salida de main .
Nuevo método TVMemMgr::reallocateDiscardable() que se puede usar a lo largo allocateDiscardable y freeDiscardable .
Nuevo método TView::textEvent() que permite recibir texto de manera eficiente, consulte la interacción del portapapeles.
Nueva clase TClipboard , ver interacción del portapapeles.
Soporte de Unicode, ver Unicode.
Soporte de color verdadero, ver colores extendidos.
Nuevo método static void TEventQueue::waitForEvents(int timeoutMs) que puede bloquear hasta timeoutMs de milisegundos que esperan eventos de entrada. Se pueden usar un timeoutMs negativo para esperar indefinidamente. Si bloquea, tiene el efecto secundario de las actualizaciones de la pantalla de descarga (a través de TScreen::flushScreen() ). Se invoca por TProgram::getEvent() con static int TProgram::eventTimeoutMs (predeterminado 20 ) como argumento para que el bucle de eventos no se convierta en un bucle ocupado que consume una CPU del 100%.
Nuevo método static void TEventQueue::wakeUp() que hace que el bucle de eventos reanude la ejecución si está bloqueado en TEventQueue::waitForEvents() . Este método es a prueba de subprocesos, ya que su propósito es desbloquear el bucle de eventos de los hilos secundarios.
Nuevo método void TView::getEvent(TEvent &, int timeoutMs) que permite esperar un evento con un tiempo de espera proporcionado por el usuario (en lugar de TProgram::eventTimeoutMs ).
Ahora es posible especificar un ancho de texto máximo o un recuento de caracteres máximo en TInputLine . Esto se realiza a través de un nuevo parámetro en el constructor de TInputLine , ushort limitMode , que controla cómo se trata el segundo parámetro del constructor, uint limit . Las constantes ilXXXX definen los valores posibles de limitMode :
ilMaxBytes (el valor predeterminado): el texto puede estar hasta limit los bytes de largo, incluido el terminador nulo.ilMaxWidth : El texto puede estar a limit altura de las columnas de ancho.ilMaxChars : el texto puede contener para limit los caracteres o grafemas no combinados. Nuevas funciones que permiten obtener los nombres de las constantes de Turbo Vision en tiempo de ejecución (por ejemplo, evCommand , kbShiftIns , etc.):
void printKeyCode (ostream &, ushort keyCode);
void printControlKeyState (ostream &, ushort controlKeyState);
void printEventCode (ostream &, ushort eventCode);
void printMouseButtonState (ostream &, ushort buttonState);
void printMouseWheelState (ostream &, ushort wheelState);
void printMouseEventFlags (ostream &, ushort eventFlags); Nueva clase TKey que se puede usar para definir nuevas combinaciones de teclas (por ejemplo Shift+Alt+Up ) especificando un código de clave y una máscara de modificadores de teclas:
auto kbShiftAltUp = TKey(kbUp, kbShift | kbAltShift);
assert (kbCtrlA == TKey( ' A ' , kbCtrlShift));
assert (TKey(kbCtrlTab, kbShift) == TKey(kbTab, kbShift | kbCtrlShift));
// Create menu hotkeys.
new TMenuItem( " ~R~estart " , cmRestart, TKey(kbDel, kbCtrlShift | kbAltShift), hcNoContext, " Ctrl-Alt-Del " )
// Examine KeyDown events:
if (event.keyDown == TKey(kbEnter, kbShift))
doStuff ();Nuevos métodos que permiten usar eventos cronometrados:
TTimerId TView::setTimer ( uint timeoutMs, int periodMs = - 1 );
void TView::killTimer (TTimerId id); setTimer inicia un temporizador que se liberará por primera vez en los milisegundos timeoutMs y luego cada periodMs milisegundos.
Si periodMs son negativos, el temporizador solo hace una sola vez y se limpia automáticamente. De lo contrario, seguirá agotando periódicamente hasta que se invoque killTimer .
Cuando se desprende un temporizador, se emite un evento evBroadcast con el comando cmTimerExpired , y message.infoPtr se establece en el TTimerId del temporizador caducado.
Los eventos de tiempo de espera se generan en TProgram::idle() . Por lo tanto, solo se procesan cuando no hay eventos de teclado o mouse disponibles.
Encontrarás algunas capturas de pantalla aquí. ¡Siéntete libre de agregar el tuyo!
Si conoce alguna aplicación de visión turbo cuyo código fuente no se haya perdido y eso podría beneficiarse de esto, hágamelo saber.
Si su aplicación se basa en este proyecto y desea que aparezca en la siguiente lista, hágamelo saber.
La API de turbo de visión se ha extendido para permitir recibir la entrada Unicode y mostrar el texto Unicode. La codificación compatible es UTF-8, por varias razones:
char * ), por lo que no requiere modificaciones intrusivas al código existente.Tenga en cuenta que cuando se construye con Borland C ++, Turbo Vision no admite Unicode. Sin embargo, esto no afecta la forma en que se escriben aplicaciones de visión turbo, ya que las extensiones de API están diseñadas para permitir el código de codificación-agnóstica.
La forma tradicional de obtener texto de un evento de presentación de teclas es la siguiente:
// 'ev' is a TEvent, and 'ev.what' equals 'evKeyDown'.
switch (ev.keyDown.keyCode) {
// Key shortcuts are usually checked first.
// ...
default : {
// The character is encoded in the current codepage
// (CP437 by default).
char c = ev. keyDown . charScan . charCode ;
// ...
}
} Algunas de las clases de visión turbo existentes que tratan con la entrada de texto aún dependen de esta metodología, que no ha cambiado. Los caracteres de un solo byte, cuando se representan en la expresión de códigos actual, continúan disponibles en ev.keyDown.charScan.charCode .
El soporte de Unicode consiste en dos nuevos campos en ev.keyDown (que es una struct KeyDownEvent ):
char text[4] , que puede contener lo que se lee del terminal: generalmente una secuencia UTF-8, pero posiblemente cualquier tipo de datos sin procesar.uchar textLength , que es el número de bytes de datos disponibles en text , de 0 a 4. Tenga en cuenta que la cadena text no está terminada en nulo. Puede obtener un TStringView de un KeyDownEvent con el método getText() .
Por lo tanto, se puede recuperar un personaje unicode de TEvent de la siguiente manera:
switch (ev.keyDown.keyCode) {
// ...
default : {
std::string_view sv = ev. keyDown . getText ();
processText (sv);
}
} Veamos desde otra perspectiva. Si el usuario tipos ñ , se genera un TEvent con la siguiente estructura keyDown :
KeyDownEvent {
union {
. keyCode = 0xA4 ,
. charScan = CharScanType {
. charCode = 164 ( ' ñ ' ), // In CP437
. scanCode = 0
}
},
. controlKeyState = 0x200 (kbInsState),
. text = { ' xC3 ' , ' xB1 ' }, // In UTF-8
. textLength = 2
} Sin embargo, si escriben € lo siguiente:
KeyDownEvent {
union {
. keyCode = 0x0 (kbNoKey), // '€' not part of CP437
. charScan = CharScanType {
. charCode = 0 ,
. scanCode = 0
}
},
. controlKeyState = 0x200 (kbInsState),
. text = { ' xE2 ' , ' x82 ' , ' xAC ' }, // In UTF-8
. textLength = 3
} Si se presiona un atajo de tecla, text está vacío:
KeyDownEvent {
union {
. keyCode = 0xB (kbCtrlK),
. charScan = CharScanType {
. charCode = 11 ( ' ♂ ' ),
. scanCode = 0
}
},
. controlKeyState = 0x20C (kbCtrlShift | kbInsState),
. text = {},
. textLength = 0
}Entonces, en resumen: las vistas diseñadas sin entrada Unicode en mente continuarán funcionando exactamente como lo hicieron antes, y las vistas que desean ser conscientes de Unicode no tendrán problemas para ser así.
El diseño original de Turbo Vision utiliza 16 bits para representar una celda de pantalla —8 bits para un carácter y 8 bits para atributos de color BIOS.
Se define un nuevo tipo TScreenCell en <tvision/scrncell.h> que es capaz de mantener un número limitado de puntos de código UTF-8 además de atributos extendidos (Bold, subrayado, cursiva ...). Sin embargo, no debe escribir texto en un TScreenCell directamente, sino hacer uso de funciones de API con conocimiento de Unicode.
Un personaje proporcionado como argumento a cualquiera de las funciones de la API de turbo visión que se ocupa de mostrar el texto se interpreta de la siguiente manera:
0x00 a 0xFF se interpretan como caracteres en la etiqueta de códigos activa. Por ejemplo, 0x7F se muestra como ⌂ y 0xF0 como ≡ si se usa CP437. Como excepción, 0x00 siempre se muestra como un espacio normal. Estos caracteres tienen todos una columna de ancho. Por ejemplo, la cadena "╔[xFE]╗" puede mostrarse como ╔[■]╗ . Esto significa que los caracteres que arrastran la caja se pueden mezclar con UTF-8 en general, lo cual es útil para la compatibilidad con atraso. Sin embargo, si confía en este comportamiento, puede obtener resultados inesperados: por ejemplo, "xC4xBF" es una secuencia UTF-8 válida y se muestra como Ŀ en lugar de ─┐ .
Uno de los problemas de soporte de Unicode es la existencia de personajes de doble ancho y combinando personajes. Esto entra en conflicto con la suposición original de Turbo Vision de que la pantalla es una cuadrícula de células ocupadas por un solo carácter cada uno. Sin embargo, estos casos se manejan de la siguiente manera:
Los personajes de doble ancho se pueden dibujar en cualquier lugar de la pantalla y no sucede nada malo si se superponen parcialmente con otros personajes.
Los caracteres de anchura cero superponen al personaje anterior. Por ejemplo, la secuencia में consiste en el carácter de un solo ancho म y los caracteres combinados े y ं . En este caso, se ajustan tres puntos de código unicode en la misma celda.
El ZERO WIDTH JOINER ( U+200D ) siempre se omite, ya que complica demasiado las cosas. Por ejemplo, puede convertir una cadena como "??" (4 columnas de ancho) en "??" (2 columnas de ancho). No todos los emuladores terminales respetan el ZWJ, por lo que, para producir resultados predecibles, la visión turbo imprimirá tanto "??" ¿ "??" como ?? .
No se producirán fallas gráficas notables siempre que su emulador terminal respete los anchos de carácter medidos por wcwidth .
Aquí hay un ejemplo de tales caracteres en el editor de texto turbo: 
La forma habitual de escribir en la pantalla es usar TDrawBuffer . Se han agregado algunos métodos y otros han cambiado su significado:
void TDrawBuffer::moveChar ( ushort indent, char c, TColorAttr attr, ushort count);
void TDrawBuffer::putChar ( ushort indent, char c); c siempre se interpreta como un carácter en la códigos de códigos activos.
ushort TDrawBuffer::moveStr ( ushort indent, TStringView str, TColorAttr attr);
ushort TDrawBuffer::moveCStr ( ushort indent, TStringView str, TAttrPair attrs); str se interpreta de acuerdo con las reglas expuestas anteriormente.
ushort TDrawBuffer::moveStr ( ushort indent, TStringView str, TColorAttr attr, ushort maxWidth, ushort strOffset = 0 ); // New
ushort TDrawBuffer::moveCStr ( ushort indent, TStringView str, TColorAttr attr, ushort maxWidth, ushort strOffset = 0 ); // New str se interpreta de acuerdo con las reglas expuestas anteriormente, pero:
maxWidth especifica la cantidad máxima de texto que debe copiarse de str , medido en el ancho del texto (no en bytes).strOffset especifica la posición inicial en str de dónde copiar, medida en el ancho de texto (no en bytes). Esto es útil para el desplazamiento horizontal. Si strOffset apunta a la mitad de un carácter de doble ancho, se copiará un espacio en lugar de la mitad correcta del carácter de doble ancho, ya que no es posible hacer tal cosa.Los valores de retorno son el número de celdas en el búfer que en realidad se llenaron con texto (que es el mismo que el ancho del texto copiado).
void TDrawBuffer::moveBuf ( ushort indent, const void *source, TColorAttr attr, ushort count); El nombre de esta función es engañoso. Incluso en su implementación original, source se trata como una cadena. Por lo tanto, es equivalente a moveStr(indent, TStringView((const char*) source, count), attr) .
Hay otras funciones útiles de unicode:
int cstrlen (TStringView s); Devuelve la longitud mostrada de s de acuerdo con las reglas antes mencionadas, descartando ~ caracteres.
int strwidth (TStringView s); // New Devuelve la longitud mostrada de s .
En Borland C ++, estos métodos asumen una codificación de un solo byte y todos los caracteres tienen una columna de ancho. Esto hace posible escribir métodos de codificación-agnóstica de codificación draw() y handleEvent() que funcionan en ambas plataformas sin una sola #ifdef .
Las funciones anteriores se implementan utilizando las funciones del espacio de nombres TText , otra extensión API. Tendrá que usarlos directamente si desea llenar los objetos TScreenCell con texto manualmente. Para dar un ejemplo, a continuación se encuentran algunas de las funciones TText . Puede encontrarlos todos con descripciones completas en <tvision/ttext.h> .
size_t TText::next (TStringView text);
size_t TText::prev (TStringView text, size_t index);
void TText::drawChar (TSpan<TScreenCell> cells, char c);
size_t TText::drawStr (TSpan<TScreenCell> cells, size_t indent, TStringView text, int textIndent);
bool TText::drawOne (TSpan<TScreenCell> cells, size_t &i, TStringView text, size_t &j); Para dibujar tampones TScreenCell en una vista, los siguientes métodos están disponibles:
void TView::writeBuf ( short x, short y, short w, short h, const TScreenCell *b); // New
void TView::writeLine ( short x, short y, short w, short h, const TScreenCell *b); // New Es tan simple como puede ser. Vamos a modificar hello.cpp como sigue:
TMenuBar * THelloApp::initMenuBar ( TRect r )
{
r. b . y = r. a . y + 1 ;
return new TMenuBar ( r,
* new TSubMenu ( " ~Ñ~ello " , kbAltH ) +
* new TMenuItem ( "階~毎~料入報最... " , GreetThemCmd, kbAltG ) +
* new TMenuItem ( "五劫~の~擦り切れ" , cmYes, kbNoKey, hcNoContext ) +
* new TMenuItem ( " העברית ~א~ינטרנט " , cmNo, kbNoKey, hcNoContext ) +
newLine () +
* new TMenuItem ( " E~x~it " , cmQuit, cmQuit, hcNoContext, " Alt-X " )
);
}
TStatusLine * THelloApp::initStatusLine ( TRect r )
{
r. a . y = r. b . y - 1 ;
return new TStatusLine ( r,
* new TStatusDef ( 0 , 0xFFFF ) +
* new TStatusItem ( " ~Alt-Ç~ Exit " , kbAltX, cmQuit ) +
* new TStatusItem ( 0 , kbF10, cmMenu )
);
}Esto es lo que parece:

draw() El siguiente es un extracto de una antigua implementación de TFileViewer::draw() (parte de la aplicación tvdemo ), que no dibuja el texto de Unicode correctamente:
if (delta.y + i < fileLines-> getCount ()) {
char s[maxLineLength+ 1 ];
p = ( char *)(fileLines-> at (delta. y +i));
if (p == 0 || strlen (p) < delta. x )
s[ 0 ] = EOS;
else
strnzcpy (s, p+delta. x , maxLineLength+ 1 );
b. moveStr ( 0 , s, c);
}
writeBuf ( 0 , i, size.x, 1 , b ); Todo lo que hace es mover parte de una cadena en fileLines a b , que es un TDrawBuffer . delta es un TPoint representando el desplazamiento de desplazamiento en la vista de texto, y i es el índice de la línea visible que se está procesando. c es el color de texto. Algunos problemas están presentes:
TDrawBuffer::moveStr(ushort, const char *, TColorAttr) toma una cadena terminada nula. Para pasar una subcadena de la línea actual, se realiza una copia en la matriz s , a riesgo de un búfer desbordado. El caso en el que la línea no encaja en s no se maneja, por lo que, como máximo, se copiarán los caracteres maxLineLenght . Además, un personaje multibirio cerca de la posición maxLineLength podría copiarse de manera incompleta y mostrarse como basura.delta.x es la primera columna visible. Con el texto codificado por multibitos, ya no es cierto que dicha columna comience en la posición delta.x en la cadena.A continuación se muestra una versión corregida del código anterior que maneja Unicode correctamente:
if (delta.y + i < fileLines-> getCount ()) {
p = ( char *)(fileLines-> at (delta. y +i));
if (p)
b. moveStr ( 0 , p, c, size. x , delta. x );
}
writeBuf ( 0 , i, size.x, 1 , b ); La sobrecarga de moveStr utilizados aquí es TDrawBuffer::moveStr(ushort indent, TStringView str, TColorAttr attr, ushort width, ushort begin) . Esta función no solo proporciona soporte unicode, sino que también nos ayuda a escribir código más limpio y superar algunas de las limitaciones previamente presentes:
maxLineLength .moveStr se encarga de imprimir la cadena que comienza en la columna delta.x . Ni siquiera debemos preocuparnos por cuántos bytes corresponden a las columnas delta.x .moveStr is instructed to copy at most size.x columns of text without us having to care about how many bytes that is nor dealing with edge cases. The code is written in an encoding-agnostic way and will work whether multibyte characters are being considered or not.TView::writeBuf already takes care of not writing beyond it. Yet it is interesting to see how an unnecessary step not only was limiting functionality but also was prone to bugs. Support for creating Unicode-aware views is in place, and most views in the original Turbo Vision library have been adapted to handle Unicode.
The following views can display Unicode text properly. Some of them also do horizontal scrolling or word wrapping; all of that should work fine.
TStaticText ( 7b15d45d ). TFrame ( 81066ee5 ). TStatusLine ( 477b3ae9 ). THistoryViewer ( 81066ee5 ). THelpViewer ( 81066ee5 , 8c7dac2a , 20f331e3 ). TListViewer ( 81066ee5 ). TMenuBox ( 81066ee5 ). TTerminal ( ee821b69 ). TOutlineViewer ( 6cc8cd38 ). TFileViewer (from the tvdemo application) ( 068bbf7a ). TFilePane (from the tvdir application) ( 9bcd897c ).The following views can, in addition, process Unicode text or user input:
TInputLine ( 81066ee5 , cb489d42 ). TEditor ( 702114dc ). Instances are in UTF-8 mode by default. You may switch back to single-byte mode by pressing Ctrl+P . This only changes how the document is displayed and the encoding of user input; it does not alter the document. This class is used in the tvedit application; you may test it there.Views not in this list may not have needed any corrections or I simply forgot to fix them. Please submit an issue if you notice anything not working as expected.
Use cases where Unicode is not supported (not an exhaustive list):
TMenuBox , TStatusLine , TButton ...). Originally, Turbo Vision offered no integration with the system clipboard, since there was no such thing on MS-DOS.
It did offer the possibility of using an instance of TEditor as an internal clipboard, via the TEditor::clipboard static member. However, TEditor was the only class able to interact with this clipboard. It was not possible to use it with TInputLine , for example.
Turbo Vision applications are now most likely to be ran in a graphical environment through a terminal emulator. In this context, it would be desirable to interact with the system clipboard in the same way as a regular GUI application would do.
To deal with this, a new class TClipboard has been added which allows accessing the system clipboard. If the system clipboard is not accessible, it will instead use an internal clipboard.
On Windows (including WSL) and macOS, clipboard integration is supported out-of-the-box.
On Unix systems other than macOS, it is necessary to install some external dependencies. See runtime requirements.
For applications running remotely (eg through SSH), clipboard integration is supported in the following situations:
ssh -X ).allowWindowOps option is enabled. Additionally, it is always possible to paste text using your terminal emulator's own Paste command (usually Ctrl+Shift+V or Cmd+V ).
To use the TClipboard class, define the macro Uses_TClipboard before including <tvision/tv.h> .
static void TClipboard::setText (TStringView text); Sets the contents of the system clipboard to text . If the system clipboard is not accessible, an internal clipboard is used instead.
static void TClipboard::requestText (); Requests the contents of the system clipboard asynchronously, which will be later received in the form of regular evKeyDown events. If the system clipboard is not accessible, an internal clipboard is used instead.
A Turbo Vision application may receive a Paste event for two different reasons:
TClipboard::requestText() was invoked. In both cases the application will receive the clipboard contents in the form of regular evKeyDown events. These events will have a kbPaste flag in keyDown.controlKeyState so that they can be distinguished from regular key presses.
Therefore, if your view can handle user input it will also handle Paste events by default. However, if the user pastes 5000 characters, the application will behave as if the user pressed the keyboard 5000 times. This involves drawing views, completing the event loop, updating the screen..., which is far from optimal if your view is a text editing component, for example.
For the purpose of dealing with this situation, another function has been added:
bool TView::textEvent (TEvent &event, TSpan< char > dest, size_t &length); textEvent() attempts to read text from consecutive evKeyDown events and stores it in a user-provided buffer dest . It returns false when no more events are available or if a non-text event is found, in which case this event is saved with putEvent() so that it can be processed in the next iteration of the event loop. Finally, it calls clearEvent(event) .
The exact number of bytes read is stored in the output parameter length , which will never be larger than dest.size() .
Here is an example on how to use it:
// 'ev' is a TEvent, and 'ev.what' equals 'evKeyDown'.
// If we received text from the clipboard...
if (ev.keyDown.controlKeyState & kbPaste) {
char buf[ 512 ];
size_t length;
// Fill 'buf' with the text in 'ev' and in
// upcoming events from the input queue.
while ( textEvent (ev, buf, length)) {
// Process 'length' bytes of text in 'buf'...
}
} The standard views TEditor and TInputLine react to the cmCut , cmCopy and cmPaste commands. However, your application first has to be set up to use these commands. Por ejemplo:
TStatusLine * TMyApplication::initStatusLine ( TRect r )
{
r. a . y = r. b . y - 1 ;
return new TStatusLine ( r,
* new TStatusDef ( 0 , 0xFFFF ) +
// ...
* new TStatusItem ( 0 , kbCtrlX, cmCut ) +
* new TStatusItem ( 0 , kbCtrlC, cmCopy ) +
* new TStatusItem ( 0 , kbCtrlV, cmPaste ) +
// ...
);
} TEditor and TInputLine automatically enable and disable these commands. For example, if a TEditor or TInputLine is focused, the cmPaste command will be enabled. If there is selected text, the cmCut and cmCopy commands will also be enabled. If no TEditor or TInputLine s are focused, then these commands will be disabled.
The Turbo Vision API has been extended to allow more than the original 16 colors.
Colors can be specified using any of the following formats:
xterm-256color palette indices (8-bit).Although Turbo Vision applications are likely to be ran in a terminal emulator, the API makes no assumptions about the display device. That is to say, the complexity of dealing with terminal emulators is hidden from the programmer and managed by Turbo Vision itself.
For example: color support varies among terminals. If the programmer uses a color format not supported by the terminal emulator, Turbo Vision will quantize it to what the terminal can display. The following images represent the quantization of a 24-bit RGB picture to 256, 16 and 8 color palettes:
| 24-bit color (original) | 256 colors |
|---|---|
![]() | ![]() |
| 16 colors | 8 colors (bold as bright) |
|---|---|
![]() | ![]() |
Extended color support basically comes down to the following:
uchar . ushort is used to represent attribute pairs. This is still the case when using Borland C++.TColorAttr has been added which replaces uchar . It specifies a foreground and background color and a style. Colors can be specified in different formats (BIOS color attributes, 24-bit RGB...). Styles are the typical ones (bold, italic, underline...). There's also TAttrPair , which replaces ushort .TDrawBuffer 's methods, which used to take uchar or ushort parameters to specify color attributes, now take TColorAttr or TAttrPair .TPalette , which used to contain an array of uchar , now contains an array of TColorAttr . The TView::mapColor method also returns TColorAttr instead of uchar .TView::mapColor has been made virtual so that the palette system can be bypassed without having to rewrite any draw methods.TColorAttr and TAttrPair can be initialized with and casted into uchar and ushort in a way such that legacy code still compiles out-of-the-box without any change in functionality.Below is a more detailed explanation aimed at developers.
In the first place we will explain the data types the programmer needs to know in order to take advantage of the extended color support. To get access to them, you may have to define the macro Uses_TColorAttr before including <tvision/tv.h> .
All the types described in this section are trivial . This means that they can be memset 'd and memcpy 'd. But variables of these types are uninitialized when declared without initializer, just like primitive types. So make sure you don't manipulate them before initializing them.
Several types are defined which represent different color formats. The reason why these types exist is to allow distinguishing color formats using the type system. Some of them also have public fields which make it easier to manipulate individual bits.
TColorBIOS represents a BIOS color. It allows accessing the r , g , b and bright bits individually, and can be casted implicitly into/from uint8_t .
The memory layout is:
b ).g ).r ).bright ). Examples of TColorBIOS usage:
TColorBIOS bios = 0x4 ; // 0x4: red.
bios.bright = 1 ; // 0xC: light red.
bios.b = bios.r; // 0xD: light magenta.
bios = bios ^ 3 ; // 0xE: yellow.
uint8_t c = bios; // Implicit conversion to integer types.In terminal emulators, BIOS colors are mapped to the basic 16 ANSI colors.
TColorRGB represents a color in 24-bit RGB. It allows accessing the r , g and b bit fields individually, and can be casted implicitly into/from uint32_t .
The memory layout is:
b ).g ).r ). Examples of TColorRGB usage:
TColorRGB rgb = 0x9370DB ; // 0xRRGGBB.
rgb = { 0x93 , 0x70 , 0xDB }; // {R, G, B}.
rgb = rgb ^ 0xFFFFFF ; // Negated.
rgb.g = rgb.r & 0x88 ; // Access to individual components.
uint32_t c = rgb; // Implicit conversion to integer types. TColorXTerm represents an index into the xterm-256color color palette. It can be casted into and from uint8_t .
TColorDesired TColorDesired represents a color which the programmer intends to show on screen, encoded in any of the supported color types.
A TColorDesired can be initialized in the following ways:
As a BIOS color: with a char literal or a TColorBIOS object:
TColorDesired bios1 = ' xF ' ;
TColorDesired bios2 = TColorBIOS( 0xF ); As a RGB color: with an int literal or a TColorRGB object:
TColorDesired rgb1 = 0xFF7700 ; // 0xRRGGBB.
TColorDesired rgb2 = TColorRGB( 0xFF , 0x77 , 0x00 ); // {R, G, B}.
TColorDesired rgb3 = TColorRGB( 0xFF7700 ); // 0xRRGGBB. As an XTerm palette index: with a TColorXTerm object.
As the terminal default color: through zero-initialization:
TColorDesired def1 {};
// Or with 'memset':
TColorDesired def2;
memset (&def2, 0 , sizeof (def2)); TColorDesired has methods to query the contained color, but you will usually not need to use them. See the struct definition in <tvision/colors.h> for more information.
Trivia: the name is inspired by Scintilla's ColourDesired .
TColorAttr TColorAttr describes the color attributes of a screen cell. This is the type you are most likely to interact with if you intend to change the colors in a view.
A TColorAttr is composed of:
A foreground color, of type TColorDesired .
A background color, of type TColorDesired .
A style bitmask containing a combination of the following flags:
slBold .slItalic .slUnderline .slBlink .slReverse .slStrike . These flags are based on the basic display attributes selectable through ANSI escape codes. The results may vary between terminal emulators. slReverse is probably the least reliable of them: prefer using the TColorAttr reverseAttribute(TColorAttr attr) free function over setting this flag.
The most straight-forward way to create a TColorAttr is by means of the TColorAttr(TColorDesired fg, TColorDesired bg, ushort style=0) and TColorAttr(int bios) constructors:
// Foreground: RGB 0x892312
// Background: RGB 0x7F00BB
// Style: Normal.
TColorAttr a1 = { TColorRGB ( 0x89 , 0x23 , 0x12 ), TColorRGB ( 0x7F , 0x00 , 0xBB )};
// Foreground: BIOS 0x7.
// Background: RGB 0x7F00BB.
// Style: Bold, Italic.
TColorAttr a2 = { ' x7 ' , 0x7F00BB , slBold | slItalic};
// Foreground: Terminal default.
// Background: BIOS 0xF.
// Style: Normal.
TColorAttr a3 = {{}, TColorBIOS ( 0xF )};
// Foreground: Terminal default.
// Background: Terminal default.
// Style: Normal.
TColorAttr a4 = {};
// Foreground: BIOS 0x0
// Background: BIOS 0x7
// Style: Normal
TColorAttr a5 = 0x70 ; The fields of a TColorAttr can be accessed with the following free functions:
TColorDesired getFore ( const TColorAttr &attr);
TColorDesired getBack ( const TColorAttr &attr);
ushort getStyle ( const TColorAttr &attr);
void setFore (TColorAttr &attr, TColorDesired fg);
void setBack (TColorAttr &attr, TColorDesired bg);
void setStyle (TColorAttr &attr, ushort style);TAttrPair TAttrPair is a pair of TColorAttr , used by some API functions to pass two attributes at once.
You may initialize a TAttrPair with the TAttrPair(const TColorAttrs &lo, const TColorAttrs &hi) constructor:
TColorAttr cNormal = { 0x234983 , 0x267232 };
TColorAttr cHigh = { 0x309283 , 0x127844 };
TAttrPair attrs = {cNormal, cHigh};
TDrawBuffer b;
b.moveCStr( 0 , " Normal text, ~Highlighted text~ " , attrs); The attributes can be accessed with the [0] and [1] subindices:
TColorAttr lo = { 0x892343 , 0x271274 };
TColorAttr hi = ' x93 ' ;
TAttrPair attrs = {lo, hi};
assert (lo == attrs[ 0 ]);
assert (hi == attrs[ 1 ]);TView Views are commonly drawn by means of a TDrawBuffer . Most TDrawBuffer member functions take color attributes by parameter. Por ejemplo:
ushort TDrawBuffer::moveStr ( ushort indent, TStringView str, TColorAttr attr);
ushort TDrawBuffer::moveCStr ( ushort indent, TStringView str, TAttrPair attrs);
void TDrawBuffer::putAttribute ( ushort indent, TColorAttr attr);However, the views provided with Turbo Vision usually store their color information in palettes. A view's palette can be queried with the following member functions:
TColorAttr TView::mapColor (uchar index);
TAttrPair TView::getColor ( ushort indices); mapColor looks up a single color attribute in the view's palette, given an index into the palette. Remember that the palette indices for each view class can be found in the Turbo Vision headers. For example, <tvision/views.h> says the following about TScrollBar :
/* ---------------------------------------------------------------------- */
/* class TScrollBar */
/* */
/* Palette layout */
/* 1 = Page areas */
/* 2 = Arrows */
/* 3 = Indicator */
/* ---------------------------------------------------------------------- */ getColor is a helper function that allows querying two cell attributes at once. Each byte in the indices parameter contains an index into the palette. The TAttrPair result contains the two cell attributes.
For example, the following can be found in the draw method of TMenuBar :
TAttrPair cNormal = getColor( 0x0301 );
TAttrPair cSelect = getColor( 0x0604 );Which would be equivalent to this:
TAttrPair cNormal = { mapColor ( 1 ), mapColor ( 3 )};
TAttrPair cSelect = { mapColor ( 4 ), mapColor ( 6 )}; As an API extension, the mapColor method has been made virtual . This makes it possible to override Turbo Vision's hierarchical palette system with a custom solution without having to rewrite the draw() method.
So, in general, there are three ways to use extended colors in views:
mapColor method: // The 'TMyScrollBar' class inherits from 'TScrollBar' and overrides 'TView::mapColor'.
TColorAttr TMyScrollBar::mapColor (uchar index) noexcept
{
// In this example the values are hardcoded,
// but they could be stored elsewhere if desired.
switch ( index )
{
case 1 : return { 0x492983 , 0x826124 }; // Page areas.
case 2 : return { 0x438939 , 0x091297 }; // Arrows.
case 3 : return { 0x123783 , 0x329812 }; // Indicator.
default : return errorAttr;
}
} By providing extended color attributes directly to TDrawBuffer methods, if the palette system is not being used. Por ejemplo:
// The 'TMyView' class inherits from 'TView' and overrides 'TView::draw'.
void TMyView::draw ()
{
TDrawBuffer b;
TColorAttr color { 0x1F1C1B , 0xFAFAFA , slBold};
b. moveStr ( 0 , " This is bold black text over a white background " , color);
/* ... */
}By modifying the palettes. There are two ways to do this:
TColorAttr . Por ejemplo: void updateAppPalette ()
{
TPalette &pal = TProgram::application-> getPalete ();
pal[ 1 ] = { 0x762892 , 0x828712 }; // TBackground.
pal[ 2 ] = { 0x874832 , 0x249838 , slBold}; // TMenuView normal text.
pal[ 3 ] = {{}, {}, slItalic | slUnderline}; // TMenuView disabled text.
/* ... */
} static const TColorAttr cpMyApp[] =
{
{ 0x762892 , 0x828712 }, // TBackground.
{ 0x874832 , 0x249838 , slBold}, // TMenuView normal text.
{{}, {}, slItalic | slUnderline}, // TMenuView disabled text.
/* ... */
};
// The 'TMyApp' class inherits from 'TApplication' and overrides 'TView::getPalette'.
TPalette & TMyApp::getPalette () const
{
static TPalette palette (cpMyApp);
return palette;
} TScreen::screenMode exposes some information about the display's color support:
(TScreen::screenMode & 0xFF) == TDisplay::smMono , the display is monocolor (only relevant in DOS).(TScreen::screenMode & 0xFF) == TDisplay::smBW80 , the display is grayscale (only relevant in DOS).(TScreen::screenMode & 0xFF) == TDisplay::smCO80 , the display supports at least 16 colors.TScreen::screenMode & TDisplay::smColor256 , the display supports at least 256 colors.TScreen::screenMode & TDisplay::smColorHigh , the display supports even more colors (eg 24-bit color). TDisplay::smColor256 is also set in this case. The types defined previously represent concepts that are also important when developing for Borland C++:
| Concepto | Layout in Borland C++ | Layout in modern platforms |
|---|---|---|
| Color Attribute | uchar . A BIOS color attribute. | struct TColorAttr . |
| Color | A 4-bit number. | struct TColorDesired . |
| Attribute Pair | ushort . An attribute in each byte. | struct TAttrPair . |
One of this project's key principles is that the API should be used in the same way both in Borland C++ and modern platforms, that is to say, without the need for #ifdef s. Another principle is that legacy code should compile out-of-the-box, and adapting it to the new features should increase complexity as little as possible.
Backward-compatibility is accomplished in the following way:
In Borland C++, TColorAttr and TAttrPair are typedef 'd to uchar and ushort , respectively.
In modern platforms, TColorAttr and TAttrPair can be used in place of uchar and ushort , respectively, since they are able to hold any value that fits into them and can be casted implicitly into/from them.
A TColorAttr initialized with uchar represents a BIOS color attribute. When converting back to uchar , the following happens:
fg and bg are BIOS colors, and style is cleared, the resulting uchar represents the same BIOS color attribute contained in the TColorAttr (as in the code above).uchar / ushort with TColorAttr / TAttrPair if they intend to support the extended color attributes. The same goes for TAttrPair and ushort , considering that it is composed of two TColorAttr .
A use case of backward-compatibility within Turbo Vision itself is the TPalette class, core of the palette system. In its original design, it used a single data type ( uchar ) to represent different things: array length, palette indices or color attributes.
The new design simply replaces uchar with TColorAttr . This means there are no changes in the way TPalette is used, yet TPalette is now able to store extended color attributes.
TColorDialog hasn't been remodeled, and thus it can't be used to pick extended color attributes at runtime.
The following pattern of code is common across draw methods of views:
void TMyView::draw ()
{
ushort cFrame, cTitle;
if (state & sfDragging)
{
cFrame = 0x0505 ;
cTitle = 0x0005 ;
}
else
{
cFrame = 0x0503 ;
cTitle = 0x0004 ;
}
cFrame = getColor (cFrame);
cTitle = getColor (cTitle);
/* ... */
} In this case, ushort is used both as a pair of palette indices and as a pair of color attributes. getColor now returns a TAttrPair , so even though this compiles out-of-the-box, extended attributes will be lost in the implicit conversion to ushort .
The code above still works just like it did originally. It's only non-BIOS color attributes that don't produce the expected result. Because of the compatibility between TAttrPair and ushort , the following is enough to enable support for extended color attributes:
- ushort cFrame, cTitle;
+ TAttrPair cFrame, cTitle;Nothing prevents you from using different variables for palette indices and color attributes, which is what should actually be done. The point of backward-compatibility is the ability to support new features without changing the program's logic, that is to say, minimizing the risk of increasing code complexity or introducing bugs.