5.2.1 Customizing and extending the Class Browser

Customizing and extending the Class Browser

One of the greatest strengths of the Class Browser is its flexibility and customizability. I think it's fair to say that the browser has set new standards in this area. In order to customize the browser, you need to know how it works internally and what its object model looks like.

Class Browser internals

As mentioned before, the Class Browser once instantiated is just a regular Visual FoxPro object. There is an exposed reference to the browser at all times, called _oBrowser. This reference points to the active instance of the browser if you have multiple instances running at the same time. Knowing this reference, it is relatively simple to start customizing the browser. Here's a little example:

_oBrowser.Caption = "My very own class browser"

As you can see, the caption of the browser changes. In the same manner, you can customize almost everything in the Class Browser. Let's say you don't like the tooltip text of the Open button. You can change it like so:

_oBrowser.cmdOpen.ToolTipText = "Exclusive Open"

How did I know the name of the Open button? Well, there are a couple of ways to explore the internal object structure of the browser. One is to use the debugger and to explore the _oBrowser reference in the Watch window. This will show all exposed objects and properties. You cannot cheat your way through as easily to find the exposed methods because the debugger doesn't show them. Fortunately, the internals of the Class Browser are documented quite well in the online help.

One internal method I use quite a lot is SetFont(). It sets the font for the Class Browser and all the windows and dialogs it will use. (The new browser also exposes this functionality through the right-click menu.) Here's how you call it:

_oBrowser.SetFont( "Comic Sans MS", "24" )

Additional parameters for font style are available as well. Another nice example is the ExportClass() method, which returns the code for the currently selected class or class library. This is the programmatic interface to the "View Class Code" feature. Calling this method rather than pressing the button is a bit more flexible. First of all, the method returns the created code, so you can simply assign it to a variable and work with it. Also, you can pass a couple of parameters, so you can decide whether the created code is displayed in a window (parameter 1 set to .T.) or you can pass a file name (parameter 2) to save the source in a file. Here's how:

_oBrowser.ExportClass(.F.,"test.prg")

One of the more useful methods is AddFile(), which allows you to programmatically open a class library. We will use this in one of our demo add-ins. Also quite useful are ModifyClass(), NewFile(), OpenFile(), and others. I could go on for pages and pages listing all the exposed methods, but I think the documentation does a nice job of that. I will stop at this point and demonstrate more methods when we create some sample add-ins (see below).

Just as important as the exposed methods are the exposed properties. Most of them hold some information about the currently selected class or class library. _oBrowser.cAlias, for instance, holds the alias name of the currently viewed library. You could use it to programmatically select the file and work with it, as in the following example:

SELECT (_oBrowser.cAlias)
WAIT WINDOW User

This displays the contents of the user field of the current class in a wait window. Another property I like (and use) a lot is aClassList. It is an array that holds a lot of important information about the currently displayed class tree. This array is used internally to build that tree and to easily reference the displayed information. You can find the exact structure of this array in the Visual FoxPro documentation.

Again, all the properties are documented quite nicely, so there is no need to reprint this information in this book. I encourage you to read the Class Browser topics in the online help.

Let's have another look at the member objects of the browser. I've already shown a simple example that demonstrates how to change a property of any member object you want. Now let's continue with this thought and explore what that means. Because the positioning properties (such as left and top) are just regular properties, you can start to rearrange objects in the browser. You can set them invisible, or enable and disable them as you like. In fact, you can add your own buttons (or other objects) to add new functionality or replace existing ones (as you'll see shortly). Just play a little bit with these possibilities and you'll realize the true power of this architecture.

Browser.dbf

A lot of things you do in the Class Browser, like moving it around on the screen or changing the font, will appear the same way the next time you start the browser. All of these settings are stored in a regular DBF file called Browser.dbf, located in the Visual FoxPro home directory. You can open it like so:

USE Home() + "browser.dbf"

There are two different types of records in this DBF, identified by the Type field. One is PREFW, which can be a user preference, a recently used file, or any kind of other item the browser remembers the next time it gets called. The second type is ADDIN, which identifies a Class Browser plug-in. I'll discuss this topic further later.

The interesting part is that the Class Browser ignores all records that are not of type PREFW or ADDIN. This means that you can use it for your own purposes if you create new types. I do this often with all kinds of tools I write. Instead of giving every single one its own resource file, I reuse Browser.dbf. However, be careful doing this, because this DBF might not exist. Browser.dbf gets created when the Class Browser is started for the first time. If you give away a tool to a programmer who doesn't use the Class Browser (shame on him!), it could happen that Browser.dbf doesn't exist. In this case I simply start the browser programmatically and release it right away. Showing the Class Browser for a brief moment might look a little weird to the user, but it surely does the trick. The other option would be to create Browser.dbf yourself, but I don't recommend that, for a very simple reason: The structure might be extended in future versions, and in this case you would have created an invalid version.

Often I'm asked why one would use Browser.dbf rather than the registry. There are a couple of good reasons: First of all, it is rather easy to use; secondly, the registry is not a good place to store record-oriented data. In other words, it is relatively easy to write some settings to the registry, but it is hard to store data that requires tabular structures as found in any FoxPro table. It's also a good idea to use the browser if you require the use of memo fields, which are practically non-existent in the registry.

Table 1 shows the structure of Browser.dbf. Take note that this file is used by the Class Browser and the Component Gallery (see below). In Table 1, I also describe the fields that are used by the Component Gallery. Columns 5 and 6 indicate the fields used by each tool.

Table 1. The structure of Browser.dbf.

Field Name

Type

Description

Example

Browser

Gallery

Platform

C(8)

The value of this field is always "WINDOWS" because Visual FoxPro is no longer a cross-platform tool. However, if the record type is "ADDIN", this field is blank.

WINDOWS

þ

þ

Type

C(12)

This field defines the type of the record. It is usually either "PREFW" or "ADDIN". Records with other type values are ignored.

PREFW, ADDIN

þ

þ

ID

C(12)

The ID further identifies the record type. "FORMINFO" records are used by the gallery and by the browser to store form preferences. "BROWSER" records contain default settings for the Class Browser. In this case, the Properties field contains more information about the settings. If the ID is "METHOD", the record defines a Class Browser add-in that is tied to a particular event or method. If the value of this field is "MENU", the record describes an add-in that shows up in the menu.

FORMINFO,
BROWSER,
METHOD,
MENU

þ

þ

Table 1, continued

Field Name

Type

Description

Example

Browser

Gallery

Default

L

Logical true for the default Component Gallery catalog.

.F.

 

þ

Global

L

Logical true for global Component Gallery catalogs.

.T.

 

þ

Backup

L

When this field is set to True, the catalog or class library is automatically copied to a \backup subfolder.

.T.

þ

þ

Name

Memo

The file name that relates to this record. This could be a class library (or any other kind of file the browser can open) or a component catalog. If the current record describes an add-in, this field has the name of the add-in.

Genrepox.vcx
Test.app
Object Metrics Add-In

þ

þ

Desc

Memo

The Component Gallery uses this field to store the description of a registered catalog.

Visual GenRepoX Catalog

 

þ

Method

Memo

Contains the name of a method an add-in is tied to. If the value of this field is "*", the add-in will fire for every method.

INIT
cmdOpen.Click
*

þ

þ

Properties

Memo

Used by the Class Browser to store preferences.

this.lRunFileDefault = .F.

this.lAddFileDefault = .F.

this.lAutoLabelEdit = .T.

this.lAdvancedEditing = .T.

þ

 

Script

Memo

Internal use by the Component Gallery.

 

 

þ

Program

Memo

This field contains the name of the add-in program to call.

Pbrowser.prg

þ

þ

ClassLib

Memo

If an add-in is a visual class, this field contains the name of the class library.

Loc.vcx

þ

þ

ClassName

Memo

If an add-in is a visual class, this field contains the name of the class.

CCountCodeLines

þ

þ

FileFilter

Memo

Used for Class Browser add-ins that apply only to certain files.

Genrepox.vcx

þ

 

Table 1, continued

Field Name

Type

Description

Example

Browser

Gallery

DispMode

N(1,0)

The window state for the browser or gallery form. This setting is stored on a per-file basis.

1

þ

þ

Top

N(6,0)

The stored top coordinate of the browser/gallery window.

30

þ

þ

Left

N(6,0)

The stored left coordinate of the browser/gallery window.

15

þ

þ

Height

N(6,0)

The stored height of the browser/gallery window.

500

þ

þ

Width

N(6,0)

The stored width of the browser/gallery window.

400

þ

þ

Height1

N(6,0)

Stores the height of the description pane in the Class Browser.

50

þ

 

Width1

N(6,0)

Stores the width of the description pane in the Class Browser.

200

þ

 

Height2

N(6,0)

Stores the height of the description pane in the Component Gallery.

 

 

þ

Width2

N(6,0)

Stores the width of the description pane in the Component Gallery.

 

 

þ

WindowStat

N(1,0)

Window state of the gallery/browser window (0=normal, 1=minimized, 2=maximized).

0

þ

þ

Protected

L

Specifies whether the Class Browser shows protected properties and methods.

.T.

þ

 

Empty

L

Specifies whether the Class Browser shows empty methods.

.F.

þ

 

Hidden

L

Specifies whether the Class Browser shows hidden properties and methods.

.T.

þ

 

DescBoxes

L

Specifies whether the description panels should be displayed.

.T.

þ

þ

AutoExpand

L

Specifies whether the treeviews should be expanded automatically.

.T.

þ

þ

PushPin

L

Specifies whether the browser/gallery window should be always on top.

.F.

þ

þ

PCBrowser

L

Specifies whether the browser should display the parent class toolbar.

.F.

þ

 

Table 1, continued

Field Name

Type

Description

Example

Browser

Gallery

ViewMode

N(1,0)

Stores the gallery display view mode (1-4).

 

 

þ

FontInfo

Memo

Stores information about the selected font.

MS Sans Serif,8,

þ

þ

FormCount

N(4,0)

Stores the number of instances open for a specific file in the browser/gallery.

 

þ

þ

Updated

T

Date and time the record was last updated.

09/03/1998 03:34:52 PM

þ

þ

Comment

Memo

Unused. Reserved for comments the user wants to enter directly.

I have to

 

 

User1

Memo

Unused. Reserved for user extensions.

*:EXTEND

 

 

User2

Memo

Unused. Reserved for user extensions.

*:EXTEND

 

 

User3

Memo

Unused. Reserved for user extensions.

*:EXTEND

 

 

User4

Memo

Unused. Reserved for user extensions.

*:EXTEND

 

 

Creating add-ins

You've now seen how to customize the browser. Unfortunately, all the changes we've made so far were temporary and were initiated from the command window. Once we closed the browser and reopened it, our changes were gone. Fortunately, the browser features a mechanism called "AddIns" that allows you to permanently customize it. Let's have a look at a simple example. Earlier I showed one line of code that temporarily changed the tooltip text of the Open button. Here is the code that accomplishes the same thing permanently. Put this code in a file called Sample1.prg:

LPARAMETERS loBrowser
loBrowser.cmdOpen.ToolTipText = "Exclusive Open"

This is the most basic add-in you can write. Every add-in has to accept one parameter, which is a reference to the browser. You can use this reference instead of _oBrowser, which is more reliable because you could have multiple browser instances.

Now, the question is how to call this add-in. First of all, you have to tell the browser that it exists. Do so using the AddIn() method:

_oBrowser.AddIn("Change ToolTipText","sample1.prg")

To register an add-in, the AddIn() method requires at least two parameters. The first one is the name of the add-in, and the second is the code or class that has to be executed. Add-ins can be PRGs, FXPs, SCXes, APPs, and EXEs. The only requirement they have to fulfill is to accept one parameter.

Okay, you've now registered the add-in in the Class Browser, so now you can call it by right-clicking on the browser, selecting "Add-ins " and then selecting the "Change ToolTipText" add-in from the menu. Now, when you move the mouse over the Open button, you'll see the changed caption. You can also close the browser, restart it, open the menu again to select the add-in, and you'll end up with the same tooltip text. That's a lot better than what we had before, but it still isn't perfect. It would be much better to have the change occur automatically which is possible. The browser allows you to associate an add-in to a certain event, so if you link the add-in to the Init() event, it gets called every time the browser starts. You can do this as follows:

_oBrowser.AddIn("Change ToolTipText","sample1.prg","INIT")

Now you can close the browser and restart it, and the tooltip text will change right away. Excellent! Also, the add-in won't be displayed in the menu anymore because it is triggered automatically. If you'd like to see the add-in in the menu as well, you'd have to register it twice. But as you can see from this example, is usually doesn't make sense to do so.

You can link add-ins to almost any event and method throughout the Class Browser and all its member objects. Let's say (I know it doesn't make a lot of sense, but bear with me) you want to change the tooltip text after you've opened one library. To do so, you could link your add-in to the Click event of the Open button, like so:

_oBrowser.AddIn("Change ToolTipText","sample1.prg","cmdOpen.Click")

You can pass other parameters to the AddIn() method. You can create add-ins for specific files only, and even for specific platforms, even though this is outdated by now.

Since our sample add-in isn't very useful, you might have already wondered how to get rid of it. This is quite easy:

_oBrowser.AddIn("Change ToolTipText",.NULL.)

Now that you know the basics about add-ins, you can start to write your first useful one. Often I would like to see the source code for my classes when browsing through them, without clicking the Export button every time. The following code is stored in Showcode.prg. It adds buttons and an editbox to the browser to display the code:

LPARAMETERS loBrowser

* We add the checkbox

loBrowser.AddObject("chkSourceCode","cSourceCodeActive")

loBrowser.chkSourceCode.Top = loBrowser.cmdOpen.Top

loBrowser.chkSourceCode.Height = loBrowser.cmdOpen.Height

loBrowser.chkSourceCode.Left = loBrowser.cmdCleanup.Left + loBrowser.cmdCleanup.Width + 5

loBrowser.chkSourceCode.Visible = .T.

* We add the editbox

loBrowser.AddObject("edtSourceCode","cSourceCodeDisplay")

RETURN

 

DEFINE CLASS cSourceCodeActive AS CheckBox

Style = 1 && Graphical checkbox

Caption = "Source" && An image would be nicer...

Width = 50

Value = .F.

FUNCTION InteractiveChange

thisform.edtSourceCode.Visible = THIS.Value

ENDFUNC

ENDDEFINE

DEFINE CLASS cSourceCodeDisplay as EditBox

ReadOnly = .T.

FUNCTION Visible_Assign (llNewVal)

IF VarType(llNewVal) = "L"

IF llNewVal

this.Left = THISFORM.oleMembers.Left

this.Width = THISFORM.oleMembers.Width

thisform.oleMembers.Height = (THISFORM.oleMembers.Height/2) - 2

thisform.txtMembers3d.Height = THISFORM.oleMembers.Height

this.Height = THISFORM.oleMembers.Height

this.Top = THISFORM.oleMembers.Top + THISFORM.oleMembers.Height + 4

ELSE

thisform.oleMembers.Height = THISFORM.oleClassList.Height

thisform.txtMembers3d.Height = THISFORM.oleMembers.Height

ENDIF

ENDIF

this.Visible = llNewVal

ENDFUNC

ENDDEFINE

This code is rather simple. Only a couple of lines hold real functionality the rest is cosmetic. When the add-in is executed, we first add a checkbox object to the browser window. The class for this checkbox is called cSourceCodeActive and is defined further down in Listing 5-1. The checkbox is graphical, so it looks like a button that can be pressed or released. Its dimensions are calculated relative to the other buttons in the browser window. This checkbox will be used later on to enable and disable the preview mode.

Once we have this checkbox, we add the editbox that will be used to show the code. The editbox is an instance of the class cSourceCodeDisplay that's defined in the code as well. It is read-only because it wouldn't make sense to edit the displayed code. There is some fancy coding in this editbox. The Visible property has an Assign method. Every time the editbox is set visible, we verify its dimensions and also have to correct the dimensions of the Details treeview to make space for the new window element. When the editbox is hidden, we adjust the size of the treeview to fill the entire right-hand panel. Whether or not the editbox is visible will be determined by the checkbox we added earlier.

Now we have to register this add-in with the Class Browser. We want this to happen every time the browser starts, so we'll register this add-in with the INIT event:

_oBrowser.AddIn("ShowCode","showcode.prg","INIT")

So now we have a checkbox and an editbox that we can toggle on and off, but we still need some code to refresh the contents of the editbox. The following code does all the work. It retrieves the source code and puts it in the editbox. It is stored in Showcode.prg:

LPARAMETERS loBrowser

IF Type("loBrowser.edtSourceCode.Visible") = "L"

IF loBrowser.edtSourceCode.Visible

loBrowser.edtSourceCode.Value = loBrowser.ExportClass()

ENDIF

ENDIF

The single function of this code is to check whether the editbox is visible. If so, it retrieves some source code from the Class Browser and puts it in the editbox. The only thing that's left to do now is to register the add-in. We don't want it to fire when the browser starts only every time the user selects a new class. The RefreshMembers() method fires at exactly the right time for our needs. So we register our second add-in like this:

_oBrowser.AddIn("ShowCode","showcode2.prg","REFRESHMEMBERS")

You might wonder how I knew about this special method. Well, it's quite simple. The browser has a property called lAddInTrace. If this property is set to .T., all the events and methods that could have an add-in are listed on the screen as soon as they fire. This is a great way to figure out where to put your code.

Figure 6 shows the result: the browser with a new button in the toolbar and the editbox beneath the Details treeview.

Figure 6. The Class Browser with the new button in the toolbar and the source code preview panel.

This add-in looks pretty professional already, but it still isn't of commercial quality. If the user resizes the browser window, for instance, the add-in doesn't react properly. So we'd have to create another add-in for the resize. There are a couple of little similar problems, but I'm sure with the knowledge you've gathered by now, you can figure them out for yourself (if the add-in isn't good enough for your own use).

A sample add-in: object metrics

In Chapter 11 I'll introduce object metrics, a great way to measure the progress and quality of your application. The problem with object metrics is that there aren't many tools that help you to measure your application. Many metrics are based on classes, and a lot of them are somehow based on lines of code (even though the number of lines of code itself isn't very useful). The following code shows an add-in that counts the lines of code in the selected class:

LPARAMETERS loBrowser

IF Empty(loBrowser.cClass)

* The user needs to select a class

MESSAGEBOX("Please select a class first.",16,"Object Metrics")

RETURN

ENDIF

SELECT (loBrowser.cAlias)

LOCAL lcCode, lnCounter, lcLine

* 1 = total lines, 2 = code lines, 3 = remarks, 4 = number of methods

LOCAL laLOC(4)

laLOC = 0

lcCode = Properties + Chr(13) + Methods

_MLINE = 0

laLOC(1) = MemLines(lcCode)

FOR lnCounter = 1 TO laLOC(1)

lcLine = MLine(lcCode,1,_MLINE)

* We check if this line has any code at all

IF NOT Empty(lcLine) AND NOT lcLine = "ENDPROC"

laLOC(2) = laLOC(2) + 1

ENDIF

* We check if this is a remark
IF Chr(38)+Chr(38) $ lcLine OR Alltrim(lcLine) = "*"

laLOC(3) = laLOC(3) + 1

ENDIF

* We check if this is a new method

IF lcLine = "PROCEDURE"

laLOC(4) = laLOC(4) + 1

ENDIF

ENDFOR


* We assemble the message

LOCAL lcMessage

lcMessage = "Lines of code in the current class:"+Chr(13)+Chr(13)

lcMessage = lcMessage + "Total lines: "+Alltrim(Str(laLOC(1)))+Chr(13)

lcMessage = lcMessage + "Lines with code: "+Alltrim(Str(laLOC(2)))+" ("+;

Alltrim(Str((laLOC(2)/(laLOC(1)/100)))) +"%)"+Chr(13)

lcMessage = lcMessage + "Lines with comments: "+Alltrim(Str(laLOC(3)))+" ("+;

Alltrim(Str((laLOC(3)/(laLOC(1)/100)))) +"%)"+Chr(13)+Chr(13)

lcMessage = lcMessage + "Number of methods: "+Alltrim(Str(laLOC(4)))+Chr(13)

lcMessage = lcMessage + "Average total lines per method: "+;
Alltrim(Str(laLOC(1)/laLOC(4)))+Chr(13)

lcMessage = lcMessage + "Average lines with code per method: "+;
Alltrim(Str(laLOC(2)/laLOC(4)))+Chr(13)

lcMessage = lcMessage + "Average lines with comments per method: "+;
Alltrim(Str(laLOC(3)/laLOC(4)))+Chr(13)


* We display the message

MESSAGEBOX(lcMessage,64,"Object Metrics")

This add-in iterates through all lines of code for each class, and counts and analyzes them according to rules described in Chapter 11. The result is a message box with the information about the actual class (see Figure 7). In a real-world scenario, it might also make sense to store the result in a table to have metrics for all the classes in your project. Making those changes would be trivial.

Figure 7. The result window of the Object Metrics add-in.

A sample add-in: the PowerBrowser

The PowerBrowser is a public domain add-in. At the time I wrote this, it was only available for Visual FoxPro 5.0, but I plan to update it for Visual FoxPro 6.0 in the near future. By the time you read this, you should be able to download a new version from the Developer's Download Files at www.hentzenwerke.com. Future updates will be available from my Web site at www.eps-software.com.

The PowerBrowser adds a number of features to the Visual FoxPro Class Browser. Some of them have been implemented in the new version and some are still useful today. For obvious reasons I'll concentrate on the second group of features. If you still use Visual FoxPro 5.0 and you are interested in the other features, you can find full documentation at www.eps-software.com.

Almost everything the PowerBrowser does happens within the regular browser window. Figure 8 shows the PowerBrowser window. Take note that this image shows the Visual FoxPro 5.0 Class Browser, but the PowerBrowser rearranged most of the items and added some icons, so it looks much like the new Class Browser.

Figure 8. The Visual FoxPro 5.0 Class Browser with the PowerBrowser add-in.

As you can see, the PowerBrowser adds a number of new items to the toolbar. Figure 9 highlights the new items. The details treeview also contains a couple of new nodes, like Non-Default Methods/Events and Non-Default Properties. These were added by hooking into the RefreshMembers() method. Every time the browser is refreshed, the add-in iterates through the Methods and Properties fields to find those methods and properties, as well as the property values.

Figure 9. The Class Browser toolbar with the new items.

The first new feature in the toolbar is the Set Manager. A set is a group of class libraries that belong together or are typically modified together. Figure 10 shows the Set Manager dialog, which allows you to open and define sets of class libraries. In addition, the auto-open feature allows you to open all files with a certain extension in a given directory. All features have been implemented using the original File Open methods of the Class Browser. Once a set is defined, it is stored in Browser.dbf. This is a good example of alternative ways to use this resource table.

Figure 10. The Set Manager dialog.

The next feature is the Power Redefine, which lost some of its importance with the new Class Browser in Visual FoxPro 6.0. Nevertheless, this is a very good example of a browser add-in. First of all, the original button was moved off the screen and replaced by a Redefine button that looks basically the same as the old one, but it behaves differently. When the user clicks this button, a menu is displayed that allows the user to choose between the Power Redefine and the original Redefine. If the user selects the original one, the Click() method of the original Redefine button (now invisible) gets called. When the user selects Power Redefine, a dialog window appears, which allows the user to select a new parent class for the currently selected class. One difficulty with the cloned Redefine button is that the original one gets enabled and disabled based on certain settings and selected classes in the browser. Of course, the new button has to be enabled and disabled as well, but the browser doesn't know about the new button. Therefore, we have to deal with this issue ourselves. I accomplished this task using an active observer (see Chapter 10). This observer is a timer that fires twice each second and checks the enabled status of the original Redefine button and applies it to the new one.

Finally, there is a group of buttons that switch the browser to an entirely different mode. When the first button is clicked, the browser is in standard mode. The second button switches the browser into low-level VCX mode (see Figure 11), which allows you to modify the actual class library low level directly in the VCX. The third button switches the browser into source-code mode (see Figure 12), which allows you to modify the methods of the current class without opening the Visual Class Designer. Each method is displayed in a separate page in a pageframe. The fourth button allows you to print information about the current class (see Figure 13). This feature is important for documentation. Finally, there is the Tools view, which simply launches a couple of wizards that are additional tools, but not browser add-ins.

These five different views are implemented using a pageframe. The buttons in the toolbar are a graphical option group that switches to different pages. This is a very simple way to display views 2 through 5. Of course, the original items of the Class Browser cannot be moved into a page. For this reason they have to be hidden individually every time you switch to a different mode.

Figure 11. The low-level VCX mode.

 

Figure 12. Editing source code in the Class Browser.

 

 

 

Figure 13. Printing class information.



Advanced Object Oriented Programming with Visual FoxPro 6. 0
Advanced Object Oriented Programming with Visual FoxPro 6.0
ISBN: 0965509389
EAN: 2147483647
Year: 1998
Pages: 113
Authors: Markus Egger

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net