ROV Divelog - Developers Guide
Author: Kevin Gomes/Rich Schramm
Revision history
1.0 26-Jun-2020 original
2.0 10-Feb-2021 added detailed sections of checkout, build and checkin steps
2.1 11-Feb-2021 added warning Xojo Project Format setting must be 'Text'
06-Jun-2022 kgomes - cleaned up the docs and removed version numbers since this doc is version controlled in Git. Added Future work section at the bottom.
Introduction
This guide is intended to provide user and developer guidance on the ROV Pilots Divelog application to support future maintenance. There are three separate applications, one for DocRicketts, one for Ventana, and one for MinROV. The Ricketts and Ventana ones are very nearly identical, however there are some variations driven by the pilot’s teams desires for customization. Historically there were two completely different applications and when it came time to port to Xojo, that separation was retained. Its ‘not a biggie’ in my opinion as these have been a very stable applications on each vessel, which have not required significant modifications for literally over a decade or more. Nearly all maintenance has been driven by operating system and database server upgrades, not by new feature requests. The MiniROV is a different application that was created specifically for entering dives 'offline' for the MiniROV and getting them into the Expedition database if needed.
Background
Initially, going back to “Tiburon days”, the first DiveLog was an Microsoft Access database written by Dale Graves for ROV Tiburon. When DocRicketts replaced Tiburon, the pilots requested a replacement. New DocRickettsDivelog application was developed using VisualBasic. The database backend was created using Microsoft SQLServer which already existed for other purposes (VARS) on both ships. Sometime later the Ventana pilots requested a similar app with their own customizations and the VentanaDiveLog was spun-off using the DocRicketts as a template.
Eventually VB4 aged to VB5, then VB6. VB6 was dropped by Microsoft in 2008 however we were able to keep a build environment ‘alive’ through use of Virtual Machines. Eventually we were no longer able to even recompile the application on anything beyond MS ‘Vista’ pre-ServicePack-1. Future deployment as a 64-bit app was simply not possible.
In 2019 we undertook porting the applications to Xojo. XOJO was picked as it was ‘similar’ to VB, was being used in numerous other MBARI GUI and console applications and is cross-platform (OSX, Windows, Linux). The database back-end was able to be kept intact with only a couple of backwards-compatible in-place datatype changes which was a huge advantage.
For the Developer
Preliminaries
Since the ‘guts’ of the two applications are the same for between Ventana and DocRicketts, in this document to refer simply to the “the application’ or “DiveLog” where it applies to any, and I will use VentanaDiveLog or DocRickettsDivelog where there is need to differentiate between them.
I begin with a description of the relational database design, then proceed to a description of various pieces of the application (Windows, Components, Data classes etc.) somewhat in isolation. Then I will move on to describing how these pieces work together.
Your Xojo Development System
Follow standard Xojo installation for your system, then add the required 3rd party 64-bit versions plugins to your Xojo “plugins” directory. See Appendix 2 “Required Third-Party Plugins and Libraries”.
Checking the code out
The code for this application is in BitBucket and the individual repostories are located here:
- Ventana: https://bitbucket.org/mbari/pilotsdivelog_ventana
- Doc Ricketts: https://bitbucket.org/mbari/pilotsdivelog_docricketts/src
- MiniROV: https://bitbucket.org/mbari/pilotsdivelog_minirov/src
Check the target repo out (clone) to your local development machine. Then perform the following steps:
- Verify the third-party plugins are in your Xojo plugins folder (see above).
- You need a
.netrc(or_netrcfor windows) file for authentication in your home directory (see section on Making the Connection - Authentication below) - Open the
VentanaDiveLog.xojo_projectofDocRickettsDiveLog.xojo_projectwith the Xojo IDE - I recommend you immediately uncheck the IDE's BreakOnExceptions option via the Main Menu Bar (Xojo>Project>BreakOnExceptions)
-
Immediately check that your Xojo>Preferences is set to save Project Format as Text

-
If you intend to build the app (vs just play with it), you now need to add your MBS plugins keys to the App.CheckMBSLicense()
- Check the IDE "Build Settings" for OSX (at bottom of left sidebar) to make sure both the Debug and the Release build steps point to ../drivers/libtdsodbc.dylib If not, drag that file from the Finder window's 'drivers' folder and drop it into the window.
-
Should now run the app from the IDE. Note that if the app has never been run on your machine it will likely pop up two warning boxes shown below. If you acknowledge the warnings click thru you should get connected to the test databases on server fog. Note if you did not uncheck 'BreakOnExceptions' earlier you will likely have to do it now because the IDE will keep halting on exceptions

Warning Popup 1 
Warning Popup 2 -
Now would be a good time to check the Preferences file (see Appendix 1 Preferences File). For debug and learning purposes, I'd highly recommend pointing to the test databases on Fog. I also recommend setting the divelog email receipients list to your email and NOT to the institute's listserv to prevent test emails reports from going out.
Final Build and code check in
When you are finally ready to build the executables for all targets.
- You MUST set you Xojo>Preferences to save ProjectFormat as Text (see above)
- Build Settings > Shared bump the version number.
- VersionHistory > Notes > Versions make an entry about what you did.
- Build the app for all targets. The IDE pops up a finder window to the new Builds folder.
- Rename the Builds folder just created, to reflect the new version (effectively creating a 'tagged build').
- Strip the MBS license keys from App.CheckMBSLicense() method.
- Save the project and exit Xojo.
- Navigate back to the top level project folder and check back in the changes.
git commit -a -m "This is what I did."
git push
Database Drivers
The application communicates with the back-end database through the MonkeybreadSoftware (MBS) SQL plugin as described in an appendix. The plugin needs to access a ‘driver’ to the database. This driver is different for Windows and Linux/OSX. This is managed through the connection method in the Data class that will be described.
There is no single, cross-platform driver I have been able to find that seamlessly works between Windows, Linux and OSX. For Windows, the MBS plugin will use the Microsoft OLEDBProvider and is a stable ‘low-impedance’ match to the Microsoft’s SQLServer. For Linux/OSX we must rely on an ODBC connection to the database. There are several driver vendors for these. The one that seems to work the best is FreeTDS. It’s installation and configuration is described further in Appendix 2 - Required Third-Party Plugins and Libraries
Making the Connection - Authentication
The App.Open() event makes a call to Data module method Connect(). This method creates a new SQLDatabaseMBS database instance and attempts the connection using the correct driver for the operating system. On success – it assigns the connected database to the Data module’s property named “DB”. All database operations are done via calls to Data.DB methods.
Connection requires an authentication step. This is determined by settings in the application’s Preferences file (see Appendix 1.) and also the operating system type. On a Windows system you can use the Integrated Security model that allows the logged-on users credential to be passed to the SQL server. This has to be set up and enabled by the IS department (for both the server and within each database).
Otherwise for Windows, Linux, or OSX you can use ‘Server managed authentication’ where the connection requires a ServerUserName and Password. Its highly recommended you stick with the user provided by the Information Engineering Group - expddba. The password is managed by a local protected (read-only-by the authenticated user) .netrc file. This is the same way we manage server authentication on linux python and shell scripts. Talk to anyone in the Information Engineering Group to help get it set up. (Sorry to be obtuse, we want to avoid free-text passwords in documentation!)
Database Design
There is an MS SQL server and database on each ship. On the Carson it is GlacierWind/VNTA_DiveLog and on WesternFlyer its AlaskanWind/DOCR_DiveLog. As can be seen in the ER diagram below (Fig 1.), the schema is very simple. There are several items I want to point out in this section.
![]() |
|---|
| Figure 1 - DiveLog ER Diagram |
Integer id’s are used as primary keys in all tables. Cardinality of relationships is shown as many-to-one (e.g. any Dive has only one Scientist ‘Person’). Likewise a Dive can only be at one Site and utilize one Toolsled. But Sites and Toolsleds can be used in zero or many Dives. Person can be the scientist on many dives.
Notice a Person can have multiple roles, e.g. Supervisor, Pilot and Scientist. Notice there is no PilotID on Dive. That is a legacy holdover from ‘old days’. Ventana wanted a single Pilot, DocRicketts, which has long duration Dives wanted only a ‘Supervisor’ to indicate the 'Chief Pilot'. The visual distinction is made in the labeling exposed through the Xojo application. Also nothing precludes a Person from being both a Scientist and a Supervisor(Pilot). The showAs attributes are used to identify what roles a person is allowed to occupy (although it is not enforced, meaning if a person’s role is revoked, they are not bumped from that role in any dives that were previously created.)
Person, Site and Toolsled exist to provide ‘picklists’ that constrain the names of the associated items so for example there are not multiple spellings of the same person, site or toolsled in the Dive table. So each of those entities has a showAsDeleted attribute. This simply allows the visible picklists in the application to be ‘pruned’ of retired people or sites as those lists can grow quite long over time. As opposed to actually deleting the entry. This only effects what is exposed in the picklist as the Person or Site needs to be retained as it could be in use for Dives created in the past.
Dive.VehicleID is hardwired default to 988 for Ventana and 1642 for DocRicketts these are useful for later processing of Dive info into the shoreside EXPD database and correspond to the PlatformLookup table entry there.
Dive.timezoneID is legacy holdover to the VB6 field. The Xojo app uses the TimeZone name and timezoneBias. More on those in the timezone discussion later in this document. Launch and Recovery datetime attributes are GMT, timezone info is used as the pilots want to view things as ‘local datetime’.
Person, Site and Toolsled entries are different between the two databases. There is no enforcement between the ships. E.g. “Bruce Robison” will have a different id in each database.
The ‘Spare’ attribute was implemented in anticipation that some other field would be requested but as yet it is unused. It is ‘plumbed’ all the way through the application but not exposed to the user – so activating it should be trivial.
Not shown in Fig 1. are the “History Tables”. Each of the four tables shown has insert, update and delete triggers that cause entries into history tables so that changes can be tracked and errors be recovered from although none of that is exposed to the Xojo application. They can be viewed using a database application like Aqua Data Studio.
Divelog Overview
The initial main window for both applications are shown Fig 2 and Fig 3.
![]() |
|---|
| Figure 2. Divelog main window for Ventana |
![]() |
|---|
| Figure 3. Divelog main window for DocRicketts |
You can note the similarities and differences. The major difference is the Ventana UI exposes the Ballast information (Comment and number of bricks). It is important to note that the same ballast info is implemented in the objects and methods within both applications, they are simply not exposed in the DocRicketts UI. DocRicketts also does not expose the “Show Pre Dive Checklist” command button (its actually just hidden on the GUI).
An IMPORTANT design feature of this application is that all changes on the main dive window are immediately posted up to the database. There is no manual ‘Apply’ concept on the main form. The database updates occur when a component’s value 'change' (or sometimes 'lostfocus') event fires (assuming validation passes). This is an attempt to reflect the pilots desire that what is seen is what is actually in the database. This is ‘very nearly’ what happens. The text fields do not update the database on every key-down/character event. The validation-then-update happens on LostFocus event when the user moves out of the field. If validation fails, the ErrorProvider for the field pops-up a red warning signal and cancels the update.
The other major window in the application is raised by clicking the “Add Dive” button. That effectively hides the Main window and raises the AddDive window (Fig 4). Unlike the main dive window, this window will not post to the database until the ‘Apply’ button is clicked. Clicking ‘Cancel’ will first ask if you really want to close-without-saving.
![]() |
|---|
| Figure 4. AddDive window for Ventana. The DocRicketts is quite similar – minus the ballast related fields. |
For DocRicketts, currently the Ballast Bricks and BallastComments fields do not exist. Ballast values and PreDive checklist features are not exposed in the DocRicketts application GUI per pilot requirements. However, those features are implemented ‘under the hood’ and could be exposed in the future with very little effort.
Other windows exist to Add/Edit Persons, Sites and Toolsleds. They can be pulled-up as needed via the menubar or a command button on the GUI. For example, the Add/Edit Person window shown below. Toolsled and Site window add/edit windows are similar.
![]() |
|---|
| Add/Edit Person Window |
Note the +/- buttons to Add or Delete elements in the Listbox. Each Person can be assigned multiple or no roles. Marking a person as inactive will allow that name to not be shown on future picklist – see also the ‘showAsDeleted' discussion in the Database Design section.
Container Controls
There a several Xojo ‘Container Controls’ worth pointing out. Container controls are graphical controls that are made-up of other controls. Two you have already seen above, the MainDiveContainer and the AddDiveContainer. While these may look and behave like standard Xojo Windows, they are actually container controls. The main window ‘Window1’ is a standard Xojo window. It has a ‘PagePanel’ control of nearly the same size as the window with two ‘sub panels’. Each panel is set to display either a MainDiveContainer instance or an AddDiveContainer instance (note that I said ‘an instance’). MainDiveContainer and AddDiveContainer are defined as full-blown Xojo classes in the IDE. They are designed in the IDE to have their own controls, event handlers, properties and methods just as if they were a standard window. Once defined as classes (super=containercontrol), one instance of each are added as “Controls” on the main “Window1” (notice the control instances are named MainDiveContainer1 and AddDiveContainer1). That is just a bit of Xojo OO programming obfuscation but it’s important to notice the detail.
Another container control that is used in several places is the DateTimePicker. It is also a class that is defined in the Xojo IDE. It is a sub-class of ContainerControl. It contains an Einhugur plugins DateControl control and a TimeControl control. Their instance names internal to control are datePicker and timePicker. The DateTimePicker also defines its own methods and event handlers. Several of these get placed on to the MainDiveContainer and AddDiveContainer to implement the Launch and Recovery datetime selectors.
![]() |
|---|
| DateTimePicker Container Control |
Most container controls will also hold a RubberViews component and implement the resizing methods as they also need to be resized with the main window when it is resized by the user.
NumericTextField
There is a NumericTextField component defined for the project. It subclasses the standard Xojo TextField component and adds methods and events to test or validate the entry and restrict entry to numeric key-presses only. Instances of this control can do immediate validation using a combination of the Modified event (say to clear an ErrorProvider as soon as anything is typed) and re-validate the input using the TextChanged event. The Modified() event is raised to the window during an internal handling of the KeyDown event in the textbox.
This control’s validation properties can be set at design-time or loaded at runtime in the window’s Open() event. Properties such as AllowDecimalPoint, AllowEmpty, AllowNegativeNumbers and TestType work in combination with the Validate() method. This is described in more detail in the section “Validation and the ErrorProvider”.
The PreDive Checklist
Both applications support the display of a PreDive checklist. The DocRicketts version currently hides this button as the pilots do not want it. When clicked, this button reads the json file specified in the preferences file and populates the pop-up window similar to below. The .json file is in the Resources folder. A sample .json file is available in the Resources of the archived Xojo Project.
![]() |
|---|
| Example Predive Checklist |
The Data Module
The object-to-relational (ORM) database ‘mapping’ is contained in the ‘Data’ module. You can find it in the IDE Navigator (left) panel. The module consists of:
- A property named DB, of type SQLDatabaseMBS
- Methods relevant to databases - including the all-important Connect() method
- Classes that match each database table
- A ‘Base’ class that is the super to the table classes.
The table-named classes (Dive, Person, Site and Toolsled) have their own methods and properties for managing the ORM to the database. They each:
- Have properties that match fields (name and datatype) in the database tables
- Have methods that perform all of the database operations (select (see List()), insert, update, delete)
- Methods and properties to manage object state (isNew, isModified, etc.)
One sad thing to note: If you examine the actual SQL in the methods of these classes you will see the queries are built-up plain text strings. This is a vulnerability to SQL injection attacks. Good form would be to use ‘prepared statements’ However - every ODBC database plugin I have tried had random crashing or hanging issues with one or more prepared statements so I went the built-up string. Since this app runs in one place only, by reliable employees, behind a firewall, and as an authenticated user - the vulnerability is deemed extremely unlikely to occur.
Using the Data Classes
Because the requirement is for the data fields on the window to be tightly bound to the backend database (giving nearly instant persistence of changes), there is a lot of added complexity to the implementation. Layer on to that, field validation to prevent unreasonable values and mistakes from persisting, and you will see there is a lot going on (sorry). Fortunately there is a repeating pattern so once you get the hang of it, its pretty consistent across the application.
Users interact with instances of data model objects (Dive, Person, Toolsled, Site) thru window components (TextFields, ListBoxes, DatetimePickers etc.).
Programmatically, we want to work with the instances or collections of those database-backed objects. It is the data model objects job to manage the interactions with the database. At the GUI programing level we don’t operate on RecordSets or directly perform SQL. That separation of concerns is what ORM tries to achieve.
As a developer, we need to implement code both sides of the equation. On the GUI side, we rely on row-based container controls like ListBox and ComboBox to hold and ‘navigate’ the collections of the data model objects. In addition to the familiar display and selection/row navigation capabilities of these controls, each row within them has a RowTag properties that is convenient for holding the object instance associated with the row.
For example, if we walk through a result set of all Dives in the database in a for/next loop, we will sequentially encounter Dive objects and assign them a to a single instance named oDive. When the dive ListBox method AddRow() is called (with the whatever the Dive.DisplayName is-e.g. the dive number) that new row’s RowTag property is also a reference (pointer) to current oDive.
lbDive.DeleteAllRows
for each oDive as Data.Dive in Data.Dive.List
lbDive.AddRow( oDive.DisplayName )
lbDive.RowTag( lbDive.LastIndex ) = oDive
next
lbDive.ListIndex = lbDive.ListCount - 1
The code snippet above clears the listbox, repopulates it with all dives, and sets it’s ListIndex to the last row (i.e. the last dive). Setting the ListBox ListIndex will trigger the ListBox Change() event. So the GUI fields can be refreshed to reflect the selected dive info using the dive object from the RowTag (more on that in a minute).
Look more at the line “for each oDive as Data.Dive in Data.Dive.List”. It is calling the very important “List” method on the Data.Dive class. This is a ‘Shared Method’ on the Dive class. (Look in the left ‘Navigator’ panel of the IDE, drill down to Data > Dive > Shared Methods). In the Inspector panel, note the method returns an array of Dive objects. Within the method is the SQL Select statement and the call to the database. So in that foreach loop you will now see we are simply walking that array of Dives and assigning references to each ListBox row’s RowTag.
Now digging deeper into that List() method of the Dive class, the pseudo code for it is below.
Dim rs As RecordSet
rs = Data.DB.SQLSelect(sql)
If rs <> Nil Then
While Not rs.EOF
//call the Dive constructor with the recordset object
dim oDive as new Dive(rs)
aro.Append(oDive)
rs.MoveNext
Wend
End
Notice a ‘new’ dive is created via a constructor that takes the recordset returned from the database. If you explore that constructor you will see it calls the Dive.ReadRecord() method to populate the new dive object according to the ORM mapping of database fields to object properties.
me.PK = rs.Field("id").IntegerValue
me.DiveNumber = rs.Field("DiveNumber").IntegerValue
me.Launch = rs.Field("Launch").DateValue
me.Recover = rs.Field("Recover").DateValue
me.Purpose = rs.Field("Purpose").StringValue
me.Results = rs.Field("Results").StringValue
(etc)
Also note each object has a constructor that takes no RecordSet argument. This is used in a case where we want to create a new instance, populate it and insert it into the database – eg add a new dive, new person, new site, etc. This ‘pattern’ is repeated in each data model class that maps to a database table.
- There is a List() method that is used to populate some list control, which is then used to ‘navigate’ to a selected item
- There is a constructor that handles the ORM from a RecordSet via a ReadRecord() method
- There are other methods for managing the object, to Save, Insert, Update, or Delete
- There are methods to keep track of if the object is brand new, has been modified, or can be safely deleted.
- There is a DisplayName ‘computed property’ (a property with getters and setters) that ‘knows’ how to build an appropriate string for the class (like lastname, firstname)for a Person or divenumber for a Dive)
- There is a TableName() shared method that is hard coded to return the TableName in the SQL database. Note the Dive class ignores this as the name has to come from Preferences.DiveTableName – but Site, Toolsled and Person do.
The class “Base” that Dive, Person, and Toolsled subclass has a couple really important properties:
- PK (with getters and setter) to hold the primary key for each instance.
- m_dictSavedProperty is a dictionary the holds (caches) a copy of whatever is currently persisted in the database for object with the current PK (so it can be tested against the local copy to see what has changed. That is how the isModified() methods work).
Data Binding
Let's leave the data model now for a bit to see how the Xojo list control is used to ‘bind’ to the visible controls on the GUI. All of the top-level viewable windows will have a one or more list controls depending on the purpose of the viewable window. They also hold a local property of the name and type of the object they represent (MainDiveContainer has m_oDive, windPersonAddEdit has m_oPerson etc). That property is used a lot throughout the code because it points to the current object being ‘worked on’
A change of the selected row of the component will cause its Change() event to fire which should initiate an action to move to display the selected item’s RowTag object and to possibly take action to persist the item.
We will first detail the case of the MainDiveContainer lbDive listbox. The lbDive listbox sits on the MainDiveContainer. Its visible property is set to false in the Open() event handler because we don’t need to see it, and the fwd/back navigation button will set the active row.
So when lbDive.Change() event triggers, the forms m_oDive property is set according to the new RowTag and then that window's Display() method is called to populate the form fields from the m_oDive object. That method will also cascade calls to set Scientist, Supervisor, Site and Toolsled combos to their correct row using the id foreign key.
So it is this cascade of Open() > DisplayList() > ListIndex() > listBox.Change > Display() that binds fields to components. Variations occur depending on the task at hand. The main concept here is that DisplayList() is used to get the objects into the list control, and Display() is used to bind current object to the GUI components.
The AddDiveContainer works slightly differently. The fields are not tightly bound to the database and the new dive insert does not happen until ‘Apply’. Examine it’s DisplayList() method.
m_oDive = new Data.Dive
m_oDive.DiveNumber = getNextDiveNumber /// <<<<< getNextDiveNumber from database
//initially we will populate it with some fields from whatever the last viewed dive
//was on the main form... just because we need 'something' to begin with
m_oDive.ScientistID = Window1.MainDiveContainer1.m_oDive.ScientistID
m_oDive.SupervisorID = Window1.MainDiveContainer1.m_oDive.SupervisorID
m_oDive.ToolsledID = Window1.MainDiveContainer1.m_oDive.ToolsledID
m_oDive.SiteID = Window1.MainDiveContainer1.m_oDive.SiteID
if StrComp( Preferences.ROVName, "Ventana", 0 ) = 0 then
m_oDive.VehicleID = App.kVehicleVentana
ElseIf StrComp( Preferences.ROVName, "DocRicketts", 0 ) = 0 then
m_oDive.VehicleID = App.kVehicleDocRicketts
else
m_oDive.VehicleID = App.kVehicleUnknown
end if
Note that it’s m_oDive property is set to a ‘new’, empty Data.Dive (its doesn’t pass a RecordSet to the constructor). It assigns the next sequential dive number by calling getNextDiveNumber(). Then it ‘suggests’ some initial values for pick lists and finally calls Display().
The Save() Method
All of the various controls that act on instances of the data model classes (eg edit fields, add buttons, delete buttons, date pickers etc) will have action event handlers that perform the desired function. Which specific event fires differs depending on whether it’s intended to be tightly-bound to the database or not.
Tightly-bound textAreas or editFields typically have a lostFocus event that calls the container object’s Save() method. For tightly-bound comboBoxes and checkBoxes it’s the Change() event. Examine the event handler for the control in the IDE to see which one is active.
Note these event handlers call the Save() method of the container or window. That Save() method typically may perform additional house-keeping and final validation and will then call the Save() method on the data model object itself. Below is a snippet from the MainDiveContainer.Save() method.
// make sure we have the wettime duration right
dim oDateLaunch, oDateRecover as Date
oDateLaunch = LaunchPicker.Value
oDateRecover = RecoverPicker.Value
dim delta as Int64 = CType(oDateRecover.TotalSeconds,Int64) - CType(oDateLaunch.TotalSeconds,Int64)
m_oDive.wettime = delta
if NOT ValidateAll then
return
end if
result = m_oDive.Save()
if result then
//the dive was updated so the listbox rowtag object is stale
lbDive.RowTag(lbDive.ListIndex)= m_oDive
end if
On non-bound controls - primarily on AddDiveContainer - the event handler that fires for changes may differ. The event handler may perform immediate validation but the Save sequence is delayed until the Apply button is pressed.
Validation and the ErrorProvider
Data validation is performed on all data entry fields to prevent unreasonable values and mistakes from persisting into the database. This is where the Einhuger “ErrorProvider” plugin is used. Checkout https://www.einhugur.com/Html/ErrorProvider/index.html. I suggest you also explore the example project that comes with the plugin when you download it.
Basically one instance of the ErrorProvider control is placed on each of MainDiveContainer and the AddDiveContainer classes. These instances are named ErrorProvider1 in both classes but they are completely separate control instances. They are normally invisible on the running GUI. When validation occurs on the value of a field or control and if validation fails, the ErrorProvider1.ShowError() method is called and passed the control handle and error message. The ErrorProvider displays a red alert just to the left or right of the control. The users hovering the mouse over the alert will display the help message.
![]() |
|---|
| Error Message Example |
Although there is only one ErrorProvider instance, it supports displaying separate error alert symbols and messages on multiple controls. It has methods to clear either individual or all warnings on the window. Typically an individual warning is cleared as soon as a control’s value begins to be modified. Validation then occurs again when appropriate change event fires, for example, when TextChange or LostFocus occurs (as shown below) for the nefMaxDepth (a NumericEditField).
nefMaxDepth.Modified()
ErrorProvider1.Clear(me)
The nefMaxDepth.Modified event is fired when any character is typed. This causes the ErrorProvider to clear the any alert (see the KeyDown event of the Class NumericEditField for how that Modified gets raised. It’s basically a fancy KeyDown event).
nefMaxDepth.TextChange()
if NOT me.Valid then
ErrorProvider1.ShowError(me,me.ErrorDisplayMessage,true)
end if
Then TextChange fires and the new value is tested. This cycle of keypress > modified(clear) > textchange(validate) happens every character so the feedback to the user is immediate.
For controls that are tightly-bound to the database (eg. on the MainDiveContainer) there is a third event - the LostFocus() event that is handled. That is where the window container’s Save() event is called to attempt to persist the change (after all fields are validated again in ValidateAll()).
The Launch and Recover DateTimePickers are a tad bit different as it is the duration between the controls that is important. Also the picker’s multiple internal event handler implementations are purposely hidden. Instead, a custom ValueChanged event is raised. From the ValueChange() a new duration between Launch and Recovery is computed (wettime) and an attempt to Save() is made. Below is a snippet from the ValueChanged() event of the Launch DatetimePicker.
if m_oDive = nil then
return
end if
dim oDateLaunch, oDateRecover as Date
oDateLaunch = LaunchPicker.Value
oDateRecover = RecoverPicker.Value
// compute wettime and display as string on GUI
dim delta as Int64 = CType(oDateRecover.TotalSeconds,Int64) - CType(oDateLaunch.TotalSeconds,Int64)
Dim intdelta as Integer = delta
efWetTime.text = SecsToTimeString(intdelta,True,True,False)
// if launch datetime has changed from whats in cache call Save()
if m_oDive.Launch.TotalSeconds <> oDateLaunch.TotalSeconds then
m_oDive.Launch = oDateLaunch
self.Save("Launch changed")
end if
Note that in the above it is the containing window’s Save() that is called which will do validation on all controls - including the Launch and Recover controls. That is where the ErrorProvider will be alerted should the duration fail the test.
Another wrinkle is where the red Alert is placed on the AddDiveContainer when duration test fails. Recall that normally the ErrorProvider is passed a reference to the control failing validation, and the Alert appears adjacent to it. On the AddDiveContainer, the form is a little crowded between the pickers and the GrabTime buttons. So a pair of non-viewable label controls were added above the pickers. These are the lblLaunchError and lblRecoverError controls. On a duration failure of this form, they are substituted for the picker and passed to the ErrorProvider so it can position of the red Alert symbol relative to it.
The Preferences file contains the defaults for settable properties such as TestType, min, max thresholds, and error messages which can be ‘tuned’ to the pilot’s requirements without recompiling the application. Preferences get loaded via App.Open() event. They either get uploaded to in the NumericEditField controls via window container’s Open.Event() or can be used directly during validation.
Vehicle Total Hours
The Pilots want to see the historical accumulated total time on the vehicle. This is the purpose behind the Dive.Wettime field and the MainDiveContainer’s efTotalWetTime textbox. Every dive has the computed dive duration stored as an attribute in the database. When the Display() method of the container moves to Display() a different dive, the database is queried via a call to Dive.GetTotalWetTime() method. Note that Dive wettime is computed within the application and persisted up to the database. There also exists a trigger in SQL server on the Dive table that also will recompute wettime when there is any update to Launch or Recover – just in case it gets modified externally, from SQL via Aqua DataStudio etc (see: [dbo].[trgr_update_launch_recovery] on the server).
Time and Timezones
Local time and timezones are very messy for any application. Since pilots want to work in local time and we need GMT on-shore in Expd database, we let the pilot’s Divelog use local time and store the PC’s timezone setting and offset from GMT in the Dive. Later we convert to GMT when the records are copied into EXPD (there is little danger as we only use the pilots dive launch/recover as a 'guide'. Video lab or others create the official dive time entries to EXPD using this and other datasources).
Edits to Launch and Recovery datetimes for dives occurring in the past are much harder. It is not possible to perfectly compute the bias going back as there is always a 1hr uncertainty around DST change dates. But that is quite a fringe-case.
So for this app, we use the bias from the moment of the original Dive insert at sea, and reuse it on any datetime Update. Hopefully the pilots got the date right when they did the original AddDive.
Logs
VentanaDiveLog has a logging capability built in. A ‘LogLevel’ of DEBUG, INFO, WARNING, FATAL or NONE is specified in startup json Preferences file. Timestamped logs will be found in the Logs directory specified there. The default LogLevel is NONE and currently the only log message implemented is in App.Open() event handler. Look for the line: Utils.log4me(App.INFO,"VentanaDiveLog log started."). To create additional log entries, lines can be added or enabled in the code that call the Utils.log4me() method with the appropriate severity.
Post Dive Report and Email
A command button on the MainDiveContainer will prepare and send a post-dive email message to recipients designated in the Preferences file. The Email sequence is initiated on by the pbEmailReportXOJO.Action event. There was a bit of a struggle to get a solution with decent feedback that the message was sent. The feedback desired is a display line on the GUI that is normally invisible, but lets the pilots know if the mail attempt was successful or not.
I went back-and-forth between the Xojo MailSocket or the MBSCURL socket. I finally got the XOJO socket to work correctly with implementation of five Events on the socket, plus a timer and two Boolean flags to track status and an EmailStatusLabel. Yuck!
- On button press, the MailSocket component is initialized and a new EmailMessage object is populated with both the subject, body and recipient payloads. The mail message is ‘Appended’ to the socket which queues it for sending.
- The two status tracking flag properties emailDisconnectedByMe and emailSentOKReceived are cleared.
- The viewable EmailStatusLabel is set to “Begin Sending”
- The MailSocket is told to send.
From there, the flow is determined by which MailSocket event handlers fire. Basically as the events (good-or-bad) fire, the boolean status flags track progression and the EmailStatusLabel changes to inform the user. A run-once, 4 second timer (emailStatusVisibilityTimer) is ‘reset’ to control how much longer we keep the status label up waiting for get confirmation from the server (or failure). If the timer fires after 4 seconds it simply clears whatever the last EmailStatusLabel text was.
If things fail you expect an Error event. However the confirmations back from the server can be different depending on operating system. On a successful MailSent you would expect to then manually disconnect from the mail server, post status, wait 4 seconds, clear the label and move on. Some cases, even with a successful send, you will get an Error event caused by the server disconnecting you automatically post-send. So within the Error event handler we must check if it was a disconnect ‘error’ (code 102) that may have fired - but after our two boolean progress flags indicated we had actually succeeded – in which case we say it’s a success, otherwise we say it’s a ‘Fail’.
APPENDIX 1: Preferences File
Xojo DiveLog looks for its startup configuration in a set of ‘Preferences’ utilizing a PreferencesModule – a set of classes provided by Paul Lefebvre at Xojo. In a nutshell, in the App.Open() event, a json ‘Preferences’ file is read from ‘Resources’ folder in a known location in the users Home folder. That location may be different on Linux, OSX and Windows. Directories and a template file are created if they do not already exist in the users home directory. The template file will then need to be edited. The Project level directory will be named according to the application. Below shows the paths for the VentanaDiveLog. DocRicketts will be similar.
- OSX:
/Users/xxxx/VentanaDiveLog/Resources/VentanaDiveLog.pref - Windows:
C:\Users\xxxx\VentanaDiveLog\Resources\VentanaDiveLog.pref - Linux:
/Users/xxxx/VentanaDiveLog/Resources/VentanaDiveLog.pref
The ‘default’ Preferences file contents for each vehicle are shown below. These name:value pairs need to be configured for the platform (Ventana or DocRicketts). Most of the key:value pair purposes should make sense given the key name. Generally though they a setup in groups that:
- Identify the database server and authentication
- Setup the email for post dive reports
- Set some defaults sizes of controls and fonts so you can adjust depending on your monitor resolution
- Set limits to some form validations to eliminate nonsensical values
Some of the pairs don’t make sense for DocRicketts, for example ballast values and PreDive checklist features are not exposed in the DocRicketts application GUI per pilot requirements. However, those features are implemented ‘under the hood’ and could be exposed in the future with very little effort.
Examine the PreferencesModule within the application’s project tree using the Xojo IDE. Specifically examine the documentation found within “Notes” folder for the module which documents the Load and Save sequences for preferences. For further reference, also see: https://blog.xojo.com/2014/01/27/saving-preferences/.
Default Preference for Ventana
{
"ROVName": "Ventana",
"LogLevel": "NONE",
"ServerName": "glacierwind.rc.mbari.org",
"ServerPort": 1433,
"IntegratedSecurity": "False",
"UserID": "expddba",
"UserAuth": "*******",
"DatabaseName": "VNTA_Divelog",
"DiveTableName": "Dive_Ventana",
"PrediveChecklist": "VentanaPreDiveChecklist.json",
"DefaultEmailServer": "mbarimail.mbari.org",
"DefaultEmailSender": "ventana@mbari.org",
"DefaultEmailDisplayName": "ventana@mbari.org",
"DefaultEmailRecipients": "rich@mbari.org, ventana@listserver.mbari.org",
"PreferredTextControlFontUnits": 2,
"PreferredTextControlFontSize": 13,
"PreferredTextControlHeight": 23,
"PreferredDateTimeControlFontUnits": 2,
"PreferredDateTimeControlFontSize": 11,
"PreferredDateTimeControlHeight": 23,
"MaxDepthLimit": 2500,
"MaxDepthLimitErrorText": "Max Depth limit exceeded (2500)",
"MaxSeaStateLimit": 12,
"MaxSeaStateLimitErrorText": "Max SeaState limit exceeded (12)",
"MaxWindSpeedLimit": 80,
"MaxWindSpeedLimitErrorText": "Max WindSpeed limit exceeded (80)",
"MaxCurrentLimit": 20,
"MaxCurrentLimitErrorText": "Max Current limit exceeded (20)",
"MaxBallastType1Limit": 100,
"MaxBallastType1LimitErrorText": "No more than 100 Ventana bricks allowed by preferences settings",
"MaxBallastType2Limit": 20,
"MaxBallastType2LimitErrorText": "No more than 20 SMD bricks allowed by preferences settings",
"MaxDiveDurationSecondsLimit": 86400,
"MinDiveDurationSecondsLimit": 60,
"DiveDurationLimitErrorText": "Recovery must be later than Launch and < 24hrs"
}
Default Preferences for Doc Ricketts
{
"ROVName": "DocRicketts",
"LogLevel": "INFO",
"ServerName": "alaskanwind.wf.mbari.org",
"ServerPort": 1433,
"IntegratedSecurity": "False",
"UserID": "expddba",
"UserAuth": "*******",
"DatabaseName": "DOCR_Divelog",
"DiveTableName": "Dive_DocRicketts",
"PrediveChecklist": "None",
"DefaultEmailServer": "mbarimail.mbari.org",
"DefaultEmailSender": "docricketts@mbari.org",
"DefaultEmailDisplayName": "docricketts@mbari.org",
"DefaultEmailRecipients": "rich@mbari.org, schramm@mbari.org",
"PreferredTextControlFontUnits": 2,
"PreferredTextControlFontSize": 13,
"PreferredTextControlHeight": 23,
"PreferredDateTimeControlFontUnits": 2,
"PreferredDateTimeControlFontSize": 11,
"PreferredDateTimeControlHeight": 23,
"MaxDepthLimit": 2500,
"MaxDepthLimitErrorText": "Max Depth limit exceeded (5000)",
"MaxSeaStateLimit": 12,
"MaxSeaStateLimitErrorText": "Max SeaState limit exceeded (12)",
"MaxWindSpeedLimit": 80,
"MaxWindSpeedLimitErrorText": "Max WindSpeed limit exceeded (80)",
"MaxCurrentLimit": 20,
"MaxCurrentLimitErrorText": "Max Current limit exceeded (20)",
"MaxBallastType1Limit": 100,
"MaxBallastType1LimitErrorText": "No more than 100 SMD bricks allowed by preferences settings",
"MaxBallastType2Limit": 20,
"MaxBallastType2LimitErrorText": "No more than 20 SMD bricks allowed by preferences settings",
"MaxDiveDurationSecondsLimit": 86400,
"MinDiveDurationSecondsLimit": 60,
"DiveDurationLimitErrorText": "Recovery must be later than Launch and < 24hrs"
}
Appendix 2 – Required Third-Party Plugins and Libraries
Monkeybread Software Plugins
The 3d-party Xojo SQLDatabaseMBS plugin purchased from Monkeybread Software (MBS) is a key component for communicating with the database driver. The MBS Xojo CURL Plugin is used to send the email post-dive reports to operations personnel.
The MBS plugins must be licensed in order to compile the DiveLog executable. The 64-bit version plugins must be installed into your Xojo plugins folder (my current case in Applications/Xojo 2019 Release3.1/Plugins folder). Both the MBS Main and CURL plugins are distributed in what MBS curiously calls the ‘Complete’ package license. The MBS SQL plugin is purchased and licensed separately. The following MBS plugins are required:
- MBS Xojo Main Plugin.xojo_plugin
- MBS Xojo CURL Plugin.xojo_plugin. <<<< is it????
- MBS Xojo SQL Plugin.xojo_plugin
And your valid MBS license keys must be properly registered in the App.CheckMBSLicence() method of the Xojo project (see App > EventHandlers > Open and method CheckMBSLicense() in the Xojo IDE for the project). The license registration code is obscured on the version controlled project. You will need to substitute valid license keys obtained from your MBS email when you purchased the plugins.
Einhuger Plugins
Additional 3rd-party plugins are purchased from Einhuger to provide components lacking in the standard Xojo suite of controls. (see: https://www.einhugur.com).
These include calendar and time/date controls. We also extensively use their ErrorProvider for form field validation and their PictureButton for customized command buttons.
The following Einhugur plugins are required (64-bit versions):
- #TypeLib.xojo_plugin
- #CoreClasses.xojo_plugin
- TimeControl.xojo_plugin
- DateControl.xojo_plugin
- PictureButton.xojo_plugin
- ErrorProvider.xojo_plugin
Note that the Einhugur plugins require a rather complicated decrypting and registration procedure see Appendix 3 - Installing Einhuger Plugins
Rubberviews Resizeable Windows and Controls Library
DiveLog makes use of a 3rd-party library called RubberViews to manage window resize events to correctly resize all controls and fonts. I purchased a ‘lifetime unencrypted’ license from https://rubberviews.com/.
RubberViews classes and controls are installed by adding the provided RubberViews_Classes folder to the applications project folder. Then adding that folder into the Xojo IDE project space (via the main menubar > Insert > Folder). Then the various classes become available as components in the Xojo IDE components Library.
Each graphical window or container control (AKA windows, container controls such as MainContainer, AddDiveContainer and DatetimePickers etc) to be resizeable must:
- Have a RubberViews control added to it (selected from the controls library and dropped onto the component. (It displays as a small grey square in the IDE which is very hard to spot). It will automatically receive a default name like ‘RubberViews1’
- Each window or component’s Open() event, must initialize the control like ‘RubberViews1.Init(Self)’
- Each window or component’s must also implement the Resized() and Resizing() event handlers.
FreeTDS OBDC Driver
The database driver for OSX and Linux deployments is FreeTDS. While it is open source and ‘should be’ able to be downloaded and built ‘anywhere’, it can be very particular. It is best if it is obtained directly from Christian at Monkeybread Software as he has built and tested it against the MBS SQL Control. At the time of this writing, a version of it is archived in the projects ‘drivers’ folder (libtdsodbc.dylib 26Aug13).
It is important for it to be in that folder whenever the application is rebuilt. A “build step” will copy it into the built application’s installation bundle so the executable can find it at runtime. For OSX, there are ‘Build Steps’ that copy the freetds driver into the deployable project ‘resources folder’ for both debug and release builds. It is critical that these steps can find the freetds driver file libtdsodbc.dylib. The figure below shows where the BuildSteps are found in the IDE. A similar pair of build steps will be needed to bundle the linux driver if we ever build it for linux (untested as of 4/2019). And it’s driver would need to be libtdsodbc.so (see Data.Connect()).
![]() |
|---|
| The OSX Build Steps for bundling-up the FreeTDS driver into the deployable app package |
Appendix 3 - Installing Einhuger Plugins
Einhugur plugins require a rather complicated decrypting and registration procedure and the documentation is not very clear. Some guidance is added here (Note Typelib plugin is free and does not require Registration).
- You need your valid license key (serial number) emailed from Einhuger when you last purchased or renewed your license.
- Download a copy of the “Registrator-Decoder” app from Einhuger and install on the same computer your Xojo IDE is on. (see: https://einhugur.com/Html/utils.html). At time of this writing, it must be version 8.0.3 or later.
- Download ‘Full Version’ copies of required plugins from https://einhugur.com/Html/all.html and save in some separate and safe directory for future use. (mine are in
/Users/Rich/XOJO/EinhugurPlugins/Circa2019). - Uncompress each plugin download. Eg. the Utils.zip will uncompress into a folder like “UtilsLib 7.1.2”. It will contain Subfolders with examples, documentation etc. The important item to look for is in the sub-folder “Xojo Plugins” with extension “.ecr”
- Run Registrator app and de-encrypt each plugin as described below.
- Move or copy the resulting “.xojo_plugin” to your XojoIDE appications “Plugins” folder.
Fire up the “Registrator App” , enter the license key owners name in the text box provided. Then select SerialNumbers tab and add your key.

Now switch to the ‘Decoding’ panel and “Browse” to the unzipped plugin folder you want to decrypt. Locate and select the appropriate .ecr file compatible with the Xojo IDE version you are using. Click on the Decrypt button and Registrator will validate your license.

Now look at the directory where you found the .ecr file. You should see an additional file of the same name but the extension no longer has the .ecr. This is the finished plugin that you will now move or copy to your Xojo IDE’s Plugins folder. Repeat for all of your required Einhuger plugins. Note the XOJO IDE must be closed and reopened to recognize the new plugins.
Appendix 4 - Installation of the Application on Target Vessel
Installation on a target vessel should approximately mirror the default structure of the Development Project – minus the ‘src’ folder. After the project is built using the Xojo IDE Build icon, Xojo creates a deployable folder for each selected target operating system. (Currently OSX and Windows 64-bit). You will find them in subdirectories below VentanaDiveLog/src/Builds-VentanaDiveLog (or obviously DocRickettsDiveLog). You will also need to inspect and edit the file in the Resources folder – the VentanaDiveLog.pref file for the target ROV system. These topics are covered in detail elsewhere in this document. Briefly though, Xojo VentanaDiveLog looks for its startup configuration in a set of ‘Preferences’ utilizing a PreferencesModule – a set of classes written by Paul Lefebvre at Xojo. (see: “Appendix 1 – Preferences” of this document). You will also have to establish the connection to the database. See authentication section of this document.
On the deployment machine you can delete the src directory as we dont support an at-sea Xojo development environment. To ‘deploy’ the app slightly varies on OSX or Windows. Basically you copy from the Builds folder executable to the top-level /Users/xxx/VentanaDiveLog folder depending on the target platform OS type. For OSX its simply the VentanaDiveLog.app which is completely self-contained. For Windows, you must drag the entire contents of the built ‘VentanaDiveLog’ folder (ie. the .exe, dll’s and the Libs must all be kept together. For example, on Windows your deployment for Ventana should look like:
C:\Users\xxxx\VentanaDiveLog
\VentanaDiveLog
\VentanaDiveLog Libs
VentanaDiveLog.exe
(a bunch of dll files)
\Resources
VentanaDiveLog.pref
\drivers
The pilots like a desktop shortcut to the VentanaDiveLog executable. (right-click on the exe and select ‘Create Shortcut', then place it on the desktop. You can also change the icon if you choose to. Right-click the shortcut and select ‘Properties’. Then from the pop-up, select ‘Change Icon” and navigate to the your choice of .ico file.
Appendix 5 – Ventana’s PreDive Checklist
Here is a listing of the initial Ventana PreDive Checklist
{
"Caption": "Ventana PreDive Checklist",
"Frames": [
{
"Name": "Left",
"Labels": [
{
"text": "Communications",
"indent": "0"
},
{
"text": "Bubinga Data Logger",
"indent": "0"
},
{
"text": "Dive Number",
"indent": "0"
},
{
"text": "CTD Syringe / POWER",
"indent": "0"
},
{
"text": "Ground Faults",
"indent": "0"
},
{
"text": "2300VAC",
"indent": "4"
},
{
"text": "Vehicle VAC",
"indent": "4"
},
{
"text": "Vehicle VDC",
"indent": "4"
},
{
"text": "Can Connections",
"indent": "0"
},
{
"text": "Camera Connections",
"indent": "0"
},
{
"text": "Cameras Washed",
"indent": "0"
},
{
"text": "Lights",
"indent": "0"
},
{
"text": "Gyro - Turns Reset",
"indent": "0"
},
{
"text": "Sonar",
"indent": "0"
},
{
"text": "USBL 6G / AA",
"indent": "0"
},
{
"text": "Navigation Tracking Ranger2",
"indent": "0"
},
{
"text": "VentanaDiveLog",
"indent": "0"
},
{
"text": "VARS Still Frames",
"indent": "0"
},
{
"text": "Lasers",
"indent": "0"
},
{
"text": "Homer",
"indent": "0"
},
{
"text": "Cans Secure",
"indent": "0"
},
{
"text": "Strobe/RDF Beacon Signal",
"indent": "0"
},
{
"text": "Drawer Pinned",
"indent": "0"
},
{
"text": "Big Drawer Drain Valve",
"indent": "0"
}
]
},
{
"Name": "Right",
"Labels": [
{
"text": "Ballast",
"indent": "0"
},
{
"text": "Tank Empty ",
"indent": "4"
},
{
"text": "Valves to Dive",
"indent": "4"
},
{
"text": "Burp Comp",
"indent": "4"
},
{
"text": "Reservoirs",
"indent": "0"
},
{
"text": "Mains",
"indent": "4"
},
{
"text": "Electrical",
"indent": "4"
},
{
"text": "Motor",
"indent": "4"
},
{
"text": "Motor Coupler",
"indent": "4"
},
{
"text": "Umbilical",
"indent": "4"
},
{
"text": "Toolsled",
"indent": "4"
},
{
"text": "Nupro Valves All",
"indent": "0"
},
{
"text": "Thrusters / Camera Enabled",
"indent": "0"
},
{
"text": "Belly Pack",
"indent": "0"
},
{
"text": "Toolsled Functions",
"indent": "0"
},
{
"text": "suction sampler pump, hose check",
"indent": "4"
},
{
"text": "indexing functions",
"indent": "4"
},
{
"text": "cameras",
"indent": "4"
},
{
"text": "lights",
"indent": "4"
},
{
"text": "hydraulic functions",
"indent": "4"
},
{
"text": "Latch Test",
"indent": "0"
},
{
"text": "Ground Strap",
"indent": "0"
},
{
"text": "Launch Time",
"indent": "0"
}
]
}
]
}
Bugs
Bugs are tracked in the source repos here:
Future Work
- Can I combine both apps into one codebase? Based on what I'm seeing, I think they could be. It looks like the main differences are:
- Carson has "Show PreDive" to show checklist for predive, DR does not
- Carson does not have seconds on Launch/Recover HH:MM:SS and DR does, but DR wants just HH:MM
- Carson has WetTime as HH:MM, DR does not, but wants that.
- Carson has Toolsled, Ventana Bricks, SMD Bricks and Ballast Comment fields and DR is asking for an 'ROV Set up text box and ballast, thes could probably be combined.
-
Implement search. A new window opens allows for searches by the list below, returns a table of results, user selects the one they want and then that opens in the main window.
- By dive number
- By scientist
- By keyword (search in ballast comment, ROV set up (to be added?), purpose, results)
- By site?
- From DJ: We would like to make the results of the dive searchable… so for example I could search for repeating problems with something like D4. I suppose other things could be searchable, like a particular scientist, but my first preference is to at least make the results searchable.
-
From Rich's notes:
I anticipated future requests coming to filter or search for dives by Scientist, Site etc. Although it was not exposed to the GUI and not fully tested, it has been briefly tested.
If you examine the Data.Dive.List() method you will see it has a couple of optional parameters. sCriteria and sOrder.
Now that you hopefully understand the role of the DisplayList() and ListBox model of populating the MainDiveContainer. The DisplayList() method currently just calls Dive.Dive.List which returns all Dives ordered by divenumber.
If you modify DisplayList to pass a criteria string, you will get a subset. For example:
dim sCriteria as string sCriteria = "SiteID in (Select id from Site where location like '%Clam Field South%')" + _ " AND ScientistID = (select id from Person where lastName = 'Barry')" for each oDive as Data.Dive in Data.Dive.List(sCriteria) lbDive.AddRow( oDive.DisplayName ) lbDive.RowTag( lbDive.LastIndex ) = oDive nextA filter selection window would need to be built and fit into the GUI worklow – but the rest of the mechanics are there to support it.
-
For DR, add ROV set up text box and ballast. I think this could be combined with ballast stuff from Ventana.
- DR: Change launch/recover times to be in HH:MM and Wet time units also to HH:MM
- Cannot add dive site (From Marko, it's possible it's just not working)
- On Ventana, emails don't get sent when on VSat. Need to fix that, but also implement some kind of queue system that will keep trying.
- Please get rid of the alarm if the recovery time is the same as or earlier than the launch time. This happens when the user clicks to add a new dive, but the dive hasn't finished yet so they don't add a recover time which then throws some kind of 'alarm'. Basically, they just add some bogus time and then edit it later to prevent the alarm.
- Make it so the system doesn’t crash and lose our entries, especially the results, so I envision this as being saved to a real time machine so when the dive log crashes the records entered up to that point are entirely preserved. This happens when they are trying to add a new dive. If they click Add and then fill out all the information and click apply, the app sometimes crashes and they lose their information. They are doing a work around where they create a new dive, apply it and then edit it after.
- It would be kinda nice if you could log brick counts down to the .5 as it stands it only goes down to 1 brick, we have several configurations where we have X.5 bricks, mostly midwater. I think this is already working, but need to verify.
- Move the MBS licensing information to an external .json file. I did this with Dataprobe and then I don't have to pull it out before checking in code, you just put the license information in the .json file and it's read on startup. See the "Open" event handler on the App class in Dataprobe for an example.
- Probably want to move away from netrc for database connection properties.








