to part (1)
Above formula (the Newton binomium) was typed in just a few seconds.
All text is editable, -undo- works.
Some more editing functions like cut and paste have to be implemented however.
My math editor rests on these pillars:
Own component with rows or columns of menu buttons.
Used for menu choices.
Description is found [ here ]
Is an extension of TBitmap with clipping rectangle, improved floodfill,
improved stretchrect, dash-dot lines of multipixel penwidths, lines with arrow points.
Description is found [ here ]
Own class with scalable fonts (3 types), build with ASCII and Greek characters,
plus some geometrical symbols.
Description is found [ here ]
Own unit/Class with operations on tree graphs.
Also provides UNDO mechanism.
Description is found [ here ]
There are 3 XBitmaps each of 760 x 1080 pixels in 32bit color format.
map2 - map1 + text
map3 - map2 + trial drawings during construction
Text is erased by copying part of map1 to map2.
Text is added to map2.
Map3 holds drawings under construction.
Graphic elements (lines, circles, color fillings) are erased by copying part of map2 to map3.
The final drawing (after mouse-up) is then made in map1.
Results are visualized by copying map3 to a paintbox on the main form.
This paintbox has a lesser height that the bitmaps, a scrollbar selects a part of map3.
Associated with each map1,2,3 are a Trect rectangle with a mapfull flag of type boolean.
XFont and XBitmap classes have a property modrect.
This is the rectangular part of the bitmap that was modified during the last operation.
This rectangle is merged with one or more rect1,2,3 to enable a copy between the bitmaps.
Only modified parts of a bitmap are copied.
Operations on rectangles
Unirect(r1,r2) : rectangle r2 is combined with r1 so r1 surrounds r2.
Interrect(r1,r2) : r1 is limited to the part overlapping r2.
Packrect(x1,y1,x2,y2 : smallInt) : Trect : build Trect from x,y values after sorting.
mapfull flags are false if the rectangle holds no information. (no modified parts)
In this case a unirect(r1,r2) equals r1 := r2 after which the flag is set true.
All variables for coordinates are of type smallInt.
This allows for coordinates outside the maps.
The maximal number of pages is 30.
The y-coordinate of an element (divided by 1080) denotes the page number.
The menu system
Above pictured is the main menu.
An enumerated variabele (which simply is a name attached to a sequential number) holds the active button.
type TMainbutton = (mbDoc,mbPage,mbEdit,mbFrame,mbText,mbGraph,mbSymbol,mbGeo, mbInfo,mbhelp,mbOff); .... var mainmenubutten : TMainbutton;Activating a button opens submenu's with new choices and buttons for property selections.
type TFontProp = record nr : byte; //font number 1..3 height : byte; base : byte; style : byte; color1 : DWORD; //foreground color2 : DWORD; //background end; ...... var fontprops : TFontProp; fontDef : array[1..maxFontDef] of TFontProp; fontdefcount : byte; //top of arrayProperty selection xxx is stored in a variable named xxxProp.
Text elements hold an index into a table which holds the font properties.
For fonts this is array fontDEF, in general: array xxxDEF[ ].
Text element[element nr].p1 is index into the font table.
There are many more property variables and xxxDEF tables.
These properties are once entered in the table and are never removed.
Adding new properties to the xxxDEF tables is done by procedures called registerXXX(....)
Control flowKeyboard and mouse generate events.
Menuvariables controlling a case statement route these events to the proper procedures
to handle text editing or drawing of graphical symbols.
procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); begin case key of VK_Next : begin nextpage; key := 0; end; VK_Prior : begin previouspage; key := 0; end; end; if key <> 0 then begin keyEvent := kbDown; keyword := key; keyShift := shift; case mainmenubutton of mbText : textKeyEvent;//--> text control unit mbEdit : case editbutton of ebGraph : ; //--> graph edit unit ebText : texteditKeyboardEvent; //--> textedit unit; end; end;//case key := 0; end;//if end;Code above shows that procedures for the editing of graphical symbols is under construction.
This is a very simple example: other similar procedures are of far greater length.
Handling textAll text elements (characters, macro's, lines) are of type:
type Telementtype = (elFree,elblock,elLine,elChar,elMacro,elYTab, elReserved); TElSet = set of TElementType; TElement = record elType : TelementType; elCode : byte; p1 : byte; p2 : byte; p3 : smallInt; x : smallInt; //position relative to parent y : smallInt; //.. width : smallInt; height : smallInt; end; ..... var element : array[1..maxelement] of TElement; //frame,line,macro,charThe relationship between these elements is recorded here:
type TLinks = record parent : dword; child : dword; next : dword; previous : dword; end; ..... var Links : array[1..maxelement] of TLinks;The Element and Link arrays have a 1:1 relation.
The Link array cannot be read or written directly:
only procedures and functions may be called to perform tree operations.
The tree system also provides for UNDO.
Contrary to properties, elements may be removed from the element array to be reused.
This happens if an element was deleted long ago and the maximum depth of the UNDO stack was reached.
In this case the latest UNDO stack entry was purged, no UNDO is possible for this deleted element
and the Tree system notifies element destruction, so the element can be set free.
XTree cannot change the Element[ ] array because Xtree is not concerned about the type of element.
Deleting an element only separates the links from the tree.
UNDO reconnects the links.
Text controlText control is handled by the textcontrol_unit.
The first concern here is cursor movement.
Cursor movement within lines:
This is the processing of left- right cursor key events.
Within a line, characters may vary in height, color and style.
The cursor (record) has the property posR:Boolean which indicates the place of the cursor:
left or right of the element.
The cursor adjusts it's size to the element of it's position.
If posR = true then
- a left key event will place the cursor left of the same element
- a right key event will place the cursor right of the next element
- at the last line element, the event is passed to the parent.
If posR = false then
- a left key event will place the cursor left of the previous element.
- een right event will place the cursor right of the same element.
- at the first element the event will be passed to the parent.
The parent of a character always is a line.
The parent of a line may be a macro or a block.
In the case of a macro cursor movement is handled by macro type specific procedures.
Macro's receiving cursor key events will redirect these events to one of their (line) children.
Also there exist cursor Up- and Down events.
These events are redirected to the parent because the cursor leaves the line or macro.
So, a lot of code is needed for cursor movement.
The cuNAV record holds control data for the cursor route in lines or macro's having 1, 2, 3 or more (line) children.
type TCursordirection = (cdLeft,cdRight,cdUp,cdDown); TCursorProc = (cpKB,cpM2P,cpM2C); //message to Parent / Child TCursorNavigation = record cdir : TCursordirection; cproc : TCursorProc; destEL : dword; //destination element srcEL : dword; //source element end; ... var cuNAV : Tcursornavigation;Here a simple piece of code where cuNAV starts it's journey:
(keyEvent was stored before by the form1.Keydown event)
.... case keyEvent of kbDown : begin case keyword of VK_LEFT : begin cuNAV.cdir := cdLeft; cuNavigate; end; VK_RIGHT : begin cuNav.cdir := cdRight; cuNavigate; end; VK_UP : begin cuNav.cdir := cdUP; cuNavigate; end; VK_DOWN : begin cuNav.cdir := cdDown; cuNavigate; end; ....steering the cursor further....
procedure CuNavigate; //switch to element type //use cuNAV var mac : TTextbutton; framecode : TFrameButton; el : dword; m23 : boolean;//root,symbol types:>1 child begin el := cuNAV.destEL; case element[el].elType of elChar : NavChar; elLine : Navline; elMacro : begin mac := TTextbutton(element[el].elCode); m23 := (textprops[mac].code and 1) = 1; case mac of txtPower, txtIndex, txtParenth : NavMacro1; txtLimit, txtPowInd, txtFraction, txtOver : NAVmacro2; txtRoot : if m23 then NAVroot else NAVmacro1; txtPowerLine : NAVpowerLine; txtIndexLine : NAVIndexLine; txtPowIndLine : NAVpowIndLine; txtSymbol : if m23 then NAVmacro3 else NAVmacro1; txtVector : NAVmacroVector; end;//case end; ....For the case of a single line macro:
procedure NavMacro1; //single line macros begin case cuNAV.cproc of cpKB : begin case cuNAV.cdir of cdLeft : if textcursor.posR then gotoChild else gotoParent; cdRight : if textcursor.posR = false then gotochild else gotoParent; cdUp, cdDown : gotoParent; end; end; cpM2P : begin case cuNAV.cdir of cdLeft : begin textcursor.posR := false; setcursorOnElement(cuNAV.destEL); end; cdRight : begin textcursor.posR := true; setcursorOnElement(cuNAV.destEL); end; cdUp, cdDown : gotoParent; end; end; cpM2C : begin case cuNAV.cdir of cdLeft : begin textcursor.posR := false; gotochild; end; cdRight : begin textcursor.posR := false; gotoChild; end; cdUp, cdDown : gotoXmatchChildN(1); end; end; end; end;Procedure gotoXmatchChild tries to place the cursor on a line element having the same X position,
so moves the cursor vertically up or down.
The GotoChild procedure:
procedure GotoChild; begin with cuNAV do begin srcEL := destEL;//destiation becomes source cproc := cpM2C;//message to child getChild(destEL,srcEL);//destination is child end; cuNavigate; end;cpKB means the keyboard was the originator.
cpM2C means cursor was send to Child.
Also there is cpM2P, cursor was send to parent.
An element has to know who was calling, where the cursor comes from.
Text handling unitsThree units take care of text procedures:
- text paint : text and macro's painting
- text calculation
For each type (elBlock,elMacro,elLine,elChar) there are three specific procedures:
paint : painting of element.
recalculate : calculate width, height and positions of the children
This is the task of the parent.
The advantage of this method is that elements may be moved without caring about their contents.
The (x,y) position of the children is relative to the parent.
If the size of an element changes due to inserts or deletes, the parent is informed.
Then the parent recalculates it's width and height together with the position of the children.
In the case of changes the parent calls it's parent.. and so on.
Finally, the last recalculated element, together with it's children, are repainted.
Note: children are painted automatically.
So, the specific paint procedures per macro only take care of graphical symbols like
fraction lines, root or sigma symbols.
The macro paint procedures also calculate these graphic symbol dimensions.
The core is this small procedure processing element changes:
procedure processELchange(el : dword); //element reports change to it's parent el //el can be macro,line,column (not char or block) //purpose is //1. to call for recalculation of parents //2. set area to be erased //3. set element that needs repainting var oldw,oldh : smallInt; Done : boolean; r1 : Trect; begin eraseflag := false; spaceflag := false; repeat repaintELnr := el; oldw := element[el].width; oldh := element[el].height; r1 := getElementRect(pageNr,el); updateRect(eraseflag,eraseRect,r1); recalculateElement(el); if (oldw <> element[el].width) or (oldh <> element[el].height) then begin getParent(el,el); Done := element[el].elType = elBlock; end else Done := true; until Done; end;oldw : old width of element
oldh : old height
eraseRect : rectangle to be erased (old element size)
getElementRect : calculates rectangle of element relative to page
updateRect : merges rectangles
recalculateElement( ): contains big case statement to select the proper element procedure.
Element Insert or Delete procedures are element type independent.
Here I conclude this part 2 description of my math editor.
More work has to be done such as copy-paste, storing and opening data, graphic symbol editing
and solving many, many errors showing up on the way.
So far, my intention was to describe the general structure.
Finally I add a formula which was typed in a few seconds: the vector dot product