Sign in Join

Live chat for Free Pascal, Lazarus, Delphi and every Pascal programmer.

Catalog · How the catalog works

Random project

tvDMX

Port of Randy Beck's tvDMX (easy to use object-oriented data entry system for Borland Pascal's Turbo Vision) to FreePascal and FreeVision

View on GitHub

Anyone may press Interesting, signed in or not. It helps others find this project.

Member rating

No ratings yet.

Sign in to rate this project.

README

tvDMX-FP

A modern FreePascal / FreeVision reimplementation of tvDMX, Randolph Beck's Turbo Vision data-entry toolkit (© 1992–98). It reproduces tvDMX's behaviour and feel on a current toolchain (FPC 3.2.2, win64), using the original DOS sources as the specification rather than transliterating them.

Published with Randy's blessing. The original code can be downloaded from https://www.randybeck.com/tvdmx.shtml.

tvDMX-FP is a small library of FreeVision views built around one idea — a picture template that describes a record as a sequence of typed fields — plus a set of ready-to-run sample programs that show it off.


Contents


Why a reimplementation, not a port

The original tvDMX targets 16-bit DOS: it stores raw pointers inside picture strings (Move(ptr, S[2], 4) — 4-byte pointers), reads record data through Seg:Ofs memory casts, and relies on Turbo Pascal's data-type layouts. On win64 (8-byte pointers, flat memory) a faithful transliteration would mean invasive, corruption-prone surgery. tvDMX-FP keeps the design — a picture template describing typed fields laid out in a record buffer — and implements it cleanly:

  • Field data lives in an explicit, safe byte buffer (TDmxData).
  • Pointers live in proper records, never inside strings.
  • The hex viewer renders the byte buffer directly (no Seg:Ofs).

Obsolete DOS/16-bit machinery — int 21h/int 17h printing, packed-date pointer tricks, memory-paragraph guards — is deliberately dropped. A few original accessories are intentionally not ported: the "experimental" SideShoe frozen-column view, the Paradox table/password dialogs, the DOS-screen viewer, and direct printer I/O.

Quick start

bash
# build + run a sample (exe lands in bin/, units are searched under src/)
sh build.sh samples/accounts.pas && ./bin/accounts.exe

# run the console regression tests
sh build.sh tests/testtempl.pas && ./bin/testtempl.exe
sh build.sh tests/testedit.pas  && ./bin/testedit.exe

build.sh wraps FPC:

bash
FPC -O2 -Fusrc -Fusamples/timedb -FEbin -FUbin/units <path/to/file.pas>

so any program builds with sh build.sh <path/to/file.pas>. The compiler is expected at c:\lazarus\fpc\3.2.2\bin\x86_64-win64\fpc.exe (the default installation path)

Repository layout

Path What
src/ the tvDMX-FP library units
tests/ console regression tests (testtempl, testedit, …)
samples/ interactive demo programs (accounts, hexedit, …)
samples/timedb/ the multi-file timesheet demo (timesheet + tsmodel)
reference/ Randolph Beck's original tvDMX 2.6 sources — the spec

Core concept: the picture template

Everything in the library is driven by a picture string parsed into a TDmxTemplate. A run of a field-code letter is one field; the letters give the type and width, and surrounding punctuation is the display picture. The template computes a record layout (each field's byte offset and size) and knows how to format a field for display and parse edited text back into the buffer.

pascal
uses DmxTempl, DmxData;
var
  Tmpl: TDmxTemplate;
  Data: TDmxData;
  rec: Integer;
begin
  Tmpl := TDmxTemplate.Create(' ssssssssssssssssss|($rr,rrr.zz)| WWWWW ');
  Data := TDmxData.Create(Tmpl.RecordSize);   { record size from the template }

  rec := Data.Append;                          { a blank record }
  Tmpl.StoreText(0, Data.Rec(rec)^, Data.RecSize, 'Ada Lovelace');
  Tmpl.StoreText(1, Data.Rec(rec)^, Data.RecSize, '1234.50');
  Tmpl.StoreText(2, Data.Rec(rec)^, Data.RecSize, '42');

  WriteLn(Tmpl.Render(Data.Rec(rec)^, Data.RecSize));
  { ->   Ada Lovelace      │ $ 1,234.50 │    42  }
end;

The record buffer is a plain array of bytes; the same buffer feeds the on-screen views, the reports, and CSV persistence.

Field codes

Code(s) Type Notes
s S # string S forces upper case; # accepts digits only
c C 0 char array C forces upper case; 0 accepts digits only
X boolean shown as Y/N (glyphs configurable)
B J byte / shortint 1 byte
W I word / integer 2 bytes
L longint 4 bytes
R r real (double) uppercase = no negatives; z keeps trailing zeroes; Z zero-fills
H hex unsigned, shown as hex digits (HHHH:HHHH)
D duration minutes stored as longint, shown 2h30m, typed 2h 30m
K / k checkbox/radio one bit in a shared bank word (K=checkbox, k=radio)
` ` delimiter
~text~ literal caption drawn verbatim (used as a field label)
$ ( ) , . numeric decor currency, parentheses-for-negative, grouping, decimal point

A numeric field may also be typed as an arithmetic expression ((3+4)*2, 1200/12, 2^10, |x|, pi, %), evaluated on commit.


Library reference

DmxTempl — the picture-template engine

TDmxTemplate parses a picture and provides the record layout, formatting and editing primitives that every other unit builds on.

Member Purpose
Create(const Template: AnsiString) parse a picture; sets RecordSize, DisplayWidth/Height
FieldCount, Field(i): TDmxField inspect parsed fields (kind, width, offset, …)
Render(const Rec; RecLen): string the whole record as a display line
FormatField / EditText format one field for display / for editing
StoreText(fi; var Rec; RecLen; s) parse text back into the buffer
AcceptsChar / AcceptsEdit per-type keystroke filtering (+ live range check)
ColumnHeader([captions]) a header line aligned to the template's columns + bars
SetRange(fi, min, max) live numeric range (reject past max, clamp on store)
SetDateFields / SetTimeFields validated date/time sub-fields (1..12, 0..23, …)
SetShowZeroes(fi, show) render a zero as blank instead of 0
SetReadOnly(fi, ro) mark a field read-only (skipped in editing, still written)
SetEnum(fi, [choices]) turn a byte/word field into a pick-list
Zeroize / ZeroizeRecord clear a field / the whole record
GetNumeric(fi; Rec; RecLen): Double read any numeric or duration field as a number

Formatting is locale-aware through module globals: DmxDecimalChar, DmxThousandChar (the equivalent of tvDMX's showDecPt / showComma), plus the DmxBoolTrue/False, DmxCheckChar, DmxRadioChar glyphs and DmxDurationDayMinutes (minutes per d, default 480). FormatDuration and ParseDuration are exposed for direct use.

DmxData — the record store

TDmxData is a growable, fixed-record-size byte buffer. Records are laid out by a template, so the on-disk form is just the records back to back.

Create(ARecSize) · Count · Rec(i): Pointer · Append · InsertAt(i) · DeleteAt(i) · Clear · Base · TotalBytes · LoadFromFile · SaveToFile.

DmxView — TDmxBrowse, a scrolling record grid

One record per row, the template supplying the columns, with a highlighted current cell and vertical/horizontal scroll. Cells edit in place (type, Enter, per-type filtering); Ins/cmDmxAddRec add a record, cmDmxDelRec deletes, Ctrl-T/Ctrl-Y zeroize a field/record, Ins (while editing) toggles overtype. Assign OnRecalc to recompute read-only columns after each edit.

pascal
var b: PDmxBrowse;
b := New(PDmxBrowse, Init(bounds, Tmpl, Data, ' Header line'));
b^.OnRecalc := @RecalcProc;   { optional derived-field hook }

DmxForm — TDmxForm, a single-record entry form

Lays one record out in two dimensions from a multi-line template (labels + input masks). The current field is always in edit mode; Tab/Enter/arrows navigate, Ctrl-F pops up a roomy 255-char editor for the current field, Space/letters cycle enum & cluster fields. ReleaseAtEnds lets it sit inside a dialog; OnRecalc recomputes derived fields.

DmxForms — EntryBox

One call builds a centred modal dialog around a TDmxForm on a working copy of a record, with OK/Cancel commit / rollback:

pascal
if EntryBox('Edit item', Tmpl, MyRec, SizeOf(MyRec)) = cmOK then
  { MyRec now holds the edited values };

DmxGroupGrid — TDmxGroupGrid, a grouped matrix view

Groups the records of a TDmxData into an outline tree by N key columns, rolls up M value columns into the group rows, and draws a frozen label column (left), horizontally-scrollable value columns, a frozen per-row Total (right), and a pinned grand-totals footer. Group rows collapse/expand; leaf value cells edit in place through the template's field parse, with roll-ups recomputed live.

pascal
var g: PDmxGroupGrid;
g := New(PDmxGroupGrid, Init(bounds, Tmpl, Data,
       [0, 1, 2],           { key columns: group by field 0, 1; leaf = field 2 }
       [3, 4, 5, 6, 7]));   { value columns to roll up }
g^.LabelHead := 'Group';
g^.OnLabel := @LabelProc;   { (level, keyValue, rec) -> display name }
g^.Rebuild;

Key methods: Rebuild, Recompute, ExpandAll, CollapseAll, ToggleCollapse.

DmxHex — TDmxHexView, a hex/ASCII viewer & editor

A hex dump over a raw byte buffer. With ReadOnly := True it is a scroll-only memory window; with ReadOnly := False it is an overtype editor — Tab switches the hex/ASCII panes, hex digits and printable chars patch bytes in place, a live offset readout shows in the header, and Ctrl-G/cmHexGoto jumps to an offset. cmHexToggleEdit flips view/edit. The buffer is fixed length — editing overwrites, never inserts.

DmxWin — ready-made windows

Wrap a view + frame in one constructor:

  • TDmxBrowseWindow.Init(bounds, title, number, Tmpl, Data, header) — hosts a TDmxBrowse; the browse is exposed as .Browse.
  • TDmxHexWindow.Init(bounds, title, number, Data, len, readOnly) — hosts a TDmxHexView, exposed as .Hex.

DmxRep — text & CSV reports

TDmxReport writes a TDmxData as a tabular text report (label header, optional line numbers, pagination, page-start/end callbacks) or as CSV, using the template as the column layout. One-call helpers and a CSV round-trip:

  • ReportToFile(Tmpl, Data, name, lineNumbers) — tabular text.
  • ReportToCsv(Tmpl, Data, name, sep) — CSV export.
  • CsvFromFile(Tmpl, Data, name, hasHeader, sep) — CSV import (the inverse of CsvToFile), one record per line.

DmxCalc — arithmetic-expression evaluator

EvalExpr(s): TCalcResult evaluates + - * / % ^, unary minus, parentheses, |abs| and pi. EvalToText(s, decimals) passes plain numbers/text through and only evaluates real formulas; LooksLikeExpr(s) reports whether a string is one. The numeric fields use it for formula entry.

DmxMCalc — pop-up calculator accessory

OpenCalc inserts (or wakes) a single modeless calculator on the desktop — expression line, memory (M+/MR/MC), √, running result — reusing DmxCalc. Summon it with cmCalcPop.

ConMouse — EnableConsoleMouse

See Mouse on Windows.


Samples

All samples live under samples/ and build with sh build.sh samples/<name>.pas. Common controls: arrows move · type / Enter edits · F6 switches window · Alt+F3 closes a window · Alt+X exits · the mouse works.

Sample Demonstrates
accounts The classic tvDMX Accounts browse: a record grid through one picture (name, SSN mask, money, date, WW:WW time), plus an F2 hex window (F4 toggles edit, Ctrl-G goto). Accounts screenshot
personal TDmxForm — a single-record EDIT RECORD entry form with labels, input masks, enum pick-lists and checkbox/radio clusters. Personals screenshot
workshop The "edit the template and the toolkit does the rest" generic browse — change two strings to describe any record.
wideshop A deliberately very wide record (26 columns) to exercise horizontal scrolling and the ◄/► in-cell markers.
samples The Payroll (browse) and Invoice (form) windows, both driving read-only computed fields through the OnRecalc hook. Hex editor screenshot
testforms EntryBox modal record editor and the DmxMCalc pop-up calculator.
hexedit A standalone file hex editor: F3 opens any file, edit it (Tab panes, overtype, Ctrl-G goto), F2 saves it back. Hex editor screenshot
timedb/timesheet A full consultant timesheet (see below).

The timesheet (samples/timedb/)

TimeDb screenshot

A multi-file application that reimagines a consultant timesheet tvDMX-native: every dataset — clients (Code, Name, Rate/h), projects, activities, the weekly grid, invoices — is a TDmxData record store described by a picture template and persisted as CSV under ./tsdata. tsmodel.pas is the model; timesheet.pas is the UI.

  • Manage → Clients / Projects / Activities (F2/F3/F4) — editable TDmxBrowse grids; Ins adds, F8 deletes, Ctrl+S saves.
  • Week sheet (F5) — a TDmxGroupGrid: a Client → Project → Activity outline with a column per weekday and a frozen Total, group subtotals and a red TOTAL footer. Type durations into a leaf day cell (2h 30m, 45m, 1:30, 2.5); the roll-ups update live. Enter/+/- collapse or expand a group, Ctrl+PgUp/Ctrl+PgDn step weeks, Ctrl+W shows money-per-client for the week.
  • Report → Monthly report (F9) — hours grouped by client → project → activity with totals and money; Ctrl+PgUp/PgDn change month, F7 exports a .txt, F8 generates invoices.
  • Report → Invoices — a browse of generated invoices; Ctrl+V views one and exports it to CSV.

The reusable CSV import the demo needed (DmxRep.CsvFromFile) was promoted into the library; the grouped week view is the library's TDmxGroupGrid.


Mouse on Windows

FreeVision/FPC enable ENABLE_MOUSE_INPUT but never clear the console's QuickEdit mode, which on modern Windows swallows mouse clicks for text selection — so the mouse looks dead. ConMouse.EnableConsoleMouse (call it right after Application.Init) clears QuickEdit and sets the extended flags, and clicks start flowing.


Credit

All design credit to Randolph Beck, author of tvDMX — an homage to a lovely piece of craft.

Reimplemented for FreePascal / FreeVision by Jessie Wadman, with Randy's blessing.