Un port moderne de Turbo Vision 2.0, le cadre classique des interfaces utilisateur basées sur le texte. Maintenant multiplateforme et avec le support Unicode.

J'ai commencé cela comme un projet personnel à la fin de 2018. En mai 2020, j'ai considéré qu'il était très proche de la parité avec l'original et j'ai décidé de l'ouvrir.
Les objectifs originaux de ce projet étaient:
À un moment donné, j'ai considéré que j'en avais fait assez, et que toute tentative de refonte de la bibliothèque et de surmonter ses limitations d'origine nécessiterait de prolonger l'API ou de briser la compatibilité en arrière, et qu'une réécriture majeure serait très probablement nécessaire.
Cependant, entre juillet et août 2020, j'ai trouvé le moyen d'intégrer le support Unicode à part entière dans l'architecture existante, j'ai écrit l'éditeur de texte Turbo et ont également rendu les nouvelles fonctionnalités disponibles sur Windows. Je suis donc convaincu que Turbo Vision peut désormais répondre à bon nombre des attentes des utilisateurs et des programmeurs modernes.
L'emplacement d'origine de ce projet est https://github.com/magiblot/tvision.
Beaucoup de choses ont changé depuis que Borland a créé Turbo Vision au début des années 90. De nombreux outils d'interface graphique séparent aujourd'hui la spécification d'apparence de la spécification du comportement, utilisent des langages plus sûrs ou dynamiques qui ne se séparent pas sur l'erreur et prennent en charge la programmation parallèle ou asynchrone, ou les deux.
Turbo Vision n'excelle dans aucune d'entre elles, mais elle surmonte certainement de nombreux problèmes auxquels les programmeurs sont toujours confrontés aujourd'hui lors de la rédaction d'applications terminales:
Oubliez les capacités du terminal et les E / S de terminaux directs. Lorsque vous rédigez une application Turbo Vision, tout ce que vous avez à vous soucier est de ce que vous voulez que votre application se comporte et ressemble - il n'est pas nécessaire d'ajouter des solutions de contournement dans votre code. Turbo Vision essaie de produire les mêmes résultats sur tous les environnements. Par exemple: Afin d'obtenir une couleur d'arrière-plan lumineuse sur la console Linux, l'attribut Blink doit être défini. Turbo Vision fait cela pour vous.
Réutilisez ce qui a déjà été fait. Turbo Vision fournit de nombreuses classes de widgets (également appelées vues ), y compris des fenêtres résidantes et qui se chevauchent, des menus effilés, des boîtes de dialogue, des boutons, des barres de défilement, des cases d'entrée, des cases à cocher et des boutons radio. Vous pouvez les utiliser et les étendre; Mais même si vous préférez créer votre propre, Turbo Vision gère déjà la répartition des événements, l'affichage de caractères Unicode FullWidth, etc.: Vous n'avez pas besoin de perdre du temps à réécrire tout cela.
Pouvez-vous imaginer écrire une interface basée sur le texte qui fonctionne à la fois sur Linux et Windows (et donc est multiplateforme) hors de la boîte, sans #ifdef s? Turbo Vision rend cela possible. Premièrement: Turbo Vision continue d'utiliser des réseaux char au lieu de compter sur le wchar_t ou TCHAR -dépendant de la mise en œuvre et dépendant de la plate-forme. Deuxièmement: Grâce à la prise en charge de l'UTF-8 dans setlocale dans les versions récentes de RTL de Microsoft, le code comme le suivant fonctionnera comme prévu:
std::ifstream f ( "コンピュータ.txt " ); // On Windows, the RTL converts this to the system encoding on-the-fly. Vous pouvez commencer avec le Guide de l'utilisateur Turbo Vision du C ++ et consulter les exemples d'applications hello , tvdemo et tvedit . Une fois que vous avez saisi les bases, je vous suggère de jeter un œil au guide de programmation Turbo Vision 2.0, qui est, à mon avis, plus intuitif et plus facile à comprendre, malgré l'utilisation de Pascal. D'ici là, vous serez probablement intéressé par l'exemple palette , qui contient une description détaillée de la façon dont les palettes sont utilisées.
N'oubliez pas de consulter également les fonctionnalités et les modifications de l'API.
Ce projet n'a pas de versions stables pour le moment. Si vous êtes un développeur, essayez de vous en tenir au dernier engagement et signalez les problèmes que vous trouvez lors de la mise à niveau.
Si vous souhaitez simplement tester les applications de démonstration:
examples-dos32.zip : exécutables 32 bits construits avec Borland C ++. Pas de support Unicode.examples-x86.zip : exécutables 32 bits construits avec MSVC. Windows Vista ou ultérieurement requis.examples-x64.zip : exécutables 64 bits construits avec MSVC. x64 Windows Vista ou ultérieurement requis. Turbo Vision peut être construit comme une bibliothèque statique avec CMake et GCC / Clang.
cmake . -B ./build -DCMAKE_BUILD_TYPE=Release && # Could also be 'Debug', 'MinSizeRel' or 'RelWithDebInfo'.
cmake --build ./build # or `cd ./build; make` Les versions CMake de plus de 3,13 peuvent ne pas prendre en charge l'option -B . Vous pouvez plutôt essayer ce qui suit:
mkdir -p build ; cd build
cmake .. -DCMAKE_BUILD_TYPE=Release &&
cmake --build .Ce qui précède produit les fichiers suivants:
libtvision.a , qui est la bibliothèque Turbo Vision.hello , tvdemo , tvedit , tvdir , qui ont été emballées avec la vision turbo d'origine (bien que certaines d'entre elles aient quelques améliorations).mmenu et palette du support technique de Borland.tvhc , le Turbo Vision Aide au compilateur. La bibliothèque et les exécutables se trouvent dans ./build .
Les exigences de construction sont:
libncursesw (notez le «w»).libgpm pour la prise en charge de la souris sur la console Linux (facultative). Si votre distribution fournit des packages de développement distincts (par exemple libncurses-dev , libgpm-dev dans les distros basés sur Debian), installez-les également.
Les exigences d'exécution sont:
xsel ou xclip pour la prise en charge du presse-papiers dans les environnements X11.wl-clipboard pour la prise en charge du presse-papiers dans les environnements Wayland. La ligne de commande minimale requise pour créer une application Turbo Vision (par exemple hello.cpp avec GCC) à partir de la racine de ce projet est:
g++ -std=c++14 -o hello hello.cpp ./build/libtvision.a -Iinclude -lncursesw -lgpmVous pouvez également avoir besoin:
-Iinclude/tvision Si votre application utilise Turbo Vision 1.x inclut ( #include <tv.h> au lieu de #include <tvision/tv.h> ).
-Iinclude/tvision/compat/borland Si votre application comprend des en-têtes Borland ( dir.h , iostream.h , etc.).
Sur Gentoo (et peut-être d'autres): -ltinfow si les deux libtinfo.so et libtinfow.so sont disponibles dans votre système. Sinon, vous pouvez obtenir un défaut de segmentation lors de l'exécution d'applications Turbo Vision (# 11). Notez que tinfo est regroupé de ncurses .
-lgpm n'est nécessaire que si la vision turbo a été construite avec le support libgpm .
Les en-têtes de compatibilité vers l'arrière dans include/tvision/compat/borland imitent le borland c ++ rtl. Le code source de Turbo Vision dépend toujours d'eux, et ils pourraient être utiles si le portage des anciennes applications. Cela signifie également que l'inclusion de tvision/tv.h apportera plusieurs noms std à l'espace de noms global.
Le processus de construction avec MSVC est légèrement plus complexe, car il y a plus d'options à choisir. Notez que vous aurez besoin de différents répertoires de construction pour différentes architectures cibles. Par exemple, pour générer des binaires optimisés:
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'. Dans l'exemple ci-dessus, tvision.lib et l'exemple des applications seront placés sur ./build/Release .
Si vous souhaitez lier Turbo Vision statiquement à la bibliothèque d'exécution de Microsoft ( /MT au lieu de /MD ), activez l'option TV_USE_STATIC_RTL ( -DTV_USE_STATIC_RTL=ON lors de l'appel cmake ).
Si vous souhaitez lier une application contre Turbo Vision, notez que MSVC ne vous permet pas de mélanger /MT avec /MD ou de déboguer avec des binaires non-debugs. Tous les composants doivent être liés à la RTL de la même manière.
Si vous développez votre propre application Turbo Vision, assurez-vous d'activer les drapeaux du compilateur suivant, sinon vous obtiendrez des erreurs de compilation lorsque vous incluez <tvision/tv.h> :
/permissive-
/Zc:__cplusplus
Si vous utilisez Turbo Vision comme sous-module CMake, ces drapeaux seront activés automatiquement.
Remarque: Turbo Vision utilise setlocale pour définir les fonctions RTL en mode UTF-8. Cela ne fonctionnera pas si vous utilisez une ancienne version du RTL.
Avec le RTL lié statiquement, et si UTF-8 est pris en charge dans setlocale , les applications Turbo Vision sont portables et fonctionnent par défaut sur Windows Vista et ultérieurement .
Une fois que votre environnement MingW est correctement configuré, la construction se fait de la même manière que Linux:
cmake . -B ./build -G " MinGW Makefiles " -DCMAKE_BUILD_TYPE=Release &&
cmake --build ./build Dans l'exemple ci-dessus, libtvision.a et tous les exemples sont dans ./build if TV_BUILD_EXAMPLES L'option est ON (par défaut).
Si vous souhaitez lier une application contre Turbo Vision, ajoutez simplement -L./build/lib -ltvision à votre lieur et -I./include à votre compilateur
Turbo Vision peut toujours être construit en tant que bibliothèque DOS ou Windows avec Borland C ++. De toute évidence, il n'y a pas de support Unicode ici.
Je peux confirmer que le processus de construction fonctionne avec:
Vous pouvez faire face à différents problèmes en fonction de votre environnement de construction. Par exemple, l'assembleur Turbo a besoin d'un patch pour fonctionner sous Windows 95. Sur Windows XP, tout semble bien fonctionner. Sur Windows 10, faire peut émettre l'erreur Fatal: Command arguments too long , qui peuvent être corrigées par la mise à niveau de la marque à celle groupée avec Borland C ++ 5.x.
Oui, cela fonctionne sur Windows 10 64 bits. Ce qui ne fonctionnera pas, c'est le programme d'installation de Borland C ++, qui est une application 16 bits. Vous devrez l'exécuter sur un autre environnement ou tenter votre chance avec WineVDM.
Un Borland Makefile peut être trouvé dans le répertoire project . La construction peut être effectuée en faisant:
cd project
make.exe < options > Où <options> peuvent être:
-DDOS32 pour les applications DPMI 32 bits (qui fonctionnent toujours sur des fenêtres 64 bits).-DWIN32 pour les applications Win32 natives 32 bits (pas possibles pour TVDemo, qui s'appuie sur farcoreleft() et d'autres antiquités).-DDEBUG pour créer des versions de débogage de l'application et de la bibliothèque.-DTVDEBUG pour lier les applications avec la version de débogage de la bibliothèque.-DOVERLAY , -DALIGNMENT={2,4} , -DEXCEPTION , -DNO_STREAMABLE , -DNOTASM pour les choses que je n'ai jamais utilisées mais apparaissaient dans les makefiles originaux. Cela compilera la bibliothèque dans un répertoire LIB à côté du project et compilera les exécutables pour les applications de démonstration dans leurs examples/* répertoires.
Je suis désolé, le Root Makefile suppose qu'il est exécuté à partir du répertoire project . Vous pouvez toujours exécuter directement les makefiles originaux (dans source/tvision et examples/* ) si vous souhaitez utiliser différents paramètres.
Turbo Vision peut être construit et installé à l'aide du gestionnaire de dépendance VCPKG:
git clone https://github.com/Microsoft/vcpkg.git
cd vcpkg
./bootstrap-vcpkg.sh
./vcpkg integrate install
./vcpkg install tvision Le port tvision de VCPKG est tenu à jour par les membres de l'équipe Microsoft et les contributeurs communautaires. Si vous trouvez qu'il est obsolète, veuillez créer une demande de problème ou d'extraction dans le référentiel VCPKG.
Si vous choisissez le système de construction CMake pour votre application, il existe deux façons principales de lier à Turbo Vision:
Installation de la vision turbo et importation avec find_package . L'installation dépend du type de générateur:
Tout d'abord, décidez d'un préfixe d'installation. La par défaut fonctionnera à l'extérieur de la boîte, mais nécessite généralement des privilèges d'administration. Sur Unix Systems, vous pouvez utiliser $HOME/.local à la place. Sur Windows, vous pouvez utiliser n'importe quel chemin personnalisé que vous souhaitez, mais vous devrez l'ajouter à la variable d'environnement CMAKE_PREFIX_PATH lors de la création de votre application.
Pour les générateurs mono-config ( Unix Makefiles , Ninja ...), vous n'avez qu'à construire et installer une fois:
cmake . -B ./build # '-DCMAKE_INSTALL_PREFIX=...' to override the install prefix.
cmake --build ./build
cmake --install ./build Pour les générateurs multi-configs ( Visual Studio , Ninja Multi-Config ...) Vous devez créer et installer toutes les configurations:
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 Ensuite, dans CMakeLists.txt de votre application, vous pouvez l'importer comme ceci:
find_package (tvision CONFIG)
target_link_libraries (my_application tvision::tvision) Ayez Turbo Vision dans un sous-module dans votre référentiel et importez-le avec add_subdirectory :
add_subdirectory (tvision) # Assuming Turbo Vision is in the 'tvision' directory.
target_link_libraries (my_application tvision) Dans les deux cas, <tvision/tv.h> sera disponible dans le chemin d'inclusion de votre application pendant la compilation, et votre application sera liée automatiquement aux bibliothèques nécessaires (ncurses, gpm ...).
tvedit .~/ en $HOME .stdin / stdout / stderr n'interfère pas avec les E / S terminales.Il existe quelques variables d'environnement qui affectent le comportement de toutes les applications de vision turbo:
TVISION_MAX_FPS : Taux de rafraîchissement maximum, par défaut 60 . Cela peut aider à maintenir la douceur dans les émulateurs terminaux avec une manipulation non efficace des caractères de dessin en boîte. Les valeurs spéciales pour cette option sont 0 , pour désactiver la limitation du taux de rafraîchissement, et -1 , pour réellement dessiner sur le terminal dans chaque appel à THardwareInfo::screenWrite (utile lors du débogage).
TVISION_CODEPAGE : Le jeu de caractères utilisé en interne par Turbo Vision pour traduire ASCII étendu en Unicode. Les valeurs valides en ce moment sont 437 et 850 , 437 étant la valeur par défaut, bien que l'ajout de plus demande très peu d'efforts.
win32-input-mode de CONPTY (disponible en WSL).TIOCLINUX ) et de la souris (via GPM) dans la console Linux.stderr est un TTY, les messages qui y sont écrits sont redirigées vers un tampon pour les empêcher de gâcher l'affichage et sont finalement imprimés vers la console lors de la sortie ou de la suspension de l'application.stderr échouera une fois le tampon plein. Si vous souhaitez préserver tout stderr , redirigez-le simplement dans un fichier à partir de la ligne de commande avec 2> .Les variables d'environnement suivantes sont également prises en compte:
TERM : NCurses l'utilise pour déterminer les capacités du terminal. Il est défini automatiquement par l'émulateur de terminal.
COLORTERM : Lorsqu'il est réglé sur truecolor ou 24bit , Turbo Vision supposera que l'émulateur terminal prend en charge la couleur 24 bits. Il est défini automatiquement par des émulateurs terminaux qui le soutiennent.
ESCDELAY : le nombre de millisecondes à attendre après avoir reçu une touche ESC appuyer, par défaut 10 . Si une autre touche est enfoncée pendant ce retard, elle sera interprétée comme une combinaison de touches ALT +. L'utilisation d'une valeur plus grande est utile lorsque le terminal ne prend pas en charge la touche ALT.
TVISION_USE_STDIO : Lorsqu'il n'est pas vide, les E / S terminales sont effectuées via stdin / stdout , afin qu'elle puisse être redirigea de la coque. Par défaut, Turbo Vision effectue des E / S terminales via /dev/tty , permettant à l'utilisateur de rediriger stdin , stdout et stderr pour leurs besoins, sans affecter la stabilité de l'application.
Par exemple, les éléments suivants laisseront out.txt vide:
tvdemo | tee out.txt Tandis que le suivant videra toutes les séquences d'échappement et le texte imprimées par l'application dans out.txt :
TVISION_USE_STDIO=1 tvdemo | tee out.txtLes éléments suivants ne sont pas disponibles lors de la compilation avec Borland C ++:
wchar_t . Remarque: Turbo Vision écrit le texte UTF-8 directement sur la console Windows. Si la console est définie en mode héritage et que la police bitmap est utilisée, les caractères Unicode ne seront pas affichés correctement (photo). Pour éviter cela, Turbo Vision détecte cette situation et essaie de changer la police de la console en Consolas ou Lucida Console .
Voici de nouvelles fonctionnalités non disponibles dans la sortie de Borland de Turbo Vision ou dans les ports open source précédents (Sigala, Set):
TInputLine s ne fait plus défiler l'affichage de texte sur la mise au point / l'évacuation, permettant au texte pertinent de rester visible.TFileViewer ( tvdemo ) et TEditor ( tvedit ). TEditor conserve la fin de la ligne sur le fichier Enregistrer, mais tous les fichiers nouvellement créés utilisent CRLF par défaut.TEditor : menu contextuel sur un clic droit.TEditor : Faites glisser un défilement avec le bouton de la souris moyenne.TEditor , TInputLine : Supprimer des mots entiers avec kbAltBack , kbCtrlBack et kbCtrlDel .TEditor : La touche d'accueil bascule entre le début de la ligne et le début du texte en retrait.TEditor : Prise en charge des fichiers supérieurs à 64 kib sur des versions 32 bits ou 64 bits.tvdemo : Applet Viewer Event Utile pour le débogage des événements.tvdemo : option pour modifier le modèle d'arrière-plan. Les écritures d'écran sont tamponnées et sont généralement envoyées au terminal une fois pour chaque itération de la boucle d'événement active (voir également TVISION_MAX_FPS ). Si vous devez mettre à jour l'écran lors d'une boucle occupée, vous pouvez utiliser TScreen::flushScreen() .
TDrawBuffer n'est plus un tableau de longueur fixe et ses méthodes empêchent les accès de réseau passé. Par conséquent, le vieux code contenant des comparaisons avec sizeof(TDrawBuffer)/sizeof(ushort) n'est plus valide; Ces chèques doivent être supprimés.
TApplication fournit désormais dosShell() , cascade() et tile() , et gère cmDosShell , cmCascade et cmTile par défaut. Ces fonctions peuvent être personnalisées en remplaçant getTileRect() et writeShellMsg() . C'est le même comportement que dans la version Pascal.
Support des roues de souris: Nouvel événement de souris evMouseWheel . La direction de la roue est spécifiée dans le nouveau champ event.mouse.wheel , dont les valeurs possibles sont mwUp , mwDown , mwLeft ou mwRight .
Prise en charge du bouton de la souris moyenne: nouveau drapeau de bouton de souris mbMiddleButton .
Le champ buttons dans les événements evMouseUp n'est plus vide. Il indique maintenant quel bouton a été libéré.
Support triple clic: nouveau drapeau de l'événement de souris meTripleClick .
Les méthodes TRect move , grow , intersect et Union désormais désormais TRect& au lieu d'être void afin qu'ils puissent être enchaînés.
TOutlineViewer permet désormais au nœud racine d'avoir des frères et sœurs.
Nouvelle fonction ushort popupMenu(TPoint where, TMenuItem &aMenu, TGroup *receiver=0) qui apparaît un TMenuPopup sur le bureau. Voir source/tvision/popupmnu.cpp .
Nouvelle méthode virtuelle TMenuItem& TEditor::initContextMenu(TPoint p) qui détermine les entrées du menu contextuel-cliquez avec le bouton droit dans TEditor .
fexpand peut désormais prendre un deuxième paramètre relativeTo .
Nouvelle classe TStringView , inspirée par std::string_view .
TStringView à la place. TStringView est compatible avec std::string_view , std::string et const char * (même nullptr ). Nouvelle classe TSpan<T> , inspirée par std::span .
Nouvelles classes TDrawSurface et TSurfaceView , voir <tvision/surface.h> .
Les sous-systèmes d'intégration du système ( THardwareInfo , TScreen , TEventQueue ...) sont désormais initialisés lors de la construction d'une TApplication pour la première fois, plutôt qu'avant main . Ils sont toujours détruits à la sortie de main .
Nouvelle méthode TVMemMgr::reallocateDiscardable() qui peut être utilisée le long de allocateDiscardable et freeDiscardable .
Nouvelle méthode TView::textEvent() qui permet de recevoir du texte de manière efficace, voir l'interaction du presse-papiers.
Nouvelle classe TClipboard , voir l'interaction du presse-papiers.
Prise en charge Unicode, voir Unicode.
Véritable support des couleurs, voir les couleurs étendues.
Nouvelle méthode static void TEventQueue::waitForEvents(int timeoutMs) qui peut bloquer pour les millisecondes timeoutMs en attente d'événements d'entrée. Un timeoutMs négatif peut être utilisé pour attendre indéfiniment. S'il bloque, il a l'effet secondaire des mises à jour d'écran de rinçage (via TScreen::flushScreen() ). Il est invoqué par TProgram::getEvent() avec static int TProgram::eventTimeoutMs (par défaut 20 ) comme argument afin que la boucle d'événement ne se transforme pas en boucle occupée consommant 100% de CPU.
Nouvelle méthode static void TEventQueue::wakeUp() qui fait que la boucle de l'événement reprend l'exécution si elle est bloquée sur TEventQueue::waitForEvents() . Cette méthode est en forme de thread, car son objectif est de débloquer la boucle d'événement à partir des threads secondaires.
Nouvelle méthode void TView::getEvent(TEvent &, int timeoutMs) qui permet d'attendre un événement avec un délai d'expiration fourni par l'utilisateur (au lieu de TProgram::eventTimeoutMs ).
Il est désormais possible de spécifier une largeur de texte maximale ou un nombre maximal de caractères dans TInputLine . Cela se fait via un nouveau paramètre dans le constructeur de TInputLine , ushort limitMode , qui contrôle comment le deuxième paramètre du constructeur, uint limit , doit être traité. Les constantes ilXXXX définissent les valeurs possibles de limitMode :
ilMaxBytes (la valeur par défaut): Le texte peut être à la limit des octets longs, y compris le terminateur nul.ilMaxWidth : Le texte peut être en place pour limit les colonnes larges.ilMaxChars : Le texte peut contenir pour limit les caractères ou les graphiques non combinés. De nouvelles fonctions qui permettent d'obtenir les noms des constantes de Turbo Vision au moment de l'exécution (par exemple 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); Nouvelle classe TKey qui peut être utilisée pour définir de nouvelles combinaisons de clés (par exemple Shift+Alt+Up ) en spécifiant un code de clé et un masque de modificateurs de clés:
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 ();Nouvelles méthodes qui permettent d'utiliser des événements chronométrés:
TTimerId TView::setTimer ( uint timeoutMs, int periodMs = - 1 );
void TView::killTimer (TTimerId id); setTimer démarre une minuterie qui sera la première fois dans les millisecondes timeoutMs , puis chaque periodMs millisecondes.
Si periodMs sont négatives, la minuterie ne tient qu'une seule fois et est nettoyée automatiquement. Sinon, il continuera à synchroniser périodiquement jusqu'à ce que killTimer soit invoqué.
Lorsqu'un temporisateur est sorti, un événement evBroadcast avec la commande cmTimerExpired est émis, et message.infoPtr est défini sur le TTimerId de la minuterie expirée.
Les événements de délai d'expiration sont générés dans TProgram::idle() . Par conséquent, ils ne sont traités que lorsqu'aucun clavier ou événement de souris n'est disponible.
Vous trouverez ici quelques captures d'écran. N'hésitez pas à ajouter le vôtre!
Si vous connaissez des applications Turbo Vision dont le code source n'a pas été perdu et cela pourrait en bénéficier, faites-le moi savoir.
Si votre application est basée sur ce projet et que vous souhaitez qu'il apparaisse dans la liste suivante, faites-le moi savoir.
L'API Turbo Vision a été étendue pour permettre la réception de l'entrée Unicode et l'affichage du texte Unicode. Le codage pris en charge est UTF-8, pour plusieurs raisons:
char * ), il ne nécessite donc pas de modifications intrusives du code existant.Notez que lorsqu'il est construit avec Borland C ++, Turbo Vision ne prend pas en charge Unicode. Cependant, cela n'affecte pas la façon dont les applications Turbo Vision sont écrites, car les extensions de l'API sont conçues pour permettre le code encoding-agtique.
La façon traditionnelle d'obtenir du texte d'un événement de presse de touches est la suivante:
// '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 ;
// ...
}
} Certaines des classes Turbo Vision existantes qui traitent de l'entrée de texte dépendent toujours de cette méthodologie, qui n'a pas changé. Les caractères à un octet, lorsqu'ils sont représentables dans le codepage actuel, continuent d'être disponibles dans ev.keyDown.charScan.charCode .
Unicode Support se compose de deux nouveaux champs dans ev.keyDown (qui est un struct KeyDownEvent ):
char text[4] , qui peut contenir tout ce qui a été lu à partir du terminal: généralement une séquence UTF-8, mais peut-être tout type de données brutes.uchar textLength , qui est le nombre d'octets de données disponibles en text , de 0 à 4. Notez que la chaîne text n'est pas terminée par nul. Vous pouvez obtenir un TStringView d'un KeyDownEvent avec la méthode getText() .
Ainsi, un personnage Unicode peut être récupéré de TEvent de la manière suivante:
switch (ev.keyDown.keyCode) {
// ...
default : {
std::string_view sv = ev. keyDown . getText ();
processText (sv);
}
} Voyons-le sous un autre point de vue. Si l'utilisateur type ñ , un TEvent est généré avec la structure keyDown suivante:
KeyDownEvent {
union {
. keyCode = 0xA4 ,
. charScan = CharScanType {
. charCode = 164 ( ' ñ ' ), // In CP437
. scanCode = 0
}
},
. controlKeyState = 0x200 (kbInsState),
. text = { ' xC3 ' , ' xB1 ' }, // In UTF-8
. textLength = 2
} Cependant, s'ils tapent € ce qui suit se produira:
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 un raccourci de touche est appuyé à la place, text est vide:
KeyDownEvent {
union {
. keyCode = 0xB (kbCtrlK),
. charScan = CharScanType {
. charCode = 11 ( ' ♂ ' ),
. scanCode = 0
}
},
. controlKeyState = 0x20C (kbCtrlShift | kbInsState),
. text = {},
. textLength = 0
}Ainsi, en bref: les vues conçues sans entrée Unicode continueront de fonctionner exactement comme elles le faisaient auparavant, et les vues qui veulent être consacrées à Unicode n'auront aucun problème à l'être.
La conception originale de Turbo Vision utilise 16 bits pour représenter une cellule d'écran —8 bits pour un caractère et 8 bits pour les attributs de couleur BIOS.
Un nouveau type TScreenCell est défini dans <tvision/scrncell.h> qui est capable de contenir un nombre limité de points de code UTF-8 en plus des attributs étendus (gras, souligné, italique ...). Cependant, vous ne devez pas écrire de texte dans un TScreenCell directement, mais utilisez à la place les fonctions API sensibles à l'Unicode.
Un personnage fourni comme argument à l'une des fonctions API Turbo Vision qui traite de l'affichage du texte est interprétée comme suit:
0x00 à 0xFF sont interprétés comme des caractères dans le codepage actif. Par exemple, 0x7F est affiché comme ⌂ et 0xF0 comme ≡ si vous utilisez CP437. À titre d'exception, 0x00 est toujours affiché comme un espace ordinaire. Ces caractères sont tous une colonne large. Par exemple, la chaîne "╔[xFE]╗" peut être affichée comme ╔[■]╗ . Cela signifie que les caractères de dessin en boîte peuvent être mélangés avec UTF-8 en général, ce qui est utile pour la compatibilité arrière. Si vous comptez sur ce comportement, vous pouvez obtenir des résultats inattendus: par exemple, "xC4xBF" est une séquence UTF-8 valide et est affiché comme Ŀ au lieu de ─┐ .
L'un des problèmes du support Unicode est l'existence de caractères à double largeur et la combinaison des caractères. Cela entre en conflit avec l'hypothèse originale de Turbo Vision selon laquelle l'écran est une grille de cellules occupées par un seul caractère chacune. Néanmoins, ces cas sont traités de la manière suivante:
Les caractères à double largeur peuvent être dessinés n'importe où sur l'écran et rien de mal ne se passe s'ils se chevauchent partiellement avec d'autres personnages.
Les personnages de largeur zéro recouvrent le personnage précédent. Par exemple, la séquence में se compose du caractère à largeur unique म et des caractères de combinaison े et ं . Dans ce cas, trois points de code Unicode sont installés dans la même cellule.
Le ZERO WIDTH JOINER ( U+200D ) est toujours omis, car il complique trop les choses. Par exemple, il peut transformer une chaîne comme "??" (4 colonnes de large) dans "??" (2 colonnes de large). Tous les émulateurs terminaux ne respectent pas le ZWJ, donc, afin de produire des résultats prévisibles, Turbo Vision imprimera les deux "??" et "??" comme ?? .
Aucun problème graphique notable ne se produira tant que votre émulateur terminal respecte les largeurs de caractères mesurées par wcwidth .
Voici un exemple de ces caractères dans l'éditeur de texte turbo: 
La façon habituelle d'écrire à l'écran est d'utiliser TDrawBuffer . Quelques méthodes ont été ajoutées et d'autres ont changé leur sens:
void TDrawBuffer::moveChar ( ushort indent, char c, TColorAttr attr, ushort count);
void TDrawBuffer::putChar ( ushort indent, char c); c est toujours interprété comme un caractère dans le codepage actif.
ushort TDrawBuffer::moveStr ( ushort indent, TStringView str, TColorAttr attr);
ushort TDrawBuffer::moveCStr ( ushort indent, TStringView str, TAttrPair attrs); str est interprété selon les règles exposées précédemment.
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 est interprété selon les règles exposées précédemment, mais:
maxWidth spécifie la quantité maximale de texte qui doit être copiée à partir de str , mesurée en largeur de texte (pas en octets).strOffset Spécifie la position initiale dans str d'où copier, mesurée en largeur de texte (pas en octets). Ceci est utile pour le défilement horizontal. Si strOffset pointe au milieu d'un caractère à double largeur, un espace sera copié au lieu de la moitié droite du caractère à double largeur, car il n'est pas possible de faire une telle chose.Les valeurs de retour sont le nombre de cellules dans le tampon qui étaient réellement remplies de texte (ce qui est la même que la largeur du texte copié).
void TDrawBuffer::moveBuf ( ushort indent, const void *source, TColorAttr attr, ushort count); Le nom de cette fonction est trompeur. Même dans son implémentation d'origine, source est traitée comme une chaîne. Il est donc équivalent à moveStr(indent, TStringView((const char*) source, count), attr) .
Il existe d'autres fonctions utiles consacrées à Unicode:
int cstrlen (TStringView s); Renvoie la longueur affichée de s selon les règles susmentionnées, en éliminant ~ caractères.
int strwidth (TStringView s); // New Renvoie la longueur affichée de s .
Sur Borland C ++, ces méthodes supposent un codage à un octet et tous les caractères étant une colonne de large. Cela permet d'écrire des méthodes d'encodage-agnostique draw() et handleEvent() qui fonctionnent sur les deux plates-formes sans un seul #ifdef .
Les fonctions ci-dessus sont implémentées à l'aide des fonctions de l'espace de noms TText , une autre extension API. Vous devrez les utiliser directement si vous souhaitez remplir manuellement des objets TScreenCell avec du texte. Pour donner un exemple, ci-dessous certaines des fonctions TText . Vous pouvez tous les trouver avec des descriptions complètes dans <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); Pour dessiner des tampons TScreenCell dans une vue, les méthodes suivantes sont 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 C'est aussi simple que possible. Modifions hello.cpp comme suit:
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 )
);
}Voici à quoi il ressemble:

draw() Ce qui suit est un extrait d'une ancienne implémentation de TFileViewer::draw() (partie de l'application tvdemo ), qui ne dessine pas correctement le texte Unicode:
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 ); Tout ce qu'il fait est de déplacer une partie d'une chaîne dans fileLines en b , qui est un TDrawBuffer . delta est un TPoint représentant le décalage de défilement dans la vue texte, et i est l'index de la ligne visible traitée. c est la couleur du texte. Quelques problèmes sont présents:
TDrawBuffer::moveStr(ushort, const char *, TColorAttr) prend une chaîne à terminaison nulle. Afin de passer une sous-chaîne de la ligne actuelle, une copie est fabriquée dans les baies s , au risque d'un dépassement tampon. Le cas où la ligne ne s'intègre pas dans s n'est pas géré, donc dans la plupart des personnages maxLineLenght sera copié. De plus, un personnage multilingue à proximité de la longueur maxLineLength a pu être copié incomplète et être affiché sous forme de déchets.delta.x est la première colonne visible. Avec du texte codé de multipyte, il n'est plus vrai qu'une telle colonne commence en position delta.x dans la chaîne.Vous trouverez ci-dessous une version corrigée du code ci-dessus qui gère correctement Unicode:
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 surcharge de moveStr utilisée ici est TDrawBuffer::moveStr(ushort indent, TStringView str, TColorAttr attr, ushort width, ushort begin) . Cette fonction fournit non seulement le support Unicode, mais nous aide également à écrire du code plus propre et à surmonter certaines des limitations précédemment présentes:
maxLineLength .moveStr s'occupe de l'impression de la chaîne à partir de la colonne delta.x . Nous n'avons même pas besoin de nous soucier du nombre d'octets correspondent aux colonnes 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. Par exemple:
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. Par exemple:
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. Par exemple:
// 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 . Par exemple: 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++:
| Concept | Layout in Borland C++ | Layout in modern platforms |
|---|---|---|
| Color Attribute | uchar . A BIOS color attribute. | struct TColorAttr . |
| Couleur | 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.