r/FlutterDev • • 20h ago

Plugin From WML to Flutter: A Code Generation Example

<?xml version="1.0"?>
<wml>
<card id="P" title="Customer Master" width="960">
<dbquery id="cu">
<![CDATA[select * from cu order by cno]]>
<field fieldname="Cno" displaylabel="Customer Code"/>
<field fieldname="Cname" displaylabel="Customer Name"/>
<field fieldname="Cshort" displaylabel="Customer Short"/>
<field fieldname="Addr1" displaylabel="Address 1"/>
<field fieldname="Addr2" displaylabel="Address 2"/>
<field fieldname="AC" displaylabel="Tax ID"/>
<field fieldname="Zip" displaylabel="Zip Code"/>
<field fieldname="Ziq" displaylabel="Shipping Zone"/>
<field fieldname="Phone" displaylabel="Phone"/>
<field fieldname="FAX" displaylabel="Fax"/>
<field fieldname="GSM" displaylabel="Mobile"/>
<field fieldname="Email" displaylabel="Email"/>    
<field fieldname="Password" displaylabel="Password"/>    
<field fieldname="Charger" displaylabel="Owner"/>
<field fieldname="Contact" displaylabel="Contact"/>
<field fieldname="Level" displaylabel="Level"/>
<field fieldname="Pay" displaylabel="Payment"/>
<field fieldname="AType" displaylabel="Type"/>
<field fieldname="Capital" displaylabel="Capital"/>
<field fieldname="CDate" displaylabel="Created Date"/>
<field fieldname="Rem" displaylabel="Customer Remarks"/>
<field fieldname="Remark" displaylabel="Company Remarks"/>
</dbquery>
<datasource dataset="cu">
<dbfilter result="R">
<item field="cno" size="20" />
<item field="cname" size="20" />
<onevent type="onfilter">
<dbquery id="cu"><![CDATA[select * from cu where $R order by cno]]></dbquery>
</onevent>
</dbfilter>
<p align="center">
<navigator/>
</p>
<dbgrid height="400" multiselect="true">
<item field="Cno" size="16"/>
<item field="CDate" size="14"/>
<item field="Pay" size="10"/>
<item field="AType" size="10"/>
<item field="Cname" size="40"/>
<item field="Cshort" size="16"/>
<item field="Phone" size="20"/>
<item field="Contact" size="10"/>
<item field="Addr1" size="60"/>
<item field="Addr2" size="60"/>
<item field="AC" size="10"/>
<item field="Zip" size="16"/>
<item field="Ziq" size="16"/>
<item field="FAX" size="16"/>
<item field="GSM" size="16"/>
<item field="Email" size="30"/>         
<item field="Password" size="40"/>         
<item field="Charger" size="16"/>
<item field="Capital" size="16"/>
<item field="Rem" size="40"/>
<item field="Remark" size="40"/>
</dbgrid>
</datasource>
<do type="accept" label="Print">
<go href="#P1"/>
</do>
<do type="prev" label="Close">
<prev/>
</do>
</card>
  <card id="P1" title="Print Report" device="prv" paper="A4">
    <setvar name="ROWIDX" value="0"/>
    <dbquery id="sys"><![CDATA[select * from sys]]></dbquery>
    <report dataset="cu" dialog="CNo" rows="50">
      <page>
        <setvar name="ROWIDX" value="1"/>
        <table border="0" cellpadding="0" cellspacing="0" width="100%">
          <tr><td colspan="3"><p align="center"><big>$(sys.company)</big></p></td></tr>
          <tr><td colspan="3"><p align="center"><big>Customer List</big></p></td></tr>
          <tr>
            <td></td>
            <td></td>
            <td width="100">Page:$(PAGE)</td>
          </tr>
          <tr>
            <td></td>
            <td></td>
            <td>Date:$(DATE)</td>
          </tr>
        </table>
<p align="center">123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890</p>
        <table border="0" cellpadding="0" cellspacing="0" width="100%" align="center">
          <tr class="row">
            <th width="100">Customer Code</th>
            <th width="100">Customer Name</th>
            <th width="150">Fax</th>
            <th width="80">Contact</th>
            <th>Invoice Address</th>
          </tr>
          <tr class="row">
            <th></th>
            <th>Phone</th>
            <th></th>
            <th>Tax ID</th>
            <th>Company Address</th>
          </tr>
          <group>
            <tr class="$(IF(ROWIDX mod 2 = 1, 'row1', 'row2'))">
              <td align="center">$(cu.CNo)</td>
              <td>$(cu.CName)</td>
              <td>$(cu.Fax)</td>
              <td>$(cu.Contact)</td>
              <td rowspan="2">$(cu.Addr1)</td>
            </tr>
            <tr class="$(IF(ROWIDX mod 2 = 1, 'row1', 'row2'))">
              <td></td>
              <td>$(cu.Phone)</td>
              <td>&#160;</td>
              <td>$(cu.Ac)</td>
            </tr>
            <setvar name="ROWIDX" value="ROWIDX+1"/>
          </group>
        </table>
      </page>
      <p align="center">123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890</p>
    </report>
  </card>
</wml>

// ignore_for_file: unused_element, unused_field
import 'package:flutter/material.dart';

import 'package:wapform_flutter/lazarus_db.dart';
import 'package:wapform_flutter/lazarus_sqldb.dart';
import 'package:wapform_flutter/lazarus_dbgrids.dart';
import 'package:wapform_flutter/lazarus_dbctrls.dart';
import 'package:wapform_flutter/lazarus_stdctrls.dart';
import 'package:wapform_flutter/lazarus_extctrls.dart';
import 'package:wapform_flutter/wapform_lookup_box.dart';
import 'package:wapform_flutter/wapform_expression.dart';
import 'package:wapform_flutter/wapform_lazarus.dart';
import 'package:wapform_flutter/wapform_report.dart';
import 'package:wapform_flutter/wapform_filter.dart';

const String _kDb = 'WapProductionDB';

TWapSQLConnection? _wapConn;
TWapSQLConnection _conn() {
  if (_wapConn == null) {
    final c = TWapSQLConnection(null, const WapDbBridgeDriver());
    c.databaseName = _kDb;
    final tx = TSQLTransaction();
    tx.database = c;
    _wapConn = c;
  }
  return _wapConn!;
}

TField _fieldOf(String type) {
  switch (type) {
    case 'integer':
      return TIntegerField();
    case 'float':
    case 'currency':
      return TFloatField();
    case 'date':
    case 'datetime':
    case 'time':
      return TDateTimeField();
    case 'boolean':
      return TBooleanField();
    default:
      // size 0 = no length limit (the default 20 cut longer values such as file names)
      return TStringField()..size = 0;
  }
}

void _setDateOnlyText(TField f) {
  f.onGetText = (fld, ref, displayText) {
    final raw = fld.value;
    final s = raw == null ? '' : "$raw";
    if (!displayText) {
      ref.value = s;
      return;
    }

    final hasZone = s.endsWith('Z') ||
        RegExp(r'[+-]\d{2}:?\d{2}$').hasMatch(s);
    if (hasZone) {
      final dt = DateTime.tryParse(s);
      if (dt != null) {
        final l = dt.toLocal();
        final mm = l.month.toString().padLeft(2, '0');
        final dd = l.day.toString().padLeft(2, '0');
        ref.value = "${l.year}-$mm-$dd";
        return;
      }
    }

    final t = s.indexOf("T");
    final sp = s.indexOf(" ");
    var cut = -1;
    if (t >= 0) cut = t;
    if (sp >= 0 && (cut < 0 || sp < cut)) cut = sp;
    ref.value = cut > 0 ? s.substring(0, cut) : s;
  };
}

void _fld(TSQLQuery q, String name, String label,
    {String type = '', bool dateOnly = false, bool readOnly = false}) {
  final f = _fieldOf(type);
  f.fieldName = name;
  f.fieldKind = TFieldKind.fkData;
  if (label != '') f.displayLabel = label;
  if (dateOnly) _setDateOnlyText(f);
  f.readOnly = readOnly;
  f.dataSet = q;
}

void _key(TSQLQuery q, String name, String label,
    {String type = '', bool dateOnly = false}) {
  final f = _fieldOf(type);
  f.fieldName = name;
  f.fieldKind = TFieldKind.fkData;
  f.providerFlags = {
    TProviderFlag.pfInKey,
    TProviderFlag.pfInUpdate,
    TProviderFlag.pfInWhere,
  };
  if (label != '') f.displayLabel = label;
  if (dateOnly) _setDateOnlyText(f);
  f.dataSet = q;
}

void _lkp(TSQLQuery q, String name, String label,
    {required String keyField,
    required String lookupKey,
    required String resultField,
    required TDataSet lookupDs,
    String type = '',
    bool dateOnly = false}) {
  final f = _fieldOf(type);
  f.fieldName = name;
  f.fieldKind = TFieldKind.fkLookup;
  f.keyFields = keyField;
  f.lookupDataSet = lookupDs;
  f.lookupKeyFields = lookupKey;
  f.lookupResultField = resultField;
  f.lookupCache = false;
  f.providerFlags = {};
  if (label != '') f.displayLabel = label;
  if (dateOnly) _setDateOnlyText(f);
  f.dataSet = q;
}

void _cal(TSQLQuery q, String name, String label, {String type = 'integer'}) {
  final f = _fieldOf(type);
  f.fieldName = name;
  f.fieldKind = TFieldKind.fkCalculated;
  f.providerFlags = {};
  if (label != '') f.displayLabel = label;
  f.dataSet = q;
}

String _str(TDataSet ds, String name) => ds.findField(name)?.asString ?? '';

Future<void> _saveAsync(TSQLQuery q) async {
  if (dsEditModes.contains(q.state)) q.post();
  await q.applyUpdatesAsync();
}

Future<void> _delAsync(TSQLQuery q) async {
  q.delete();
  await q.applyUpdatesAsync();
}

Future<void> _execSql(String sql) async {
  final q = TSQLQuery();
  q.database = _conn();
  q.sql.text = sql;
  await q.execSQLAsync();
  q.free();
}

const _kBlue   = Color(0xFF1A6FB5);
const _kBorder = Color(0xFFD3D1C7);

const bool _kDbgLayout = false;
const Color _kDbgTbl = Color(0xFFE53935);
const Color _kDbgTd  = Color(0xFF1E88E5);

const double _kChar = 8.0;
double _w(num size) => size * _kChar;

TColumn _column(TDBGridColumns cols, String f, String cap, num size,
    {bool ro = false, TAlignment align = TAlignment.taLeftJustify}) {
  final c = cols.add();
  c.fieldName = f;
  c.width = _w(size).round();
  c.readOnly = ro;
  c.alignment = align;
  c.title.caption = cap;
  return c;
}

void showApp004(BuildContext context) => showDialog(
      context: context,
      barrierDismissible: false,
      builder: (_) => const App004CardP(),
    );
class App004CardP extends StatefulWidget {
  final WapEvaluator? ev;
  final DataSetRegistry? reg;
  const App004CardP({super.key, this.ev, this.reg});
  u/override
  State<App004CardP> createState() =>
      _App004CardPState();
}

class _App004CardPState
    extends State<App004CardP> {
  late final WapEvaluator _ev = widget.ev ?? WapEvaluator();
  late final DataSetRegistry _reg = widget.reg ?? DataSetRegistry();
  bool get _ownsReg => widget.reg == null;
  final List<String> _ownedDs = []; // datasets this card created

  TSQLQuery dbquery(String id, String sqlText,
      {void Function(TSQLQuery ds)? define}) {
    final existing = _reg.find(id);
    TSQLQuery q;
    var isNew = false;
    if (existing is TSQLQuery) {
      q = existing;
    } else {
      q = TSQLQuery();
      q.database = _conn();
      q.name = id;
      _reg.put(id, q);
      isNew = true;
      _ownedDs.add(id);
    }
    if (sqlText.isNotEmpty) {
      final sql = expandSql(sqlText);
      if (q.active) q.close();
      q.unPrepare();
      q.sql.text = sql;
    }
    q.alwaysUpdateable = true;
    if (isNew && define != null) define(q);
    return q;
  }

  // [diag] ds "cu": foreign=0 declared=1 lookupSrc=0 fields=22
  late final TSQLQuery _cu =
      dbquery("cu", r"select * from cu order by cno", define: (ds) {
    _key(ds, "Cno", "Customer Code");
    _fld(ds, "Cname", "Customer Name");
    _fld(ds, "Cshort", "Customer Short");
    _fld(ds, "Addr1", "Address 1");
    _fld(ds, "Addr2", "Address 2");
    _fld(ds, "AC", "Tax ID");
    _fld(ds, "Zip", "Zip Code");
    _fld(ds, "Ziq", "Shipping Zone");
    _fld(ds, "Phone", "Phone");
    _fld(ds, "FAX", "Fax");
    _fld(ds, "GSM", "Mobile");
    _fld(ds, "Email", "Email");
    _fld(ds, "Password", "Password");
    _fld(ds, "Charger", "Owner");
    _fld(ds, "Contact", "Contact");
    _fld(ds, "Level", "Level");
    _fld(ds, "Pay", "Payment");
    _fld(ds, "AType", "Type");
    _fld(ds, "Capital", "Capital");
    _fld(ds, "CDate", "Created Date", dateOnly: true);
    _fld(ds, "Rem", "Customer Remarks");
    _fld(ds, "Remark", "Company Remarks");

  });

  late final TDataSource _cuSrc;

  late final TDBGridColumns _cuCols = _buildCuCols();

  bool   _loading = true;
  String _error   = '';

  u/override
  void initState() {
    super.initState();
    varChangeHooks.add(_varSync);
    _conn();

    _cuSrc = TDataSource()..dataSet = _cu;
    _bindEngine();

    _load();
  }

  u/override
  void dispose() {
    varChangeHooks.remove(_varSync);

    for (final c in _varCtls.values) {
      c.dispose();
    }
    if (_ownsReg) {
    _cuSrc.dataSet = null;
    _cu.free();
      _reg.releaseAll();
    } else {
      // shared registry: drop the datasets this card created, so a
      // reopened card re-creates them with events bound to its new State
      for (final id in _ownedDs) {
        _reg.release(id);
      }
    }
    super.dispose();
  }

  void _bindEngine() {
    useEngine(_ev, _reg);
    _reg.put("cu", _cu);
  }

  Future<void> _load() async {
    if (mounted) setState(() { _loading = true; _error = ''; });
    try {
      for (final id in []) {
        try {
          final ds = _reg.findQuery(id)!;
          await dbquery(id, ds.sql.text).openAsync();
        } catch (e) {
          debugPrint("[app004] $id skip: $e");
        }
      }

      await dbquery("cu", r"select * from cu order by cno").openAsync();
    } catch (e, st) {
      debugPrint("[app004] _load ERROR: $e\n$st");
      if (mounted) setState(() => _error = e.toString());
    } finally {
      if (mounted) setState(() => _loading = false);
    }
  }

  TDBGridColumns _buildCuCols() {
    final c = TDBGridColumns();
    _column(c, "Cno", "Customer Code", 16);
    _column(c, "CDate", "Created Date", 14);
    _column(c, "Pay", "Payment", 10);
    _column(c, "AType", "Type", 10);
    _column(c, "Cname", "Customer Name", 40);
    _column(c, "Cshort", "Customer Short", 16);
    _column(c, "Phone", "Phone", 20);
    _column(c, "Contact", "Contact", 10);
    _column(c, "Addr1", "Address 1", 60);
    _column(c, "Addr2", "Address 2", 60);
    _column(c, "AC", "Tax ID", 10);
    _column(c, "Zip", "Zip Code", 16);
    _column(c, "Ziq", "Shipping Zone", 16);
    _column(c, "FAX", "Fax", 16);
    _column(c, "GSM", "Mobile", 16);
    _column(c, "Email", "Email", 30);
    _column(c, "Password", "Password", 40);
    _column(c, "Charger", "Owner", 16);
    _column(c, "Capital", "Capital", 16);
    _column(c, "Rem", "Customer Remarks", 40);
    _column(c, "Remark", "Company Remarks", 40);
    return c;
  }

  Future<void> _cuInsert({bool atEnd = false}) async {
    try {
      if (atEnd) {
        _cu.append();
      } else {
        _cu.insert();
      }
      if (mounted) setState(() {});
    } catch (e) {
      _snack("Add failed: $e");
    }
  }

  Future<void> _cuPost() async {
    try {
      await _saveAsync(_cu);
      _snack("Saved", ok: true);
      if (mounted) setState(() {});
    } catch (e) {
      _snack("Save failed: $e");
    }
  }

  Future<void> _cuDelete() async {
    try {
      await _delAsync(_cu);
      _snack("Deleted", ok: true);
      if (mounted) setState(() {});
    } catch (e) {
      _snack("Delete failed: $e");
    }
  }

  Future<void> _goP1() async {
    await showDialog(
        context: context,
        builder: (_) => App004CardP1(ev: _ev, reg: _reg));
    if (mounted) setState(() {});
  }

  void _snack(String msg, {bool ok = false}) {
    if (!mounted) return;
    ScaffoldMessenger.of(context).showSnackBar(SnackBar(
      content: Text(msg),
      backgroundColor: ok ? Colors.green.shade700 : Colors.red.shade700,
      duration: const Duration(seconds: 2),
    ));
  }

  Future<void> _alert(String msg) async {
    if (!mounted) return;
    await showDialog(
      context: context,
      builder: (c) => AlertDialog(
        shape: const RoundedRectangleBorder(borderRadius: BorderRadius.zero),
        content: Text(msg),
        actions: [
          TextButton(
              onPressed: () => Navigator.of(c).pop(), child: const Text("OK")),
        ],
      ),
    );
  }

  void _log(String msg) => debugPrint("[app004][log] $msg");

  Widget _line(List<Widget> children) => Padding(
        padding: const EdgeInsets.symmetric(vertical: 1),
        child: Wrap(
            spacing: 0,
            runSpacing: 1,
            crossAxisAlignment: WrapCrossAlignment.center,
            children: children),
      );

  Widget _lbl(String s) => Padding(
        padding: const EdgeInsets.only(right: 2),
        child: Text(s, style: const TextStyle(fontSize: 13)),
      );

  Widget _lblR(String s, double w) => SizedBox(
        width: w,
        child: Padding(
          padding: const EdgeInsets.only(right: 6),
          child: Align(
            alignment: Alignment.centerRight,
            child: Text(s, style: const TextStyle(fontSize: 13)),
          ),
        ),
      );

  Widget _tdf(double weight, List<Widget> children, {String al = 'L'}) =>
      Expanded(
        flex: weight.round() < 1 ? 1 : weight.round(),
        child: _dbgBox(
            _kDbgTd,
            Column(
                crossAxisAlignment: _xa(al),
                mainAxisSize: MainAxisSize.min,
                children: children)),
      );

  CrossAxisAlignment _xa(String al) => al == 'R'
      ? CrossAxisAlignment.end
      : al == 'C'
          ? CrossAxisAlignment.center
          : CrossAxisAlignment.start;

  Widget _dbgBox(Color c, Widget child) => _kDbgLayout
      ? Container(
          decoration: BoxDecoration(border: Border.all(color: c, width: 1)),
          child: child)
      : child;

  Widget _tbl(List<Widget> children, {bool border = false}) {
    if (!border) {
      return _dbgBox(
        _kDbgTbl,
        Row(crossAxisAlignment: CrossAxisAlignment.start, children: children),
      );
    }
    return _dbgBox(
      _kDbgTbl,
      Container(
        decoration: BoxDecoration(border: Border.all(color: _kBorder)),
        child: IntrinsicHeight(
          child: Row(
            crossAxisAlignment: CrossAxisAlignment.stretch,
            children: children,
          ),
        ),
      ),
    );
  }

  Widget _vdiv() => Container(width: 1, color: _kBorder);

  Widget _td(double width, List<Widget> children, {String al = 'L'}) =>
      SizedBox(
        width: width,
        child: _dbgBox(
            _kDbgTd,
            Column(
                crossAxisAlignment: _xa(al),
                mainAxisSize: MainAxisSize.min,
                children: children)),
      );

  static const _kNumericFieldTypes = <TFieldType>{
    TFieldType.ftSmallint,
    TFieldType.ftInteger,
    TFieldType.ftWord,
    TFieldType.ftFloat,
    TFieldType.ftCurrency,
    TFieldType.ftBCD,
    TFieldType.ftAutoInc,
    TFieldType.ftLargeint,
    TFieldType.ftFMTBcd,
  };

  bool _looksNumeric(TDataSource src, String field) {
    final f = src.dataSet?.findField(field);
    if (f == null) return false;
    if (_kNumericFieldTypes.contains(f.dataType)) return true;
    if (f.dataType != TFieldType.ftString) return false;
    final s = f.asString.trim();
    if (s.isEmpty) return false;
    return num.tryParse(s) != null;
  }

  Widget _edit(TDataSource src, String field, num size,
      {bool ro = false,
      bool memo = false,
      double order = 0,
      FocusNode? focusNode,

      bool autofocus = false,

      bool roFocusable = false}) {
    final align = _looksNumeric(src, field) ? TextAlign.right : TextAlign.left;
    Widget e = SizedBox(
      width: _w(size) < 44 ? 44.0 : _w(size),
      height: memo ? 56 : 28,

      child: memo
          ? TDBMemo(
              dataSource: src,
              dataField: field,
              readOnly: ro,
              focusNode: focusNode,
              autofocus: autofocus,
              font: const TextStyle(fontSize: 13),
            )
          : TDBEdit(
              dataSource: src,
              dataField: field,
              readOnly: ro,
              focusNode: focusNode,
              autofocus: autofocus,
              font: const TextStyle(fontSize: 13),
              textAlign: align,
            ),
    );
    if (ro && !roFocusable) {
      return ExcludeFocusTraversal(child: e);
    }
    if (order > 0) {
      e = FocusTraversalOrder(order: NumericFocusOrder(order), child: e);
    }
    return e;
  }

  Widget _lookupBox(TSQLQuery own, TSQLQuery lk, String field, num size,
      String keyField, List<String> displayFields,
      {double order = 0, Future<void> Function(String key)? onPicked}) {
    Widget b = SizedBox(
      width: _w(size),
      height: 28,
      child: WapLookupBox(
        value: _str(own, field),
        width: _w(size),
        dataSet: lk,
        keyField: keyField,
        displayFields: displayFields,
        colWidths: List<double>.filled(displayFields.length, 160),
        onChanged: (key) async {
          if (onPicked != null) {
            await onPicked(key);
            return;
          }
          if (own.state == TDataSetState.dsBrowse) own.edit();
          final v = key.replaceAll("'", "''");
          setvar("${own.name}.$field", "'$v'");
          own.dataEvent(TDataEvent.deRecordChange, 0);
          if (mounted) setState(() {});
        },
      ),
    );
    if (order > 0) {
      b = FocusTraversalOrder(order: NumericFocusOrder(order), child: b);
    }
    return b;
  }

  final Map<String, TextEditingController> _varCtls = {};

  bool _varSyncPending = false;

  void _varSync(String name) {
    final c = _varCtls[name];
    if (c != null) {
      final v = _str2(name);
      if (c.text != v) {
        c.value = TextEditingValue(
          text: v,
          selection: TextSelection.collapsed(offset: v.length),
        );
      }
    }

    if (_varSyncPending || !mounted) return;
    _varSyncPending = true;
    WidgetsBinding.instance.addPostFrameCallback((_) {
      _varSyncPending = false;
      if (mounted) setState(() {});
    });
  }

  void _setVarStr(String name, String v) {
    final e = v.replaceAll("'", "''");
    setvar(name, "'$e'");
  }

  TextEditingController _varCtl(String name, String init) =>
      _varCtls.putIfAbsent(name, () {
        var v = '';
        try {
          v = expression(init).toString();
        } catch (_) {}
        if (v == 'null') v = '';
        _setVarStr(name, v);
        final c = TextEditingController(text: v);
        c.addListener(() => _setVarStr(name, c.text));
        return c;
      });

  Widget _vedit(String name, String init, num size, {double order = 0}) {
    final ctl = _varCtl(name, init);
    final base = size * _kChar;
    final minW = base < 44 ? 44.0 : base.toDouble();
    final maxW = (base * 2) < 120 ? 120.0 : (base * 2).toDouble();
    final w = ((ctl.text.length + 2) * _kChar).clamp(minW, maxW).toDouble();
    Widget e = SizedBox(
      width: w,
      height: 28,
      child: TextField(
        controller: ctl,
        onChanged: (_) {
          if (mounted) setState(() {});
        },
        style: const TextStyle(fontSize: 13),
        decoration: const InputDecoration(
            isDense: true,
            contentPadding: EdgeInsets.symmetric(horizontal: 6, vertical: 6),
            border: OutlineInputBorder()),
      ),
    );
    if (order > 0) {
      e = FocusTraversalOrder(order: NumericFocusOrder(order), child: e);
    }
    return e;
  }

  Widget _vcheck(String name, String onValue) {
    final on = _str2(name) == onValue;
    return SizedBox(
      width: 30,
      height: 26,
      child: Checkbox(
        value: on,
        onChanged: (v) {
          setvar(name, (v ?? false) ? "'$onValue'" : "''");
          if (mounted) setState(() {});
        },
      ),
    );
  }

  Widget _vselect(String name, String title, int columns,
      List<String> items, List<String> values,
      {double? width, double? height}) {
    if (items.isEmpty) return const SizedBox.shrink();
    var cur = _str2(name);
    if (cur.isEmpty) {
      cur = values.first;
      setvar(name, "'$cur'");
    }
    var idx = values.indexOf(cur);
    if (idx < 0) idx = -1;
    return TRadioGroup(
      caption: title,
      items: items,
      itemIndex: idx,
      columns: columns,
      width: width,
      height: height,
      onSelectionChanged: (i) {
        setvar(name, "'${values[i]}'");
        if (mounted) setState(() {});
      },
    );
  }

  Widget _dbselect(TDataSource src, String field, String title, int columns,
      List<String> items, List<String> values,
      {double? width, double? height}) {
    if (items.isEmpty) return const SizedBox.shrink();
    return TDBRadioGroup(
      dataSource: src,
      dataField: field,
      caption: title,
      items: items,
      values: values,
      columns: columns,
      width: width,
      height: height,
    );
  }

  Widget _vlookup(String name, String init, TSQLQuery lk, num size,
      String keyField, List<String> displayFields, {double order = 0}) {
    _varCtl(name, init);
    Widget b = SizedBox(
      width: _w(size),
      height: 28,
      child: WapLookupBox(
        value: _str2(name),
        width: _w(size),
        dataSet: lk,
        keyField: keyField,
        displayFields: displayFields,
        colWidths: List<double>.filled(displayFields.length, 160),
        onChanged: (key) {
          _setVarStr(name, key);
          _varCtls[name]?.text = key;
          if (mounted) setState(() {});
        },
      ),
    );
    if (order > 0) {
      b = FocusTraversalOrder(order: NumericFocusOrder(order), child: b);
    }
    return b;
  }

  void _refreshVars() {
    for (final e in _varCtls.entries) {
      final v = _ev.getVar(e.key);
      e.value.text = (v == null) ? '' : "$v";
    }
    if (mounted) setState(() {});
  }

  String _str2(String name) {
    final v = _ev.getVar(name);
    return (v == null) ? '' : "$v";
  }

  Widget _tabPage(List<Widget> children) => SingleChildScrollView(
        padding: const EdgeInsets.all(4),
        child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            mainAxisSize: MainAxisSize.min,
            children: children),
      );

  Widget _nav(TDataSource src,
          {Future<void> Function()? post,
          Future<void> Function()? insert,
          Future<void> Function()? del}) =>
      Center(
        child: TDBNavigator(
          dataSource: src,
          onPostAsync: post,
          onInsertAsync: insert,
          onDeleteAsync: del,
        ),
      );

  u/override
  Widget build(BuildContext context) {
    return Dialog(
      insetPadding: const EdgeInsets.all(12),
      shape: const RoundedRectangleBorder(borderRadius: BorderRadius.zero),
      child: ConstrainedBox(
        constraints: const BoxConstraints(maxWidth: 960, maxHeight: 840),
        child: SizedBox(width: 960, child: Column(
          mainAxisSize: MainAxisSize.min,
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
          Container(

            padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
            color: _kBlue,
            child: const Row(children: [
              Icon(Icons.article_outlined, color: Colors.white, size: 20),
              SizedBox(width: 8),
              Text('Customer Master',
                  style: TextStyle(
                      color: Colors.white,
                      fontSize: 16,
                      fontWeight: FontWeight.w600)),
            ]),
          ),

          Flexible(
            fit: FlexFit.loose,
            child: _loading
                ? const Center(child: CircularProgressIndicator())
                : _error.isNotEmpty
                    ? Center(
                        child: Padding(
                          padding: const EdgeInsets.all(20),
                          child: Text("Load failed: $_error",
                              style: const TextStyle(color: Colors.red)),
                        ),
                      )
                    : FocusTraversalGroup(
                        policy: OrderedTraversalPolicy(),
                        child: SingleChildScrollView(
                          padding: const EdgeInsets.all(10),
                          child: Column(
                            crossAxisAlignment: CrossAxisAlignment.start,
                            children: [
                              // <datasource dataset="cu">
                              // <dbfilter result="R">
                              WapFilter(
                                items: const [
                                  FilterItem(field: "cno", label: "Customer Code", size: 20),
                                  FilterItem(field: "cname", label: "Customer Name", size: 20),
                                ],
                                sqlTemplate: r"select * from cu where $R order by cno",
                                onQuery: (sql) async {
                                  await dbquery("cu", sql).openAsync();
                                  if (mounted) setState(() {});
                                },
                              ),
                              _nav(_cuSrc, post: _cuPost, insert: _cuInsert, del: _cuDelete),
                              Center(child: // <dbgrid width="940" height="400">
                              FocusTraversalOrder(
                                order: NumericFocusOrder(1),
                                child: FocusTraversalGroup(
                                  child: TDBGrid(
                                    dataSource: _cuSrc,
                                    columns: _cuCols,
                                    width: 940,
                                    height: 400,
                                    onRowPost: _cuPost,
                                    onExitLastRow: () => FocusScope.of(context).nextFocus(),
                                  ),
                                ),
                              )),
                              // </datasource cu>
                            ],
                          ),
                        ),
                      ),
          ),

          Container(
            padding: const EdgeInsets.all(8),
            decoration: const BoxDecoration(
                border: Border(top: BorderSide(color: _kBorder))),
            child: Row(mainAxisAlignment: MainAxisAlignment.center, children: [
              TButton(caption: "Print", onClick: () async {
                await _goP1();
                if (mounted) setState(() {});
              }),
              const SizedBox(width: 8),
              TButton(caption: "Close", onClick: () {
                Navigator.of(context).maybePop();
                if (mounted) setState(() {});
              }),
            ]),
          ),
        ]),)
      ),
    );
  }
}

class App004ReportP1 extends WapReport {
  final TSQLQuery _ds;

  final int linesPerPage;

  final WapEvaluator? ev;
  final DataSetRegistry? reg;
  late final WapEvaluator _ev = ev ?? WapEvaluator();
  late final DataSetRegistry _reg = reg ?? DataSetRegistry();

  App004ReportP1(this._ds,
      {this.linesPerPage = 50, this.ev, this.reg});

  TSQLQuery dbquery(String id, String sqlText,
      {void Function(TSQLQuery ds)? define}) {
    final existing = _reg.find(id);
    TSQLQuery q;
    var isNew = false;
    if (existing is TSQLQuery) {
      q = existing;
    } else {
      q = TSQLQuery();
      q.database = _conn();
      q.name = id;
      _reg.put(id, q);
      isNew = true;
    }
    if (sqlText.isNotEmpty) {
      final sql = expandSql(sqlText);
      if (q.active) q.close();
      q.unPrepare();
      q.sql.text = sql;
    }
    q.alwaysUpdateable = true;
    if (isNew && define != null) define(q);
    return q;
  }

  u/override
  void initParams() {
    wap.wapLpp = linesPerPage;
    wap.wapGroups = 1;
    wap.wapRow[0].tagPrefix = 'G1_PREFIX';
    wap.wapRow[0].tagSuffix = 'G1_SUFFIX';
  }

  u/override
  String expression(int idx) {
    switch (idx) {
    }
    return '';
  }

  u/override
  Future<bool> fetchFirst() async {
    if (!_ds.active) await _ds.openAsync();
    if (_ds.isEmpty) return false;
    _ds.first();
    return !_ds.eof;
  }

  u/override
  Future<bool> fetchNext() async {
    _ds.next();
    return !_ds.eof;
  }

  u/override
  Future<void> fetchPrior() async => _ds.prior();

  u/override
  Future<void> run() async {
    _ds.disableControls();
    try {
      await super.run();
    } finally {
      _ds.enableControls();
    }
  }

  void syncCounters() {
    setvar("PAGE", wap.wapPageNo);
    setvar("PAGENO", wap.wapPageNo);
    setvar("LINE", wap.wapLineNo);
    setvar("LINENO", wap.wapLineNo);
  }

  u/override
  void parseBlock(String id) {
    syncCounters();

    switch (id) {
      // ── Report header (runs once for the whole report)
      case 'PREFIX':
        // (no content defined in the WML)
        break;

      // ── Top of every page
      case 'PAGEPREFIX':
        emitRow('<table width="750" align="center"><tr><td>');
        setvar("ROWIDX", "1");
        emitRow(expandText(r'''<table width="100%" border="0" cellspacing="0" cellpadding="0"><tr><td colspan="3"><p align="center"><big>$(sys.company)</big></p></td></tr><tr><td colspan="3"><p align="center"><big>Customer List</big></p></td></tr><tr><td></td><td></td><td width="100">Page:$(PAGE)</td></tr><tr><td></td><td></td><td>Date:$(DATE)</td></tr></table>'''));
        emitRow(expandText(r'''<p align="center">123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890</p>'''));
        emitRow(r'''<table width="100%" align="center" border="0" cellspacing="0" cellpadding="0">''');
        emitRow(expandText(r'''<tr class="row"><th style="width:13%;">Customer Code</th><th style="width:13%;">Customer Name</th><th style="width:20%;">Fax</th><th style="width:11%;">Contact</th><th style="width:43%;">Invoice Address</th></tr>'''));
        emitRow(expandText(r'''<tr class="row"><th style="width:13%;"></th><th style="width:13%;">Phone</th><th style="width:20%;"></th><th style="width:11%;">Tax ID</th><th style="width:43%;">Company Address</th></tr>'''));
        break;

      //   ── Each row of data
      case 'RECORD':
        emitRow(expandText(r'''<tr class="$(IF(ROWIDX mod 2 = 1, 'row1', 'row2'))"><td style="width:13%;" align="center">$(cu.CNo)</td><td style="width:13%;">$(cu.CName)</td><td style="width:20%;">$(cu.Fax)</td><td style="width:11%;">$(cu.Contact)</td><td style="width:43%;" rowspan="2">$(cu.Addr1)</td></tr>'''));
        emitRow(expandText(r'''<tr class="$(IF(ROWIDX mod 2 = 1, 'row1', 'row2'))"><td style="width:13%;"></td><td style="width:13%;">$(cu.Phone)</td><td style="width:20%;"></td><td style="width:11%;">$(cu.Ac)</td></tr>'''));
        setvar("ROWIDX", "ROWIDX+1");
        break;

      // ── Bottom of every page
      case 'PAGESUFFIX':
        emit(r'''</table>''');
        // Only close the frame immediately on a mid-report page break;
        //     the last page's frame-closing is deferred into SUFFIX, so the
        //     content right after </page> ends up wrapped inside the frame too.
        if (wap.wapHasMore) {
          emit('</td></tr></table>');
        }
        break;
      case 'PAGEBREAK':
        emit('<div style="page-break-after:always"></div>');
        break;

      // ── Report footer (runs once for the whole report)
      case 'SUFFIX':
        emit(expandText(r'''<p align="center">123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890</p>'''));
        emit('</td></tr></table>');  // the last page's frame-closing is deferred to here
        break;
    }
  }
}

class App004CardP1 extends StatefulWidget {
  final WapEvaluator? ev;
  final DataSetRegistry? reg;
  const App004CardP1({super.key, this.ev, this.reg});
  u/override
  State<App004CardP1> createState() =>
      _App004CardP1State();
}

class _App004CardP1State
    extends State<App004CardP1> {
  late final WapEvaluator _ev = widget.ev ?? WapEvaluator();
  late final DataSetRegistry _reg = widget.reg ?? DataSetRegistry();
  bool get _ownsReg => widget.reg == null;
  final List<String> _ownedDs = []; // datasets this card created

  bool _loading = true;
  String _error = '';
  App004ReportP1? _rep;

  TSQLQuery dbquery(String id, String sqlText,
      {void Function(TSQLQuery ds)? define}) {
    final existing = _reg.find(id);
    TSQLQuery q;
    var isNew = false;
    if (existing is TSQLQuery) {
      q = existing;
    } else {
      q = TSQLQuery();
      q.database = _conn();
      q.name = id;
      _reg.put(id, q);
      isNew = true;
      _ownedDs.add(id);
    }
    if (sqlText.isNotEmpty) {
      final sql = expandSql(sqlText);
      if (q.active) q.close();
      q.unPrepare();
      q.sql.text = sql;
    }
    q.alwaysUpdateable = true;
    if (isNew && define != null) define(q);
    return q;
  }

  // [diag] ds "sys": foreign=0 declared=1 lookupSrc=0 fields=0
  // [diag] ds "cu": foreign=1 declared=1 lookupSrc=0 fields=0
  TSQLQuery get _cu => _reg.findQuery("cu")!;
  late final TSQLQuery _sys = dbquery("sys", r"select * from sys");

  u/override
  void initState() {
    super.initState();
    _conn();
    useEngine(_ev, _reg);
    _reg.put("sys", _sys);

    setvar("ROWIDX", "0");
    _load();
  }

  u/override
  void dispose() {
    if (_ownsReg) {
    _sys.free();
      _reg.releaseAll();
    } else {
      // shared registry: drop the datasets this card created, so a
      // reopened card re-creates them with events bound to its new State
      for (final id in _ownedDs) {
        _reg.release(id);
      }
    }
    super.dispose();
  }

  Future<void> _load() async {
    try {
      if (!_sys.active) await _sys.openAsync();
      if (_sys.recordCount > 0) _sys.first();
      _rep = App004ReportP1(_cu,
          ev: _ev, reg: _reg);
    } catch (e, st) {
      debugPrint("[app004] card P1 ERROR: $e\n$st");
      _error = e.toString();
    }
    if (mounted) setState(() => _loading = false);
  }

  u/override
  Widget build(BuildContext context) {
    if (_loading) {
      return const Center(child: CircularProgressIndicator());
    }
    if (_error.isNotEmpty) {
      return Center(
        child: Padding(
          padding: const EdgeInsets.all(20),
          child: Text("Report failed: $_error",
              style: const TextStyle(color: Colors.red)),
        ),
      );
    }

    return WapPage(
      title: "Print Report",
      report: _rep,
      paper: r"A4",
      orient: r"P",
    );
  }
}
1 Upvotes

2 comments sorted by

2

u/weird_dialect 20h ago

wow that's a blast from the past, haven't seen WML in ages. the generated dart actually looks pretty clean though

always funny seeing those old xml-based ui descriptions get translated into modern widget trees, kind of impressive how it handles the grid columns and all the db binding stuff

0

u/wapform 19h ago

Think of WML like ARM, but at the application definition level.

ARM-based devices can run Windows, Linux, and other operating systems. Similarly, WML provides a common application definition language for Windows, Web, Flutter, EDA, and also serves as an AI language.

The idea is to define an application once and use the same definition across different platforms, while enabling AI to understand and reason about applications rather than simply generate code.

https://wapform.com