Παρασκευή 28 Ιανουαρίου 2022

Version 11

1. Image Control like a console for input and output:

The Ver11 in Info file has this code:

declare form1 form
declare image1 Image form form1
METHOD image1,"MOVE", 1000,1000, 6000, 3300
With image1,"KeyEvent", true, "Enabled" as enabled, "tabstop", true, "showcaret" as caret, "default", true
enabled=true
caret=true
layer image1 {
form 20, 10
CLS 4,0 : PEN 15
DOUBLE
REPORT 2, "Example"
NORMAL
CLS #336633, 2
REFRESH 10
}
function form1.click {
enabled= not enabled
if enabled then method image1, "GetFocus"
}
bufall=stack
buf$=""
function getline$() {
if stack.size=0 then exit
shift 1,-stack.size
exp$=""
while not empty
exp$+=letter$
end while
=exp$
}
function image1.lostfocus {
layer image1 {
cursor 0, row:print over $(0), getline$(buf$, ! stack(bufall))
}
}
function image1.gotfocus {
layer image1 {
cursor 0, row:print over $(0), buf$;
}
}
function image1.keydown {
read new &k, &s
if s<>2 then exit
if k=86 then
local clip$=clipboard$
if clip$<>"" then keyboard clip$ : call local image1.keypress()
k=0:s=0
else.if k=67 then
local clip$=getline$(buf$, ! stack(bufall))
if clip$<>"" then clipboard clip$
k=0:s=0
end if
}
Def CheckSurrogateLead(w as integer)=uint(w)>0xD7FF and uint(w)<0xDC00
// HERE WE USE THE TAIL WHEN WE DELETE CHARS (SO WE CAN DELETE DOUBLE WORD CHARACTERS)
Def CheckSurrogateTrail(w as integer)=uint(w)>0xDBFF and uint(w)<0xE000
buf1$=""
function image1.keypress {
if not caret then caret=true
layer image1 {
LOCAL a$, onemore as boolean
a$=KEY$
DO
if a$=chr$(8) then
onemore=false
if len(buf$)>0 then
local w=chrcode(right$(buf$,1))
do
onemore=false
if CheckSurrogateTrail(w) then onemore=true
buf$=left$(buf$, len(buf$)-1)
until not onemore or len(buf$)=0
end if
if len(buf$)=0 and len(bufall)>0 then
stack bufall {read buf$}
if onemore then
buf$=left$(buf$, len(buf$)-1)
end if
cursor 0, row:print over $(0),buf$;
else
if pos>0 then cursor 0, row:print over $(0),buf$;
end if
else.if len.disp(a$)=0 and chrcode(a$)>32 or chrcode(a$)<0 then
// THIS IS FOR COMBINING_DIACRITICAL_MARKS LIKE CHRCODE$(0x301)
// COMBINING ACUTE ACCENT
// hold Alt press + 3 0 1 release Alt
buf$+=a$
cursor 0, row:print over $(0), buf$;
else.iF a$=CHR$(13) then
buf$=getline$(buf$, !bufall)
layer {print buf$:refresh}
cursor 0, row:print over $(0),buf$
print: buf$=""
else.if a$>=" " then
if len.disp(buf$)=width-1 then
stack bufall {push buf$}:buf$="": cursor 0, row:print over
end if
print a$; : buf$+=a$
end if
a$=KEY$
UNTIL a$=""
refresh
}
}
METHOD form1 "SHOW", 1


declare form1 NOTHING


The Image1.KeyPress is an event service function. The actual key retrieved using Key$ (which here not waiting for a key, as in pure M2000 console). We use a Do Until, loop to get all keys, before the end of service.

This is the first version, where we can write a big line and under the hood is a stack object, the bufall, which hold chunks of input line (those parts we didn't show). When we press Enter all chunks combined using the getline$() function. The statement: buf$=getline$(buf$, !bufall) call the getline$ and push on getline$() stack on top buf$ and under all items from bufall (so !bufall just send items from bufall which is a stack to functions stack, and this is fast, because each item in stack has one pointer to copy, the bufall get empty after that).

Some times we change the focus of Image1 so we get the lostFocus event and reset the line from the beginning (first character at the left of "console", or we say left justify). Because we have to prepare the final string, immediatetly, we use this getline$(buf$, ! stack(bufall)) where stack(bufall) return a copy of bufall, so the bufall stack remain intact. The ! operator used for arrays (tuple) and stacks, but for stacks this retrieve all items, and leave them empty.

About Stacks and Arrays.

We can make stacks from arrays, and arrays from stacks. The actual type of stack is mStiva (a com object) and for array (tuple) is mArray. 

a=stack((1,2,3))
? type$(a)="mStiva"
? a
? stackitem(a,2)=2
b=array(stack:=1,2,3)
? type$(b)="mArray"
? b
? array(b, 1)=2

Variables a and b are pointers to objects. There is another interface for arrays, which used with identifiers with parenthesis. These arrays are like values. We have to define them using Dim (or Local or Global), We can use up to 10 dimensions, and we can set bounds like this A(1 to 10), B(-4 to -3, 100 to 103) . We can use pointers to arrays like the c variable in the following example:

Dim A(10)=1, B()
B()=A()
B(3)+=10
c=B()
c++
? B(3)=12, B(0)=2, A(0)=1

So c++ add one in each numeric item of B(). Also pointers to arrays handle the array as one dimension. Arrays expand from the first dimension (preserving items). The k variable now is a pointer to k(), but read items as one dimension. Using array() we can use the true dimensions. We can change dimensions with another Dim, and if we place new items these will be of type Empty.

dim K(3,2)
k(0,0)=1,2,3,4,5,6
? K()
dim K(4,2)
k(3,0)=7, 8
? K()

k=k()
? k#val(7)=8

Print array(k, 0,0)


2. Splitter Control using a Listbox

We can make a splitter control using a listbox and proper configuration, through properties and methods, and use of proper events.

In the Info file (info.gsb) included in setup file the new mEditor which utilize the splitter as vertical splitter between the editor's editbox and the help editbox (you have to reload to user directory, using Dir AppDir$ : Load Info  and then press F1 to save it to M2000 user directory - also this change the current directory to user directory). Open the mEditor source using Edit mEditor statement in M2000 console.


We can make a fake splitter control using events from form, but the new splitter can show the new split without actually do the split (without resizing any controls we have to split), until we get the mouseup event. Also we can change the color at the moving stage, and restore it at the final mouseup event. The splitter configure to not included as a tabstop (so pressing TAB and Shift TAB we don't choose it). I am thinking to implement a keyboard control for splitter (so then the TAB Stop  is usefull)..


We can make three types of splitter, horizontal, vertical and both. Because a ListBox can be place on top of an Image control, we can use splitter to split controls on an Image (container).

A listbox can use the included logic for moving, either as HeaderOnly (so we use it as a simple frame, with a color and perhaps some characters as icons, for title), or we can with a list. We can configure to move the control or the container of control. The form's caption is on glis4.cls user control (the same under Listbox) which use this logic to move the form.

From mEditor source, this is the code to configure the Listbox, named Handle1 on form NotePad.
Declare Handle1 Listbox Form NotePad

Because the splitter we want to be visible only if HelpPad (the EditBox for help) is visible, we need to link property "Visible" to handle1.visible (dots can be used for variable names). The handle1.visible is not a boolean variable, so we cant do this handle1.visible~ to invert the value, because it is a PropReference object type (an object to hard link the property with an identifier). This object for this property has a boolean value. Any non zero number converted to True, and zero number converted to False. By default in M2000, True is not boolean, has value -1, and False hase value 0. Comparisons in M2000 return boolean type.

If we don't want a control to be visible when the form shown the first time, we have to lock it. The unlocked control's visible property change from false to true by the form at the show method of form. When a control is not visible tabstop not used. For Handle1 we want this happen always, so we place false in "tabstop" property.
For handling the title back color we link mcolor, and for the wanted return value we need the handle1.top (linked to "top" property, is a single float number),


With Handle1, "Visible" as handle1.visible, "locked" as handle1.locked, "headeronly", true, "tabstop", false, "TitleBackColor" as mcol, "top" as handle1.top
Method Handle1, "UseVerMove"
mcol=#FFA000
handle1.locked=true
handle1.visible=false

So we start using Locked=true, visible=false.

Before the opening of NotePad we have to call the resize event function by code, using this:

Call Local Notepad.Resize()

The Call Local place the same namespace as the current one, so from Notepad.Resize the interpreter think is running in module not in function, so the Call Local means call with same scope as the current. Although the scope is the same (as namespace), actually every new identifier erased at the return from call. Because events also called using a call like call local (except the stack of values is new), we have always use New identifier before we read values from stack, to make new identifiers (to shadow identifiers with same name in same scope, which prevent from altering values). Also if we want variables for local use, we can defined using Local statement. Also if we want a new link for a property, we can use  as new.

So this is the resize function (as event service function):

Function Notepad.Resize {
Local FromHelp=false
if match("N") then Read FromHelp
Layer NotePad { Cls Color(255, 160, 0) ,0}
local tHeight1=theight*2
Local free=NP.Height-tHeight1-twipsX*3
local oldhelpshow=helpshow
If NP.height>1800 Then {
If helpview Then
if oldhelpshow else
Method Handle1, "move",twipsX*3, tHeight1+free-free/3-twipsX*3, NP.Width-twipsX*6,twipsY*3
end if
if handle1.locked then handle1.locked=false
local third=handle1.top-tHeight1
if third<twipsX*10 then third=twipsX*10
if free<third then third=free/3
Method Pad,"move", twipsX*3, tHeight1, NP.Width-twipsX*6, third
third+=tHeight1
Method Handle1, "move",twipsX*3, third, NP.Width-twipsX*6,twipsX*3
Method HelpPad,"move", twipsX*3, third+twipsX*3, NP.Width-twipsX*6,free-third-twipsY*3+tHeight1
handle1.visible=true
helpshow=true
Method HelpPad, "resize"
Else
helpshow=false
handle1.visible=false
Method Pad,"move", twipsX*3, tHeight1, NP.Width-twipsX*6, NP.Height-tHeight1-twipsY*3
End If
Method Pad,"Resize"
}
}

Because we want the color of splitter control (the Handle1, a listbox used as splitter) we have to handle these events:

Function Handle1.ValidMove {
Drop
Read New &Y
mcol=7
if y<tHeight+1000 then y=tHeight+1000 : exit
if y>NP.Height-tHeight then y=NP.Height-tHeight
}
Function Handle1.MouseUp {
Call Local Notepad.Resize()
mcol=#FFA000
}

The ValidMove get two byref values, we drop one and we use only the Y (vertical move). So we can stop the move by handling this Y value, if Y get out of bounds we like.

When the move stopped releasing the mouse button we get the MouseUp event and we call the resize event service function of Notepad (the form), plus we restore the color. The #77A000 is the M2000 orange color.

The Handle1 also change Y when we resize the form Notepad, but not always. See line:

if free<third then third=free/3
in Notepad.resize function

Also for this Notepad, I add a new item in Edit menu, with F1 accelerator key for changing Wrap mode.


Δεν υπάρχουν σχόλια:

Δημοσίευση σχολίου