Graphical User Interfaces - A Complete Study
Part 4 - ErgonOS Development Begins

Written by Stéphane Richard (Mystikshadows)

INTRODUCTION:

Part 4, you read it right, this is where are are in this GUI Development series. There's still plenty to write about in this series so I don't foresee an end to this series any time soon. To those of you who just tuned into this series, I strongly recommend you read Part 1, Part 2 and Part 3 before continuing here. In these first 3 parts, we've covered what the ErgonOS GUI will look like, how it will work, we've enumerated the workload of what we'll need to do, we defined the order we'll be coding it and established some coding standards and naming conventions for the source modules. I've highlighted what I believe to be the importance of documenting as much as possible when undertaking a project like a GUI (it's not a small project, therefore, any time you spend documenting is time well spent). With all this in mind I believe we are now ready for the next step, The ErgonOS GUI development itself.

Note that from this point on in the series I will be including the source files. This way, you'll save alot of typing for one thing and you'll be able to really get familiar with the coding I'll be doing in the course of this series. Needless to say this zip file will grow as we add modules and code in the modules as this series progresses. So then, let's get right to it shall we?

THE ERGONOS ENGINE DIAGRAM:

A couple of days after the release of my first two part of the series, I started exchanging a few emails with an individual that asked a few questions about certain concepts. One of the suggestions he gave me was to create a diagram of the engines specifically about the dependencies of the engines and where they fall in he ErgonOS inner structures. So I created this diagram to explain these dependencies (dependencies that I detailed textwise in the 3rd part of this series.

ErgonOS Engine Diagram

I think we can all agree that in this case, a picture really is worth a 1000 words. I think this diagram really helps visualize where each engine falls in the bigger picture and also helps us locate things from a functional point of view as well. Althought the diagram pretty much speaks for itself, here are a few points that might be of interest to you.

You might want to create yourself a hardcopy of this diagram as I'll be refering to it every now and then in the rest of this series. As well, I'll be creating more of these diagrams, one for each engine to show the innerworkings of each of them. For now, this global diagram will do fine for the remainder of this 4th part of the series. I left out the E.A.G.L.E. engine as it's a programming language, not an engine per se. Needless to say that E.A.G.L.E. will have access to all other engines depending on what it needs to accomplish.

With this in mind, I think it's high time we get coding don't you? Basically, in this 4th part, we'll begin work in two parts of the projects. The first will be the ErgonOS Shared Modules (which are the Shared______ modules) and our ErgonOS Main Nodules (which are the ErgonOS______ modules as per our naming convention). We'll create some code in some of the modules in each group and we'll see where it will take us. We'll begin with the shared modules because the main modules will need some of the things we'll be defining in the shared modules.

THE ERGONOS SHARED MODULES:

Basically, anything that can be used in atleast 2 engines should be in the shared set of modules, it's really as simple as that and by doing that we can avoid alot of redundancy in our code and keep any global alterations in one place. As we develop these engines, this is one of he place that will grow and/or change alot as we fine tune our work. For now we'll basically follow that simple rule (if it can be used in more than one module it goes in the shared set of modules). Moving right along then, we'll start, like we did before, with the SharedConstants.bi module. The main reason I like to define constants is one of readability of code. Sure it might take a little longer to type KeyBackspace rather than 8 but it makes for much more readable code. The choice is yours of course, me, I prefer readable code. Since this module (and the other shared modules) will be included in other modules, we will begin by a preprocessor statement to make sure the module isn't repeatedly included.

' ----------------------------------------------- ' Preprocessor to include this module only once ' ----------------------------------------------- #IFNDEF SharedConstants #DEFINE SharedConstants

Pretty simple isn't it? This #IFNDEF tells us that if the ErgonOSConstants define doesn't exist, we can proceed with what follows in the module. The next line defined ErgonOSConstant. This means that the next time a modules includes ErgonOSConstants.bi, it will know that ErgonOSConstants has been defined and will not go into creating all the constants again (which will avoid a duplicate definition error). We'll be using this technique in all modules that will potentially be used in more than one engine. Now we can continue with the constant definitions themselves. A very common set of constants to define, that will be used throughout the whole project, is keyboard constants.

' ------------------------------- ' Keyboard Constant Definitions ' ------------------------------- CONST KeyF1 = 59 CONST KeyF2 = 60 CONST KeyF3 = 61 CONST KeyF4 = 62 CONST KeyF5 = 63 CONST KeyF6 = 64 CONST KeyF7 = 65 CONST KeyF8 = 66 CONST KeyF9 = 67 CONST KeyF10 = 68 CONST KeyF11 = 133 CONST KeyF12 = 134 CONST KeyInsert = 82 CONST KeyDelete = 83 CONST KeyHome = 71 CONST KeyEnd = 79 CONST KeyPageUp = 73 CONST KeyPageDown = 81 CONST KeyUpArrow = 72 CONST KeyDownArrow = 80 CONST KeyLeftArrow = 75 CONST KeyRightArrow = 77 CONST KeyBackspace = 14 CONST KeyTabulation = 15 CONST KeyEnter = 13 CONST KeyEscape = 27 CONST KeySpace = 32 CONST KeyLeftShift = 42 CONST KeyRightShift = 54 CONST KeyAlternate = 56 CONST KeyControl = 29 CONST KeyCapsLock = 58

You'll see later, in the main loop, that these keyboard constants will greatly help the readability of the code. Another good thing to have here are the standard DOS color constants. For part of the initialization process, we'll want to give some feedback to the user as to what is currently happening in the process. And we'll use these constant to give a clear feedback to the user in significant color combination. The color constants are defined like so:

' ---------------------------- ' Color Constant Definitions ' ---------------------------- CONST Black = 0 CONST Blue = 1 CONST Green = 2 CONST Cyan = 3 CONST Red = 4 CONST Purple = 5 CONST Brown = 6 CONST Grey = 7 CONST DarkGrey = 8 CONST LightBlue = 9 CONST LightGreen = 10 CONST LightCyan = 11 CONST LightRed = 12 CONST LightMagenta = 13 CONST Yellow = 14 CONST White = 15

Finally, well need to close the #IFNDEF statement at the beginning of the module. You might have noticed that #IFNDEF pretty much works like a standard IF statement and it does. This means that like the standard IF statement, it needs it's closing statement. In FreeBasic, you close this preprocessor like this:

' --------------------------------------------------------- ' This #ENDIF closes to module inclusion check at the top ' --------------------------------------------------------- #ENDIF

This is it for the SharedConstants.bi, atleast for now as we might think of adding a few things as the project progresses. TWe can now begin our SharedEnumerations.bi module. Enumerations are named list of values defined under a data type name. They are ideal o use when you have a fixed set of values that a given variable should have. Everywhere in the GUI project, different parts of the application will need to set the mouse cursor to different images depending on if it renders ErgonOS in a busy state, or if we're on a control that needs a different mouse cursor shape to help make clear what the expected action on the control is. So this will be the first of our enumerations. We'll also include the Module Inclusion check as it will be needed here too.

' ----------------------------------------------- ' Preprocessor to include this module only once ' ----------------------------------------------- #IFNDEF SharedEnumerations #DEFINE SharedEnumerations ' -------------------------------------------------------------- ' NAME: MousePointers ' DESCRIPTION: Enumeration to hold the different mouse pointer ' types available ' -------------------------------------------------------------- ENUM MousePointers MouseDefault MouseBlock MouseCross MouseIBeam MouseIcon MouseSize MouseLeftArrow MouseNorthSouth MouseRightArrow MouseWestEast MouseUpArrow MouseHourGlass MouseDownArrow END ENUM

Enumerations work because the first named value starts at 0 and every other named value after that increments by one. Hence MouseDefault will be at 0, MouseBlock will be 1, MouseCross will be 2 and so on and so forth. If you want to change this order of values, you just need to add an = sign and the new value. For example, if we'd change MouseIBeam to MouseIBeam = 10, then MouseIBeam would be 10, MouseIcon would be 11, MouseSize would be 12, and so on. This is important to know because in some cases, you would need to change the default enumeration pattern. In ErgonOS, any form that will appear on top of the Standard work areas (the 6 zones we defined in the first part of the series), will have different border styles depending on more than criteria. These border styles can be defined in an enumeration, like this:

' ------------------------------------------------------------- ' NAME: BorderStyles ' DESCRIPTION: Forms and frames can be drawing with different ' different border sets ' ------------------------------------------------------------- ENUM BorderStyles BorderNone BorderSolid BorderFixedSingle BorderSizableSingle BorderFixedDouble BorderSizableDouble BorderFixedSolid BorderSizableSolid END ENUM ' --------------------------------------------------------- ' This #ENDIF closes to module inclusion check at the top ' --------------------------------------------------------- #ENDIF

That's all we'll need, atleast for now, in the SharedEnumerations.bi module. As any other modules, things may be added in the future, but for now, we don't need anything else. We can jump to our SharedUserTypes.bi which is where our shared user defined types will reside. There's alot of things to put in here, I will go about gradually so it's easier to follow through. Since right now, we're concerned about the startup sequence and the configuration file, we will include only the user defined types that we will need to do just that, startup the system and load the configuration data from the file. The first type we'll define here is our ApplicationType Structure. It is defined in the following manner:

' ----------------------------------------------- ' Preprocessor to include this module only once ' ----------------------------------------------- #IFNDEF SharedUserTypes #DEFINE SharedUserTypes ' ---------------------------------------------------------------------- ' NAME: ApplicationType ' DESCRIPTION: This Type holds general Application related information ' such as the screen resolution, the color depth which ' zones are visible and the zone sizes. ' ---------------------------------------------------------------------- TYPE ApplicationType ScreenHeight AS INTEGER ScreenWidth AS INTEGER ColorDepth AS INTEGER ProjectVisible AS BYTE ProjectWidth AS INTEGER ProjectHeight AS INTEGER RelatedVisible AS BYTE RelatedWidth AS INTEGER RelatedHeight AS INTEGER SearchVisible AS BYTE SearchWidth AS INTEGER SearchHeight AS INTEGER DocumentVisible AS BYTE DocumentWidth AS INTEGER DocumentHeight AS INTEGER CommandVisible AS BYTE CommandWidth AS INTEGER CommandHeight AS INTEGER ShortcutsVisible AS BYTE ShortcutWidth AS INTEGER ShortcutHeight AS INTEGER END TYPE

Of course this started with our Module Inclusion Check for ErgonOSUserTypes. Also remember that the ApplicationType structure needs to stay as general as possible and that any specific detail on each of the zones will be defined in each of the zone structures. Although the different zones will be resizable to allow the user more space for the document zone, each of the zones can be defined with a standard WindowType data structure. Such a structure can be defined like so:

' ------------------------------------------------------------ ' TYPE NAME: WindowType ' DESCRIPTION: This holds all the needed information to ' create a window on the screen including if ' all controls on the window should be shown ' or hiding, the windows title, it's size, ' it's color combinations and its coordinates. ' ------------------------------------------------------------ TYPE WindowType WindowNumber AS INTEGER WindowType AS INTEGER WindowTitle AS STRING WindowTop AS USHORT WindowLeft AS USHORT WindowHeight AS USHORT WindowWidth AS USHORT TitleForeground AS USHORT TitleBackground AS USHORT ControlForeground AS USHORT ControlBackground AS USHORT ActiveForeground AS USHORT ActiveBackground AS USHORT InactiveForeground AS USHORT InactiveBackground AS USHORT BorderForeground AS USHORT BorderBackground AS USHORT HighlightForeground AS USHORT HighlightBackground AS USHORT ShadowForeground AS USHORT ShadowBackground AS USHORT IsActive AS BYTE ShowMinimize AS BYTE ShowMaximize AS BYTE ShowClose AS BYTE ShowControl AS BYTE ShowResizeHandle AS BYTE ShowVerticalScrollbar AS BYTE ShowHorizontalScrollbar AS BYTE END TYPE

As you can see, this is a big User Defined Type, the fields are pretty much self explanatory in their names. Every single Window and component that is shown on the screen will have it's unique WindowNumber. This will allow events to be identified with the corresponding window and controled that the event was created from. One thing to remember is that all the Show______ fields will mean that a component will be need to support each of them. hence command buttons for the Minimize, Maximize, Close and Control buttons. Another area for the Resize (at the lower right of the window and two scrollbar components for the two scroll bars will be optionally showing when drawing the window.

Next on the list is the global parameters so to speak. I like to call these environment settings as they influence the way we see things in ErgonOS. This structure will hold information like currency symbol, comma and decimal seperators, date format, time format and other global parameters such as these. This structure is called the EnvironmentType and is defined like so:

' ------------------------------------------------------------ ' TYPE NAME: EnvironmentInformation ' DESCRIPTION: This holds the environment settings and ' other configuration data that is active when ' in an ErgonOS session. ' ------------------------------------------------------------ TYPE EnvironmentType DateSeperator AS STRING ' Default '/' DateFormat AS STRING ' Default 'MM/DD/CCYY' TimeSeparator AS STRING ' Default ':' TimeFormat AS STRING ' Default 'HH:MM:SS' DecimalSeperator AS STRING ' Default '.' ThousandSeperator AS STRING ' Default ',' CurrencySymbol AS STRING ' Default '$' SystemPath AS STRING ' Where the system is DefaultProjectPath AS STRING ' Where the projects are DefaultSearchPath AS STRING ' Where to search first DefaultResourcePath AS STRING ' Where bitmaps are stored DefaultDocumentPath AS STRING ' Where Documents are stored DefaultToolsPath AS STRING ' where Tools are saved in END TYPE ' --------------------------------------------------------- ' This #ENDIF closes to module inclusion check at the top ' --------------------------------------------------------- #ENDIF

As far as Startup sequence goes, this is all we'll need in the SharedUserTypes.bi module. You'll notice our closing #ENDIF is present here too as it must. There is no doubt that alot of user defined types will be added here in the near future to accomodate the rest of the information we'll need. But again, for the sake of the startup sequence and configuration data, this is all that we will need to use. It's important to know at this stage, that once these data structures are populated with their corresponding data, we'll use them as global structures that will need to be available to any and all other engines and modules that will be present in the project.

The other shared modules, at this point of the project will simply stay empty (except for their module level comments) as nothing else is really needed at this point. Keep in mind that this will change when we start tackling the engines themselves. Since most of the constants, enumerations and user defined types have been defined here in the Shared set of modules, the main set of modules will concentrate on more functionality rather than data definition. Let's get right to the definition of the main set of modules.

THE ERGONOS MAIN MODULES:

The main set of modules are of course the modules that will contain Ergon O.S. Specifically related constants, enumerations, user defined types, subs and functions that we will need at the Ergon O.S. Level only. For example, the loading and saving of the configuration file will be at this level. Let's begin with ErgonOSConstants.bi which is where our contants will be defined.

' ----------------------------------------------- ' Preprocessor to include this module only once ' ----------------------------------------------- #IFNDEF ErgonOSConstants #DEFINE ErgonOSConstants ' ------------------------------------------------------ ' General Application Information Constant Definitions ' ------------------------------------------------------ CONST ApplicationName = "Ergon O.S." CONST AuthorName = "Stephane Richard" CONST AuthorNick = "MystikShadows" CONST AuthorEmail = "MystikShadows@gmail.com" CONST VersionMajor = 1 CONST VersionMinor = 0 CONST VersionRevision = 0 CONST VersionRelease = "a" CONST BuildNumber = 1 CONST VersionStamp = "1.00.000a Build 0001" CONST ProjectDescription = "An OpenSource Ergonomically designed document based G.U.I. system" CONST ProjectLicensing = "Released under the G.P.L. 2.0 licensing sheme." CONST ProjectCopyright = "Copyright (c) November 2005 - Stephane Richard" CONST ProjectWebsite = "Coming Soon"

These general application constants are there for informational purposes only. As you can see, they hold application specific information that we will using when displaying information about the current version of Ergon O.S. at startup and when the About Dialog is called upon. At the Ergon O.S. level, we can also add the configuration file specific constants, like so:

' ---------------------------------------------------- ' Ergon O.S. Configuration file constant definitions ' ---------------------------------------------------- CONST ConfigurationFile = "ErgonOS.CFG" CONST ApplicationSection = "[APPLICATION]" CONST EnvironmentSection = "[ENVIRONMENT]" CONST PathsSection = "[PATHS]" CONST DesktopSection = "[DESKTOP]" CONST ColorSetupSection = "[COLORS]" CONST SoftwareSection = "[SOFTWARE]" CONST StartupSection = "[STARTUP]"

Finally, we'll add two constants for known command line paramaters These will allow us to compare them later to know what part of the code to execute next in the ErgonOS startup sequence. Here are the constant definitions. At this point there's two command line parameters I'll want to add here /C and /E. Now /C will allow to load a different configuration file. If /C is not specified, "ErgonOS.cfg" will be loaded (that's the default configuration file). /E will allow to execute a file that will contain a list of standard commands to execute sequentially. If /E is not specified, then no command will be executed.

' ---------------------------------------------------- ' Ergon O.S. Configuration file constant definitions ' ---------------------------------------------------- CONST AlternateSetup = "/C" ' Load non default ErgonOS Setup File CONST CommandFileExecution = "/E" ' Load and run Specific Command File ' --------------------------------------------------------- ' This #ENDIF closes to module inclusion check at the top ' --------------------------------------------------------- #ENDIF

That's about it for now as far as this module is concerned. The configuration constants simply hold the configuration Name and the configuration sections (that will be present in the configuration file when it is created. Now, more sections might be added later, but this should be enought to give you an idea of what could go in this module. Next module is ErgonOSEnumerations.bi, however in our case (it might be different for your project, or mine, later in my development effort) but as of now, I have nothing to put into this module so I will leave it present in the folder, but empty aside it's module level comments.

This takes us to the next module, ErgonOSUserTypes.bi where the user defined types will go. Technically, you might think that we could put the user defined types we'll need to load the information from the configuration file. And at first, that's where I was going to put them. But thinking from the Ergon O.S. perspective itself, I am forced to put these user defined types in the SharedUserTypes.bi module (see above). You see, the information loaded from the configuration file will need to be accessible throughout the rest of the modules. If I put the User Types in the ErgonOSUserTypes.bi file, I will forcibly need to include two modules, rather than just one, to get the full scope of shared user defined types Programming wise, it's just an extra '#INCLUDE: '' statement, no big deal, however, from my organization's perspective, if I can use just one include, I will (which also means I will only need to look in one place if I ever need to change a field definition for example). So again, for now, I will leave ErgonOSUserTypes.bi empty aside it's module level comments.

This brings us to the ErgonOSHelper.bas module. As you recall, this modules holds essentially subs and functions that help the main module (or EngineCore module) perform it's tasks. As such, this module, for now, will have 3 functions. The first is called ValidateParameters which will make sure that the passed command line pararmeters are indeed valid ones as per the list of valid command line parameters. This function is defined like so:

' ----------------------------------------------- ' Preprocessor to include this module only once ' ----------------------------------------------- #IFNDEF ErgonOSHelper #DEFINE ErgonOSHelper ' =============================================================== ' NAME: ValidateParameters() ' PARAMETERS: None ' RETURNS: 0 of parameters are valid, Invalid Param number ' ASSUMES: Nothing ' CALLED FROM: ErgonOS.bas Main Program Section ' --------------------------------------------------------------- ' DESCRIPTION: This Sub makes sure that any parameters that ' passed to the application are valid ones. An ' Error will be reported if a parameter is not ' valid but the application will not exit instead ' the IDE will start in default mode or process ' the rest of the valid parameters. ' =============================================================== FUNCTION ValidateParameters() AS INTEGER DIM argc AS INTEGER DIM argv AS INTEGER DIM WorkResult AS INTEGER argc = 1 WorkResult = 0 DO argv = COMMAND$(argc) IF LEN(argv) = 0 THEN EXIT DO END IF IF argv <> AlternateSetup AND argv <> CommandFileExecution THEN WorkResult = argc END IF argc = argc + 1 LOOP ValidateParameters = WorkResult END FUNCTION

The next function we'll put in ErgonOSHelper.bas is the function that deals with the loading of the configuration file itself. It will open the right configuration file (ErgonOS.cfg or another file if the /C: command line parameter was passed to the executable). And peruse it while loading its values into the proper User Defined Types.

' =============================================================== ' NAME: LoadConfiguration() ' PARAMETERS: ConfigFile AS STRING ' RETURNS: 0 if no errors, Err number if anything happened ' ASSUMES: ConfigFile is valid path and file name. ' CALLED FROM: ErgonOS.bas Main Program Section ' --------------------------------------------------------------- ' DESCRIPTION: This Sub takes the passed configuration file ' name, opens it and loads up the values into the ' appropriate User Defined Types so that they are ' available to the rest of the application. ' =============================================================== FUNCTION LoadConfiguration(ConfigFile AS STRING) AS INTEGER DIM ErrorMessage AS STRING DIM WorkLine AS STRING DIM WorkResult AS INTEGER DIM FileHandle AS INTEGER ' -------------------------------- ' Turn On Local Error Management ' -------------------------------- ON LOCAL ERROR GOTO ErrorManager ' ---------------------------------------------------- ' Get next available File handle and open ConfigFile ' ---------------------------------------------------- WorkResult = 0 FileHandle = FREEFILE OPEN ConfigFile FOR INPUT AS #FileHandle ' ----------------------------------------------- ' Skip Everything until we get to the a section ' ----------------------------------------------- DO WHILE WorkLine <> "[APPLICATION]" LINE INPUT #FileHandle, WorkLine LOOP ' ---------------------------------------------------- ' Next we load field values into the data structures ' Note that this is not implemented yet but this ' will be a series of Field assignement for all the ' fields of he Application and Environment Types ' based on values read from the file Here's a few ' examples so you know how I'll do it. ' ---------------------------------------------------- LINE INPUT #FileHandle, WorkLine Application.ScreenHeight = VAL(RIGHT$(WorkLine, LEN(WorkLine) - 30)) LINE INPUT #FileHandle, WorkLine Application.ScreenWidth = VAL(RIGHT$(WorkLine, LEN(WorkLine) - 30)) LINE INPUT #FileHandle, WorkLine Application.ColorDepth = VAL(RIGHT$(WorkLine, LEN(WorkLine) - 30)) ' -------------------------------------- ' Close the file and exit the function ' -------------------------------------- CLOSE #FileHandle OpenConfigurationFile = WorkResult EXIT FUNCTION ' -------------------------- ' Error Management Section ' -------------------------- ErrorManager: ' --------------------------------------- ' Create the full ErrorMessage Contents ' --------------------------------------- ErrorMessage = "Engine: ErgonOS" + CHR$(13) + _ "Module: ErgonOS.bas" + CHR$(13) + _ "Function: LoadConfiguration()" + CHR$(13) + _ "Parameters: (" + ConfigFile + ")" + CHR$(13) + _ "Error: " + STR$(Err) + ":" + Err$ ' -------------------------------------------------------- ' Depending on settings, either log or display the error ' -------------------------------------------------------- IF ErrorLogging = True THEN LogError(ErrorMessage) ELSE DisplayError(ErrorMessage) END IF END FUNCTION

You can notice here that when reading values I used "VAL(RIGHT$(WorkLine, LEN(WorkLine) - 30))" If you are familiar with windows INI files, I am using a similar structure. Because I am not loading it using the conventional Windows API functions (since I'm in DOS), I do not suffer from the 64Kb limit that windows INI file have. So the first 30 characters of any item I will be loading will be 30 characters. As such, I only need to get from the 31st character all the way to the end of the current line which is what the code does. I didn't implement all of the function yet but all you need to know is any field that will be retrieved from the file will use either "VAL(RIGHT$(WorkLine, LEN(WorkLine) - 30))" for numeric values or "RIGHT$(WorkLine, LEN(WorkLine) - 30)" for Character values.

The last sub I will be adding to the ErgonOSHelper.bas module is called SetupDefaultConfiguration(). It has no parameters and makes no assumption. It will simply fill in the different fields I've defined in the User Defined Types for the ApplicationType and the EnvironmentType (atleast for now, more values will be assigned as other engines are implemented in the course of this series).

' =============================================================== ' NAME: SetupDefaultConfiguration() ' PARAMETERS: None ' RETURNS: No valies ' ASSUMES: Nothing ' CALLED FROM: ErgonOS.bas Main Program Section ' --------------------------------------------------------------- ' DESCRIPTION: This Sub is called when the normal loading of a ' configuration file failed. It basically assigns ' default values into the necessary data ' structures to allow ErgonOS to boot in a normal ' mode if it can. ' =============================================================== SUB SetupDefaultConfiguration() ' --------------------------------------------- ' Setup the Application's default information ' --------------------------------------------- WITH Application .ScreenWidth = 800 .ScreenHeight = 600 .ColorDepth = 8 .ProjectVisible = 1 .ProjectWidth = 200 .ProjectHeight = 200 .RelatedVisible = 1 .RelatedWidth = 200 .RelatedHeight = 200 .SearchVisible = 1 .SearchWidth = 200 .SearchHeight = 200 .DocumentVisible = 1 .DocumentWidth = 600 .DocumentHeight = 400 .CommandVisible = 1 .CommandWidth = 600 .CommandHeight = 130 .ShortcutsVisible = 1 .ShortcutWidth = 600 .ShortcutHeight = 70 END WITH ' --------------------------------------------- ' Setup the Environment's default information ' --------------------------------------------- WITH Environment .DateSeperator = "/" .DateFormat = "MM/DD/CCYY" .TimeSeperator = ":" .TimeFormat = "HH:MM:SS" .DecimalSeperator = "." .ThousandSeperator = "," .CurrencySymbol = "$" .SystemPath = "C:\ErgonOS\" .DefaultProjectPath = "C:\ErgonOS\Projects\" .DefaultSearchPath = "C:\ErgonOS\Search\" .DefaultResourcePath = "C:\ErgonOS\Resources\" .DefaultDocumentPath = "C:\ErgonOS\Documents\" .DefaultToolsPath = "C:\ErgonOS\Tools\" END WITH END SUB ' --------------------------------------------------------- ' This #ENDIF closes to module inclusion check at the top ' --------------------------------------------------------- #ENDIF

We are now at the main ErgonOS module. We'll use ErgonOS.bas as it's name. Basically, this main module is only there to get things ready for the other main engines to set themselves at which point they take over control of the program. So then, to accomplish this, the first thing ErgonOS.bas needs to do is include the modules from the other engines that it will need. In this part of the series, we'll only need to include the ErgonOS main modules and the Shared modules where we actually included code. These are included like so:

' -------------------------------------- ' Module and Library Inclusion Section ' -------------------------------------- '$INCLUDE: 'SharedConstants.bi' '$INCLUDE: 'SharedUsertypes.bi' '$INCLUDE: 'ErgonOSConstants.bi' '$INCLUDE: 'ErgonOSHelper.bas'

This is the structure I'll be using for the include order. I'll always start with the Shared modules. then the modules named from the same engine the current file pertains to. Finally, under those, as needed, I'll include other related engine modules as needed. Once we have our extermal modules included, we now need a few global variables to hold what we'll be reading from the configuration file.

' ------------------------------------------- ' Globally available Variables declarations ' ------------------------------------------- DIM SHARED Application AS ApplicationType DIM SHARED Environment AS EnvironmentType DIM SHARED ConfigFile AS STRING DIM SHARED FileResult AS INTEGER

The next part of this module deals with the command line parameters, it makes use of the function we defined in ErgonOSHelper.bas to assure that nothing invalid was passed to the ErgonOS.exe file. This early in the startup sequence, all we need to check for is if we will be using an alternate configuration file or if we'll use the default. I will also add aa check here so that if a /E: parameter was supplied. This command file will be executed after the whole system boots up because we will expect to be in the GUI itself in order to execute that command file.

' ------------------------------------------------------ ' If there's any parameters, we'll validate them here. ' ------------------------------------------------------ IF COMMAND$ <> "" THEN ValidArguments = ValidateParameters() IF ValidArguments = 0 THEN ' --------------------------------------------- ' Test to see if a /C: parameter was supplied ' --------------------------------------------- IF UCASE$(LEFT$(COMMAND$(1), 3)) = "/C:" THEN ' -------------------------------------- ' Going to open the passed config file ' -------------------------------------- ConfigFile = RIGHT$(TRIM$(COMMAND$(1)), LEN(TRIM$(COMMAND$(1))) - 3) ELSE ' --------------------------------------- ' Going to open the default config file ' --------------------------------------- ConfigFile = "ErgonOS.cfg" END IF ' --------------------------------------------- ' Test to see if a /E: parameter was supplied ' --------------------------------------------- IF UCASE$(LEFT$(COMMAND$(1), 3)) = "/E:" THEN CanExecuteCommandFile = 1 END IF END IF END IF

So far it seems simple enough doesn't it? Basically, it's time, at this point, to start giving the user some feedback as to what is going on as the bigger processes and time takers will happen, when needed, starting from this point on. So let's start by displaying some general application information to the user so he/she knows that the right application was indeed executed.

' -------------------------------------------------------- ' We start printing some general information to the user ' -------------------------------------------------------- WIDTH 80, 50 CLS COLOR White, Black: PRINT ApplicationName; COLOR White, Black: PRINT " Version " + VersionStamp COLOR Yellow, Black: PRINT ProjectDescription COLOR Yellow, Black: PRINT "Brought to you by " + AuthorName COLOR LightCyan, Black: PRINT ProjectLicensing COLOR LightCyan, Black: PRINT ProjectCopyright

Note that I used strictly constant values here, not directly typed text. The main reason is that should you decide to follow my structure and organization for your own GUI projects, you can just change these constant values to your own names and values in the right module and this will simply display the new information. One of the things this coding technique allows is to create highly reusable code. And in as many cases as possible, I will be using techniques that allow for reusable code throughout the whole project. Since I'll plan on writing more projects, coding this way right now gives you a broad repertoire of reusable code that will really save you alot of time in your future development endeavours.

The rest of this module will continue to to give the user feedback as far as what is currently happening at the different phases of the startup sequence. Right now, there's only one step, we will simply load the configuration file by calling our LoadConfiguration() function that we defined above. This will also show you how to give simple but sufficient feedback to the user as far as what's happening and how things went.

' ------------------------------------------ ' We can now load the configuration file ' ------------------------------------------ COLOR Yellow, Black: PRINT "Loading Configuration File. "; FileResult = LoadConfiguration(ConfigFile) IF FileResult = 0 THEN ' ------------------------------------------ ' If there's no errors, we print "Success" ' ------------------------------------------ COLOR LightGreen, Black: PRINT "Success" ELSE ' -------------------------------------------- ' If there was error we setup default values ' -------------------------------------------- COLOR LightRed, Black: PRINT "Failed" COLOR Yellow, Black: PRINT "Creating Default Configuration" CALL SetupDefaultConfiguration() END IF

Color coded feedback is basically what I call this. Green for success red for failure. This is how I like to give straighforward feedback to the user. You can see that in the ELSE part of this IF statement I also tried an alternate approach to make the program work. Of course some parts of the startup sequence cannot have an alternate method like this but when it can, it's a good idea to implement one as it gives the whole system a certain feeling of stability and reliability. The key rule here is "If there's a way for it to work, it should be coded.". In other words, we can't assume that everything will go perfectly or the whole project will break as soon as something doesn't go perfectly. We need to assume that nothing will work and see what we can do, in each of the situations that could present itseif, to make sure that the program has a chance to work in some way. If you can remember this at the beginning of a project like this, it will definitaly influence you whole project for the better.

The next piece of code is just a series of comments for now. Since these parts will depend on things that are not implemented yet, these will only show you what will happen when they are implemented.

' ----------------------------------------------------------- ' 1. Acquire low level System Information (from LAHP) ' 2. Acquire list of storage media (from LAHP) ' 3. Acquire all available Screen Resolutions (from VADM) ' 4. Setup all default visible components (from EVRIL) ' 5. Set the screen into desired mode (from VADM) ' 6. Draw all components on screen (from EVRIL) ' 7. Go into Main ErgonOS Event Management Loop (from TEMS) ' -----------------------------------------------------------

Now after these are implemented and the GUI system itself is loaded and ready, we can go ahead and proceed to executing a Command File if one was specified as a command line parameter when executing ErgonOS.exe. Right now it's just a comment, but when our CAPS engine will be implemented, we will be calling the ExecuteCommandFile() function.

' ------------------------------------------------------- ' If a Command file to run was specified, we execute it ' ------------------------------------------------------- IF CanExecuteCommandFile = 1 THEN ' Later we'll call the code to execute a command file. END IF

As you can see, things are pretty straightforward and sequential up this point. This is basically the reason why it's called a startup sequence. Like I mentionned before, as these steps execute themselves, if there's a possible backup plan, we will be implementing them to give our system the most possible chances to be able to start itself up as properly as it can. For each of those situation, when they arrive, i'll explain to you what can cause is and if there is an alternate way to get things done.

FINALLY, PART 4 CONCLUDED:

And there you have it. It's been quite a chapter in the series hasn't it? Basically, I'm trying to keep things as clear as I can both in the tutorial itself as well as in the source code I present. This is why things are documented as they are and why I used clear names for everything I've coded so far. As I mentioned in part 3 of the series, I will be following these standards throughout the series, it should help keep everything as clear as possible which is the ultimate goal of the series. As always, you can always send me emails if you have questions about anything you've read so far in this series. We need to make sure that you can follow through in the next parts of the series and to be able to do so, things need to be clear.

In the next part of the series, we'll concentrate on L.A.H.P. (Low level Access to Hardware and Peripheral), the first of our major engines. We'll detail everything that LAHP needs to be aware of and everything it needs to acquire to make it available to the rest of the ErgonOS engines and modules. We'll see how integrating a new module can and will affect the other engines and modules already present in the project and we'll also see how and where we will be calling the LAHP functionality from the Central ErgonOS set of modules. Until then, happy reading, learning and understanding.


MystikShadows
Stéphane Richard
srichard@adaworld.com