From fe8113ef2ede83309601992e0da9c0201740aeb0 Mon Sep 17 00:00:00 2001 From: "Axel K. Reinhold" Date: Tue, 3 Sep 2024 12:36:33 +0200 Subject: [PATCH] Field (#25) * fld1 * noedit0 * fieldev * field1 --- docs/DeveloperGuide.md | 427 ++++++++++++++++++++++------------------- docs/UserGuide.md | 5 +- generate/Makefile | 2 +- generate/adjfield.c | 2 +- generate/editform | 15 +- generate/formax.inp | 62 +++--- generate/scotty.sql | 30 +-- generate/testform | 4 +- runform/Makefile | 5 +- runform/field.cpp | 149 +++++++++----- runform/field.h | 5 + runform/function.cpp | 65 +++++-- runform/function.h | 4 + runform/page.cpp | 21 +- runform/runform.h | 13 +- runform/screen.cpp | 8 +- runform/screen.h | 2 +- 17 files changed, 481 insertions(+), 338 deletions(-) diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index 1032d5d..2fdd9a8 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -1,191 +1,117 @@ -Form Terms -========== - - - Application - - A form or a series of forms that satisfies a business function. - - - Base table - - The database table on which a block is based. - - - Base table field - - A field that corresponds to a column in the base table - of the block that owned the field. - - - Block - - A logical collection of fields in a form. A block can - correspond to one table in the database or to no table. - - - Constant text - - Text that appears in a form time it is run. - - - Context - - A concept that you can use to determine what parts of a - form you can currently access from within the interface. - - - Designer - - An application developer or programmer who uses - **formax** to create and modify forms. - - - Field - - An area on a page that can display data and accept - operator input. The data that they display can - correspond to data from a column in a database table. - - - Form - - A logical collection of blocks, fields and triggers. - - - Form database - - A sqlite-database containing all the objects that make - up the form. - - - Multi-record block - - A block that can display more than one record at a time. - - - Object - - A group of data, such as a form, block, field or trigger. - - - Operator - - An administrator of a **formax** application. He is - responsible for a stable, performant and secure runtime - environment. - - - Page - - A collection of display information. Similar in concept - to a slide Projection, a page displays fields and - constant text to operators on their computer or terminal - screen when they execute a form. - - - Pop-up window - - A **formax** object that overlays an area of the current - display. The form displays a pop-up window in response - to some event or user action. - - - Record - - Data from one row in a database table or view, as - presented in a form. - - - Scope - - The domain, or range, in which a trigger operates. This - domain is determined by the level (form, block, or field) - at which you define the trigger. - - - Single-record block - - A block that can display only one record at a time. - - - Trigger - - A piece of logic that is executed at, or "tiggered" by, - a form event. - - - User - - An end-user of a **formax** application. - -Database Terms -============== - - - Column - - In a database table, a "vertical" group of cells that - represent one kind of data. - - - Constraint - - A rule or restriction concerning a piece of data that is - enforced at the data level, rather than the object or - application level. - - - Database - - A collection of tables controlled by one data dictionary. - - - Foreign key - - A value or column in one table that refers to a primary - key in another table. - - - Index - - An optional structure associated with a table that is - used by the database software to locate rows of the - table quickly, and optionally to guarantee that every - row is unique. - - - Lock - - A restriction that assigns temporary ownership, or - control, of a database resource (table or row) to a - user. A lock can prevent other users from changing data - that a user is updating. - - - Primary key - - Information used to identify a row in a table. Also - known as a key. - - - Row - - In a database table, a "horizontal" group of column - values. - - - Table - - The basic unit of information in a relational database - management system. A table is a two dimensional grid - that is made up of rows and columns. - - - Transaction - - A logical unit of work. Specifically, a transaction is - the group of events that occurs between the data changes - and the user commiting them. - -Components -========== -**formax** consists of the following programs, or -components, that you can execute independently from the -command line. - -generate --------- -makeform is a shell script which can produce the sql script -(.inp) which in turn can create the form database (.frm) by -using the sqlite3 utility. The produced sql script -represents in practice the source code of the form -application and can be edited to change the default -behaviour or to add objects to the form. - -editform --------- -can edit the form layout by an editor and the fields table -database with a form. The editor is called with the .inp -file to make other adjustments before the .frm is recreated. - -runform -------- -Execute forms (.frm) interactively on a terminal by the -end-user. +What is **formax**? +=================== + +**formax** is a "no-code" development framework for forms +based applications. These applications can enter, query, +update and delete data in any database which has an +ODBC-driver. With no programming simply by assigning form +fields to database tables and columns a working form can be +build within 5 minutes without any coding. + +The forms run within a terminal. They are full-screen, +interactive and keyboard driven. Any terminal with a +terminfo (ncurses) database is supported with usable +function keys and color support. + +The system is enterprise-ready. Security, reliability, cost +and operational excellence are fully covered and the +important properties for developers - performance and +sustainability are on top with C++ and open source. + +Terms +===== + +| Term | Explanation | +|------------------|----------------------------------------| +| Application | forms that satisfy a business function | +| Base table | database table a block is based on | +| Base table field | field that corresponds to a column in | +| | the base table of the fields block | +| Block | logical collection of fields in a form | +| | corresponds to table in the database | +| Constant text | text that appears in a running form | +| Context | concept that you can use to determine | +| | what parts of a form you can access | +| Designer | application developer or programmer | +| Field | area on a page that can display data | +| | and accept user input | +| Form | collection of blocks, fields, triggers | +| Form database | sqlite-database containing the form | +| Multirecord block| block with more than one record | +| Object | group of data, such as a form, block, | +| | field or trigger | +| Operator | administrator of an application | +| | responsible for a stable, performant | +| | and secure runtime environment | +| Page | collection of display information | +| Pop-up window | overlays an area of the current display| +| | displays in response to user action | +| Record | data from one row in a database table | +| Scope | domain in which a trigger operates | +| | determined by the level (form, block, | +| | or field) of the trigger definition | +| Trigger | piece of logic executed at a form event| +| User | end-user of a **formax** application | +| Column | "vertical" group of cells in a database| +| Constraint | rule or restriction concerning a piece | +| | of data that is enforced at data level,| +| | rather than the application level | +| Database | collection of tables | +| Foreign key | column that refers to a primary key | +| Index | optional structure of a table that is | +| | used by the database software to locate| +| | rows quickly and optionally guarantee | +| | that every row is unique | +| Lock | restriction that assigns control of a | +| | resource to a user - can prevent other | +| | users from changing data meanwhile | +| Primary key | information used to identify a row | +| Row | "horizontal" group of cells in database| +| Table | basic unit of information in database | +| | 2 dimensional grid of rows and columns | +| Transaction | logical unit of work | + +Programs +======== + +**formax** contains the following programs, that you can +execute independently from the command line: + + - makeform + +is a shell script which can produce a form for a table with +default functionality. The input parameters are just the table +and column names - the output is a sql script (.inp). This +script can create the form database (.frm). In practice the +produced sql script represents the source code of the form. +You edit the script to change the default behaviour or to +add objects to the form. The form database is all you need +to run the application. + + - editform + +is another shell script. It offers a more convenient way to +change a form by three steps. First it extracts the +boilerplate from the main page and loads it into your +preferred editor. Edit the form layout and the field +positions and lengths with a WYSIWYG concept. The edited +file is inserted back to the form. The next step is running +a form to modify all the fields properties, which makes up a +great portion of any form development work. Last the editor +is called with the .inp file to make other fine adjustments +before the .frm is recreated. + + - runform + +is the heart of the system. This single executable runs +forms (.frm) interactively on a terminal. It needs only two +parameters to run - the form database file and the ODBC-DSN. +It is run by the end-user typically wrapped into a shell +script within his ssh startup command. Objects ======= + A **formax** application is made up of objects. These objects contain all the information that is needed for an application. They have a 1:1 relationship to the tables in @@ -193,47 +119,72 @@ the form-database and to the classes in the C++ source. In the source-code there is a separate class for every object for reading the form-database and for processing. -Forms ------ +Form +---- + The primary object of a **formax** application is the form. A form is made up of additional objects. These objects link the form to database elements, such as columns and tables, and provide control over the flow of execution. -Blocks ------- +Block +----- + Describe each section or subsection of the form, and serve as the basis of default database interaction. -Fields ------- +Field +----- + Represent columns or data entry areas and describe how the data should be displayed and validated and how an operator should interact with the data while it is entered. -Pages ------ +Page +---- + Are collections of display information, such as constant text. All fields are displayed on some page. -Triggers --------- +Trigger +------- + Are sets of processing commands associated with event points, such as when a particular function key is pressed by the operator. Processing ========== -When a form is executed **formax** follows a pre-defined set + +When a form is executed runform follows a pre-defined set of rules for how actions should occur. These actions include navigation whithin the application and the validation of data. Within the processing rules, you can customize the default behaviour to meet the needs of your application. +Status and mode +--------------- + +On top of the screen runform displays a status line. The +status line indicates the current form, block, field, record +and mode. The current mode determines the action that will +be processed with the current data (block, field, record) by +using the keyboard. There are four main modes for the four +main actions and the special Edit-mode for entering data. + +| Mode | Action | +|------------------|---------------------------------------| +| Insert-Mode | Create -> Enter new records/data | +| Query-Mode | Retrieve -> Search in database | +| Update-Mode | Update -> Display and change data | +| Delete-Mode | Delete -> Delete records/data | +| Edit-Mode | Edit data | + Events and Functions -------------------- + All processing centers around events. Put simply, events are -things that occur when a form is exeecuted. **formax** knows +things that occur when a form is exeecuted. runform knows about events and handles them by executing functions. Note that during processing, events are usually nested. That is, the occurence of one event usually invokes functions that @@ -243,6 +194,7 @@ processes of navigation and validation. Trigger Points -------------- + Every function that an event calls might have on or more trigger point associated with it. A trigger point is temporal place in an event with a specific trigger type is @@ -254,22 +206,93 @@ Navigation ---------- Navigation is an internal function that is invoked by -specific events. **formax** perfoms navigation primarily to +specific events. runform perfoms navigation primarily to move the cursor from one location to another. The main concepts of navigation are the navigation unit and the cursor. The navigation unit is always the field. Validation ---------- + Validation is an internal function that is invoked by -specific events. Validation is the process by which -**formax** determines whether the data in an object is valid -or correct. +specific events. Validation is the process by which runform +determines whether the data in an object is valid or +correct. Trigger Processing ------------------ + Events invoke functions, which have trigger points. When -**formax** processes a trigger point, it executes, or fires, +runform processes a trigger point, it executes, or fires, the associated trigger. Every trigger pont has a specific type of trigger associated with it. +Development +=========== + +In version 1.x developing with **formax** is kind of a +rudimentary task only supported by a simple script +`editform`. Version 1 first attention applys to user +experience and documentation. Anything beyond layout editing +and field properties has to be done by editing the .inp +file and needs knowledge from the source code or adapted +experience from Oracle Forms jobs. Since 1.x can only handle +one block, table and page this is not a big deal anyway. +Follow the "Getting started" instruction in the README first +to get the basic idea for development quickly. + +Page layout +----------- + +The `editform` script can extract the pages boilerplate +(background) into your preferred editor. The placeholder for +the fields are `$nn___` with nn as the field number and the +`_`s imply the display length of the field. A trailing `. +` means the length is only 1 character (for binary 0/1 +fields). The edited text file is incorporated into the form +database after writing. + +Field properties +---------------- + +After the layout `editform` brings up runform itself with a +form for the forms fields. The effect of the properties is +as follows: + +| Property | Effect for runform | +|----------|-----------------------------------------------| +| Id | primary key - DO NOT CHANGE | +| form_id | form id = 1 - DO NOT CHANGE | +| blkn | block id = 1 - DO NOT CHANGE | +| pagn | page id = 1 - DO NOT CHANGE | +| name | name corresponds to column name of the table | +| Help | help text | +| Seq | sequence for user navigation | +| Line | line position in layout (*1) | +| Col | column position in layout (*1) | +| Dlen | display length in layout (*1) | +| Type | column type 0:ALL 1:CHAR 2:INT 3:FLOAT 4:DATE | +| Length | column length | +| Table | 0=no database column 1=database column | +| Pkey | primary key - DO NOT CHANGE | +| Default | default value in Insert-Mode | +| Enter | allowed to edit in Insert-Mode | +| Query | allowed to edit in Query-Mode | +| Update | allowed to edit in Update-Mode | +| UpdateNul| allowed to edit in Update-Mode when NULL | +| Mandatory| not NULL in Insert-Mode | +| Uppercase| convert to uppercase | +| Low | minimum value | +| High | maximum value | +| Pattern | regular expression (pcre) | +| ListOfVal| title | + +(*1) This property is adjusted by the `editform` script upon + the field placeholders from the boilerplate text file. + +.inp file edit +-------------- + +At the end `editform` brings up the editor with the .inp +file loaded. Look into the source code and into the table +schemas for informations to alter the forms behaviour. diff --git a/docs/UserGuide.md b/docs/UserGuide.md index 492c530..cc6bf6a 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -87,7 +87,10 @@ categories: Same behaviour as alphanumeric keys with the exception of the shifted [1-9] keys which act as speed navigation - in non Edit-Mode for the respective fields. + in non Edit-Mode for the respective fields. And the + [Space] key which toggles true/false for boolean fields. + And the [+] and [-] keys which increment or decrement + integer fields. - Navigation = [Left] [Right] [Up] [Down] [PageUp] [PageDn] [Home] [End] [Tab] [Backtab] diff --git a/generate/Makefile b/generate/Makefile index 7fea805..91bbc16 100644 --- a/generate/Makefile +++ b/generate/Makefile @@ -20,5 +20,5 @@ scotty.inp: testform makeform ./testform >$@ clean: - rm -f formax.frm scotty.inp scotty.frm* scotty.sq3 ads_* adjfield runform + rm -f formax.frm* scotty.inp scotty.frm* scotty.sq3 ads_* adjfield runform diff --git a/generate/adjfield.c b/generate/adjfield.c index 5f8aa33..bc2d73b 100644 --- a/generate/adjfield.c +++ b/generate/adjfield.c @@ -16,7 +16,7 @@ int main() { f[0] = NXT; f[1] = t = NXT; len = 2; - while(t != '\n' && (t == '_' || (t >= '0' && t <= '9'))) { t = NXT; len++; } + while(t == '_' || t == '.' || (t >= '0' && t <= '9')) { len = t=='.' ? 1 : len+1; t = NXT; } printf("UPDATE fields set line = %d, col = %d, dlen = %d where id = %d;\n", line, act, len, atoi(f)); } if (t == '\n') { diff --git a/generate/editform b/generate/editform index 12bd5b4..7bd7411 100755 --- a/generate/editform +++ b/generate/editform @@ -1,4 +1,15 @@ #!/bin/sh +if [ "`dirname $0`" = "." ]; then + ADJFIELD=./adjfield + RUNFORM=../runform/runform + FORMAXFRM=formax.frm + LOGGING=-g/tmp/formax.log +else + ADJFIELD=adjfield + RUNFORM=runform + FORMAXFRM=${ARX:-/opt/arx}/lib/formax.frm + LOGGING="" +fi FORM=$1 if ! cp -v $FORM.frm $FORM.frm.bac; then echo "$FORM.frm not found" >&2 @@ -13,9 +24,9 @@ IFS=% sed "s/^/'/;s/\$/'/" $FORM.map | while read TEXT; do LINE=`expr $LINE + 1` $SQL "insert into maps (line, mtext) values ($LINE, $TEXT)" done -${ARX:-/opt/arx}/bin/adjfield <$FORM.map | $SQL +$ADJFIELD <$FORM.map | $SQL rm $FORM.map -${ARX:-/opt/arx}/bin/runform -x ${ARX:-/opt/arx}/lib/formax.frm $FORM.frm +$RUNFORM $LOGGING -x $FORMAXFRM $FORM.frm $SQL .dump >$FORM.inp ${EDITOR:-vi} $FORM.inp rm $FORM.frm diff --git a/generate/formax.inp b/generate/formax.inp index 9bd4319..2535a9a 100644 --- a/generate/formax.inp +++ b/generate/formax.inp @@ -83,22 +83,22 @@ INSERT INTO fields VALUES(6,1,1,1,'seq',60,0,30,3,1,0,'',5,7,1,1,1,1,0,0,'',0,0, INSERT INTO fields VALUES(7,1,1,1,'ftype',70,0,30,2,1,0,'',5,21,1,1,1,1,0,0,'',0,0,0,0,'',''); INSERT INTO fields VALUES(8,1,1,1,'len',80,0,30,2,1,0,'',6,21,1,1,1,1,0,0,'',0,0,0,0,'',''); INSERT INTO fields VALUES(9,1,1,1,'dlen',68,0,30,3,1,0,'',8,7,1,1,1,1,0,0,'',0,0,0,0,'',''); -INSERT INTO fields VALUES(10,1,1,1,'btab',100,0,30,3,1,0,'',7,21,1,1,1,1,0,0,'',0,0,0,0,'',''); -INSERT INTO fields VALUES(11,1,1,1,'key',110,0,30,3,1,0,'',7,30,1,1,1,1,0,0,'',0,0,0,0,'',''); +INSERT INTO fields VALUES(10,1,1,1,'btab',100,0,30,1,1,0,'',7,21,1,1,1,1,0,0,'',0,0,0,0,'',''); +INSERT INTO fields VALUES(11,1,1,1,'key',110,0,30,1,1,0,'',7,31,1,1,1,1,0,0,'',0,0,0,0,'',''); INSERT INTO fields VALUES(12,1,1,1,'dflt',120,0,30,20,1,0,'',8,21,1,1,1,1,0,0,'',0,0,0,0,'',''); INSERT INTO fields VALUES(13,1,1,1,'line',65,0,30,3,1,0,'',6,7,1,1,1,1,0,0,'',0,0,0,0,'',''); INSERT INTO fields VALUES(14,1,1,1,'col',66,0,30,3,1,0,'',7,7,1,1,1,1,0,0,'',0,0,0,0,'',''); -INSERT INTO fields VALUES(15,1,1,1,'enter',150,0,30,3,1,0,'',11,13,1,1,1,1,0,0,'',0,0,0,0,'',''); -INSERT INTO fields VALUES(16,1,1,1,'query',160,0,30,3,1,0,'',12,13,1,1,1,1,0,0,'',0,0,0,0,'',''); -INSERT INTO fields VALUES(17,1,1,1,'upd',170,0,30,3,1,0,'',13,13,1,1,1,1,0,0,'',0,0,0,0,'',''); -INSERT INTO fields VALUES(18,1,1,1,'updnul',180,0,30,3,1,0,'',14,13,1,1,1,1,0,0,'',0,0,0,0,'',''); -INSERT INTO fields VALUES(19,1,1,1,'mand',190,0,30,3,1,0,'',15,13,1,1,1,1,0,0,'',0,0,0,0,'',''); -INSERT INTO fields VALUES(20,1,1,1,'upper',200,0,30,3,1,0,'',16,13,1,1,1,1,0,0,'',0,0,0,0,'',''); +INSERT INTO fields VALUES(15,1,1,1,'enter',150,0,30,1,1,0,'',11,13,1,1,1,1,0,0,'',0,0,0,0,'',''); +INSERT INTO fields VALUES(16,1,1,1,'query',160,0,30,1,1,0,'',12,13,1,1,1,1,0,0,'',0,0,0,0,'',''); +INSERT INTO fields VALUES(17,1,1,1,'upd',170,0,30,1,1,0,'',13,13,1,1,1,1,0,0,'',0,0,0,0,'',''); +INSERT INTO fields VALUES(18,1,1,1,'updnul',180,0,30,1,1,0,'',14,13,1,1,1,1,0,0,'',0,0,0,0,'',''); +INSERT INTO fields VALUES(19,1,1,1,'mand',190,0,30,1,1,0,'',15,13,1,1,1,1,0,0,'',0,0,0,0,'',''); +INSERT INTO fields VALUES(20,1,1,1,'upper',200,0,30,1,1,0,'',16,13,1,1,1,1,0,0,'',0,0,0,0,'',''); INSERT INTO fields VALUES(21,1,1,1,'lovtit',210,0,30,20,1,0,'',17,13,1,1,1,1,0,0,'',0,0,0,0,'',''); INSERT INTO fields VALUES(22,1,1,1,'lov_id',220,0,30,3,1,0,'',17,36,1,1,1,1,0,0,'',0,0,0,0,'',''); INSERT INTO fields VALUES(23,1,1,1,'lovi_id',230,0,30,3,1,0,'',17,42,1,1,1,1,0,0,'',0,0,0,0,'',''); INSERT INTO fields VALUES(24,1,1,1,'low',240,0,30,11,1,0,'',20,13,1,1,1,1,0,0,'',0,0,0,0,'',''); -INSERT INTO fields VALUES(25,1,1,1,'high',250,0,30,11,1,0,'',20,36,1,1,1,1,0,0,'',0,0,0,0,'',''); +INSERT INTO fields VALUES(25,1,1,1,'high',250,0,30,11,1,0,'',20,30,1,1,1,1,0,0,'',0,0,0,0,'',''); INSERT INTO fields VALUES(26,1,1,1,'valpatn',260,0,30,34,1,0,'',21,13,1,1,1,1,0,0,'',0,0,0,0,'',''); INSERT INTO fields VALUES(27,1,1,1,'help',55,0,30,40,1,0,'',2,13,1,1,1,1,0,0,'',0,0,0,0,'',''); CREATE TABLE maps @@ -107,27 +107,27 @@ CREATE TABLE maps line INTEGER NOT NULL DEFAULT 1, mtext TEXT NOT NULL DEFAULT '' ); -INSERT INTO maps VALUES(212,1,1,' Id $1 / $2 $3 blkn $4 pagn $5______________________'); -INSERT INTO maps VALUES(213,1,2,' Help $27_____________________________________'); -INSERT INTO maps VALUES(214,1,3,''); -INSERT INTO maps VALUES(215,1,4,' Layout Properties'); -INSERT INTO maps VALUES(216,1,5,' Seq $6_ Type $7 0:ALL 1:CHAR 2:INT 3:FLOAT 4:DATE'); -INSERT INTO maps VALUES(217,1,6,' Line $13 Length $8'); -INSERT INTO maps VALUES(218,1,7,' Col $14 Table $10 Pkey $11'); -INSERT INTO maps VALUES(219,1,8,' Dlen $9_ Default $12_________________'); -INSERT INTO maps VALUES(220,1,9,''); -INSERT INTO maps VALUES(221,1,10,' Action'); -INSERT INTO maps VALUES(222,1,11,' Enter $15'); -INSERT INTO maps VALUES(223,1,12,' Query $16'); -INSERT INTO maps VALUES(224,1,13,' Update $17'); -INSERT INTO maps VALUES(225,1,14,' UpdateNul $18'); -INSERT INTO maps VALUES(226,1,15,' Mandatory $19'); -INSERT INTO maps VALUES(227,1,16,' Uppercase $20 Blk Id'); -INSERT INTO maps VALUES(228,1,17,' ListOfVal $21_________________ / $22 / $23'); -INSERT INTO maps VALUES(229,1,18,''); -INSERT INTO maps VALUES(230,1,19,' Validation'); -INSERT INTO maps VALUES(231,1,20,' Low $24________ High $25________'); -INSERT INTO maps VALUES(232,1,21,' Pattern $26_______________________________'); +INSERT INTO maps VALUES(401,1,1,' Id $1 / $2 $3 blkn $4 pagn $5______________________'); +INSERT INTO maps VALUES(402,1,2,' Help $27_____________________________________'); +INSERT INTO maps VALUES(403,1,3,''); +INSERT INTO maps VALUES(404,1,4,' Layout Database'); +INSERT INTO maps VALUES(405,1,5,' Seq $6_ Type $7 0:ALL 1:CHAR 2:INT 3:FLOAT 4:DATE'); +INSERT INTO maps VALUES(406,1,6,' Line $13 Length $8'); +INSERT INTO maps VALUES(407,1,7,' Col $14 Table $10. Pkey $11. '); +INSERT INTO maps VALUES(408,1,8,' Dlen $9_ Default $12_________________'); +INSERT INTO maps VALUES(409,1,9,''); +INSERT INTO maps VALUES(410,1,10,' Action'); +INSERT INTO maps VALUES(411,1,11,' Enter $15. edit in Insert-Mode'); +INSERT INTO maps VALUES(412,1,12,' Query $16. edit in Query-Mode'); +INSERT INTO maps VALUES(413,1,13,' Update $17. edit in Update-Mode'); +INSERT INTO maps VALUES(414,1,14,' UpdateNul $18. edit in Update-Mode if NULL'); +INSERT INTO maps VALUES(415,1,15,' Mandatory $19. must in Insert-Mode'); +INSERT INTO maps VALUES(416,1,16,' Uppercase $20. convert uppercase Blk Id'); +INSERT INTO maps VALUES(417,1,17,' ListOfVal $21_________________ / $22 / $23'); +INSERT INTO maps VALUES(418,1,18,''); +INSERT INTO maps VALUES(419,1,19,' Validation'); +INSERT INTO maps VALUES(420,1,20,' Low $24________ High $25________'); +INSERT INTO maps VALUES(421,1,21,' Pattern $26_______________________________'); CREATE TABLE pages (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, form_id INTEGER NOT NULL DEFAULT 1, @@ -159,5 +159,5 @@ INSERT INTO triggers VALUES(1,1,'enter_the_form',0,0,0,'0'); DELETE FROM sqlite_sequence; INSERT INTO sqlite_sequence VALUES('triggers',1); INSERT INTO sqlite_sequence VALUES('pages',1); -INSERT INTO sqlite_sequence VALUES('maps',232); +INSERT INTO sqlite_sequence VALUES('maps',421); COMMIT; diff --git a/generate/scotty.sql b/generate/scotty.sql index 5990e21..9d7feb6 100644 --- a/generate/scotty.sql +++ b/generate/scotty.sql @@ -15,20 +15,20 @@ create table emps mgr integer, hiredate text, sal real, - comm real, + comm integer, dept_id integer ); -insert into emps values (7369, 'MC''SMITH', 'CLERK', 7902, '1980-12-17', 800, NULL, 20); -insert into emps values (7499, 'ALLEN', 'SALESMAN', 7698, '1981-02-20', 1600, 300, 30); -insert into emps values (7521, 'WARD', 'SALESMAN', 7698, '1981-02-22', 1250, 500, 30); -insert into emps values (7566, 'JONES', 'MANAGER', 7839, '1981-04-02', 2975, NULL, 20); -insert into emps values (7654, 'MARTIN', 'SALESMAN', 7698, '1981-09-28', 1250, 1400, 30); -insert into emps values (7698, 'BLAKE', 'MANAGER', 7839, '1981-05-01', 2850, NULL, 30); -insert into emps values (7782, 'CLARK', 'MANAGER', 7839, '1981-06-09', 2450, NULL, 10); -insert into emps values (7788, 'SCOTT', 'ANALYST', 7566, '1987-04-19', 3000, NULL, 20); -insert into emps values (7839, 'KING', 'PRESIDENT', NULL, '1981-11-17', 5000, NULL, 10); -insert into emps values (7844, 'TURNER', 'SALESMAN', 7698, '1981-09-08', 1500, 0, 30); -insert into emps values (7876, 'ADAMS', 'CLERK', 7788, '1987-05-23', 1100, NULL, 20); -insert into emps values (7900, 'JAMES', 'CLERK', 7698, '1981-12-03', 950, NULL, 30); -insert into emps values (7902, 'FORD', 'ANALYST', 7566, '1981-12-03', 3000, NULL, 20); -insert into emps values (7934, 'MILLER', 'CLERK', 7782, '1982-01-23', 1300, NULL, 10); +insert into emps values (7369, 'MC''SMITH', 'CLERK', 7902, '1980-12-17', NULL, 0, 20); +insert into emps values (7499, 'ALLEN', 'SALESMAN', 7698, '1981-02-20', 1600, 0, 30); +insert into emps values (7521, 'WARD', 'SALESMAN', 7698, '1981-02-22', 1250, 1, 30); +insert into emps values (7566, 'JONES', 'MANAGER', 7839, '1981-04-02', 2975, 0, 20); +insert into emps values (7654, 'MARTIN', 'SALESMAN', 7698, '1981-09-28', 1250, 0, 30); +insert into emps values (7698, 'BLAKE', 'MANAGER', 7839, '1981-05-01', 2850, 1, 30); +insert into emps values (7782, 'CLARK', 'MANAGER', 7839, '1981-06-09', 2450, 0, 10); +insert into emps values (7788, 'SCOTT', 'ANALYST', 7566, '1987-04-19', 3000, 1, 20); +insert into emps values (7839, 'KING', 'PRESIDENT', NULL, '1981-11-17', 5000, 1, 10); +insert into emps values (7844, 'TURNER', 'SALESMAN', 7698, '1981-09-08', 1500, 0, 30); +insert into emps values (7876, 'ADAMS', 'CLERK', 7788, '1987-05-23', 1100, 1, 20); +insert into emps values (7900, 'JAMES', 'CLERK', 7698, '1981-12-03', 950, 0, 30); +insert into emps values (7902, 'FORD', 'ANALYST', 7566, '1981-12-03', 3000, 0, 20); +insert into emps values (7934, 'MILLER', 'CLERK', 7782, '1982-01-23', 1300, 0, 10); diff --git a/generate/testform b/generate/testform index af00955..c3b00c7 100755 --- a/generate/testform +++ b/generate/testform @@ -9,8 +9,10 @@ echo "UPDATE maps set mtext = mtext || ' $1' where line = $LINE;" } ARX=`pwd`/.. sh ./makeform scotty formax1 emps id ename job mgr hiredate sal comm dept_id cat <w)(CB.currentrecord, sequencenum); } -int Field::edit(int pos) { +int Field::toggle() { +char **c; +if (CM == MOD_UPDATE && fldtype() == FTY_BOOL) { + c = valuep(); + if (*c && strlen(*c)==1) { + switch (**c) { + case '0': **c = '1'; return KEF_NXTFLD; + case '1': **c = '0'; return KEF_NXTFLD; + } + } +} +return 0; +} + +int Field::increment(int ival) { +char **c; +int a; +char buf2[SMLSIZE]; +if (CM == MOD_UPDATE && fieldtype == FTY_INT) { + c = valuep(); + if (*c) { + a = atoi(*c) + ival; + letf(t(buf2), "%d", a); + if (validate(c, buf2) != KEF_CANCEL) return 0; // KEF_NXTFLD; + } +} +return 0; +} + +int Field::validate(char **c, char *buf) { +char *u; +re_t re; int s; +char buf2[SMLSIZE]; +if (*validreg) { + re = re_compile(validreg); + if (re_matchp(re, buf, &s) == -1) { // || s != (int)strlen(buf) + MSG1(MSG_NOMATCH, validreg); + return KEF_CANCEL; + } +} +switch (fieldtype) { + case FTY_DATE: + s = colquery(buf, buf2, "q", 0, 268); + if (*buf2 == '{' && (u = rindex(buf2, ' ')) && u == buf2+strlen(buf2)-9) { + strncpy(buf, u+1, 4); + *(buf+4) = '-'; + strncpy(buf+5, u+5, 2); + *(buf+7) = '-'; + strncpy(buf+8, u+7, 2); + *(buf+10) = '\0'; + } + re = re_compile("^[12]\\d\\d\\d-[012]\\d-[0123]\\d$"); + if (re_matchp(re, buf, &s) == -1) { + MSG1(MSG_NOMATCH, "YYYY-MM-DD"); + return KEF_CANCEL; + } + break; + case FTY_INT: + case FTY_BOOL: + if (lowvalue + highvalue != 0 && (lowvalue > atoi(buf) || highvalue < atoi(buf))) { + letf(t(buf2), "%d - %d", lowvalue, highvalue); + MSG1(MSG_NORANGE, buf2); + return KEF_CANCEL; + } + break; + case FTY_FLOAT: break; + case FTY_CHAR: break; + case FTY_ALL: break; +} +if (*c==NULL && *buf) *c = strdup(buf); +else { + if (strlen(buf) > strlen(*c)) *c = (char*)realloc(*c, strlen(buf)+1); + if (strcmp(*c, buf)) strcpy(*c, buf); +} +return 0; +} + +int Field::edit(int pos) { int pressed; char buf[SMLSIZE]; -char buf2[SMLSIZE]; -char *u; char **c; -re_t re; +int s; pressed = 0; -switch(F(rmode)) { +switch(CM) { case MOD_UPDATE: if (isprimarykey) { MSG(MSG_EDITKEY); return KEF_CANCEL; } case MOD_INSERT: - if (!updateable) { MSG(MSG_FLDPROT); return KEF_CANCEL; } + if (noedit()) { MSG(MSG_FLDPROT); return KEF_CANCEL; } if (F(b[blockindex].q->rows)) { c = valuep(); if (*c) let(buf, *c); else *buf = '\0'; - pressed = F(p[0].sedit)(buf, pos, fieldtype); - if (*validreg) { - re = re_compile(validreg); - if (re_matchp(re, buf, &s) == -1) { // || s != (int)strlen(buf) - MSG1(MSG_NOMATCH, validreg); - return KEF_CANCEL; - } - } - // missing range validation - switch (fieldtype) { - case FTY_DATE: - s = colquery(buf, buf2, "q", 0, 268); - if (*buf2 == '{' && (u = rindex(buf2, ' ')) && u == buf2+strlen(buf2)-9) { - strncpy(buf, u+1, 4); - *(buf+4) = '-'; - strncpy(buf+5, u+5, 2); - *(buf+7) = '-'; - strncpy(buf+8, u+7, 2); - *(buf+10) = '\0'; - } - re = re_compile("^[12]\\d\\d\\d-[012]\\d-[0123]\\d$"); - if (re_matchp(re, buf, &s) == -1) { - MSG1(MSG_NOMATCH, "YYYY-MM-DD"); - return KEF_CANCEL; - } - break; - case FTY_ALL: - case FTY_INT: - case FTY_FLOAT: - case FTY_CHAR: break; - } - if (*c==NULL && *buf) *c = strdup(buf); - else { - if (strlen(buf) > strlen(*c)) *c = (char*)realloc(*c, strlen(buf)+1); - if (strcmp(*c, buf)) strcpy(*c, buf); - } + pressed = F(p[0].sedit)(buf, pos, fldtype(), fieldlen); + if (pressed != KEF_CANCEL && validate(c, buf) == KEF_CANCEL) pressed = KEF_CANCEL; } break; case MOD_QUERY: - pressed = F(p[0].sedit)(queryhuman, pos, FTY_ALL); + pressed = F(p[0].sedit)(queryhuman, pos, FTY_ALL, SMLSIZE); s = colquery(queryhuman, querywhere, name, querycharm, 0); break; case MOD_DELETE: diff --git a/runform/field.h b/runform/field.h index 9411240..ea45e97 100644 --- a/runform/field.h +++ b/runform/field.h @@ -8,7 +8,10 @@ class Field { int isprimarykey; int init(Qdata *fld, int rix); void clear(); + int toggle(); + int increment(int ival); int edit(int pos); + int noedit(); void show(int cur); private: char **valuep(); @@ -18,6 +21,8 @@ class Field { int col; int blockindex; int pageindex; + int validate(char **c, char *buf); + ftype fldtype(); ftype fieldtype; int fieldlen; int basetable; diff --git a/runform/function.cpp b/runform/function.cpp index ce84b5b..71d149e 100644 --- a/runform/function.cpp +++ b/runform/function.cpp @@ -41,7 +41,7 @@ switch(F(lastcmd)) { case KEF_NXTREC: LK = next_record(); break; case KEF_PREREC: LK = previous_record(); break; case KEF_INSERT: - switch(F(rmode)) { + switch(CM) { case MOD_UPDATE: case MOD_QUERY: LK = insert_record(); break; case MOD_INSERT: LK = F(dirty) ? create_record() : clear_record(); break; @@ -49,7 +49,7 @@ switch(F(lastcmd)) { } break; case KEF_BACKDEL: /* fbackdel() */ case KEF_DELETE: - switch(F(rmode)) { + switch(CM) { case MOD_QUERY: LK = fedit(KEF_DEL); break; case MOD_UPDATE: LK = delete_record(); break; case MOD_INSERT: LK = clear_record(); break; @@ -59,7 +59,7 @@ switch(F(lastcmd)) { case KEF_QUERY: LK = enter_query(); break; case KEF_NAVI10: case KEF_COMMIT: - switch(F(rmode)) { + switch(CM) { case MOD_QUERY: LK = execute_query(); break; case MOD_UPDATE: LK = enter_query(); break; case MOD_INSERT: LK = F(dirty) ? create_record() : clear_record(); break; @@ -68,14 +68,17 @@ switch(F(lastcmd)) { case KEF_EXIT: LK = fexit(); break; case KEF_QUIT: case KEF_CANCEL: - switch(F(rmode)) { + switch(CM) { case MOD_UPDATE: case MOD_QUERY: LK = fquit(); break; case MOD_INSERT: LK = clear_record(); break; - case MOD_DELETE: F(rmode) = MOD_UPDATE; LK = 0; break; + case MOD_DELETE: switch_mode(MOD_UPDATE); LK = 0; break; } break; case KEF_RIGHT: LK = fedit(0); break; case KEF_LEFT: LK = fedit(-1); break; + case ' ': LK = ftoggle(); break; + case '+': LK = fincrement(1); break; + case '-': LK = fincrement(-1); break; default: if (isprintable(LK)) LK = fedit(-1000 - LK); @@ -96,6 +99,12 @@ return 0; } /* NAVIGATION */ +int Function::switch_mode(fmode mod) { +CM = mod; +if (CF.noedit()) fmove(0, 0); +return 0; +} + int Function::next_item() { if (!trigger(TRT_NEXTITEM)) fmove(0, 1); return 0; @@ -114,17 +123,18 @@ int Function::fmove(int bi, int fi) { //F(curblock) = (F(curblock) + F(numblock) + bi) % F(numblock); if (fi < NFIELD1) F(curfield) = CB.blockfields[ (CF.sequencenum-1 + CB.fieldcount + fi) % CB.fieldcount ]; else F(curfield) = fi - NFIELD1 - 1; +if (CF.noedit()) fmove(0, fi<0 ? -1 : 1); return 0; } int Function::fmover(int ri) { -switch (F(rmode)) { +switch (CM) { case MOD_QUERY: return 0; break; case MOD_UPDATE: break; case MOD_INSERT: if (!yesno(MSG(MSG_DIRTY))) create_record(); else clear_record(); break; case MOD_DELETE: if (!yesno(MSG(MSG_DIRTY))) destroy_record(); break; } -F(rmode) = MOD_UPDATE; +switch_mode(MOD_UPDATE); if (CB.currentrecord > 0) { CB.currentrecord += ri; if (CB.currentrecord > CB.q->rows) { @@ -141,9 +151,9 @@ return 0; /* EDITING */ int Function::insert_record() { -if (F(rmode) == MOD_UPDATE || F(rmode) == MOD_QUERY) { +if (CM == MOD_UPDATE || CM == MOD_QUERY) { CB.q->splice(CB.currentrecord++); - F(rmode) = MOD_INSERT; + switch_mode(MOD_INSERT); } else { MSG(MSG_QUERYM); } @@ -152,14 +162,27 @@ return 0; int Function::create_record() { if (CB.insert(CB.currentrecord)) MSG1(MSG_SQL, CB.sqlcmd); -F(rmode) = MOD_UPDATE; +switch_mode(MOD_UPDATE); return 0; } +int Function::ftoggle() { +changed = CF.toggle(); +if (changed == KEF_CANCEL) return 0; +if (CB.update(CB.currentrecord, CF.sequencenum)) MSG1(MSG_SQL, CB.sqlcmd); +return changed; +} + +int Function::fincrement(int ival) { +changed = CF.increment(ival); +if (changed == KEF_CANCEL) return 0; +if (CB.update(CB.currentrecord, CF.sequencenum)) MSG1(MSG_SQL, CB.sqlcmd); +return changed; +} + int Function::fedit(int pos) { -int changed; changed = 0; -switch(F(rmode)) { +switch(CM) { case MOD_INSERT: case MOD_QUERY: if (pos == KEF_DEL) CF.clear(); else changed = CF.edit(pos); @@ -171,15 +194,15 @@ switch(F(rmode)) { case MOD_DELETE: break; } -if (F(rmode) != MOD_QUERY && changed) F(dirty) = 1; +if (CM != MOD_QUERY && changed) F(dirty) = 1; return changed==KEF_CANCEL ? 0 : changed; } int Function::fexit() { - switch(F(rmode)) { + switch(CM) { case MOD_QUERY: case MOD_UPDATE: /*MSG(MSG_CLEAN);*/ break; - case MOD_INSERT: LK = create_record(); break; + case MOD_INSERT: if (F(dirty)) create_record(); break; case MOD_DELETE: LK = destroy_record(); break; } notrunning = -1; @@ -187,7 +210,7 @@ return 0; } int Function::fquit() { - switch(F(rmode)) { + switch(CM) { case MOD_QUERY: case MOD_UPDATE: break; case MOD_INSERT: @@ -201,7 +224,7 @@ int Function::enter_query() { F(b[1]).clear(); CB.currentrecord = 0; F(dirty) = 0; -F(rmode) = MOD_QUERY; +switch_mode(MOD_QUERY); return 0; } @@ -209,7 +232,7 @@ int Function::execute_query() { if (F(b[1]).select()) MSG1(MSG_SQL, CB.sqlcmd); else { if (CB.q->rows > 0) { CB.currentrecord = 1; - F(rmode) = MOD_UPDATE; + switch_mode(MOD_UPDATE); } else { return insert_record(); } @@ -218,7 +241,7 @@ return 0; } int Function::delete_record() { -F(rmode) = MOD_DELETE; +switch_mode(MOD_DELETE); return 0; } @@ -229,14 +252,14 @@ if (deleprompt) s = MSG(MSG_DELASK); if (yesno(s)) { F(b[1]).destroy(CB.currentrecord); clear_record(); -} else F(rmode) = MOD_UPDATE; +} else switch_mode(MOD_UPDATE); return 0; } int Function::clear_record() { CB.q->splice(-CB.currentrecord); if (CB.currentrecord > CB.q->rows) CB.currentrecord = CB.q->rows; -if (CB.q->rows) F(rmode) = MOD_UPDATE; else enter_query(); +if (CB.q->rows) switch_mode(MOD_UPDATE); else enter_query(); return 0; } diff --git a/runform/function.h b/runform/function.h index 5ce367d..5ca9e44 100644 --- a/runform/function.h +++ b/runform/function.h @@ -9,10 +9,14 @@ class Function { protected: private: int notrunning; + int changed; int trigger(int tid); int enter_the_form(); + int switch_mode(fmode mod); int fmove(int bi, int fi); int fmover(int ri); + int ftoggle(); + int fincrement(int ival); int fedit(int pos); int fexit(); int fquit(); diff --git a/runform/page.cpp b/runform/page.cpp index 0d80c82..8c38295 100644 --- a/runform/page.cpp +++ b/runform/page.cpp @@ -15,11 +15,26 @@ return 0; // must be rewritten for multiple pages int Page::maps(Qdata *qma) { -int i, r; +int i, r, y; +char *t, *p; for (i = 1; i <= qma->rows; i++) { r = qma->n(i, 1) - 1; if (r > NLINES) return 1; map[r] = qma->c(i, 2); + y = 1; + p = NULL; + // white the $nn_ pos markers + for (t=map[r]; y; t++) { + if (*t == '$') { + p = t; + } else { + if (p && (!(*t == '_' || *t == '.' || (*t >= '0' && *t <= '9')))) { + if (t - p > 1) while (p < t) *p++ = ' '; + p = NULL; + } + } + if (!*t) y = 0; + } } return 0; } @@ -42,7 +57,7 @@ static const char *rmodes[] = RMODENAMES; int Page::wait() { int i; char commit[16]; -switch (F(rmode)) { +switch (CM) { case MOD_QUERY: strcpy(commit, " Execute-Query"); break; case MOD_UPDATE: strcpy(commit, " Enter-Query"); break; case MOD_INSERT: strcpy(commit, F(dirty) ? " Insert-Record" : " Clear-Record" ); break; @@ -55,7 +70,7 @@ writef(0, 16, 0, 8, "%s", username); writef(0, 25, 0, 8, "%s", CB.table); writef(0, 34, 0, 9, "%s", CF.name); writef(0, 44, 0, 9, "%4d/%4d", CB.currentrecord, CB.q->rows); -writef(0, 54, COL_HEADER,6,"%s", rmodes[F(rmode)]); +writef(0, 54, COL_HEADER,6,"%s", rmodes[CM]); writef(0, 61, COL_HEADER,3,"%s", (char*)(insertmode ? "Ins" : "Rep")); //ites(0, 67, "runform-"); //itef(0, 70, 0, 4, "%4d", F(lastcmd)); diff --git a/runform/runform.h b/runform/runform.h index df95ba8..8128e39 100644 --- a/runform/runform.h +++ b/runform/runform.h @@ -1,7 +1,7 @@ // constants macros and central procedures enum odrvr { ODR_SQLITE, ODR_ORACLE, ODR_PG, ODR_MYSQL, ODR_SQLSRVR, ODR_ADS, ODR_UNKNOWN }; enum fmode { MOD_INSERT, MOD_QUERY, MOD_UPDATE, MOD_DELETE }; -enum ftype { FTY_ALL, FTY_CHAR, FTY_INT, FTY_FLOAT, FTY_DATE }; +enum ftype { FTY_ALL, FTY_CHAR, FTY_INT, FTY_FLOAT, FTY_DATE, FTY_BOOL }; #define RMODENAMES { "Insert", "Query ", "Update", "Delete" } #include "../version.h" @@ -44,11 +44,12 @@ enum ftype { FTY_ALL, FTY_CHAR, FTY_INT, FTY_FLOAT, FTY_DATE }; #define t(target) target, sizeof(target) #define F(method) f.method -#define CB f.b[f.curblock] -#define CF f.l[f.curfield] -#define LK f.lastkey -#define MSG(n) f.p[0].message(n, NULL) -#define MSG1(n,c) f.p[0].message(n, c) +#define CM F(rmode) +#define CB F(b)[F(curblock)] +#define CF F(l)[F(curfield)] +#define LK F(lastkey) +#define MSG(n) F(p)[0].message(n, NULL) +#define MSG1(n,c) F(p)[0].message(n, c) extern Function u; extern int yesno(int c); diff --git a/runform/screen.cpp b/runform/screen.cpp index bc446f6..b4f331c 100644 --- a/runform/screen.cpp +++ b/runform/screen.cpp @@ -179,21 +179,23 @@ switch(ch) { return ch; } -int Screen::sedit(char *toe, int pos, ftype fty) { +int Screen::sedit(char *toe, int pos, ftype fty, int len) { char *legal; char legalall[] = ""; char legalint[] = "0123456789"; +char legalbool[] = "01"; char legalfloat[] = "0123456789."; char legaldate[] = "0123456789./-"; switch (fty) { case FTY_DATE: legal = legaldate; break; case FTY_INT: legal = legalint; break; + case FTY_BOOL: legal = legalbool; break; case FTY_FLOAT: legal = legalfloat; break; case FTY_CHAR: case FTY_ALL: default: legal = legalall; } -return F(p[0]).getst(0, 0, 80, EDITCOLOR, toe, pos, legal, SMLSIZE, NULL); +return F(p[0]).getst(0, 0, 80, EDITCOLOR, toe, pos, legal, len, NULL); } /* Allows the user to edit a string with only certain characters allowed @@ -324,7 +326,7 @@ while (!done) { /* input loop */ if (len < max || pos < len-1) { pos++; sx++; - } + } else {c = KEY_ENTER; done = TRUE;} } else beep(); /* no valid char */ } else done = TRUE; } /* switch */ diff --git a/runform/screen.h b/runform/screen.h index edcfb9b..34fd5db 100644 --- a/runform/screen.h +++ b/runform/screen.h @@ -18,7 +18,7 @@ class Screen { void toggle(); int wgetc(); int getkb(); - int sedit(char *toe, int pos, ftype fty); + int sedit(char *toe, int pos, ftype fty, int len); int getst(int y, int x, int width, int att, char *s, int pos, char *legal, int max, int *chg); int ysiz; int xsiz;