Second life for Clipper Application by Aleksandar Stefanovic

Alaska Hybrid Start for Clipper programmers
Clipper migration can be easy if you know how to do that and where to start.

1

Second life for Clipper Application by Aleksandar Stefanovic Windows applications differ from DOS applications. Windows provides more then thousand functions you can call. There is different program structure. Remember, from the user’s point of view program seems to run in bordered rectangle (window). When we start application a window appears, when we close the window, the programs stops. When the user made a click in the window, application responds. You can have more then one window active at the same time, window can be visible or hidden by other windows, window need procedure to repaint themselves. Remember what Microsoft want at start. Software that is easy to use. If someone learned one windows application, he or she can use many others. WHY? Standard GUI elements, they are already familiar with it. Standard windows have borders, title bar, menu bar, drop down menu, control box, system minimize and maximize buttons, scroll bars… Windows part inside windows borders without title bar and menu bar we call client area. There we display information to the user. In clipper DOS application we use screen for output and we have full control of it. On windows, this is another story. We can use something that ALASKA call HYBRID system or we can use full GUI system. Many programmers said that it is better to move on to full GUI. For some applications I believe that they are right but sometimes we DON’T NEED that. I love HYBRID system. Why they call it this way? Because you have window who act like old DOS screen, you can control it by row and colons. All clipper display functions work well with that kind of windows. Inside them you can use all GUI elements but… This application can look nice but remember, if you want OLD way of application control and if you thing that your users can and will accept it just do it. On the other hand if you have time to separate display logic from your application you can try to migrate to full GUI application. Some accounting applications (most clipper ones are like that) don’t need that event driven logic and you can only try to make nicer screens. You have to decide what to do and how you will make migration to windows world. At start HYBRID model is perfect. There is some limitation and you must live with that. So, for clipper programmer first thing that is how to have window for input and output which act like old DOS screen. In description of Alaska was said that it is 100% clipper compatible. We can use our clipper display functions and commands. Question is not how but where. In special window that is created using Alaska XbpCrt class. This is window just like other but we have row and column in it and we can use it in “old” DOS way. Nice thing is that we can create it before our let say clipper application start. For this we will place it in special Alaska procedure APPSYS. This procedure is executed before our MAIN function. We can write it once and we can forget about it.

2

Second life for Clipper Application by Aleksandar Stefanovic Lets write this procedure. ////////////////////////////////////////////////////////////////////// // APPSYS.PRG ////////////////////////////////////////////////////////////////////// #include "appevent.ch" #include "xbp.ch" #define DEF_ROWS #define DEF_COLS 40 120

PROCEDURE AppSys() LOCAL oCrt LOCAL aSizeDesktop, aPos local DEF_FONTHEIGHT:=16 local DEF_FONTWIDTH:=8 public maincrtobj public maincrtwin aSizeDesktop := AppDesktop():currentSize() aPos := { (aSizeDesktop[1]-(DEF_COLS * DEF_FONTWIDTH)) /2, ; (aSizeDesktop[2]-(DEF_ROWS * DEF_FONTHEIGHT)) /2 } Do Case Case aSizeDeskTop[1] = 1024 Def_FontHeight := 18 Def_FontWidth := 10 Endcase oCrt := XbpCrt():New ( NIL, NIL, aPos, DEF_ROWS, DEF_COLS ) oCrt:FontWidth := DEF_FONTWIDTH oCrt:FontHeight := DEF_FONTHEIGHT oCrt:title := "This is Title" oCRT:icon := ID_AS_ICON oCrt:FontName := "Alaska Crt" oCrt:sysmenu:=.f. oCrt:visible:=.t. oCrt:automark:=.f. oCrt:Create() oCrt:PresSpace() SetAppWindow ( oCrt ) maincrtobj:=oCrt maincrtwin:=oCrt:gethwnd() RETURN

3

Second life for Clipper Application by Aleksandar Stefanovic As you see we define numbers for rows end columns. In DOS time we have limited number of screen sizes but in ALASKA we have bigger freedom. We start from windows desktop. We can get information about screen size (in pixels) and use it to place our window on it. We can use different font size but here is first limit. We can’t use all windows fonts. Why? Simple, we need fonts where we have the same size for all characters. We have rows and columns. We can try to use other fonts in this crt window but we will not get error if Alaska can’t display it in crt window. Under windows we don’t have access to hardware divices but we can use windows functions to access them. We can get identification and sometimes we call it handlers. AppDesktop() function will give us desktop handler. Now we can use function currentSize() and we will get array with X and Y size of desktop. Alaska don’t have support for calling windows functions to change screen resolution, we can only detect current screen size and act on it. If this size is 1024 and 768 we can create “DOS” window that has 40 rows and 120 columns with font size 18 by 10 pixels. This is nice if we have in our old DOS clipper applications support for only 80 by 25 screen size, we can use this extra space for something else (pictures for example) and our screen will not be monotony black screen. How to create this class? This way: First we must define it. oCrt := XbpCrt():New ( NIL, NIL, aPos, DEF_ROWS, DEF_COLS )

In variable oCrt we have handler for our new class instance. Function that actually create our class is method function NEW() with parameters XbpCrt():new( [<oParent>] , [<oOwner>] , [<aPos>] , ; [<nRowCount>], [<nColCount>], [<cTitle>], ; [<lVisible>] ) --> oXbpCrt We will create instance of our window. Parent and owner are something that every window have, If we put NIL for these parameters we want DESKTOP to be parent and no owner. When we create this class we can define rows and columns numbers, title for this window and is it visible or not. Now we can start with 3 files, project.xpj, appsys.prg and testcrt.prg

4

Second life for Clipper Application by Aleksandar Stefanovic Project.xpj [PROJECT] COMPILE = xpp COMPILE_FLAGS = /q /l DEBUG = yes GUI = yes LINKER = alink LINK_FLAGS = RC_COMPILE = arc RC_FLAGS = /v VERSION = 2.0 TESTCRT.XPJ [TESTCRT.XPJ] testcrt.exe [testcrt.exe] testcrt.prg appsys.prg testcrt.obj appsys.obj appsys.prg Appsys listing ////////////////////////////////////////////////////////////////////// // APPSYS.PRG ////////////////////////////////////////////////////////////////////// #include "xbp.ch" PROCEDURE AppSys() #define DEF_ROWS 40 #define DEF_COLS 120 LOCAL oCrt LOCAL aSizeDesktop, aPos local DEF_FONTHEIGHT:=16 local DEF_FONTWIDTH:=8 public maincrtobj public maincrtwin aSizeDesktop:=AppDesktop():currentSize() Do Case Case aSizeDeskTop[1] = 1024 Def_FontHeight := 18

5

Second life for Clipper Application by Aleksandar Stefanovic Def_FontWidth := 10 Endcase oCrt := XbpCrt():New ( NIL, NIL, aPos, DEF_ROWS, DEF_COLS ) oCrt:FontWidth := DEF_FONTWIDTH oCrt:FontHeight := DEF_FONTHEIGHT oCrt:title := "Title for our application" oCrt:FontName := "Alaska Crt" oCrt:Create() oCrt:PresSpace() SetAppWindow ( oCrt ) maincrtobj:=oCrt maincrtwin:=oCrt:gethwnd() RETURN Testcrt listing: ////////////////////////////////////////////////////////////////////// // // TESTCRT.PRG // ////////////////////////////////////////////////////////////////////// #include "Appevent.ch" #include "Xbp.ch" PROCEDURE Main LOCAL nEvent DO WHILE nEvent <> xbeP_Close nEvent := AppEvent( @mp1, @mp2, @oXbp ) oXbp:handleEvent( nEvent, mp1, mp2 ) ENDDO RETURN

6

Second life for Clipper Application by Aleksandar Stefanovic When we start PBUILD we will get testcrt.exe and now we have active window for our application like old DOS one.

Let stop for a moment and learn how we can change some parameters of our window. Properties for XbpCrt are AlwaysOnTop, border, clipChildren, closeable, fontHeight, fontWidth, fontName, gridMove, icon, minMax, SysMenu, takList, title, titleBar,visible, autoFocus. AutoMark, DropFont. DropZone, helpLink, maxCol, maxRow, mouseMode, modalResult, asyncFlush, toolTipText, useShortCuts, xSize, ySize. Don’t be afraid of this, all of them have default values so you don’t need to change it. But sometimes if you need or like you can set it to values you want. Creating, reconfigurating or destroying our window with these methods. :create( [<oParent>] , [<oOwner>] , [<aPos>] , ; [<nRowCount>], [<nColCount>], [<cTitle>], ; [<lVisible>] ) --> self

7

Second life for Clipper Application by Aleksandar Stefanovic :configure( [<oParent>] , [<oOwner>] , [<aPos>] , ; [<nRowCount>], [<nColCount>], [<cTitle>], ; [<lVisible>] ) --> self :destroy() there is more methods that you can use to get some values or to change them :currentPos() :currentSize() :showModal() :getFrameState() :getModalState() :hide() :isVisible() ;menuBar() :presSpace() :setFont() :setCompoundname() :setFrameState() :setModalState() :setPointer() :setTrackPointer() :setPos() :setSize() :setTitle() :show() :toBack() :toFront() If you want you can control and act on mouse events using code blocks. You can define events for: enter(), leave(), lbClick(), lbDblClick(), lbDown(), lbUp(), mbClick(), mbDblClick(), mbDown(), mbUp(), motion(), rbClick(), rbDblClick(), rbDown(), rbUp(), wheel(). How you can use these events? Declare function MyMouseEnter() and MyMouseLeave() Function MyMouseEnter(aPos) @ 1,1 say “Mouse enter” return (NIL) Function MyMouseLeave() @ 1,1 say “Mouse lieve” return (NIL)

8

Second life for Clipper Application by Aleksandar Stefanovic We will define code blocks Local block_me:={|s|MyMouseEnter(s)} Local block_ml:={||MyMouseLeave()}

Now we must make connection with our window and these functions: oCrt:enter:=block_me oCrt:leave:=block_ml All these function we can put in our TESTCRT.PRG. This is only demonstration, but you have to use Setmouse(.t.) And oCcrt:setTrackPointer(.t.) In testcrt module we don’t have accees to oCrt but in APPSYS we define public variable maincrtobj so you must use it. New our testcrt looks like this #include "Appevent.ch" #include "Xbp.ch" PROCEDURE Main LOCAL nEvent local block_me:={|s|MyMouseEnter(s)} local block_ml:={||MyMouseLeave()} maincrtobj:enter:=block_me maincrtobj:leave:=block_ml SetMouse(.t.) maincrtobj:setTrackpointer(.t.) DO WHILE nEvent <> xbeP_Close nEvent := AppEvent( @mp1, @mp2, @oXbp ) oXbp:handleEvent( nEvent, mp1, mp2 ) ENDDO RETURN Function MyMouseEnter(aPos) @ 1,1 say "Mouse Enter"

9

Second life for Clipper Application by Aleksandar Stefanovic return (NIL)

Function MyMouseLeave() @ 1,1 say "Mouse Leave" return (NIL) Sometimes these functions DON’T WORK WELL. If we start our application and mouse is not inside our windows, when we move it Alaska trigger our functions for mouse detection but if application start and mouse is inside, NOTHING is happened. Nobody is perfect. Other support is much, much better. So, let’s find out some “nice” thing that we can do with our xbpcrt window. If we need to change window title we can use: Maincrtobj:setTitle(“New title”) Changing frame state with maincrtobj:setFrameState(XBPDLG_FRAMESTAT_MINIMIZED) maincrtobj:setFrameState(XBPDLG_FRAMESTAT_NORMALIZED) maincrtobj:setFrameState(XBPDLG_FRAMESTAT_MAXMIZED) Hide and show window with maincrtobj:hide() maincrtobj:show() Border can be set using maincrtobj:border:= one of these constants XBPDLG_NO_BORDER XBPDLG_SIZEBORDER XBPDLG_THINBORDER XBPDLG_DLGBORDER XBPDLG_RAISEDBORDERTHICK XBPDLG_RAISEDBORDERTHIN XBPDLG_RECESSEDBORDERTHICK XBPDLG_RECESSEDBORDERTHIN XBPDLG_RAISEDBORDERTHICK_FIXED XBPDLG_RAISEDBORDERTHIN_FIXED XBPDLG_RECESSEDBORDERTHICK_FIXED XBPDLG_RECESSEDBORDERTHIN_FIXED System menu (in left upper corner) can be disabled maincrtobj:sysmenu:=.f. Pushbuttons in right upper corner can be hidden with

10

Second life for Clipper Application by Aleksandar Stefanovic

Maincrtobj:closeable:=.f. Maincrtobj:minMax:=.f. I think that this is enough for start playing with CRT window. On the other hand if you ever want to use other windows API functions that need window handle you will get it using Maincrtobj:gethwnd() For now we have window where all text display functions work and we can add other things that Alaska simple call xbase parts. Now we can go on. What most window applications have? Menu. Now we will add menu to our window. In our application we will add: AS_MENU( SetAppWindow():menuBar() ) We will define menu options in separate function as_menu. function AS_MENU( oMenubar ) LOCAL oMenu oMenu := XbpMenu():new( oMenuBar ) oMenu:title := "~System" oMenu:create() oMenu:itemSelected := {|nItem| MenuDispatcher( 100+nItem ) } oMenu:addItem( {"~a) End" , NIL} ) oMenu:addItem( {NIL, NIL, XBPMENUBAR_MIS_SEPARATOR, 0} ) oMenu:addItem( {"~b) Indexing service", {||Indexserv()}} ) oMenubar:addItem( {oMenu, NIL} )

oMenu := XbpMenu():new( oMenuBar ) oMenu:title := "~AS Software" oMenu:create() oMenu:itemSelected := {|nItem| MenuDispatcher( 700+nItem ) } oMenu:addItem( {"~a) About application" ,{||aboutas()}} ) oMenubar:addItem( {oMenu, NIL} ) RETURN (NIL) Menu have to be build.

11

Second life for Clipper Application by Aleksandar Stefanovic First we must get handler for our menu oMenu := XbpMenu():new( oMenuBar ) Now we will add title oMenu:title := "~System" and we create it oMenu:create() and we will add items (one by one). First we define numeric values for menu items: oMenu:itemSelected := {|nItem| MenuDispatcher( 100+nItem ) } Then we define text and action oMenu:addItem( {"~a) End" , NIL} ) oMenu:addItem( {NIL, NIL, XBPMENUBAR_MIS_SEPARATOR, 0} ) oMenu:addItem( {"~b) Indexing service", {||Indexserv()}} ) oMenubar:addItem( {oMenu, NIL} ) We need this procedure to:

PROCEDURE MenuDispatcher( nSelection ) DO CASE CASE nSelection == 101 QUIT ENDCASE RETURN

First option will quit our application, then we have separator, then we add code block to start one our function. We then send this data to windows. This way we will define all application options. All applications have big number of parts, I believe that your clipper application is structured this way, so it will not be a big problem to make menu options to call your modules. To avoid problems, fist menu start with 100+nItem, second with 200+nItem… Don’t forget this. 12

Second life for Clipper Application by Aleksandar Stefanovic

We must have PRG file with function definition for our Indexserv() function and aboutas() Now we can add one more option for access our application modules. We will add pushbutton but with pictures in it. We need picture handle and we will load image from external file. oBMP1:=XbpBitmap():new() oBMP1:loadfile("pic1.jpg") oBMP2:=XbpBitmap():new() oBMP2:loadfile("pic2.jpg") We will add 3 more pictures for decoration oBMPdec0:=XbpBitmap():new() oBMPdec0:loadfile("dec0.jpg") oBMPdec1:=XbpBitmap():new() oBMPdec1:loadfile("dec1.jpg") oBMP1 and oBMP2 are handler for our pictures pushbutton (all pictures are 150x150 pixels. First we need handler for it. Xbpb := XbpPushButton():new() We will create it oXbpb:create( , , {10,450}, {150,150}) We create button 150x150 pixel at location 10,450. We use cargo variable oXbpb:cargo := .F When button is activated we will start our test1 module. oXbpb:activate := {|mp1, mp2, obj| obj:cargo := ! obj:cargo, ; DrawBMPall( obj,oBMP2,0 ),test1() } We have 2 pictures for our button and we want to act when mouse pointer is over it oXbpb:paint := {|mp1, mp2, obj| DrawBMPall( obj,oBMP1,0 ) } oXbpb:enter := {|mp1, mp2, obj| DrawBMPall( obj,oBMP1,0) } oXbpb:leave := {|mp1, mp2, obj| DrawBMPall( obj,oBMP1,0) } oXbpb:lbdown := {|mp1, mp2, obj| DrawBMPall( obj,oBMP2,0 ) } oXbpb:motion := {|mp1, mp2, obj| DrawBMPall( obj,oBMP2,0 ) } oXbpb:lbup := {|mp1, mp2, obj| DrawBMPall( obj,oBMP1,0 ) } oXbpb:drawmode:=XBP_DRAW_OWNER

13

Second life for Clipper Application by Aleksandar Stefanovic DrawBmpall(oXbpb,oBMP1,-1) Function for displaying pictures inside pushbutton is STATIC PROCEDURE DrawBMPall( oControl,oBitmap,nn) LOCAL oPS, oPS := oControl:lockPS() oBitmap:draw( oPS, {0 , 0}) oControl:unlockPS( oPS ) do case case nn==0 oBMPden0:draw(oPSg,{500,50}) case nn==1 oBMPden1:draw(oPSg,{500,50}) ENDCASE RETURN

What we do here? First we draw inside button. For this we need to use functions lock() and unlock() to get access to button surface. Other pictures we draw inside our main crt window. Notice that we don’t need to lock and unlock our main window, but when we want to draw inside xbase parts we must use it. Lock is must call to enable pushbutton drawing but don’t forget to unlock it. It is important not only for our application, windows OS need that. When mouse pointer move over pushbutton, we have picture change in button and in our main window. Nice and easy. When we click on picture we start application module. Until now we have explain XbpCrt, XbpMenu, XbpBitmap, XbpPushButton. Remember, when we draw inside CRT window we can erase it using standard CLS function. All drawing will be erased but pushbutton don’t. We can hide it using hide() method or destroy() when we don’t need it any more. Now our testcrt.prg is #include "Appevent.ch" #include "Xbp.ch" PROCEDURE Main LOCAL nEvent local oBMP1,oBMP2 local oXbpb,oPS private oPSg private oBMPdec0,oBMPdec1 oPS := SetAppWindow():presSpace()

14

Second life for Clipper Application by Aleksandar Stefanovic oPSg:=oPS SetMouse(.t.)

oBMP1:=XbpBitmap():new() oBMP1:loadfile("pic1.jpg") oBMP2:=XbpBitmap():new() oBMP2:loadfile("pic2.jpg")

oBMPdec0:=XbpBitmap():new() oBMPdec0:loadfile("dec0.jpg") oBMPdec1:=XbpBitmap():new() oBMPdec1:loadfile("dec1.jpg") oXbpb := XbpPushButton():new() oXbpb:create( , , {10,450}, {150,150}) oXbpb:cargo := .F. oXbpb:activate := {|mp1, mp2, obj| obj:cargo := ! obj:cargo, ; DrawBMPall( obj,oBMP2,1 ),test1() } oXbpb:paint := {|mp1, mp2, obj| DrawBMPall( obj,oBMP1,1 ) } oXbpb:enter := {|mp1, mp2, obj| DrawBMPall( obj,oBMP1,1) } oXbpb:leave := {|mp1, mp2, obj| DrawBMPall( obj,oBMP1,0) } oXbpb:lbdown := {|mp1, mp2, obj| DrawBMPall( obj,oBMP2,1 ) } oXbpb:motion := {|mp1, mp2, obj| DrawBMPall( obj,oBMP2,1 ) } oXbpb:lbup := {|mp1, mp2, obj| DrawBMPall( obj,oBMP1,1 ) } oXbpb:drawmode:=XBP_DRAW_OWNER DrawBmpall(oXbpb,oBMP1,0)

AS_MENU( SetAppWindow():menuBar() ) setcolor("W/N") DO WHILE nEvent <> xbeP_Close nEvent := AppEvent( @mp1, @mp2, @oXbp ) oXbp:handleEvent( nEvent, mp1, mp2 ) ENDDO RETURN PROCEDURE MenuDispatcher( nSelection ) DO CASE CASE nSelection == 101 QUIT

15

Second life for Clipper Application by Aleksandar Stefanovic ENDCASE RETURN

PROCEDURE AS_MENU( oMenubar ) LOCAL oMenu oMenu := XbpMenu():new( oMenuBar ) oMenu:title := "~Sistem" oMenu:create() oMenu:itemSelected := {|nItem| MenuDispatcher( 100+nItem ) } oMenu:addItem( {"~a) End" , NIL} ) oMenu:addItem( {NIL, NIL, XBPMENUBAR_MIS_SEPARATOR, 0} ) oMenu:addItem( {"~b) Indexing", {||indexserv()}} ) oMenubar:addItem( {oMenu, NIL} )

oMenu := XbpMenu():new( oMenuBar ) oMenu:title := "~AS Software" oMenu:create() oMenu:itemSelected := {|nItem| MenuDispatcher( 700+nItem ) } oMenu:addItem( {"~a) Informacije o programu" ,{||aboutas()}} ) oMenubar:addItem( {oMenu, NIL} ) RETURN

STATIC PROCEDURE DrawBMPall( oControl,oBitmap,nn) LOCAL oPS oPS := oControl:lockPS() oBitmap:draw( oPS, {0 , 0}) oControl:unlockPS( oPS ) do case case nn==0 oBMPdec0:draw(oPSg,{500,50}) case nn==1 oBMPdec1:draw(oPSg,{500,50}) ENDCASE RETURN function test1() msgbox("WE ARE NOW IN TEST1 function") return (NIL)

16

Second life for Clipper Application by Aleksandar Stefanovic func indexserv() return (NIL) func aboutas() return (NIL)

This is screen when we start our application. When we move mouse over picture button, button will change and picture to.

Now our old Clipper application can look better.

17