Theoretical Customizations: Deriving from TProgram¶
This document shows examples of custom application classes that derive from TProgram instead of TApplication, demonstrating selective subsystem initialization.
Background¶
In Borland Turbo Vision, TApplication derives from TProgram and adds five subsystems:
1. Memory manager (InitMemory/DoneMemory)
2. Video manager (InitVideo/DoneVideo)
3. Event manager (InitEvents/DoneEvents)
4. System error handler (InitSysError/DoneSysError)
5. History list manager (InitHistory/DoneHistory)
By deriving directly from TProgram, you can selectively enable only the subsystems you need.
Example 1: Minimal Application (No History Lists)¶
Use Case: A simple utility that doesn't need input field history.
// C++ (Borland Turbo Vision style)
#include <tv.h>
class TMinimalApp : public TProgram
{
public:
TMinimalApp();
virtual ~TMinimalApp();
static TStatusLine *initStatusLine(TRect r);
static TMenuBar *initMenuBar(TRect r);
static TDeskTop *initDeskTop(TRect r);
};
TMinimalApp::TMinimalApp() :
TProgInit(&TMinimalApp::initStatusLine,
&TMinimalApp::initMenuBar,
&TMinimalApp::initDeskTop)
{
// Initialize only the subsystems we need
InitMemory(); // Always need memory manager for safety pool
InitVideo(); // Need video for screen management
InitEvents(); // Need events for keyboard/mouse
InitSysError(); // Need error handling
// SKIP InitHistory() - we don't need input field history!
// Now call TProgram constructor behavior
// (This is already done by TProgram's constructor)
}
TMinimalApp::~TMinimalApp()
{
// Clean up subsystems in reverse order
DoneSysError();
DoneEvents();
DoneVideo();
DoneMemory();
// NO DoneHistory() call - we never initialized it
}
TStatusLine *TMinimalApp::initStatusLine(TRect r)
{
r.a.y = r.b.y - 1;
return new TStatusLine(r,
*new TStatusDef(0, 0xFFFF) +
*new TStatusItem("~F10~ Menu", kbF10, cmMenu) +
*new TStatusItem("~Alt-X~ Exit", kbAltX, cmQuit)
);
}
TMenuBar *TMinimalApp::initMenuBar(TRect r)
{
r.b.y = r.a.y + 1;
return new TMenuBar(r,
*new TSubMenu("~F~ile", kbAltF) +
*new TMenuItem("E~x~it", cmQuit, kbAltX, hcNoContext, "Alt-X")
);
}
TDeskTop *TMinimalApp::initDeskTop(TRect r)
{
r.a.y++;
r.b.y--;
return new TDeskTop(r);
}
// Usage
int main()
{
TMinimalApp app;
app.run();
return 0;
}
Memory Savings: Without history lists, saves ~4KB of heap space (default HistorySize).
Example 2: Read-Only Display Application¶
Use Case: A log viewer or status monitor that only displays information (no user input).
#include <tv.h>
class TDisplayApp : public TProgram
{
public:
TDisplayApp();
virtual ~TDisplayApp();
// Override getEvent to only handle quit keys
virtual void getEvent(TEvent& event);
static TStatusLine *initStatusLine(TRect r);
static TMenuBar *initMenuBar(TRect r) { return 0; } // No menu
static TDeskTop *initDeskTop(TRect r);
};
TDisplayApp::TDisplayApp() :
TProgInit(&TDisplayApp::initStatusLine,
&TDisplayApp::initMenuBar,
&TDisplayApp::initDeskTop)
{
InitMemory(); // Need memory manager
InitVideo(); // Need video for display
// SKIP InitEvents() - minimal event handling
// SKIP InitSysError() - we'll handle errors ourselves
// SKIP InitHistory() - no user input
}
TDisplayApp::~TDisplayApp()
{
DoneVideo();
DoneMemory();
}
void TDisplayApp::getEvent(TEvent& event)
{
// Simplified event handling - only check for quit keys
TProgram::getEvent(event);
if (event.what == evKeyDown)
{
if (event.keyDown.keyCode == kbEsc ||
event.keyDown.keyCode == kbAltX)
{
event.what = evCommand;
event.message.command = cmQuit;
}
}
}
TStatusLine *TDisplayApp::initStatusLine(TRect r)
{
r.a.y = r.b.y - 1;
return new TStatusLine(r,
*new TStatusDef(0, 0xFFFF) +
*new TStatusItem("~Esc~ Exit", kbEsc, cmQuit)
);
}
TDeskTop *TDisplayApp::initDeskTop(TRect r)
{
r.a.y = 1; // No menu bar
r.b.y--;
return new TDeskTop(r);
}
// Usage: Display-only application
int main()
{
TDisplayApp app;
// Create a read-only window
TRect r(10, 5, 70, 20);
TWindow *w = new TWindow(r, "System Status", wnNoNumber);
// Add static text (no input controls)
TStaticText *st = new TStaticText(r, "Monitoring system...");
w->insert(st);
app.deskTop->insert(w);
app.run();
return 0;
}
Example 3: Embedded System Application¶
Use Case: Running on embedded hardware with limited memory and no persistent storage.
#include <tv.h>
class TEmbeddedApp : public TProgram
{
private:
bool lowMemoryMode;
public:
TEmbeddedApp(unsigned availableMemory);
virtual ~TEmbeddedApp();
virtual void idle();
virtual void outOfMemory();
static TStatusLine *initStatusLine(TRect r);
static TMenuBar *initMenuBar(TRect r);
static TDeskTop *initDeskTop(TRect r);
};
TEmbeddedApp::TEmbeddedApp(unsigned availableMemory) :
TProgInit(&TEmbeddedApp::initStatusLine,
&TEmbeddedApp::initMenuBar,
&TEmbeddedApp::initDeskTop)
{
// Determine which subsystems we can afford
lowMemoryMode = (availableMemory < 64 * 1024); // Less than 64KB
if (lowMemoryMode)
{
// Minimal subsystems
InitMemory(); // Essential
InitVideo(); // Essential for display
InitEvents(); // Essential for input
// SKIP InitSysError() - handle inline
// SKIP InitHistory() - no room for history
}
else
{
// Full subsystems
InitMemory();
InitVideo();
InitEvents();
InitSysError();
InitHistory();
}
}
TEmbeddedApp::~TEmbeddedApp()
{
if (!lowMemoryMode)
{
DoneHistory();
DoneSysError();
}
DoneEvents();
DoneVideo();
DoneMemory();
}
void TEmbeddedApp::idle()
{
TProgram::idle();
// In embedded systems, yield CPU to other tasks
// or enter low-power mode during idle
#ifdef EMBEDDED_RTOS
taskYield();
#endif
}
void TEmbeddedApp::outOfMemory()
{
// Embedded-specific out-of-memory handling
messageBox("Critical: Out of memory! Restarting...",
mfError | mfOKButton);
// In embedded systems, might trigger a watchdog reset
#ifdef EMBEDDED_SYSTEM
systemReset();
#else
exit(1);
#endif
}
TStatusLine *TEmbeddedApp::initStatusLine(TRect r)
{
r.a.y = r.b.y - 1;
return new TStatusLine(r,
*new TStatusDef(0, 0xFFFF) +
*new TStatusItem("", kbF10, cmMenu) +
*new TStatusItem("Quit", kbAltX, cmQuit)
);
}
TMenuBar *TEmbeddedApp::initMenuBar(TRect r)
{
r.b.y = r.a.y + 1;
return new TMenuBar(r,
*new TSubMenu("System", kbAltS) +
*new TMenuItem("Status", cmStatus, kbF1) +
*new TMenuItem("Reset", cmReset, kbCtrlR) +
*new TMenuLine() +
*new TMenuItem("Exit", cmQuit, kbAltX)
);
}
TDeskTop *TEmbeddedApp::initDeskTop(TRect r)
{
r.a.y++;
r.b.y--;
return new TDeskTop(r);
}
Example 4: Testing Framework Application¶
Use Case: Automated testing harness that doesn't need full UI subsystems.
#include <tv.h>
class TTestApp : public TProgram
{
private:
bool headlessMode;
int testsPassed;
int testsFailed;
public:
TTestApp(bool headless = false);
virtual ~TTestApp();
void runTest(const char *name, bool (*testFunc)());
void printResults();
static TStatusLine *initStatusLine(TRect r);
static TMenuBar *initMenuBar(TRect r);
static TDeskTop *initDeskTop(TRect r);
};
TTestApp::TTestApp(bool headless) :
TProgInit(&TTestApp::initStatusLine,
&TTestApp::initMenuBar,
&TTestApp::initDeskTop),
headlessMode(headless),
testsPassed(0),
testsFailed(0)
{
InitMemory(); // Need memory management
if (!headlessMode)
{
InitVideo(); // Only init video if not headless
}
InitEvents(); // Need event system for test coordination
// SKIP InitSysError() - tests handle their own errors
// SKIP InitHistory() - not needed for testing
}
TTestApp::~TTestApp()
{
DoneEvents();
if (!headlessMode)
{
DoneVideo();
}
DoneMemory();
}
void TTestApp::runTest(const char *name, bool (*testFunc)())
{
if (!headlessMode)
{
// Visual feedback
TRect r(10, 5, 70, 10);
TWindow *w = new TWindow(r, name, wnNoNumber);
deskTop->insert(w);
}
bool result = testFunc();
if (result)
{
testsPassed++;
}
else
{
testsFailed++;
}
if (!headlessMode)
{
// Remove test window
deskTop->current->setState(sfVisible, False);
}
}
void TTestApp::printResults()
{
printf("Tests Passed: %d\n", testsPassed);
printf("Tests Failed: %d\n", testsFailed);
}
TStatusLine *TTestApp::initStatusLine(TRect r)
{
r.a.y = r.b.y - 1;
return new TStatusLine(r,
*new TStatusDef(0, 0xFFFF) +
*new TStatusItem("Running tests...", kbNoKey, cmNone)
);
}
TMenuBar *TTestApp::initMenuBar(TRect r)
{
return 0; // No menu in test mode
}
TDeskTop *TTestApp::initDeskTop(TRect r)
{
r.a.y = 1;
r.b.y--;
return new TDeskTop(r);
}
// Usage
bool testAddition() { return (2 + 2 == 4); }
bool testSubtraction() { return (5 - 3 == 2); }
int main(int argc, char *argv[])
{
bool headless = (argc > 1 && strcmp(argv[1], "--headless") == 0);
TTestApp app(headless);
app.runTest("Addition Test", testAddition);
app.runTest("Subtraction Test", testSubtraction);
app.printResults();
return 0;
}
Summary¶
These examples demonstrate why TProgram exists as an intermediate class:
| Example | Skipped Subsystems | Benefit |
|---|---|---|
| Minimal App | History | ~4KB memory savings |
| Display-Only | Events, SysError, History | Simpler code, faster startup |
| Embedded | SysError, History (conditional) | Adapts to memory constraints |
| Test Framework | Video (conditional), SysError, History | Headless testing support |
In Practice: These use cases are rare. Most developers just used TApplication because:
1. The memory overhead is negligible on modern systems
2. You almost always need all subsystems
3. Selective initialization is error-prone
4. The flexibility isn't worth the complexity
This is why the Rust port correctly merges TProgram and TApplication into a single Application struct - the theoretical flexibility of TProgram provides no practical value in modern development.