diff options
-rw-r--r-- | Documentation/Makefile | 10 | ||||
-rw-r--r-- | Documentation/user-manual.txt | 612 | ||||
-rw-r--r-- | Makefile | 30 | ||||
-rw-r--r-- | cochran.c | 224 | ||||
-rw-r--r-- | display-gtk.h | 20 | ||||
-rw-r--r-- | dive.c | 121 | ||||
-rw-r--r-- | dive.h | 8 | ||||
-rw-r--r-- | divelist.c | 52 | ||||
-rw-r--r-- | equipment.c | 23 | ||||
-rw-r--r-- | file.c | 136 | ||||
-rw-r--r-- | file.h | 11 | ||||
-rw-r--r-- | gtk-gui.c | 88 | ||||
-rw-r--r-- | info.c | 6 | ||||
-rw-r--r-- | libdivecomputer.c | 4 | ||||
-rw-r--r-- | linux.c | 14 | ||||
-rw-r--r-- | macos.c | 119 | ||||
-rw-r--r-- | main.c | 6 | ||||
-rw-r--r-- | packaging/macosx/Info.plist | 4 | ||||
-rwxr-xr-x | packaging/macosx/subsurface.sh | 5 | ||||
-rw-r--r-- | packaging/windows/subsurface.nsi | 5 | ||||
-rw-r--r-- | parse-xml.c | 273 | ||||
-rw-r--r-- | profile.c | 5 | ||||
-rw-r--r-- | statistics.c | 289 | ||||
-rw-r--r-- | uemis.c | 2 | ||||
-rw-r--r-- | windows.c | 14 | ||||
-rw-r--r-- | xslt/jdivelog2subsurface.xslt | 99 |
26 files changed, 1542 insertions, 638 deletions
diff --git a/Documentation/Makefile b/Documentation/Makefile new file mode 100644 index 000000000..273eae521 --- /dev/null +++ b/Documentation/Makefile @@ -0,0 +1,10 @@ +ASCIIDOC=asciidoc +BROWSER=firefox + +doc: user-manual.html + +show: user-manual.html + $(BROWSER) user-manual.html + +user-manual.html: user-manual.txt + $(ASCIIDOC) user-manual.txt diff --git a/Documentation/user-manual.txt b/Documentation/user-manual.txt index 181360140..75fb3a338 100644 --- a/Documentation/user-manual.txt +++ b/Documentation/user-manual.txt @@ -1,19 +1,23 @@ -Subsurface 1.2 - -User Manual - -Version 0.0.4 - -Autor documentation: Jacco van Koll <jko@haringstad.com> +Subsurface 1.2 User Manual +========================== +Jacco van_Koll <jko@haringstad.com> +v0.0.7, January 2012 +:Author Initials: JKO +:toc: +:icons: +:numbered: +:website: http://subsurface.hohndel.org Scope of this document is the usage of the program. Please read the build manual for instructions how to build the -software and (if needed) it´s dependencies. +software and (if needed) its dependencies. Audience: Fun Divers, Tec Divers, Professional Divers -1. Introduction: +[[S_Introduction]] +Introduction: +------------- Subsurface was started because of a lack of viable dive log software on Linux. It turns out that the resulting software was easily ported @@ -24,18 +28,22 @@ already very usable for divers with supported dive-computers. In this manual the Suunto Viper will be used for all examples. -2. Requirements +[[S_Requirements]] +Requirements +------------ Before you are able to import information from your divecomputer into Subsurface, you need some preparation. Do you have the following: - 1. Your Divecomputer - Compatible with libdivecomputer (see list in Appendix A) + 1. Your Divecomputer - Compatible with libdivecomputer (see list in <<AppendixA,Appendix A>>) 2. Communication interface - Cable to connect your divecomputer to your PC/Laptop/Netbook 3. Working installation of Subsurface 4. If needed, the manual of your divecomputer -3. Start using the program: +[[S_StartUsing]] +Start using the program +----------------------- When you start the program for the first time, it shows no information at all. This is because the program does not automatically load the @@ -45,12 +53,14 @@ There is a menu, containing 'File', 'Log', 'Filter' and Help. The screen is devided in 3 area's: - Area with 3 tabs: Dive Notes, Equipment, Info & Stats - Area next to the 3 tabs, which will contain the dive profile - Area with the dives (usually called dive list), which can be sorted by number, date, etc. + - Area with 3 tabs: Dive Notes, Equipment, Info & Stats + - Area next to the 3 tabs which will contain the dive profile + - Area with the dives (usually called dive list) which can be sorted by number, date, etc. -4. Import new dives +[[S_ImportNewDives]] +Import new dives from your divecomputer +--------------------------------------- Before you start fiddeling around with your divecomputer, note that there are divecomputers that consume more power when they are in the @@ -62,40 +72,42 @@ be recharged when connected to the USB port. Now it is time to hook up your divecomputer to your Linux system: - Make sure that your OS has the required drivers installed + - Make sure that your OS has the required drivers installed - On Linux this means you need to have the correct kernel - module loaded. Most distributions will do this automatically - for you. + * On Linux this means you need to have the correct kernel + module loaded. Most distributions will do this automatically + for you. - On Windows, the OS should offer to download the correct - driver when you connect to the USB port. + * On Windows, the OS should offer to download the correct + driver when you connect to the USB port. - On a Mac you at times have to manually hunt for the correct - driver. For example the correct driver for the Mares Puck - devices can be found as Mac_OSX_VCP_Driver.zip at - http://www.silabs.com/support/pages/support.aspx?ProductFamily=USB+Bridges + * On a Mac you at times have to manually hunt for the correct + driver. For example the correct driver for the Mares Puck + devices can be found as Mac_OSX_VCP_Driver.zip at + http://www.silabs.com/support/pages/support.aspx?ProductFamily=USB+Bridges - Connect your interface cable to a free USB port + - Connect your interface cable to a free USB port - Put your divecomputer into PC Communication mode. (For Suunto Viper, press Mode - 1 Memory - 3 TR-PC) + - Put your divecomputer into PC Communication mode. (For Suunto Viper, press Mode - 1 Memory - 3 TR-PC) (You should consult the manual of your specific divecomputer for your brand and type) - Go in Subsurface to 'File - Import' - Within the popup, under Dive computer, choose your brand and type. Here we choose Suunto Vyper. - Change the devicename under which your interface is connected. - On Linux, default is /dev/ttyUSB0 - On Windows, default is COM3 - On Mac, default is ... specific to the dive computer + - Go in Subsurface to 'File - Import' + * Within the popup, under Dive computer, choose your brand and type. Here we choose Suunto Vyper. + * Change the devicename under which your interface is connected. + ** On Linux, default is /dev/ttyUSB0 + ** On Windows, default is COM3 + ** On Mac, default is ... specific to the dive computer - Click the 'OK' button. + * Click the 'OK' button. - Now watch how your data is retrieved from your divecomputer! - Depending on your type of computer and/or number of dives, this - could take some time. Please be patient. + - Now watch how your data is retrieved from your divecomputer! + Depending on your type of computer and/or number of dives, this + could take some time. Please be patient. -5. Viewing and completing your logs +[[S_ViewingLogs]] +Viewing and completing your logs +-------------------------------- When all data from your divecomputer is transferred, you will see a listing of your dives in Area 3. @@ -104,99 +116,110 @@ An example: On Sunday Oct 23, 2011 you made a dive. In the log line of this dive, you see the following information: - - #: 12 Dive number - Date: Sun, Oct 23, 2011 10:50 Date and time of your dive - *: Your rating (none at this time) - m: 12.8 Your maximum depth in meters - min: 31:20 Your dive-time in minutes and seconds - Deg. C: 13.0 Lowest water temperature during your dive - Cyl: Your used cylinder (none at this time) - O2%: air What type of mixture - SAC: SAC (none at this time) - Location: Where you performed your dive (empty) - - As you can see, some information is already there because it is - retrieved from your divecomputer. Some information is waiting for - you to be added. By double clicking on this dive, you can view and - complete the log. - - -6. Edit the dive info +[width="70%",cols="<5%,10%,<20%,<65%",grid="none",frame="none",style="monospaced"] +|=============================================================================== +|| # | 12 | Dive number +|| Date | Sun, Oct 23, 2011 10:50 | Date and time of your dive +|| * | | Your rating (none at this time) +|| m | 12.8 | Your maximum depth in meters +|| min | 31:20 | Your dive-time in minutes and seconds +|| Deg. C | 13.0 | Lowest water temperature during your dive +|| Cyl | | Your used cylinder (none at this time) +|| O2% | air | What type of mixture +|| SAC | | SAC (none at this time) +|| Location | | Where you performed your dive (empty) +|=============================================================================== + +As you can see, some information is already there because it is +retrieved from your divecomputer. Some information is waiting for +you to be added. By double clicking on this dive, you can view and +complete the log. + + +[[S_EditDiveInfo]] +Edit the dive info +------------------ When you double click on the dive log line, the editor window opens. Now you can add information that is missing. Let start with completing the example: -You double clicked on dive #12, as described in 5. Viewing and -completing your logs. The Dive Info window pops up and you will see +You double clicked on dive #12, as described in <<S_ViewingLogs,Viewing and +completing your logs>>. The Dive Info window pops up and you will see the following: - - Location: An input where you can enter your new location, or you can choose with the pull-down previous locations - Dive Master: An input where you can enter the name of your Dive Master, or you can choose with the pull-down a previous name - Buddy: An input where you can enter het name of you Buddy, or you can choose with the pull-down a previous name - Rating: A pull-down where you can rate your dive. - Notes: A free input where you can enter information about your dive. What you've seen, etc. +[horizontal] + *Location*:: An input where you can enter your new location, or you can choose with the pull-down previous locations + *Dive Master*:: An input where you can enter the name of your Dive Master, or you can choose with the pull-down a previous name + *Buddy*:: An input where you can enter het name of you Buddy, or you can choose with the pull-down a previous name + *Rating*:: A pull-down where you can rate your dive. + *Notes*:: A free input where you can enter information about your dive. What you've seen, etc. In this example we use the following information: - - Location: Oostvoornse Meer - Dive Master: S. de Vries - Buddy: S. de Vries - Rating: 3 stars - Notes: First dive here. Good visibility. Did see the concrete poles, some crab and fish. Very nice and easy dive. - Made movie with 'headcam'. +[horizontal] + *Location*:: Oostvoornse Meer + *Dive Master*:: S. de Vries + *Buddy*:: S. de Vries + *Rating*:: 3 stars + *Notes*:: First dive here. Good visibility. Did see the concrete poles, some crab and fish. Very nice and easy dive. + + Made movie with headcam. Now don't press ok yet! +[[S_EditEquipmentInfo]] +Edit equipment info +------------------- -7. Edit equipment info - -You also want to edit your Cylinder information. And in the previous -chapter, this was not edited. There is still another item to edit in -the Dive Info screen: +You also want to edit your Cylinder information. And in the +<<S_EditDiveInfo, previous chapter>>, this was not edited. There is +still another item to edit in the Dive Info screen: - Cylinder: A double-click fieldset. Here you can edit your Cylinder information + - Cylinder: A double-click fieldset. Here you can edit your Cylinder information So, when you double click on the cylinder info, you get another popup. This popup gives you the following: - Cylinder: Pull-down where you can choose your Cylinder, or add your own - Size: The volume if not 'filled' - Pressure: The maximum pressure of this Cylinder - Optional: - Start Pressure: What was the pressure starting the dive - End Pressure: What was the pressure ending the dive - Nitrox: What was the percentage of blend + - Cylinder: Pull-down where you can choose your Cylinder, or add your own + - Size: The volume if not `filled' + - Pressure: The maximum pressure of this Cylinder + - Optional: + * Start Pressure: What was the pressure starting the dive + * End Pressure: What was the pressure ending the dive + * Nitrox: What was the percentage of blend Now we are going to enter the data: - Cylinder: 15.0 l - Size: 15.0 - Pressure: 220 + - Cylinder: 15.0 l + - Size: 15.0 + - Pressure: 220 - Now tick the option for Start & End pressure +Now tick the option for Start & End pressure - Start Pressure: 180 - End Pressure: 60 - Press Ok + - Start Pressure: 180 + - End Pressure: 60 + - Press Ok Now your dive information for this dive is complete. You can now press ok in the Dive Info screen and view the results. - -8. Adding equipment info +[[S_AddingEquipment]] +Adding equipment info +--------------------- In Area with the 3 tabs there is the tab Equipment. With this tab, you can add Cylinders. We are going to add an additional Cylinder: - In the main screen, click on the Equipment tab. This shows your Cylinder you added in 7. - Now press the Add button and the Cylinder popup comes back. - Just like you added your Cylinder information in 7. Edit equipment info, you add your cylinder - information for the second Cylinder. Fill in all the information about this Cylinder and press OK. + - In the main screen, click on the Equipment tab. This shows your + Cylinder you added in 7. + + - Now press the Add button and the Cylinder popup comes back. + - Just like you added your Cylinder information in 7. Edit equipment + info, you add your cylinder information for the second Cylinder. + Fill in all the information about this Cylinder and press OK. -9. View info & Stats +[[S_ViewInfoStats]] +View info & Stats +----------------- After adding all the information, you can use the tab Info & Stats. This tab will provide you with all the (statistical and @@ -204,31 +227,32 @@ calculated) information regarding your dive. The information contains: - Dive Info: - - Date: Date and time of your dive - Dive Time: Duration of your dive - Surf Intv: Interval between previous dive and this dive - Max Depth: Maximum depth of this dive - Avg Depth: The average depth of this dive - Water Temp: Lowest temperature of the water - SAC: The amount of Surface Air Consumption liters per minute - OTU: The Oxygen Toxicity Units of this dive - O2/He: Amount of Oxygen/Helium - Gas Used: The total volume of gas used during this dive - - Statistics: - - Total time: Total time of all your dives together, calculated - Avg Time: The average divetime of your dives, calculated - Max Depth: The maximum depth of all your dives - Avg Depth: The average depth of all your dives, calculated - Max SAC: Highest of Surface Air Consumption of all your dives - Min SAC: Lowest of Surface Air Consumption of all your dives - Avg SAC: Average Surface Air Consuption of all your dives, calculated - - -10. Setting up preferences + - Dive Info: + + ** Date: Date and time of your dive + ** Dive Time: Duration of your dive + ** Surf Intv: Interval between previous dive and this dive + ** Max Depth: Maximum depth of this dive + ** Avg Depth: The average depth of this dive + ** Water Temp: Lowest temperature of the water + ** SAC: The amount of Surface Air Consumption liters per minute + ** OTU: The Oxygen Toxicity Units of this dive + ** O2/He: Amount of Oxygen/Helium + ** Gas Used: The total volume of gas used during this dive + + - Statistics: + + ** Total time: Total time of all your dives together, calculated + ** Avg Time: The average divetime of your dives, calculated + ** Max Depth: The maximum depth of all your dives + ** Avg Depth: The average depth of all your dives, calculated + ** Max SAC: Highest of Surface Air Consumption of all your dives + ** Min SAC: Lowest of Surface Air Consumption of all your dives + ** Avg SAC: Average Surface Air Consuption of all your dives, calculated + +[[S_SettingUpPreferences]] +Setting up preferences +---------------------- Subsurface has the ability to modify the preferences you want. By using menu 'File - Preferences' you will be presented a popup with the @@ -236,19 +260,19 @@ using menu 'File - Preferences' you will be presented a popup with the words, use Metric or Imperial. You can set the following options: - Depth: Your diving depth in Meters or Feet - Pressure: The pressure of your tank(s) in Bar/Ato or PSI (Pressure Square Inch) - Volume: The volume of your tank(s) in Liter or CuFt (Cubic Feet) (At sea-level pressure) - Temperature: The temperature of the water in Celcius or Fahrenheit + - Depth: Your diving depth in Meters or Feet + - Pressure: The pressure of your tank(s) in Bar/Ato or PSI (Pressure Square Inch) + - Volume: The volume of your tank(s) in Liter or CuFt (Cubic Feet) (At sea-level pressure) + - Temperature: The temperature of the water in Celcius or Fahrenheit In the main screen, you did see in Area 3, some information. In the Columns options, you can enable/disable options you would like to show there: - Show Temp: Shows the temperature of your dive - Show Cyl: Shows the cylinder(s) of your dive - Show O2%: Shows the O2% of your dive - Show SAC: Shows the SAC of your dive (Surface Air Consumption) - Show OTU: Shows the OTU of your dive (Oxygen Toxicity Units) + - Show Temp: Shows the temperature of your dive + - Show Cyl: Shows the cylinder(s) of your dive + - Show O2%: Shows the O2% of your dive + - Show SAC: Shows the SAC of your dive (Surface Air Consumption) + - Show OTU: Shows the OTU of your dive (Oxygen Toxicity Units) And, you can change the font usage of the program. @@ -256,39 +280,41 @@ I will give an example here: I am a diver in The Netherlands, using the Metric System. Therefor, I go to the menu File, choose Preferences here. In the Units section, I -use the folowing: +use the following: - Depth: Meter - Pressure: Bar - Volume: Liter - Temperature: Celcius + - Depth: Meter + - Pressure: Bar + - Volume: Liter + - Temperature: Celcius I would like to see the: - Temperature - Show Cyl - Show O2% - Show SAC + + - Temperature + - Show Cyl + - Show O2% + - Show SAC As a beginning diver, I don't need to track my OTUs. So I leave this one not enabled. Clicking OK on the dialog stores these settings. - -11. How to find the Device Name +[[S_HowFindDeviceName]] +How to find the Device Name +--------------------------- When you connect your divecomputer by using an USB connector, most of the -time, the default of ´/dev/ttyUSB0' should work. But if you have other +time, the default of '/dev/ttyUSB0' should work. But if you have other Serial to USB devices, this can be different because '/dev/ttyUSB0' is already in use. One of the ways to find out what your dive name is: - Disconnect your usb cable of your dive computer - Open a terminal - Type the command: 'dmesg' and press enter - Plug in your usb cable of your divecomputer - Type the command: 'dmesg' and press enter + - Disconnect your usb cable of your dive computer + - Open a terminal + - Type the command: 'dmesg' and press enter + - Plug in your usb cable of your divecomputer + - Type the command: 'dmesg' and press enter Within your terminal you should see a message similair to this one: @@ -313,45 +339,303 @@ detected and is connected to 'ttyUSB3'. Now you use this information in the import settings as '/dev/ttyUSB3'. Your divecomputer interface is connected and you should be able to import your dives. +[[S_ImportingDivesJDivelog]] +Importing dives from JDivelog +----------------------------- + +Maybe you have been using JDivelog and you have a lot of dives logged in +this program. You don't have to type all information by hand into +Subsurface, because you can import your divelogs from JDivelog. -Appendix A +JDivelog stores its information into files with the extension of .jlb. +These .jlb contain all the information that has been stored, except your +images in xml format. + +By using the menu 'File - Import' you get the popup, like described in +<<S_ImportNewDives, chapter 4>>, Importing new dives. Within this +popup there is the option to import existing files which are already +on your computer. To import your JDivelog file(s) do the following: + + - Open 'File - Import' on the menu + - Use the file locator under XML file name + - Browse your directories to the location where your *.jlb file is + - Select your existing *.jlb file and click 'open' + - Click the OK button in the popup + +After a few moments, you see your existing logs in Subsurface. Now you can +edit your dives like explained in <<S_EditDiveInfo, chapter 6>>. + +Information that is imported from JDivelog into the location field: + + - Extended dive location information + +Information that is merged into the location or notes field: + + - Used amount of weight + - Used type of suit + - Used type of gloves + - Type of dive + - Dive activity + +Alternatively, you can start subsurface with the --import command line +which will have the same effect: + + subsurface MyDives.xml --import JDivelogDives.jlb + +will open your divelog (assuming that's called MyDives.xml) and then +import the dives from JdivelogDives.jlb. You can now save the combined +divelog back as MyDives.xml. + +Subsurface will similarly import xml exports from DivingLog as well as +Suunto DiveManager. + +When importing dives subsurface tries to detect multiple records for +the same dive and merges the information as best as it can. So as long +as there are no time zone issues (or other reasons that would cause the +beginning time of the dives to be substantially different) subsurface +will not create duplicate entries. + +[[S_ImportingDivesSuunto]] +Importing dives from Suunto Divemanager 3.* +------------------------------------------- + +Before you can start importing dives from Suunto Divemanager, you first +have to export the dives you want to import. Subsurface does not import +directly from the Suunto Divemanager log files. The following procedures +unpacking instructions for Linux and Windows. + +Export from Suunto Divemanager +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + - Start Suunto Divemanager and login with the name containing the logs + - Do not start the import wizard to import dives from your computer. + - In the navigation tree on the left side of the program-window, select your dives. + - Within the list of dives, select the dives you would like to import later: + * To select certain dives: hold ctrl and point & click the dive + * To select all dives: Select the first dive, hold down shift and select the last dive + - With the dives marked, use the program menu 'File - Export' + - The export popup will show + - Within this popup, there is one field called Export Path. + * Click the button browse next to the field Export Path + ** A file-manager like window pops up + ** Navigate to the directory where you want to store the Divelog.SDE file + ** Optional change the name of the file you want to save + ** Click 'Save' + * You are back in the Export popup. Press the button 'Export' + - Your dives are now exported to the file Divelogs.SDE. + +Unpacking the Divelogs.SDE on Windows +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Renaming your file to a .zip: + + - Use the filemanager (explorer) and navigate to your Divelogs.SDE file + - Right click on the Divelogs.SDE file and choose 'Rename' + * Change the name into Divelogs.SDE.zip + * Press enter when done. A warning popup shows: + + The file could be unusable when changing the extension. Are you sure: + Press OK. + + * Your filemanager will show now the filename Divelogs.SDE.zip + +When you double click your Divelogs.SDE.zip file, your preferred archiving +tool will start and show you the list of xml files that are in the zip +archive. Select all the xml files and extract them to a place where you +can find them later in the process. + +Unpacking the Divelogs.SDE on Linux +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The assumption is that you have exported your Divelogs.SDE on a Windows +system. You have to transfer the file to a location where you can read it +from within your Linux environment. You can use file-transfer, shared +storage or an USB storage device to do this. +The example uses an USB storage: + + - Insert your USB storage into your Windows computer + - Use the filemanager (explorer) to navigate to the location where your Divelogs.SDE file is located + - Copy the file to your USB storage: + * Select the file by 1 click + * Press Ctrl+c + * Navigate to your USB Storage + * Press Ctrl+v + - Disconnect your USB storage by right clicking your USB storage in the explorer and choose Eject + - Insert your USB storage into your Linux computer + - Use your favourite filemanager to navigate to your USB storage + - Copy the file to /tmp by: + * Right click on the file + * select copy + * navigate to /tmp + * press Ctrl+v or use the menu 'Edit - Paste' + - The file is now transfered to /tmp + +Now the file is in /tmp, we can extract the xml files from it. You can do +this by hand, or use the example script in <<AppendixB,Appendix B>>. + +To extract the xml files, we need to open a terminal and use the following +commands: + + cd /tmp + mkdir suunto + cd suunto + unzip ../Divelogs.SDE + +Your divelogs have now been extracted from the Divelogs.SDE file and you +can import them with the command: + + subsurface *.xml + +And with the menu 'File - Save' you can save your dives into the +Subsurface format. + +[[S_Menu]] +The menu and sub-menus +---------------------- + +Within Subsurface, there are several menu and sub-menu options. All of +those will be described here with their function. + +The file menu +~~~~~~~~~~~~~ + +The file menu is used for the following menu options: + + - Open:: Open your saved Subsurface xml file(s) + - Save:: Save your current divelogs or changes you made to your divelogs + - Print:: Print your current divelog profiles and information about the dive + - Import:: Import your dives from your divecomputer, JDivelogs or Suunto Divemanager + - Preferences:: Set your preferences as described in <<S_SettingUpPreferences,chapter 10>> + - Quit:: Quit the program + +The Log menu +~~~~~~~~~~~~ + +Within the Log menu, there are only 2 sub-items: + + - Renumber:: This option provides you with a popup. Within this + popup you can choose what the first number of your dives should be + for this set of dives. + - View:: This is a submenu containing: + * List:: Show only the list of dives you have made + * Profile:: Show only the dive profile of the selected dive + * Info:: Show only the 3 tab information screen + * Three:: Show the 'default' 3 screen setup + +The Filter menu +~~~~~~~~~~~~~~~ + +This menu gives you the choice to enable or disable Events for the +selected divelog(s). At this time, you can enable or disable ascent. +When you enable ascent for your dives, within the dive profile, a yellow +marker with exclamation sign (!) will show on the points where you have +ascented. + +The Help menu +~~~~~~~~~~~~~ + +The Help menu shows only the About, which contains the version and author +information and License button. + + +[[AppendixA]] +Appendix A: Supported Dive Computers +------------------------------------ The use of libdivecomputer provides the support for divecomputers. Within the list of computers in the 'File - import' menu, you will see a listing of divecomputers. This list is covering a compatible set. Please check your users manual to check if your computer will be supported. - Supported divecomputers: + Supported divecomputers:: - Atomics: + Atomics:: Cobalt - Cressi: + + Cressi:: Edi - Mares: + + Mares:: Icon HD Nemo Puck - Oceanic: + Air + + Oceanic:: Veo250 VT Pro - OSTC: * + + OSTC:: DR5 2N - Reefnet: + + Reefnet:: Sensus Sensus Pro Sensus Ultra - Suunto: + + Suunto:: + Cobra + 2 + 3 + D3 D9 + D4 + D4i + D6 + D6i + D9tx Eon + Gekko + HelO2 + Mosquito Solution - Viper - Viper Air - Uwatec: + Alpha + Nitrox/Vario + Stinger + Vyper + 2 + Air + Vytec + DS + Zoop + + Uwatec:: Aladin Memo Mouse Smart - Zeagle: + + Zeagle:: N2iTiON 3 * OSTC computers are listed in the pull-down menu as OSTC. All 3 types are supported. + + +[[AppendixB]] +Appendix B: Suunto Export Unpacking Script +------------------------------------------ + + #!/bin/bash + # + # Small basic example script to unpack Suunto Export files + # for the use with Subsurface + # + + echo -n "Enter the directory where you stored your Suunto Divemanager export file: " + read SuuntoExportDir + + echo -n "Enter the name of your Suunto Divemanager export file: " + read SuuntoExportFile + + echo "You have entered: $SuuntoExportDir/$SuuntoExportFile" + + cd $SuuntoExportDir + + if [ -e ./$SuuntoExportFile ]; then + mkdir SuuntoXML + cd SuuntoXML + unzip ../$SuuntoExportFile + subsurface *.xml + else + echo "Nothing found! Try again!" + fi @@ -36,13 +36,13 @@ UNAME := $(shell $(CC) -dumpmachine 2>&1 | grep -E -o "linux|darwin|win") # ifeq ($(CC), i686-w64-mingw32-gcc) # ok, we are cross building for Windows - LIBDIVECOMPUTERINCLUDES = `$(PKGCONFIG) --cflags libdivecomputer` - LIBDIVECOMPUTERARCHIVE = `$(PKGCONFIG) --libs libdivecomputer` + LIBDIVECOMPUTERINCLUDES = $(shell $(PKGCONFIG) --cflags libdivecomputer) + LIBDIVECOMPUTERARCHIVE = $(shell $(PKGCONFIG) --libs libdivecomputer) RESFILE = packaging/windows/subsurface.res LDFLAGS += -Wl,-subsystem,windows else ifeq ($(UNAME), darwin) - LIBDIVECOMPUTERINCLUDES = `$(PKGCONFIG) --cflags libdivecomputer` - LIBDIVECOMPUTERARCHIVE = `$(PKGCONFIG) --libs libdivecomputer` + LIBDIVECOMPUTERINCLUDES = $(shell $(PKGCONFIG) --cflags libdivecomputer) + LIBDIVECOMPUTERARCHIVE = $(shell $(PKGCONFIG) --libs libdivecomputer) else libdc-local := $(wildcard /usr/local/lib/libdivecomputer.a) libdc-local64 := $(wildcard /usr/local/lib64/libdivecomputer.a) @@ -87,6 +87,11 @@ GLIB2CFLAGS = $(shell $(PKGCONFIG) --cflags glib-2.0) GTK2CFLAGS = $(shell $(PKGCONFIG) --cflags gtk+-2.0) CFLAGS += $(shell $(XSLCONFIG) --cflags) +LIBZIP = $(shell $(PKGCONFIG) --libs libzip 2> /dev/null) +ifneq ($(strip $(LIBZIP)),) + ZIP = -DLIBZIP $(shell $(PKGCONFIG) --cflags libzip) +endif + ifeq ($(UNAME), linux) LIBGCONF2 = $(shell $(PKGCONFIG) --libs gconf-2.0) GCONF2CFLAGS = $(shell $(PKGCONFIG) --cflags gconf-2.0) @@ -97,7 +102,8 @@ else ifeq ($(UNAME), darwin) OSSUPPORT_CFLAGS = $(GTK2CFLAGS) MACOSXINSTALL = /Applications/Subsurface.app MACOSXFILES = packaging/macosx - EXTRALIBS = -framework CoreFoundation + EXTRALIBS = $(shell $(PKGCONFIG) --libs gtk-mac-integration) -framework CoreFoundation + CFLAGS += $(shell $(PKGCONFIG) --cflags gtk-mac-integration) else OSSUPPORT = windows OSSUPPORT_CFLAGS = $(GTK2CFLAGS) @@ -110,11 +116,11 @@ ifneq ($(strip $(LIBXSLT)),) endif endif -LIBS = $(LIBXML2) $(LIBXSLT) $(LIBGTK) $(LIBGCONF2) $(LIBDIVECOMPUTER) $(EXTRALIBS) -lpthread +LIBS = $(LIBXML2) $(LIBXSLT) $(LIBGTK) $(LIBGCONF2) $(LIBDIVECOMPUTER) $(EXTRALIBS) $(LIBZIP) -lpthread -lm OBJS = main.o dive.o profile.o info.o equipment.o divelist.o \ parse-xml.o save-xml.o libdivecomputer.o print.o uemis.o \ - gtk-gui.o statistics.o $(OSSUPPORT).o $(RESFILE) + gtk-gui.o statistics.o file.o cochran.o $(OSSUPPORT).o $(RESFILE) $(NAME): $(OBJS) $(CC) $(LDFLAGS) -o $(NAME) $(OBJS) $(LIBS) @@ -141,12 +147,17 @@ install-macosx: $(NAME) $(INSTALL) -d -m 755 $(MACOSXINSTALL)/Contents/Resources $(INSTALL) -d -m 755 $(MACOSXINSTALL)/Contents/MacOS $(INSTALL) $(NAME) $(MACOSXINSTALL)/Contents/MacOS/ - $(INSTALL) $(MACOSXFILES)/subsurface.sh $(MACOSXINSTALL)/Contents/MacOS/ $(INSTALL) $(MACOSXFILES)/PkgInfo $(MACOSXINSTALL)/Contents/ $(INSTALL) $(MACOSXFILES)/Info.plist $(MACOSXINSTALL)/Contents/ $(INSTALL) $(ICONFILE) $(MACOSXINSTALL)/Contents/Resources/ $(INSTALL) $(MACOSXFILES)/Subsurface.icns $(MACOSXINSTALL)/Contents/Resources/ +file.o: file.c dive.h file.h + $(CC) $(CFLAGS) $(GLIB2CFLAGS) $(XML2CFLAGS) $(XSLT) $(ZIP) -c file.c + +cochran.o: cochran.c dive.h file.h + $(CC) $(CFLAGS) $(GLIB2CFLAGS) $(XML2CFLAGS) $(XSLT) $(ZIP) -c cochran.c + parse-xml.o: parse-xml.c dive.h $(CC) $(CFLAGS) $(GLIB2CFLAGS) $(XML2CFLAGS) $(XSLT) -c parse-xml.c @@ -194,5 +205,8 @@ uemis.o: uemis.c dive.h uemis.h $(OSSUPPORT).o: $(OSSUPPORT).c display-gtk.h $(CC) $(CFLAGS) $(OSSUPPORT_CFLAGS) -c $(OSSUPPORT).c +doc: + $(MAKE) -C Documentation doc + clean: rm -f $(OBJS) *~ $(NAME) diff --git a/cochran.c b/cochran.c new file mode 100644 index 000000000..360f2eb7c --- /dev/null +++ b/cochran.c @@ -0,0 +1,224 @@ +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +#include "dive.h" +#include "file.h" + +#define DON + +/* + * The Cochran file format is designed to be annoying to read. It's roughly: + * + * 0x00000: room for 65534 4-byte words, giving the starting offsets + * of the dives themselves. + * + * 0x3fff8: the size of the file + 1 + * 0x3ffff: 0 (high 32 bits of filesize? Bogus: the offsets into the file + * are 32-bit, so it can't be a large file anyway) + * + * 0x40000: "block 0": the decoding block. The first byte is some random + * value (0x46 in the files I have access to), the next 200+ bytes or so + * are the "scrambling array" that needs to be added into the file + * contents to make sense of them. + * + * The descrambling array seems to be of some random size which is likely + * determinable from the array somehow, the two test files I have it as + * 230 bytes and 234 bytes respectively. + */ +static unsigned int partial_decode(unsigned int start, unsigned int end, + const unsigned char *decode, unsigned offset, unsigned mod, + const unsigned char *buf, unsigned int size, unsigned char *dst) +{ + unsigned i, sum = 0; + + for (i = start ; i < end; i++) { + unsigned char d = decode[offset++]; + if (i >= size) + break; + if (offset == mod) + offset = 0; + d += buf[i]; + if (dst) + dst[i] = d; + sum += d; + } + return sum; +} + +/* + * The decode buffer size can be figured out by simply trying our the + * decode: we expect that the scrambled contents are largely random, and + * thus tend to have half the bits set. Summing over the bytes is going + * to give an average of 0x80 per byte. + * + * The decoded array is mostly full of zeroes, so the sum is lower. + * + * Works for me. + */ +static int figure_out_modulus(const unsigned char *decode, const unsigned char *dive, unsigned int size) +{ + int mod, best = -1; + unsigned int min = ~0u; + + if (size < 0x1000) + return best; + + for (mod = 50; mod < 300; mod++) { + unsigned int sum; + + sum = partial_decode(0, 0x0fff, decode, 1, mod, dive, size, NULL); + if (sum < min) { + min = sum; + best = mod; + } + } + return best; +} + +#define hexchar(n) ("0123456789abcdef"[(n)&15]) + +static int show_line(unsigned offset, const unsigned char *data, unsigned size, int show_empty) +{ + unsigned char bits; + int i, off; + char buffer[120]; + + if (size > 16) + size = 16; + + bits = 0; + memset(buffer, ' ', sizeof(buffer)); + off = sprintf(buffer, "%06x ", offset); + for (i = 0; i < size; i++) { + char *hex = buffer + off + 3*i; + char *asc = buffer + off + 50 + i; + unsigned char byte = data[i]; + + hex[0] = hexchar(byte>>4); + hex[1] = hexchar(byte); + bits |= byte; + if (byte < 32 || byte > 126) + byte = '.'; + asc[0] = byte; + asc[1] = 0; + } + + if (bits) { + puts(buffer); + return 1; + } + if (show_empty) + puts("..."); + return 0; +} + +static void cochran_debug_write(const char *filename, const unsigned char *data, unsigned size) +{ + int i, show = 1; + + for (i = 0; i < size; i += 16) + show = show_line(i, data + i, size - i, show); +} + +static void parse_cochran_header(const char *filename, + const unsigned char *decode, unsigned mod, + const unsigned char *in, unsigned size) +{ + char *buf = malloc(size); + + /* Do the "null decode" using a one-byte decode array of '\0' */ + partial_decode(0 , 0x0b14, "", 0, 1, in, size, buf); + + /* + * The header scrambling is different form the dive + * scrambling. Oh yay! + */ + partial_decode(0x010e, 0x0b14, decode, 0, mod, in, size, buf); + partial_decode(0x0b14, 0x1b14, decode, 0, mod, in, size, buf); + partial_decode(0x1b14, 0x2b14, decode, 0, mod, in, size, buf); + partial_decode(0x2b14, 0x3b14, decode, 0, mod, in, size, buf); + partial_decode(0x3b14, 0x5414, decode, 0, mod, in, size, buf); + partial_decode(0x5414, size, decode, 0, mod, in, size, buf); + + printf("\n%s, header\n\n", filename); + cochran_debug_write(filename, buf, size); + + free(buf); +} + +static void parse_cochran_dive(const char *filename, int dive, + const unsigned char *decode, unsigned mod, + const unsigned char *in, unsigned size) +{ + char *buf = malloc(size); +#ifdef DON + unsigned int offset = 0x4a14; +#else + unsigned int offset = 0x4b14; +#endif + + /* + * The scrambling has odd boundaries. I think the boundaries + * match some data structure size, but I don't know. They were + * discovered the same way we dynamically discover the decode + * size: automatically looking for least random output. + * + * The boundaries are also this confused "off-by-one" thing, + * the same way the file size is off by one. It's as if the + * cochran software forgot to write one byte at the beginning. + */ + partial_decode(0 , 0x0fff, decode, 1, mod, in, size, buf); + partial_decode(0x0fff, 0x1fff, decode, 0, mod, in, size, buf); + partial_decode(0x1fff, 0x2fff, decode, 0, mod, in, size, buf); + partial_decode(0x2fff, 0x48ff, decode, 0, mod, in, size, buf); + + /* + * This is not all the descrambling you need - the above are just + * what appears to be the fixed-size blocks. The rest is also + * scrambled, but there seems to be size differences in the data, + * so this just descrambles part of it: + */ + partial_decode(0x48ff, offset, decode, 0, mod, in, size, buf); + partial_decode(offset, size, decode, 0, mod, in, size, buf); + + printf("\n%s, dive %d\n\n", filename, dive); + cochran_debug_write(filename, buf, size); + + free(buf); +} + +int try_to_open_cochran(const char *filename, struct memblock *mem, GError **error) +{ + unsigned int i; + unsigned int mod; + unsigned int *offsets, dive1, dive2; + unsigned char *decode = mem->buffer + 0x40001; + + if (mem->size < 0x40000) + return 0; + offsets = mem->buffer; + dive1 = offsets[0]; + dive2 = offsets[1]; + if (dive1 < 0x40000 || dive2 < dive1 || dive2 > mem->size) + return 0; + + mod = figure_out_modulus(decode, mem->buffer + dive1, dive2 - dive1); + + parse_cochran_header(filename, decode, mod, mem->buffer + 0x40000, dive1 - 0x40000); + + for (i = 0; i < 65534; i++) { + dive1 = offsets[i]; + dive2 = offsets[i+1]; + if (dive2 < dive1) + break; + if (dive2 > mem->size) + break; + parse_cochran_dive(filename, i+1, decode, mod, mem->buffer + dive1, dive2 - dive1); + } + + exit(0); +} diff --git a/display-gtk.h b/display-gtk.h index b20495731..f12e42996 100644 --- a/display-gtk.h +++ b/display-gtk.h @@ -27,12 +27,25 @@ typedef enum { #define BOOL_TO_PTR(_cond) ((_cond) ? (void *)1 : NULL) #define PTR_TO_BOOL(_ptr) ((_ptr) != NULL) +#if defined __APPLE__ +#define CTRLCHAR "<Meta>" +#define PREFERENCE_ACCEL "<Meta>comma" +#else +#define CTRLCHAR "<Control>" +#define PREFERENCE_ACCEL NULL +#endif + extern void subsurface_open_conf(void); extern void subsurface_set_conf(char *name, pref_type_t type, const void *value); extern const void *subsurface_get_conf(char *name, pref_type_t type); extern void subsurface_close_conf(void); extern const char *subsurface_USB_name(void); +extern const char *subsurface_icon_name(void); +extern void subsurface_ui_setup(GtkSettings *settings, GtkWidget *menubar, + GtkWidget *vbox, GtkUIManager *ui_manager); + +extern const char *divelist_font; extern visible_cols_t visible_cols; @@ -48,11 +61,16 @@ extern GtkWidget *dive_profile_widget(void); extern GtkWidget *dive_info_frame(void); extern GtkWidget *extended_dive_info_widget(void); extern GtkWidget *equipment_widget(void); -extern GtkWidget *stats_widget(void); +extern GtkWidget *single_stats_widget(void); +extern GtkWidget *total_stats_widget(void); extern GtkWidget *cylinder_list_widget(void); extern GtkWidget *dive_list_create(void); +unsigned int amount_selected; + +extern void process_selected_dives(GList *, GtkTreeModel *); + typedef void (*data_func_t)(GtkTreeViewColumn *col, GtkCellRenderer *renderer, GtkTreeModel *model, @@ -160,7 +160,7 @@ struct sample *prepare_sample(struct dive **divep) return NULL; } -void finish_sample(struct dive *dive, struct sample *sample) +void finish_sample(struct dive *dive) { dive->samples++; } @@ -234,6 +234,122 @@ static int same_rounded_pressure(pressure_t a, pressure_t b) return abs(a.mbar - b.mbar) <= 500; } +static void sanitize_gasmix(struct gasmix *mix) +{ + unsigned int o2, he; + + o2 = mix->o2.permille; + he = mix->he.permille; + + /* Regular air: leave empty */ + if (!he) { + if (!o2) + return; + /* 20.9% or 21% O2 is just air */ + if (o2 >= 209 && o2 <= 210) { + mix->o2.permille = 0; + return; + } + } + + /* Sane mix? */ + if (o2 <= 1000 && he <= 1000 && o2+he <= 1000) + return; + fprintf(stderr, "Odd gasmix: %d O2 %d He\n", o2, he); + memset(mix, 0, sizeof(*mix)); +} + +/* + * See if the size/workingpressure looks like some standard cylinder + * size, eg "AL80". + */ +static void match_standard_cylinder(cylinder_type_t *type) +{ + double cuft; + int psi, len; + const char *fmt; + char buffer[20], *p; + + /* Do we already have a cylinder description? */ + if (type->description) + return; + + cuft = ml_to_cuft(type->size.mliter); + cuft *= to_ATM(type->workingpressure); + psi = to_PSI(type->workingpressure); + + switch (psi) { + case 2300 ... 2500: /* 2400 psi: LP tank */ + fmt = "LP%d"; + break; + case 2600 ... 2700: /* 2640 psi: LP+10% */ + fmt = "LP%d"; + break; + case 2900 ... 3100: /* 3000 psi: ALx tank */ + fmt = "AL%d"; + break; + case 3400 ... 3500: /* 3442 psi: HP tank */ + fmt = "HP%d"; + break; + case 3700 ... 3850: /* HP+10% */ + fmt = "HP%d+"; + break; + default: + return; + } + len = snprintf(buffer, sizeof(buffer), fmt, (int) (cuft+0.5)); + p = malloc(len+1); + if (!p) + return; + memcpy(p, buffer, len+1); + type->description = p; +} + + +/* + * There are two ways to give cylinder size information: + * - total amount of gas in cuft (depends on working pressure and physical size) + * - physical size + * + * where "physical size" is the one that actually matters and is sane. + * + * We internally use physical size only. But we save the workingpressure + * so that we can do the conversion if required. + */ +static void sanitize_cylinder_type(cylinder_type_t *type) +{ + double volume_of_air, atm, volume; + + /* If we have no working pressure, it had *better* be just a physical size! */ + if (!type->workingpressure.mbar) + return; + + /* No size either? Nothing to go on */ + if (!type->size.mliter) + return; + + if (input_units.volume == CUFT) { + /* confusing - we don't really start from ml but millicuft !*/ + volume_of_air = cuft_to_l(type->size.mliter); + atm = to_ATM(type->workingpressure); /* working pressure in atm */ + volume = volume_of_air / atm; /* milliliters at 1 atm: "true size" */ + type->size.mliter = volume + 0.5; + } + + /* Ok, we have both size and pressure: try to match a description */ + match_standard_cylinder(type); +} + +static void sanitize_cylinder_info(struct dive *dive) +{ + int i; + + for (i = 0; i < MAX_CYLINDERS; i++) { + sanitize_gasmix(&dive->cylinder[i].gasmix); + sanitize_cylinder_type(&dive->cylinder[i].type); + } +} + struct dive *fixup_dive(struct dive *dive) { int i,j; @@ -246,6 +362,7 @@ struct dive *fixup_dive(struct dive *dive) int lasttemp = 0, lastpressure = 0; int pressure_delta[MAX_CYLINDERS] = {INT_MAX, }; + sanitize_cylinder_info(dive); for (i = 0; i < dive->samples; i++) { struct sample *sample = dive->sample + i; int time = sample->time.seconds; @@ -373,7 +490,7 @@ static struct dive *add_sample(struct sample *sample, int time, struct dive *div return NULL; *p = *sample; p->time.seconds = time; - finish_sample(dive, p); + finish_sample(dive); return dive; } @@ -272,9 +272,11 @@ static inline struct dive *get_dive(unsigned int nr) } extern void parse_xml_init(void); -extern void parse_xml_file(const char *filename, GError **error); +extern void parse_xml_buffer(const char *url, const char *buf, int size, GError **error); extern void set_filename(const char *filename); +extern void parse_file(const char *filename, GError **error); + #ifdef XSLT extern xmlDoc *test_xslt_transforms(xmlDoc *doc); #endif @@ -299,7 +301,7 @@ extern struct dive *alloc_dive(void); extern void record_dive(struct dive *dive); extern struct sample *prepare_sample(struct dive **divep); -extern void finish_sample(struct dive *dive, struct sample *sample); +extern void finish_sample(struct dive *dive); extern void report_dives(gboolean imported); extern struct dive *fixup_dive(struct dive *dive); @@ -344,4 +346,6 @@ const char *monthname(int mon); #define FIVE_STARS UTF8_BLACKSTAR UTF8_BLACKSTAR UTF8_BLACKSTAR UTF8_BLACKSTAR UTF8_BLACKSTAR extern const char *star_strings[]; +#define AIR_PERMILLE 209 + #endif /* DIVE_H */ diff --git a/divelist.c b/divelist.c index f664cde49..2054ca017 100644 --- a/divelist.c +++ b/divelist.c @@ -73,6 +73,7 @@ static void selection_cb(GtkTreeSelection *selection, GtkTreeModel *model) return; case 1: /* just pick that dive as selected */ + amount_selected = 1; path = g_list_nth_data(selected_dives, 0); if (gtk_tree_model_get_iter(model, &iter, path)) { gtk_tree_model_get_value(model, &iter, DIVE_INDEX, &value); @@ -86,6 +87,9 @@ static void selection_cb(GtkTreeSelection *selection, GtkTreeModel *model) * is the most intuitive solution. * I do however want to keep around which dives have * been selected */ + amount_selected = g_list_length(selected_dives); + process_selected_dives(selected_dives, model); + repaint_dive(); return; } } @@ -230,7 +234,7 @@ static void temperature_data_func(GtkTreeViewColumn *col, * - Nitrox trumps air (even if hypoxic) * These are the same rules as the inter-dive sorting rules. */ -static void get_dive_gas(struct dive *dive, int *o2, int *he, int *o2low) +static void get_dive_gas(struct dive *dive, int *o2_p, int *he_p, int *o2low_p) { int i; int maxo2 = -1, maxhe = -1, mino2 = 1000; @@ -244,7 +248,7 @@ static void get_dive_gas(struct dive *dive, int *o2, int *he, int *o2low) if (cylinder_none(cyl)) continue; if (!o2) - o2 = 209; + o2 = AIR_PERMILLE; if (o2 < mino2) mino2 = o2; if (he > maxhe) @@ -258,11 +262,11 @@ newmax: maxo2 = o2; } /* All air? Show/sort as "air"/zero */ - if (!maxhe && maxo2 == 209 && mino2 == maxo2) + if (!maxhe && maxo2 == AIR_PERMILLE && mino2 == maxo2) maxo2 = mino2 = 0; - *o2 = maxo2; - *he = maxhe; - *o2low = mino2; + *o2_p = maxo2; + *he_p = maxhe; + *o2low_p = mino2; } static gint nitrox_sort_func(GtkTreeModel *model, @@ -292,6 +296,8 @@ static gint nitrox_sort_func(GtkTreeModel *model, return a_he - b_he; } +#define UTF8_ELLIPSIS "\xE2\x80\xA6" + static void nitrox_data_func(GtkTreeViewColumn *col, GtkCellRenderer *renderer, GtkTreeModel *model, @@ -315,7 +321,7 @@ static void nitrox_data_func(GtkTreeViewColumn *col, if (o2 == o2low) snprintf(buffer, sizeof(buffer), "%d", o2); else - snprintf(buffer, sizeof(buffer), "%d-%d", o2low, o2); + snprintf(buffer, sizeof(buffer), "%d" UTF8_ELLIPSIS "%d", o2low, o2); else strcpy(buffer, "air"); @@ -390,8 +396,10 @@ static int calculate_otu(struct dive *dive) struct sample *sample = dive->sample + i; struct sample *psample = sample - 1; t = sample->time.seconds - psample->time.seconds; - po2 = dive->cylinder[sample->cylinderindex].gasmix.o2.permille / 1000.0 * - (sample->depth.mm + 10000) / 10000.0; + int o2 = dive->cylinder[sample->cylinderindex].gasmix.o2.permille; + if (!o2) + o2 = AIR_PERMILLE; + po2 = o2 / 1000.0 * (sample->depth.mm + 10000) / 10000.0; if (po2 >= 0.5) otu += pow(po2 - 0.5, 0.83) * t / 30.0; } @@ -621,7 +629,7 @@ static struct divelist_column { sort_func_t sort; unsigned int flags; int *visible; -} column[] = { +} dl_column[] = { [DIVE_NR] = { "#", NULL, NULL, ALIGN_RIGHT | UNSORTABLE }, [DIVE_DATE] = { "Date", date_data_func, NULL, ALIGN_LEFT }, [DIVE_RATING] = { UTF8_BLACKSTAR, star_data_func, NULL, ALIGN_LEFT }, @@ -638,7 +646,7 @@ static struct divelist_column { static GtkTreeViewColumn *divelist_column(struct DiveList *dl, struct divelist_column *col) { - int index = col - &column[0]; + int index = col - &dl_column[0]; const char *title = col->header; data_func_t data_func = col->data; sort_func_t sort_func = col->sort; @@ -705,17 +713,17 @@ GtkWidget *dive_list_create(void) gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_MULTIPLE); gtk_widget_set_size_request(dive_list.tree_view, 200, 200); - dive_list.nr = divelist_column(&dive_list, column + DIVE_NR); - dive_list.date = divelist_column(&dive_list, column + DIVE_DATE); - dive_list.stars = divelist_column(&dive_list, column + DIVE_RATING); - dive_list.depth = divelist_column(&dive_list, column + DIVE_DEPTH); - dive_list.duration = divelist_column(&dive_list, column + DIVE_DURATION); - dive_list.temperature = divelist_column(&dive_list, column + DIVE_TEMPERATURE); - dive_list.cylinder = divelist_column(&dive_list, column + DIVE_CYLINDER); - dive_list.nitrox = divelist_column(&dive_list, column + DIVE_NITROX); - dive_list.sac = divelist_column(&dive_list, column + DIVE_SAC); - dive_list.otu = divelist_column(&dive_list, column + DIVE_OTU); - dive_list.location = divelist_column(&dive_list, column + DIVE_LOCATION); + dive_list.nr = divelist_column(&dive_list, dl_column + DIVE_NR); + dive_list.date = divelist_column(&dive_list, dl_column + DIVE_DATE); + dive_list.stars = divelist_column(&dive_list, dl_column + DIVE_RATING); + dive_list.depth = divelist_column(&dive_list, dl_column + DIVE_DEPTH); + dive_list.duration = divelist_column(&dive_list, dl_column + DIVE_DURATION); + dive_list.temperature = divelist_column(&dive_list, dl_column + DIVE_TEMPERATURE); + dive_list.cylinder = divelist_column(&dive_list, dl_column + DIVE_CYLINDER); + dive_list.nitrox = divelist_column(&dive_list, dl_column + DIVE_NITROX); + dive_list.sac = divelist_column(&dive_list, dl_column + DIVE_SAC); + dive_list.otu = divelist_column(&dive_list, dl_column + DIVE_OTU); + dive_list.location = divelist_column(&dive_list, dl_column + DIVE_LOCATION); fill_dive_list(); diff --git a/equipment.c b/equipment.c index 579c45542..abfb0e7b5 100644 --- a/equipment.c +++ b/equipment.c @@ -81,9 +81,8 @@ static int convert_pressure(int mbar, double *p) return decimals; } -static int convert_volume_pressure(int ml, int mbar, double *v, double *p) +static void convert_volume_pressure(int ml, int mbar, double *v, double *p) { - int decimals = 1; double volume, pressure; volume = ml / 1000.0; @@ -95,13 +94,11 @@ static int convert_volume_pressure(int ml, int mbar, double *v, double *p) if (output_units.pressure == PSI) { pressure = mbar_to_PSI(mbar); - decimals = 0; } else pressure = mbar / 1000.0; } *v = volume; *p = pressure; - return decimals; } static int convert_weight(int grams, double *m) @@ -405,7 +402,7 @@ static void show_cylinder(cylinder_t *cyl, struct cylinder_widget *cylinder) o2 = cyl->gasmix.o2.permille / 10.0; he = cyl->gasmix.he.permille / 10.0; if (!o2) - o2 = 21.0; + o2 = AIR_PERMILLE / 10.0; gtk_spin_button_set_value(GTK_SPIN_BUTTON(cylinder->o2), o2); gtk_spin_button_set_value(GTK_SPIN_BUTTON(cylinder->he), he); } @@ -448,7 +445,7 @@ int weightsystem_none(void *_data) return !ws->weight.grams && !ws->description; } -static void set_one_cylinder(int index, void *_data, GtkListStore *model, GtkTreeIter *iter) +static void set_one_cylinder(void *_data, GtkListStore *model, GtkTreeIter *iter) { cylinder_t *cyl = _data; unsigned int start, end; @@ -466,7 +463,7 @@ static void set_one_cylinder(int index, void *_data, GtkListStore *model, GtkTre -1); } -static void set_one_weightsystem(int index, void *_data, GtkListStore *model, GtkTreeIter *iter) +static void set_one_weightsystem(void *_data, GtkListStore *model, GtkTreeIter *iter) { weightsystem_t *ws = _data; @@ -494,7 +491,7 @@ static void show_equipment(struct dive *dive, int max, struct equipment_list *equipment_list, void*(*ptr_function)(struct dive*, int), int(*none_function)(void *), - void(*set_one_function)(int, void*, GtkListStore*, GtkTreeIter *)) + void(*set_one_function)(void*, GtkListStore*, GtkTreeIter *)) { int i, used; void *data; @@ -518,7 +515,7 @@ static void show_equipment(struct dive *dive, int max, for (i = 0; i < used; i++) { data = ptr_function(dive, i); gtk_list_store_append(model, &iter); - set_one_function(i, data, model, &iter); + set_one_function(data, model, &iter); } } @@ -1073,7 +1070,7 @@ static void edit_cb(GtkButton *button, GtkTreeView *tree_view) if (!edit_cylinder_dialog(index, &cyl)) return; - set_one_cylinder(index, &cyl, model, &iter); + set_one_cylinder(&cyl, model, &iter); repaint_dive(); } @@ -1089,7 +1086,7 @@ static void add_cb(GtkButton *button, GtkTreeView *tree_view) return; gtk_list_store_append(model, &iter); - set_one_cylinder(index, &cyl, model, &iter); + set_one_cylinder(&cyl, model, &iter); selection = gtk_tree_view_get_selection(tree_view); gtk_tree_selection_select_iter(selection, &iter); @@ -1153,7 +1150,7 @@ static void ws_edit_cb(GtkButton *button, GtkTreeView *tree_view) if (!edit_weightsystem_dialog(index, &ws)) return; - set_one_weightsystem(index, &ws, model, &iter); + set_one_weightsystem(&ws, model, &iter); repaint_dive(); } @@ -1169,7 +1166,7 @@ static void ws_add_cb(GtkButton *button, GtkTreeView *tree_view) return; gtk_list_store_append(model, &iter); - set_one_weightsystem(index, &ws, model, &iter); + set_one_weightsystem(&ws, model, &iter); selection = gtk_tree_view_get_selection(tree_view); gtk_tree_selection_select_iter(selection, &iter); @@ -0,0 +1,136 @@ +#include <unistd.h> +#include <fcntl.h> +#include <sys/stat.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> + +#include "dive.h" +#include "file.h" + +static int readfile(const char *filename, struct memblock *mem) +{ + int ret, fd = open(filename, O_RDONLY); + struct stat st; + char *buf; + + mem->buffer = NULL; + mem->size = 0; + + fd = open(filename, O_RDONLY); + if (fd < 0) + return fd; + ret = fstat(fd, &st); + if (ret < 0) + goto out; + ret = -EINVAL; + if (!S_ISREG(st.st_mode)) + goto out; + ret = 0; + if (!st.st_size) + goto out; + buf = malloc(st.st_size+1); + ret = -1; + errno = ENOMEM; + if (!buf) + goto out; + mem->buffer = buf; + mem->size = st.st_size; + ret = read(fd, buf, mem->size); + if (ret < 0) + goto free; + buf[ret] = 0; + if (ret == mem->size) + goto out; + errno = EIO; + ret = -1; +free: + free(mem->buffer); + mem->buffer = NULL; + mem->size = 0; +out: + close(fd); + return ret; +} + +#ifdef LIBZIP +#include <zip.h> + +static void suunto_read(struct zip_file *file, GError **error) +{ + int size = 1024, n, read = 0; + char *mem = malloc(size); + + while ((n = zip_fread(file, mem+read, size-read)) > 0) { + read += n; + size = read * 3 / 2; + mem = realloc(mem, size); + } + parse_xml_buffer("SDE file", mem, read, error); + free(mem); +} +#endif + +static int try_to_open_suunto(const char *filename, struct memblock *mem, GError **error) +{ + int success = 0; +#ifdef LIBZIP + /* Grr. libzip needs to re-open the file, it can't take a buffer */ + struct zip *zip = zip_open(filename, ZIP_CHECKCONS, NULL); + + if (zip) { + int index; + for (index = 0; ;index++) { + struct zip_file *file = zip_fopen_index(zip, index, 0); + if (!file) + break; + suunto_read(file, error); + zip_fclose(file); + success++; + } + zip_close(zip); + } +#endif + return success; +} + +static int open_by_filename(const char *filename, const char *fmt, struct memblock *mem, GError **error) +{ + /* Suunto Dive Manager files: SDE */ + if (!strcasecmp(fmt, "SDE")) + return try_to_open_suunto(filename, mem, error); + + /* Truly nasty intentionally obfuscated Cochran Anal software */ + if (!strcasecmp(fmt, "CAN")) + return try_to_open_cochran(filename, mem, error); + + return 0; +} + +static void parse_file_buffer(const char *filename, struct memblock *mem, GError **error) +{ + char *fmt = strrchr(filename, '.'); + if (fmt && open_by_filename(filename, fmt+1, mem, error)) + return; + + parse_xml_buffer(filename, mem->buffer, mem->size, error); +} + +void parse_file(const char *filename, GError **error) +{ + struct memblock mem; + + if (readfile(filename, &mem) < 0) { + fprintf(stderr, "Failed to read '%s'.\n", filename); + if (error) { + *error = g_error_new(g_quark_from_string("subsurface"), + DIVE_ERROR_PARSE, + "Failed to read '%s'", + filename); + } + return; + } + + parse_file_buffer(filename, &mem, error); + free(mem.buffer); +} @@ -0,0 +1,11 @@ +#ifndef FILE_H +#define FILE_H + +struct memblock { + void *buffer; + size_t size; +}; + +extern int try_to_open_cochran(const char *filename, struct memblock *mem, GError **error); + +#endif @@ -7,6 +7,7 @@ #include <string.h> #include <stdlib.h> #include <time.h> +#include <unistd.h> #include "dive.h" #include "divelist.h" @@ -22,7 +23,6 @@ GtkWidget *error_label; GtkWidget *vpane, *hpane; int error_count; -#define DIVELIST_DEFAULT_FONT "Sans 8" const char *divelist_font; struct units output_units; @@ -107,14 +107,14 @@ static void file_open(GtkWidget *w, gpointer data) gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(dialog), filter); if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) { - GSList *filenames; + GSList *filenames, *fn_glist; char *filename; - filenames = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(dialog)); + filenames = fn_glist = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(dialog)); GError *error = NULL; while(filenames != NULL) { filename = filenames->data; - parse_xml_file(filename, &error); + parse_file(filename, &error); if (error != NULL) { report_error(error); @@ -125,7 +125,7 @@ static void file_open(GtkWidget *w, gpointer data) g_free(filename); filenames = g_slist_next(filenames); } - g_slist_free(filenames); + g_slist_free(fn_glist); report_dives(FALSE); } gtk_widget_destroy(dialog); @@ -245,7 +245,7 @@ GtkTreeViewColumn *tree_view_column(GtkWidget *tree_view, int index, const char return col; } -static void create_radio(GtkWidget *vbox, const char *name, ...) +static void create_radio(GtkWidget *vbox, const char *w_name, ...) { va_list args; GtkRadioButton *group = NULL; @@ -254,10 +254,10 @@ static void create_radio(GtkWidget *vbox, const char *name, ...) box = gtk_hbox_new(TRUE, 10); gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, FALSE, 0); - label = gtk_label_new(name); + label = gtk_label_new(w_name); gtk_box_pack_start(GTK_BOX(box), label, TRUE, TRUE, 0); - va_start(args, name); + va_start(args, w_name); for (;;) { int enabled; const char *name; @@ -531,11 +531,7 @@ static void about_dialog(GtkWidget *w, gpointer data) GdkPixbuf *logo = NULL; if (need_icon) { -#if defined __linux__ || defined __APPLE__ - GtkWidget *image = gtk_image_new_from_file("subsurface.svg"); -#elif defined WIN32 - GtkWidget *image = gtk_image_new_from_file("subsurface.ico"); -#endif + GtkWidget *image = gtk_image_new_from_file(subsurface_icon_name()); if (image) { logo = gtk_image_get_pixbuf(GTK_IMAGE(image)); @@ -585,19 +581,19 @@ static GtkActionEntry menu_items[] = { { "ViewMenuAction", GTK_STOCK_FILE, "View", NULL, NULL, NULL}, { "FilterMenuAction", GTK_STOCK_FILE, "Filter", NULL, NULL, NULL}, { "HelpMenuAction", GTK_STOCK_HELP, "Help", NULL, NULL, NULL}, - { "OpenFile", GTK_STOCK_OPEN, NULL, "<control>O", NULL, G_CALLBACK(file_open) }, - { "SaveFile", GTK_STOCK_SAVE, NULL, "<control>S", NULL, G_CALLBACK(file_save) }, - { "Print", GTK_STOCK_PRINT, NULL, "<control>P", NULL, G_CALLBACK(do_print) }, + { "OpenFile", GTK_STOCK_OPEN, NULL, CTRLCHAR "O", NULL, G_CALLBACK(file_open) }, + { "SaveFile", GTK_STOCK_SAVE, NULL, CTRLCHAR "S", NULL, G_CALLBACK(file_save) }, + { "Print", GTK_STOCK_PRINT, NULL, CTRLCHAR "P", NULL, G_CALLBACK(do_print) }, { "Import", NULL, "Import", NULL, NULL, G_CALLBACK(import_dialog) }, - { "Preferences", NULL, "Preferences", NULL, NULL, G_CALLBACK(preferences_dialog) }, + { "Preferences", NULL, "Preferences", PREFERENCE_ACCEL, NULL, G_CALLBACK(preferences_dialog) }, { "Renumber", NULL, "Renumber", NULL, NULL, G_CALLBACK(renumber_dialog) }, { "SelectEvents", NULL, "SelectEvents", NULL, NULL, G_CALLBACK(selectevents_dialog) }, - { "Quit", GTK_STOCK_QUIT, NULL, "<control>Q", NULL, G_CALLBACK(quit) }, + { "Quit", GTK_STOCK_QUIT, NULL, CTRLCHAR "Q", NULL, G_CALLBACK(quit) }, { "About", GTK_STOCK_ABOUT, NULL, NULL, NULL, G_CALLBACK(about_dialog) }, - { "ViewList", NULL, "List", "<control>1", NULL, G_CALLBACK(view_list) }, - { "ViewProfile", NULL, "Profile", "<control>2", NULL, G_CALLBACK(view_profile) }, - { "ViewInfo", NULL, "Info", "<control>3", NULL, G_CALLBACK(view_info) }, - { "ViewThree", NULL, "Three", "<control>4", NULL, G_CALLBACK(view_three) }, + { "ViewList", NULL, "List", CTRLCHAR "1", NULL, G_CALLBACK(view_list) }, + { "ViewProfile", NULL, "Profile", CTRLCHAR "2", NULL, G_CALLBACK(view_profile) }, + { "ViewInfo", NULL, "Info", CTRLCHAR "3", NULL, G_CALLBACK(view_info) }, + { "ViewThree", NULL, "Three", CTRLCHAR "4", NULL, G_CALLBACK(view_three) }, }; static gint nmenu_items = sizeof (menu_items) / sizeof (menu_items[0]); @@ -634,12 +630,11 @@ static const gchar* ui_string = " \ </ui> \ "; -static GtkWidget *get_menubar_menu(GtkWidget *window) +static GtkWidget *get_menubar_menu(GtkWidget *window, GtkUIManager *ui_manager) { GtkActionGroup *action_group = gtk_action_group_new("Menu"); gtk_action_group_add_actions(action_group, menu_items, nmenu_items, 0); - GtkUIManager *ui_manager = gtk_ui_manager_new(); gtk_ui_manager_insert_action_group(ui_manager, action_group, 0); GError* error = 0; gtk_ui_manager_add_ui_from_string(GTK_UI_MANAGER(ui_manager), ui_string, -1, &error); @@ -659,15 +654,14 @@ void init_ui(int *argcp, char ***argvp) { GtkWidget *win; GtkWidget *notebook; - GtkWidget *dive_info; + GtkWidget *nb_page; GtkWidget *dive_list; - GtkWidget *equipment; - GtkWidget *stats; GtkWidget *menubar; GtkWidget *vbox; GdkScreen *screen; GtkIconTheme *icon_theme=NULL; GtkSettings *settings; + GtkUIManager *ui_manager; gtk_init(argcp, argvp); settings = gtk_settings_get_default(); @@ -695,9 +689,6 @@ void init_ui(int *argcp, char ***argvp) divelist_font = subsurface_get_conf("divelist_font", PREF_STRING); - if (!divelist_font) - divelist_font = DIVELIST_DEFAULT_FONT; - error_info_bar = NULL; win = gtk_window_new(GTK_WINDOW_TOPLEVEL); g_set_application_name ("subsurface"); @@ -712,12 +703,11 @@ void init_ui(int *argcp, char ***argvp) gtk_window_set_default_icon_name ("subsurface"); } } - if (need_icon) -#if defined __linux__ || defined __APPLE__ - gtk_window_set_icon_from_file(GTK_WINDOW(win), "subsurface.svg", NULL); -#elif defined WIN32 - gtk_window_set_icon_from_file(GTK_WINDOW(win), "subsurface.ico", NULL); -#endif + if (need_icon) { + const char *icon_name = subsurface_icon_name(); + if (!access(icon_name, R_OK)) + gtk_window_set_icon_from_file(GTK_WINDOW(win), icon_name, NULL); + } g_signal_connect(G_OBJECT(win), "delete-event", G_CALLBACK(on_delete), NULL); g_signal_connect(G_OBJECT(win), "destroy", G_CALLBACK(on_destroy), NULL); main_window = win; @@ -726,8 +716,10 @@ void init_ui(int *argcp, char ***argvp) gtk_container_add(GTK_CONTAINER(win), vbox); main_vbox = vbox; - menubar = get_menubar_menu(win); - gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, FALSE, 0); + ui_manager = gtk_ui_manager_new(); + menubar = get_menubar_menu(win, ui_manager); + + subsurface_ui_setup(settings, menubar, vbox, ui_manager); vpane = gtk_vpaned_new(); gtk_box_pack_start(GTK_BOX(vbox), vpane, TRUE, TRUE, 3); @@ -751,16 +743,20 @@ void init_ui(int *argcp, char ***argvp) gtk_paned_add2(GTK_PANED(hpane), dive_profile); /* Frame for extended dive info */ - dive_info = extended_dive_info_widget(); - gtk_notebook_append_page(GTK_NOTEBOOK(notebook), dive_info, gtk_label_new("Dive Notes")); + nb_page = extended_dive_info_widget(); + gtk_notebook_append_page(GTK_NOTEBOOK(notebook), nb_page, gtk_label_new("Dive Notes")); /* Frame for dive equipment */ - equipment = equipment_widget(); - gtk_notebook_append_page(GTK_NOTEBOOK(notebook), equipment, gtk_label_new("Equipment")); + nb_page = equipment_widget(); + gtk_notebook_append_page(GTK_NOTEBOOK(notebook), nb_page, gtk_label_new("Equipment")); + + /* Frame for single dive statistics */ + nb_page = single_stats_widget(); + gtk_notebook_append_page(GTK_NOTEBOOK(notebook), nb_page, gtk_label_new("Dive Info")); - /* Frame for dive statistics */ - stats = stats_widget(); - gtk_notebook_append_page(GTK_NOTEBOOK(notebook), stats, gtk_label_new("Info & Stats")); + /* Frame for total dive statistics */ + nb_page = total_stats_widget(); + gtk_notebook_append_page(GTK_NOTEBOOK(notebook), nb_page, gtk_label_new("Stats")); gtk_widget_set_app_paintable(win, TRUE); gtk_widget_show_all(win); @@ -975,7 +971,7 @@ static GtkWidget *xml_file_selector(GtkWidget *vbox, GtkWidget *main_dialog) static void do_import_file(gpointer data, gpointer user_data) { GError *error = NULL; - parse_xml_file(data, &error); + parse_file(data, &error); if (error != NULL) { @@ -258,13 +258,13 @@ void add_location(const char *string) static int get_rating(const char *string) { - int rating = 0; + int rating_val = 0; int i; for (i = 0; i <= 5; i++) if (!strcmp(star_strings[i],string)) - rating = i; - return rating; + rating_val = i; + return rating_val; } struct dive_info { diff --git a/libdivecomputer.c b/libdivecomputer.c index 9d4c1065a..4ff41486d 100644 --- a/libdivecomputer.c +++ b/libdivecomputer.c @@ -109,7 +109,7 @@ static int parse_gasmixes(struct dive *dive, parser_t *parser, int ngases) he = gasmix.helium * 1000 + 0.5; /* Ignore bogus data - libdivecomputer does some crazy stuff */ - if (o2 < 210 || o2 >= 1000) + if (o2 <= AIR_PERMILLE || o2 >= 1000) o2 = 0; if (he < 0 || he >= 800 || o2+he >= 1000) he = 0; @@ -175,7 +175,7 @@ sample_cb(parser_sample_type_t type, parser_sample_value_t value, void *userdata case SAMPLE_TYPE_TIME: sample = prepare_sample(divep); sample->time.seconds = value.time; - finish_sample(*divep, sample); + finish_sample(*divep); break; case SAMPLE_TYPE_DEPTH: sample->depth.mm = value.depth * 1000 + 0.5; @@ -2,6 +2,7 @@ /* implements Linux specific functions */ #include "display-gtk.h" #include <gconf/gconf-client.h> +#define DIVELIST_DEFAULT_FONT "Sans 8" GConfClient *gconf; @@ -51,3 +52,16 @@ const char *subsurface_USB_name() { return "/dev/ttyUSB0"; } + +const char *subsurface_icon_name() +{ + return "subsurface.svg"; +} + +void subsurface_ui_setup(GtkSettings *settings, GtkWidget *menubar, + GtkWidget *vbox, GtkUIManager *ui_manager) +{ + if (!divelist_font) + divelist_font = DIVELIST_DEFAULT_FONT; + gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, FALSE, 0); +} @@ -2,10 +2,10 @@ /* implements Mac OS X specific functions */ #include "display-gtk.h" #include <CoreFoundation/CoreFoundation.h> +#include <mach-o/dyld.h> +#include "gtkosxapplication.h" -static CFURLRef fileURL; -static CFPropertyListRef propertyList; -static CFMutableDictionaryRef dict = NULL; +static GtkOSXApplication *osx_app; /* macos defines CFSTR to create a CFString object from a constant, * but no similar macros if a C string variable is supposed to be @@ -15,61 +15,45 @@ static CFMutableDictionaryRef dict = NULL; (_var), kCFStringEncodingMacRoman, \ kCFAllocatorNull) +#define SUBSURFACE_PREFERENCES CFSTR("org.hohndel.subsurface") +#define ICON_NAME "Subsurface.icns" +#define UI_FONT "Arial Unicode MS 12" +#define DIVELIST_MAC_DEFAULT_FONT "Arial Unicode MS 9" + void subsurface_open_conf(void) { - CFStringRef errorString; - CFDataRef resourceData; - Boolean status; - SInt32 errorCode; - - fileURL = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, - CFSTR("subsurface.pref"),// file path name - kCFURLPOSIXPathStyle, // interpret as POSIX path - false ); // is it a directory? - - status = CFURLCreateDataAndPropertiesFromResource(kCFAllocatorDefault, - fileURL, &resourceData, - NULL, NULL, &errorCode); - if (status) { - propertyList = CFPropertyListCreateFromXMLData(kCFAllocatorDefault, - resourceData, kCFPropertyListImmutable, - &errorString); - CFRelease(resourceData); - } + /* nothing at this time */ } void subsurface_set_conf(char *name, pref_type_t type, const void *value) { - if (!dict) - dict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, - &kCFTypeDictionaryKeyCallBacks, - &kCFTypeDictionaryValueCallBacks); switch (type) { case PREF_BOOL: - CFDictionarySetValue(dict, CFSTR_VAR(name), value == NULL ? CFSTR("0") : CFSTR("1")); + CFPreferencesSetAppValue(CFSTR_VAR(name), + value == NULL ? kCFBooleanFalse : kCFBooleanTrue, SUBSURFACE_PREFERENCES); break; case PREF_STRING: - CFDictionarySetValue(dict, CFSTR_VAR(name), CFSTR_VAR(value)); + CFPreferencesSetAppValue(CFSTR_VAR(name), CFSTR_VAR(value), SUBSURFACE_PREFERENCES); } } + const void *subsurface_get_conf(char *name, pref_type_t type) { - CFStringRef dict_entry; - - /* if no settings exist, we return the value for FALSE */ - if (!propertyList) - return NULL; + Boolean boolpref; + CFPropertyListRef strpref; switch (type) { case PREF_BOOL: - dict_entry = CFDictionaryGetValue(propertyList, CFSTR_VAR(name)); - if (dict_entry && ! CFStringCompare(CFSTR("1"), dict_entry, 0)) + boolpref = CFPreferencesGetAppBooleanValue(CFSTR_VAR(name), SUBSURFACE_PREFERENCES, FALSE); + if (boolpref) return (void *) 1; else return NULL; case PREF_STRING: - return CFStringGetCStringPtr(CFDictionaryGetValue(propertyList, - CFSTR_VAR(name)), kCFStringEncodingMacRoman); + strpref = CFPreferencesCopyAppValue(CFSTR_VAR(name), SUBSURFACE_PREFERENCES); + if (!strpref) + return NULL; + return CFStringGetCStringPtr(strpref, kCFStringEncodingMacRoman); } /* we shouldn't get here, but having this line makes the compiler happy */ return NULL; @@ -77,20 +61,59 @@ const void *subsurface_get_conf(char *name, pref_type_t type) void subsurface_close_conf(void) { - Boolean status; - SInt32 errorCode; - CFDataRef xmlData; - - propertyList = dict; - dict = NULL; - xmlData = CFPropertyListCreateXMLData(kCFAllocatorDefault, propertyList); - status = CFURLWriteDataAndPropertiesToResource (fileURL, xmlData, NULL, &errorCode); - // some error handling - but really, what can we do? - CFRelease(xmlData); - CFRelease(propertyList); + int ok = CFPreferencesAppSynchronize(SUBSURFACE_PREFERENCES); + if (!ok) + fprintf(stderr,"Could not save preferences\n"); } const char *subsurface_USB_name() { return "/dev/tty.SLAB_USBtoUART"; } + +const char *subsurface_icon_name() +{ + static char path[1024]; + + snprintf(path, 1024, "%s/%s", quartz_application_get_resource_path(), ICON_NAME); + + return path; +} + +void subsurface_ui_setup(GtkSettings *settings, GtkWidget *menubar, + GtkWidget *vbox, GtkUIManager *ui_manager) +{ + GtkWidget *menu_item, *sep; + + if (!divelist_font) + divelist_font = DIVELIST_MAC_DEFAULT_FONT; + g_object_set(G_OBJECT(settings), "gtk-font-name", UI_FONT, NULL); + + osx_app = g_object_new(GTK_TYPE_OSX_APPLICATION, NULL); + gtk_widget_hide (menubar); + gtk_osxapplication_set_menu_bar(osx_app, GTK_MENU_SHELL(menubar)); + + sep = gtk_ui_manager_get_widget(ui_manager, "/MainMenu/FileMenu/Separator3"); + gtk_widget_destroy(sep); + sep = gtk_ui_manager_get_widget(ui_manager, "/MainMenu/FileMenu/Separator2"); + gtk_widget_destroy(sep); + + menu_item = gtk_ui_manager_get_widget(ui_manager, "/MainMenu/FileMenu/Quit"); + gtk_widget_hide (menu_item); + menu_item = gtk_ui_manager_get_widget(ui_manager, "/MainMenu/Help/About"); + gtk_osxapplication_insert_app_menu_item(osx_app, menu_item, 0); + + sep = gtk_separator_menu_item_new(); + g_object_ref(sep); + gtk_osxapplication_insert_app_menu_item (osx_app, sep, 1); + + menu_item = gtk_ui_manager_get_widget(ui_manager, "/MainMenu/FileMenu/Preferences"); + gtk_osxapplication_insert_app_menu_item(osx_app, menu_item, 2); + + sep = gtk_separator_menu_item_new(); + g_object_ref(sep); + gtk_osxapplication_insert_app_menu_item (osx_app, sep, 3); + + gtk_osxapplication_set_use_quartz_accelerators(osx_app, TRUE); + gtk_osxapplication_ready(osx_app); +} @@ -101,7 +101,7 @@ static gboolean imported = FALSE; * This doesn't really report anything at all. We just sort the * dives, the GUI does the reporting */ -void report_dives(gboolean imported) +void report_dives(gboolean is_imported) { int i; int preexisting = dive_table.preexisting; @@ -135,7 +135,7 @@ void report_dives(gboolean imported) i--; } - if (imported) { + if (is_imported) { /* Was the previous dive table state numbered? */ if (last && last->number) try_to_renumber(last, preexisting); @@ -226,7 +226,7 @@ int main(int argc, char **argv) continue; } GError *error = NULL; - parse_xml_file(a, &error); + parse_file(a, &error); if (error != NULL) { diff --git a/packaging/macosx/Info.plist b/packaging/macosx/Info.plist index 4ee12438b..f3ac10cdd 100644 --- a/packaging/macosx/Info.plist +++ b/packaging/macosx/Info.plist @@ -13,8 +13,8 @@ <key>CFBundleSignature</key> <string>????</string> <key>CFBundleExecutable</key> - <string>subsurface.sh</string> + <string>subsurface</string> <key>CFBundleIdentifier</key> - <string>torvalds.subsurface</string> + <string>org.hohndel.subsurface</string> </dict> </plist> diff --git a/packaging/macosx/subsurface.sh b/packaging/macosx/subsurface.sh deleted file mode 100755 index ee9427c37..000000000 --- a/packaging/macosx/subsurface.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/sh - -cd `dirname $0`/../Resources -../MacOS/subsurface & -exit 0 diff --git a/packaging/windows/subsurface.nsi b/packaging/windows/subsurface.nsi index 09a6eb0e6..280991f69 100644 --- a/packaging/windows/subsurface.nsi +++ b/packaging/windows/subsurface.nsi @@ -80,11 +80,14 @@ file /oname=libpangoft2-1.0-0.dll dll\libpangoft2-1.0-0.dll file /oname=libpangowin32-1.0-0.dll dll\libpangowin32-1.0-0.dll file /oname=libpixman-1-0.dll dll\libpixman-1-0.dll file /oname=libpng15-15.dll dll\libpng15-15.dll -file /oname=libtiff-3.dll dll\libtiff-3.dll +file /oname=libtiff-5.dll dll\libtiff-5.dll file /oname=libxml2-2.dll dll\libxml2-2.dll file /oname=libxslt-1.dll dll\libxslt-1.dll file /oname=pthreadGC2.dll dll\pthreadGC2.dll file /oname=zlib1.dll dll\zlib1.dll +file /oname=libusb-1.0.dll dll\libusb-1.0.dll +file /oname=SuuntoSDM.xslt ../../xslt/SuuntoSDM.xslt +file /oname=jdivelog2subsurface.xslt ../../xslt/jdivelog2subsurface.xslt sectionEnd section "uninstall" diff --git a/parse-xml.c b/parse-xml.c index 34afdb9f6..e920a11f6 100644 --- a/parse-xml.c +++ b/parse-xml.c @@ -97,16 +97,16 @@ const struct units IMPERIAL_units = { /* * Dive info as it is being built up.. */ -static struct dive *dive; -static struct sample *sample; +static struct dive *cur_dive; +static struct sample *cur_sample; static struct { int active; duration_t time; int type, flags, value; const char *name; -} event; -static struct tm tm; -static int cylinder_index, ws_index; +} cur_event; +static struct tm cur_tm; +static int cur_cylinder_index, cur_ws_index; static enum import_source { UNKNOWN, @@ -152,22 +152,22 @@ static void divedate(char *buffer, void *_when) time_t *when = _when; int success = 0; - success = tm.tm_sec | tm.tm_min | tm.tm_hour; + success = cur_tm.tm_sec | cur_tm.tm_min | cur_tm.tm_hour; if (sscanf(buffer, "%d.%d.%d", &d, &m, &y) == 3) { - tm.tm_year = y; - tm.tm_mon = m-1; - tm.tm_mday = d; + cur_tm.tm_year = y; + cur_tm.tm_mon = m-1; + cur_tm.tm_mday = d; } else if (sscanf(buffer, "%d-%d-%d", &y, &m, &d) == 3) { - tm.tm_year = y; - tm.tm_mon = m-1; - tm.tm_mday = d; + cur_tm.tm_year = y; + cur_tm.tm_mon = m-1; + cur_tm.tm_mday = d; } else { fprintf(stderr, "Unable to parse date '%s'\n", buffer); success = 0; } if (success) - *when = utc_mktime(&tm); + *when = utc_mktime(&cur_tm); free(buffer); } @@ -178,11 +178,11 @@ static void divetime(char *buffer, void *_when) time_t *when = _when; if (sscanf(buffer, "%d:%d:%d", &h, &m, &s) >= 2) { - tm.tm_hour = h; - tm.tm_min = m; - tm.tm_sec = s; - if (tm.tm_year) - *when = utc_mktime(&tm); + cur_tm.tm_hour = h; + cur_tm.tm_min = m; + cur_tm.tm_sec = s; + if (cur_tm.tm_year) + *when = utc_mktime(&cur_tm); } free(buffer); } @@ -196,13 +196,13 @@ static void divedatetime(char *buffer, void *_when) if (sscanf(buffer, "%d-%d-%d %d:%d:%d", &y, &m, &d, &hr, &min, &sec) == 6) { - tm.tm_year = y; - tm.tm_mon = m-1; - tm.tm_mday = d; - tm.tm_hour = hr; - tm.tm_min = min; - tm.tm_sec = sec; - *when = utc_mktime(&tm); + cur_tm.tm_year = y; + cur_tm.tm_mon = m-1; + cur_tm.tm_mday = d; + cur_tm.tm_hour = hr; + cur_tm.tm_min = min; + cur_tm.tm_sec = sec; + *when = utc_mktime(&cur_tm); } free(buffer); } @@ -398,7 +398,7 @@ static void gasmix(char *buffer, void *_fraction) /* libdivecomputer does negative percentages. */ if (*buffer == '-') return; - if (cylinder_index < MAX_CYLINDERS) + if (cur_cylinder_index < MAX_CYLINDERS) percent(buffer, _fraction); } @@ -595,8 +595,8 @@ static void eventtime(char *buffer, void *_duration) { duration_t *duration = _duration; sampletime(buffer, duration); - if (sample) - duration->seconds += sample->time.seconds; + if (cur_sample) + duration->seconds += cur_sample->time.seconds; } static void try_to_fill_event(const char *name, char *buf) @@ -604,17 +604,17 @@ static void try_to_fill_event(const char *name, char *buf) int len = strlen(name); start_match("event", name, buf); - if (MATCH(".event", utf8_string, &event.name)) + if (MATCH(".event", utf8_string, &cur_event.name)) return; - if (MATCH(".name", utf8_string, &event.name)) + if (MATCH(".name", utf8_string, &cur_event.name)) return; - if (MATCH(".time", eventtime, &event.time)) + if (MATCH(".time", eventtime, &cur_event.time)) return; - if (MATCH(".type", get_index, &event.type)) + if (MATCH(".type", get_index, &cur_event.type)) return; - if (MATCH(".flags", get_index, &event.flags)) + if (MATCH(".flags", get_index, &cur_event.flags)) return; - if (MATCH(".value", get_index, &event.value)) + if (MATCH(".value", get_index, &cur_event.value)) return; nonmatch("event", name, buf); } @@ -826,7 +826,7 @@ static int uemis_gas_template; static int uemis_cylinder_index(void *_cylinder) { cylinder_t *cylinder = _cylinder; - unsigned int index = cylinder - dive->cylinder; + unsigned int index = cylinder - cur_dive->cylinder; if (index > 6) { fprintf(stderr, "Uemis cylinder pointer calculations broken\n"); @@ -858,14 +858,14 @@ static void uemis_cylindersize(char *buffer, void *_cylinder) { int index = uemis_cylinder_index(_cylinder); if (index >= 0) - cylindersize(buffer, &dive->cylinder[index].type.size); + cylindersize(buffer, &cur_dive->cylinder[index].type.size); } static void uemis_percent(char *buffer, void *_cylinder) { int index = uemis_cylinder_index(_cylinder); if (index >= 0) - percent(buffer, &dive->cylinder[index].gasmix.o2); + percent(buffer, &cur_dive->cylinder[index].gasmix.o2); } static int uemis_dive_match(struct dive **divep, const char *name, int len, char *buf) @@ -1048,27 +1048,27 @@ static void try_to_fill_dive(struct dive **divep, const char *name, char *buf) return; if (MATCH(".rating", get_index, &dive->rating)) return; - if (MATCH(".cylinder.size", cylindersize, &dive->cylinder[cylinder_index].type.size)) + if (MATCH(".cylinder.size", cylindersize, &dive->cylinder[cur_cylinder_index].type.size)) return; - if (MATCH(".cylinder.workpressure", pressure, &dive->cylinder[cylinder_index].type.workingpressure)) + if (MATCH(".cylinder.workpressure", pressure, &dive->cylinder[cur_cylinder_index].type.workingpressure)) return; - if (MATCH(".cylinder.description", utf8_string, &dive->cylinder[cylinder_index].type.description)) + if (MATCH(".cylinder.description", utf8_string, &dive->cylinder[cur_cylinder_index].type.description)) return; - if (MATCH(".cylinder.start", pressure, &dive->cylinder[cylinder_index].start)) + if (MATCH(".cylinder.start", pressure, &dive->cylinder[cur_cylinder_index].start)) return; - if (MATCH(".cylinder.end", pressure, &dive->cylinder[cylinder_index].end)) + if (MATCH(".cylinder.end", pressure, &dive->cylinder[cur_cylinder_index].end)) return; - if (MATCH(".weightsystem.description", utf8_string, &dive->weightsystem[ws_index].description)) + if (MATCH(".weightsystem.description", utf8_string, &dive->weightsystem[cur_ws_index].description)) return; - if (MATCH(".weightsystem.weight", weight, &dive->weightsystem[ws_index].weight)) + if (MATCH(".weightsystem.weight", weight, &dive->weightsystem[cur_ws_index].weight)) return; - if (MATCH("weight", weight, &dive->weightsystem[ws_index].weight)) + if (MATCH("weight", weight, &dive->weightsystem[cur_ws_index].weight)) return; - if (MATCH(".o2", gasmix, &dive->cylinder[cylinder_index].gasmix.o2)) + if (MATCH(".o2", gasmix, &dive->cylinder[cur_cylinder_index].gasmix.o2)) return; - if (MATCH(".n2", gasmix_nitrogen, &dive->cylinder[cylinder_index].gasmix)) + if (MATCH(".n2", gasmix_nitrogen, &dive->cylinder[cur_cylinder_index].gasmix)) return; - if (MATCH(".he", gasmix, &dive->cylinder[cylinder_index].gasmix.he)) + if (MATCH(".he", gasmix, &dive->cylinder[cur_cylinder_index].gasmix.he)) return; nonmatch("dive", name, buf); @@ -1082,150 +1082,35 @@ static void try_to_fill_dive(struct dive **divep, const char *name, char *buf) */ static void dive_start(void) { - if (dive) + if (cur_dive) return; - dive = alloc_dive(); - memset(&tm, 0, sizeof(tm)); -} - -static void sanitize_gasmix(struct gasmix *mix) -{ - unsigned int o2, he; - - o2 = mix->o2.permille; - he = mix->he.permille; - - /* Regular air: leave empty */ - if (!he) { - if (!o2) - return; - /* 20.9% or 21% O2 is just air */ - if (o2 >= 209 && o2 <= 210) { - mix->o2.permille = 0; - return; - } - } - - /* Sane mix? */ - if (o2 <= 1000 && he <= 1000 && o2+he <= 1000) - return; - fprintf(stderr, "Odd gasmix: %d O2 %d He\n", o2, he); - memset(mix, 0, sizeof(*mix)); -} - -/* - * See if the size/workingpressure looks like some standard cylinder - * size, eg "AL80". - */ -static void match_standard_cylinder(cylinder_type_t *type) -{ - double cuft; - int psi, len; - const char *fmt; - char buffer[20], *p; - - /* Do we already have a cylinder description? */ - if (type->description) - return; - - cuft = ml_to_cuft(type->size.mliter); - cuft *= to_ATM(type->workingpressure); - psi = to_PSI(type->workingpressure); - - switch (psi) { - case 2300 ... 2500: /* 2400 psi: LP tank */ - fmt = "LP%d"; - break; - case 2600 ... 2700: /* 2640 psi: LP+10% */ - fmt = "LP%d"; - break; - case 2900 ... 3100: /* 3000 psi: ALx tank */ - fmt = "AL%d"; - break; - case 3400 ... 3500: /* 3442 psi: HP tank */ - fmt = "HP%d"; - break; - case 3700 ... 3850: /* HP+10% */ - fmt = "HP%d+"; - break; - default: - return; - } - len = snprintf(buffer, sizeof(buffer), fmt, (int) (cuft+0.5)); - p = malloc(len+1); - if (!p) - return; - memcpy(p, buffer, len+1); - type->description = p; -} - - -/* - * There are two ways to give cylinder size information: - * - total amount of gas in cuft (depends on working pressure and physical size) - * - physical size - * - * where "physical size" is the one that actually matters and is sane. - * - * We internally use physical size only. But we save the workingpressure - * so that we can do the conversion if required. - */ -static void sanitize_cylinder_type(cylinder_type_t *type) -{ - double volume_of_air, atm, volume; - - /* If we have no working pressure, it had *better* be just a physical size! */ - if (!type->workingpressure.mbar) - return; - - /* No size either? Nothing to go on */ - if (!type->size.mliter) - return; - - if (input_units.volume == CUFT) { - /* confusing - we don't really start from ml but millicuft !*/ - volume_of_air = cuft_to_l(type->size.mliter); - atm = to_ATM(type->workingpressure); /* working pressure in atm */ - volume = volume_of_air / atm; /* milliliters at 1 atm: "true size" */ - type->size.mliter = volume + 0.5; - } - - /* Ok, we have both size and pressure: try to match a description */ - match_standard_cylinder(type); -} - -static void sanitize_cylinder_info(struct dive *dive) -{ - int i; - - for (i = 0; i < MAX_CYLINDERS; i++) { - sanitize_gasmix(&dive->cylinder[i].gasmix); - sanitize_cylinder_type(&dive->cylinder[i].type); - } + cur_dive = alloc_dive(); + memset(&cur_tm, 0, sizeof(cur_tm)); } static void dive_end(void) { - if (!dive) + if (!cur_dive) return; - sanitize_cylinder_info(dive); - record_dive(dive); - dive = NULL; - cylinder_index = 0; - ws_index = 0; + record_dive(cur_dive); + cur_dive = NULL; + cur_cylinder_index = 0; + cur_ws_index = 0; } static void event_start(void) { - memset(&event, 0, sizeof(event)); - event.active = 1; + memset(&cur_event, 0, sizeof(cur_event)); + cur_event.active = 1; } static void event_end(void) { - if (event.name && strcmp(event.name, "surface") != 0) - add_event(dive, event.time.seconds, event.type, event.flags, event.value, event.name); - event.active = 0; + if (cur_event.name && strcmp(cur_event.name, "surface") != 0) + add_event(cur_dive, cur_event.time.seconds, + cur_event.type, cur_event.flags, + cur_event.value, cur_event.name); + cur_event.active = 0; } static void cylinder_start(void) @@ -1234,7 +1119,7 @@ static void cylinder_start(void) static void cylinder_end(void) { - cylinder_index++; + cur_cylinder_index++; } static void ws_start(void) @@ -1243,21 +1128,21 @@ static void ws_start(void) static void ws_end(void) { - ws_index++; + cur_ws_index++; } static void sample_start(void) { - sample = prepare_sample(&dive); + cur_sample = prepare_sample(&cur_dive); } static void sample_end(void) { - if (!dive) + if (!cur_dive) return; - finish_sample(dive, sample); - sample = NULL; + finish_sample(cur_dive); + cur_sample = NULL; } static void entry(const char *name, int size, const char *raw) @@ -1268,16 +1153,16 @@ static void entry(const char *name, int size, const char *raw) return; memcpy(buf, raw, size); buf[size] = 0; - if (event.active) { + if (cur_event.active) { try_to_fill_event(name, buf); return; } - if (sample) { - try_to_fill_sample(sample, name, buf); + if (cur_sample) { + try_to_fill_sample(cur_sample, name, buf); return; } - if (dive) { - try_to_fill_dive(&dive, name, buf); + if (cur_dive) { + try_to_fill_dive(&cur_dive, name, buf); return; } } @@ -1455,25 +1340,25 @@ static void reset_all(void) import_source = UNKNOWN; } -void parse_xml_file(const char *filename, GError **error) +void parse_xml_buffer(const char *url, const char *buffer, int size, GError **error) { xmlDoc *doc; - doc = xmlReadFile(filename, NULL, 0); + doc = xmlReadMemory(buffer, size, url, NULL, 0); if (!doc) { - fprintf(stderr, "Failed to parse '%s'.\n", filename); + fprintf(stderr, "Failed to parse '%s'.\n", url); if (error != NULL) { *error = g_error_new(g_quark_from_string("subsurface"), DIVE_ERROR_PARSE, "Failed to parse '%s'", - filename); + url); } return; } /* we assume that the last (or only) filename passed as argument is a * great filename to use as default when saving the dives */ - set_filename(filename); + set_filename(url); reset_all(); dive_start(); #ifdef XSLT @@ -1014,8 +1014,7 @@ static void dump_pr_track(pr_track_t **track_pr) } } -static void fill_missing_tank_pressures(struct dive *dive, struct plot_info *pi, - pr_track_t **track_pr) +static void fill_missing_tank_pressures(struct plot_info *pi, pr_track_t **track_pr) { pr_track_t *list = NULL; pr_track_t *nlist = NULL; @@ -1338,7 +1337,7 @@ static struct plot_info *create_plot_info(struct dive *dive, int nr_samples, str pi->meandepth = dive->meandepth.mm; if (missing_pr) { - fill_missing_tank_pressures(dive, pi, track_pr); + fill_missing_tank_pressures(pi, track_pr); } for (cyl = 0; cyl < MAX_CYLINDERS; cyl++) list_free(track_pr[cyl]); diff --git a/statistics.c b/statistics.c index f8f9d0e24..65efdf145 100644 --- a/statistics.c +++ b/statistics.c @@ -29,38 +29,101 @@ typedef struct { *sac, *otu, *o2he, - *gas_used, - *total_time, + *gas_used; +} single_stat_widget_t; + +static single_stat_widget_t single_w; + +typedef struct { + GtkWidget *total_time, *avg_time, + *shortest_time, + *longest_time, *max_overall_depth, + *min_overall_depth, *avg_overall_depth, *min_sac, *avg_sac, - *max_sac; -} info_stat_widget_t; + *max_sac, + *selection_size, + *max_temp, + *avg_temp, + *min_temp; +} total_stats_widget_t; -static info_stat_widget_t info_stat_w; +static total_stats_widget_t stats_w; typedef struct { duration_t total_time; /* avg_time is simply total_time / nr -- let's not keep this */ + duration_t shortest_time; + duration_t longest_time; depth_t max_depth; + depth_t min_depth; depth_t avg_depth; volume_t max_sac; volume_t min_sac; volume_t avg_sac; -} info_stat_t; + int max_temp; + int min_temp; + unsigned int combined_temp; + unsigned int combined_count; + unsigned int selection_size; +} stats_t; -static info_stat_t info_stat; +static stats_t stats; +static stats_t stats_selection; + + +static void process_dive(struct dive *dp, stats_t *stats) +{ + int old_tt, sac_time = 0; + const char *unit; + + old_tt = stats->total_time.seconds; + stats->total_time.seconds += dp->duration.seconds; + if (dp->duration.seconds > stats->longest_time.seconds) + stats->longest_time.seconds = dp->duration.seconds; + if (stats->shortest_time.seconds == 0 || dp->duration.seconds < stats->shortest_time.seconds) + stats->shortest_time.seconds = dp->duration.seconds; + if (dp->maxdepth.mm > stats->max_depth.mm) + stats->max_depth.mm = dp->maxdepth.mm; + if (stats->min_depth.mm == 0 || dp->maxdepth.mm < stats->min_depth.mm) + stats->min_depth.mm = dp->maxdepth.mm; + stats->avg_depth.mm = (1.0 * old_tt * stats->avg_depth.mm + + dp->duration.seconds * dp->meandepth.mm) / stats->total_time.seconds; + if (dp->sac > 2800) { /* less than .1 cuft/min (2800ml/min) is bogus */ + int old_sac_time = sac_time; + sac_time += dp->duration.seconds; + stats->avg_sac.mliter = (1.0 * old_sac_time * stats->avg_sac.mliter + + dp->duration.seconds * dp->sac) / sac_time ; + if (dp->sac > stats->max_sac.mliter) + stats->max_sac.mliter = dp->sac; + if (stats->min_sac.mliter == 0 || dp->sac < stats->min_sac.mliter) + stats->min_sac.mliter = dp->sac; + } + if (dp->watertemp.mkelvin) { + if (stats->min_temp == 0 || dp->watertemp.mkelvin < stats->min_temp) + stats->min_temp = dp->watertemp.mkelvin; + if (dp->watertemp.mkelvin > stats->max_temp) + stats->max_temp = dp->watertemp.mkelvin; + stats->combined_temp += get_temp_units(dp->watertemp.mkelvin, &unit); + stats->combined_count++; + } +} static void process_all_dives(struct dive *dive, struct dive **prev_dive) { int idx; struct dive *dp; - int old_tt, sac_time = 0; *prev_dive = NULL; - memset(&info_stat, 0, sizeof(info_stat)); + memset(&stats, 0, sizeof(stats)); + if (dive_table.nr > 0) { + stats.shortest_time.seconds = dive_table.dives[0]->duration.seconds; + stats.min_depth.mm = dive_table.dives[0]->maxdepth.mm; + stats.selection_size = dive_table.nr; + } /* this relies on the fact that the dives in the dive_table * are in chronological order */ for (idx = 0; idx < dive_table.nr; idx++) { @@ -70,22 +133,28 @@ static void process_all_dives(struct dive *dive, struct dive **prev_dive) if (idx > 0) *prev_dive = dive_table.dives[idx-1]; } - old_tt = info_stat.total_time.seconds; - info_stat.total_time.seconds += dp->duration.seconds; - if (dp->maxdepth.mm > info_stat.max_depth.mm) - info_stat.max_depth.mm = dp->maxdepth.mm; - info_stat.avg_depth.mm = (1.0 * old_tt * info_stat.avg_depth.mm + - dp->duration.seconds * dp->meandepth.mm) / info_stat.total_time.seconds; - if (dp->sac > 2800) { /* less than .1 cuft/min (2800ml/min) is bogus */ - int old_sac_time = sac_time; - sac_time += dp->duration.seconds; - info_stat.avg_sac.mliter = (1.0 * old_sac_time * info_stat.avg_sac.mliter + - dp->duration.seconds * dp->sac) / sac_time ; - if (dp->sac > info_stat.max_sac.mliter) - info_stat.max_sac.mliter = dp->sac; - if (info_stat.min_sac.mliter == 0 || dp->sac < info_stat.min_sac.mliter) - info_stat.min_sac.mliter = dp->sac; + process_dive(dp, &stats); + } +} + +void process_selected_dives(GList *selected_dives, GtkTreeModel *model) +{ + struct dive *dp; + unsigned int i; + GtkTreeIter iter; + GtkTreePath *path; + + memset(&stats_selection, 0, sizeof(stats_selection)); + stats_selection.selection_size = amount_selected; + + for (i = 0; i < amount_selected; ++i) { + GValue value = {0, }; + path = g_list_nth_data(selected_dives, i); + if (gtk_tree_model_get_iter(model, &iter, path)) { + gtk_tree_model_get_value(model, &iter, 0, &value); + dp = get_dive(g_value_get_int(&value)); } + process_dive(dp, &stats_selection); } } @@ -117,7 +186,7 @@ static char * get_time_string(int seconds, int maxdays) return buf; } -void show_dive_stats(struct dive *dive) +static void show_single_dive_stats(struct dive *dive) { char buf[80]; double value; @@ -137,28 +206,28 @@ void show_dive_stats(struct dive *dive) tm->tm_mday, tm->tm_year + 1900, tm->tm_hour, tm->tm_min); - set_label(info_stat_w.date, buf); - set_label(info_stat_w.dive_time, "%d min", (dive->duration.seconds + 30) / 60); + set_label(single_w.date, buf); + set_label(single_w.dive_time, "%d min", (dive->duration.seconds + 30) / 60); if (prev_dive) - set_label(info_stat_w.surf_intv, + set_label(single_w.surf_intv, get_time_string(dive->when - (prev_dive->when + prev_dive->duration.seconds), 4)); else - set_label(info_stat_w.surf_intv, "unknown"); + set_label(single_w.surf_intv, "unknown"); value = get_depth_units(dive->maxdepth.mm, &decimals, &unit); - set_label(info_stat_w.max_depth, "%.*f %s", decimals, value, unit); + set_label(single_w.max_depth, "%.*f %s", decimals, value, unit); value = get_depth_units(dive->meandepth.mm, &decimals, &unit); - set_label(info_stat_w.avg_depth, "%.*f %s", decimals, value, unit); + set_label(single_w.avg_depth, "%.*f %s", decimals, value, unit); if (dive->watertemp.mkelvin) { value = get_temp_units(dive->watertemp.mkelvin, &unit); - set_label(info_stat_w.water_temp, "%.1f %s", value, unit); + set_label(single_w.water_temp, "%.1f %s", value, unit); } else - set_label(info_stat_w.water_temp, ""); + set_label(single_w.water_temp, ""); value = get_volume_units(dive->sac, &decimals, &unit); if (value > 0) { - set_label(info_stat_w.sac, "%.*f %s/min", decimals, value, unit); + set_label(single_w.sac, "%.*f %s/min", decimals, value, unit); } else - set_label(info_stat_w.sac, ""); - set_label(info_stat_w.otu, "%d", dive->otu); + set_label(single_w.sac, ""); + set_label(single_w.otu, "%d", dive->otu); offset = 0; gas_used = 0; buf[0] = '\0'; @@ -171,7 +240,7 @@ void show_dive_stats(struct dive *dive) end = cyl->end.mbar ? : cyl->sample_end.mbar; if (!cylinder_none(cyl)) { /* 0% O2 strangely means air, so 21% - I don't like that at all */ - int o2 = cyl->gasmix.o2.permille ? : 209; + int o2 = cyl->gasmix.o2.permille ? : AIR_PERMILLE; if (offset > 0) { snprintf(buf+offset, 80-offset, ", "); offset += 2; @@ -185,25 +254,61 @@ void show_dive_stats(struct dive *dive) if (cyl->type.size.mliter && start && end) gas_used += cyl->type.size.mliter / 1000.0 * (start - end); } - set_label(info_stat_w.o2he, buf); + set_label(single_w.o2he, buf); if (gas_used) { value = get_volume_units(gas_used, &decimals, &unit); - set_label(info_stat_w.gas_used, "%.*f %s", decimals, value, unit); + set_label(single_w.gas_used, "%.*f %s", decimals, value, unit); } else - set_label(info_stat_w.gas_used, ""); - /* and now do the statistics */ - set_label(info_stat_w.total_time, get_time_string(info_stat.total_time.seconds, 0)); - set_label(info_stat_w.avg_time, get_time_string(info_stat.total_time.seconds / dive_table.nr, 0)); - value = get_depth_units(info_stat.max_depth.mm, &decimals, &unit); - set_label(info_stat_w.max_overall_depth, "%.*f %s", decimals, value, unit); - value = get_depth_units(info_stat.avg_depth.mm, &decimals, &unit); - set_label(info_stat_w.avg_overall_depth, "%.*f %s", decimals, value, unit); - value = get_volume_units(info_stat.max_sac.mliter, &decimals, &unit); - set_label(info_stat_w.max_sac, "%.*f %s/min", decimals, value, unit); - value = get_volume_units(info_stat.min_sac.mliter, &decimals, &unit); - set_label(info_stat_w.min_sac, "%.*f %s/min", decimals, value, unit); - value = get_volume_units(info_stat.avg_sac.mliter, &decimals, &unit); - set_label(info_stat_w.avg_sac, "%.*f %s/min", decimals, value, unit); + set_label(single_w.gas_used, ""); +} + +static void show_total_dive_stats(struct dive *dive) +{ + double value; + int decimals; + const char *unit; + stats_t *stats_ptr; + + if (amount_selected < 2) + stats_ptr = &stats; + else + stats_ptr = &stats_selection; + + set_label(stats_w.selection_size, "%d", stats_ptr->selection_size); + if (stats_ptr->min_temp) { + value = get_temp_units(stats_ptr->min_temp, &unit); + set_label(stats_w.min_temp, "%.1f %s", value, unit); + } + if (stats_ptr->combined_temp && stats_ptr->combined_count) + set_label(stats_w.avg_temp, "%.1f %s", stats_ptr->combined_temp / (stats_ptr->combined_count * 1.0), unit); + if (stats_ptr->max_temp) { + value = get_temp_units(stats_ptr->max_temp, &unit); + set_label(stats_w.max_temp, "%.1f %s", value, unit); + } + set_label(stats_w.total_time, get_time_string(stats_ptr->total_time.seconds, 0)); + set_label(stats_w.avg_time, get_time_string(stats_ptr->total_time.seconds / stats_ptr->selection_size, 0)); + set_label(stats_w.longest_time, get_time_string(stats_ptr->longest_time.seconds, 0)); + set_label(stats_w.shortest_time, get_time_string(stats_ptr->shortest_time.seconds, 0)); + value = get_depth_units(stats_ptr->max_depth.mm, &decimals, &unit); + set_label(stats_w.max_overall_depth, "%.*f %s", decimals, value, unit); + value = get_depth_units(stats_ptr->min_depth.mm, &decimals, &unit); + set_label(stats_w.min_overall_depth, "%.*f %s", decimals, value, unit); + value = get_depth_units(stats_ptr->avg_depth.mm, &decimals, &unit); + set_label(stats_w.avg_overall_depth, "%.*f %s", decimals, value, unit); + value = get_volume_units(stats_ptr->max_sac.mliter, &decimals, &unit); + set_label(stats_w.max_sac, "%.*f %s/min", decimals, value, unit); + value = get_volume_units(stats_ptr->min_sac.mliter, &decimals, &unit); + set_label(stats_w.min_sac, "%.*f %s/min", decimals, value, unit); + value = get_volume_units(stats_ptr->avg_sac.mliter, &decimals, &unit); + set_label(stats_w.avg_sac, "%.*f %s/min", decimals, value, unit); +} + +void show_dive_stats(struct dive *dive) +{ + /* they have to be called in this order, as 'total' depends on + * calculations done in 'single' */ + show_single_dive_stats(dive); + show_total_dive_stats(dive); } void flush_dive_stats_changes(struct dive *dive) @@ -224,64 +329,88 @@ static GtkWidget *new_info_label_in_frame(GtkWidget *box, const char *label) return label_widget; } -GtkWidget *stats_widget(void) +GtkWidget *total_stats_widget(void) { - - GtkWidget *vbox, *hbox, *infoframe, *statsframe, *framebox; + GtkWidget *vbox, *hbox, *statsframe, *framebox; vbox = gtk_vbox_new(FALSE, 3); - infoframe = gtk_frame_new("Dive Info"); - gtk_box_pack_start(GTK_BOX(vbox), infoframe, TRUE, FALSE, 3); + statsframe = gtk_frame_new("Statistics"); + gtk_box_pack_start(GTK_BOX(vbox), statsframe, TRUE, FALSE, 3); framebox = gtk_vbox_new(FALSE, 3); - gtk_container_add(GTK_CONTAINER(infoframe), framebox); + gtk_container_add(GTK_CONTAINER(statsframe), framebox); /* first row */ hbox = gtk_hbox_new(FALSE, 3); gtk_box_pack_start(GTK_BOX(framebox), hbox, TRUE, FALSE, 3); - - info_stat_w.date = new_info_label_in_frame(hbox, "Date"); - info_stat_w.dive_time = new_info_label_in_frame(hbox, "Dive Time"); - info_stat_w.surf_intv = new_info_label_in_frame(hbox, "Surf Intv"); + stats_w.selection_size = new_info_label_in_frame(hbox, "Dives"); + stats_w.max_temp = new_info_label_in_frame(hbox, "Max Temp"); + stats_w.min_temp = new_info_label_in_frame(hbox, "Min Temp"); + stats_w.avg_temp = new_info_label_in_frame(hbox, "Avg Temp"); /* second row */ hbox = gtk_hbox_new(FALSE, 3); gtk_box_pack_start(GTK_BOX(framebox), hbox, TRUE, FALSE, 3); - info_stat_w.max_depth = new_info_label_in_frame(hbox, "Max Depth"); - info_stat_w.avg_depth = new_info_label_in_frame(hbox, "Avg Depth"); - info_stat_w.water_temp = new_info_label_in_frame(hbox, "Water Temp"); + stats_w.total_time = new_info_label_in_frame(hbox, "Total Time"); + stats_w.avg_time = new_info_label_in_frame(hbox, "Avg Time"); + stats_w.longest_time = new_info_label_in_frame(hbox, "Longest Dive"); + stats_w.shortest_time = new_info_label_in_frame(hbox, "Shortest Dive"); /* third row */ hbox = gtk_hbox_new(FALSE, 3); gtk_box_pack_start(GTK_BOX(framebox), hbox, TRUE, FALSE, 3); - info_stat_w.sac = new_info_label_in_frame(hbox, "SAC"); - info_stat_w.otu = new_info_label_in_frame(hbox, "OTU"); - info_stat_w.o2he = new_info_label_in_frame(hbox, "O" UTF8_SUBSCRIPT_2 " / He"); - info_stat_w.gas_used = new_info_label_in_frame(hbox, "Gas Used"); + stats_w.max_overall_depth = new_info_label_in_frame(hbox, "Max Depth"); + stats_w.min_overall_depth = new_info_label_in_frame(hbox, "Min Depth"); + stats_w.avg_overall_depth = new_info_label_in_frame(hbox, "Avg Depth"); - statsframe = gtk_frame_new("Statistics"); - gtk_box_pack_start(GTK_BOX(vbox), statsframe, TRUE, FALSE, 3); + /* fourth row */ + hbox = gtk_hbox_new(FALSE, 3); + gtk_box_pack_start(GTK_BOX(framebox), hbox, TRUE, FALSE, 3); + + stats_w.max_sac = new_info_label_in_frame(hbox, "Max SAC"); + stats_w.min_sac = new_info_label_in_frame(hbox, "Min SAC"); + stats_w.avg_sac = new_info_label_in_frame(hbox, "Avg SAC"); + + return vbox; +} + +GtkWidget *single_stats_widget(void) +{ + GtkWidget *vbox, *hbox, *infoframe, *framebox; + + vbox = gtk_vbox_new(FALSE, 3); + + infoframe = gtk_frame_new("Dive Info"); + gtk_box_pack_start(GTK_BOX(vbox), infoframe, TRUE, FALSE, 3); framebox = gtk_vbox_new(FALSE, 3); - gtk_container_add(GTK_CONTAINER(statsframe), framebox); + gtk_container_add(GTK_CONTAINER(infoframe), framebox); /* first row */ hbox = gtk_hbox_new(FALSE, 3); gtk_box_pack_start(GTK_BOX(framebox), hbox, TRUE, FALSE, 3); - info_stat_w.total_time = new_info_label_in_frame(hbox, "Total Time"); - info_stat_w.avg_time = new_info_label_in_frame(hbox, "Avg Time"); - info_stat_w.max_overall_depth = new_info_label_in_frame(hbox, "Max Depth"); - info_stat_w.avg_overall_depth = new_info_label_in_frame(hbox, "Avg Depth"); + single_w.date = new_info_label_in_frame(hbox, "Date"); + single_w.dive_time = new_info_label_in_frame(hbox, "Dive Time"); + single_w.surf_intv = new_info_label_in_frame(hbox, "Surf Intv"); /* second row */ hbox = gtk_hbox_new(FALSE, 3); gtk_box_pack_start(GTK_BOX(framebox), hbox, TRUE, FALSE, 3); - info_stat_w.max_sac = new_info_label_in_frame(hbox, "Max SAC"); - info_stat_w.min_sac = new_info_label_in_frame(hbox, "Min SAC"); - info_stat_w.avg_sac = new_info_label_in_frame(hbox, "Avg SAC"); + single_w.max_depth = new_info_label_in_frame(hbox, "Max Depth"); + single_w.avg_depth = new_info_label_in_frame(hbox, "Avg Depth"); + single_w.water_temp = new_info_label_in_frame(hbox, "Water Temp"); + + /* third row */ + hbox = gtk_hbox_new(FALSE, 3); + gtk_box_pack_start(GTK_BOX(framebox), hbox, TRUE, FALSE, 3); + + single_w.sac = new_info_label_in_frame(hbox, "SAC"); + single_w.otu = new_info_label_in_frame(hbox, "OTU"); + single_w.o2he = new_info_label_in_frame(hbox, "O" UTF8_SUBSCRIPT_2 " / He"); + single_w.gas_used = new_info_label_in_frame(hbox, "Gas Used"); return vbox; } @@ -231,7 +231,7 @@ void uemis_parse_divelog_binary(char *base64, void *datap) { sample->cylinderindex = u_sample->active_tank; sample->cylinderpressure.mbar= u_sample->tank_pressure * 10; uemis_event(dive, sample, u_sample); - finish_sample(dive, sample); + finish_sample(dive); i += 0x25; u_sample++; } @@ -2,6 +2,7 @@ /* implements Windows specific functions */ #include "display-gtk.h" #include <windows.h> +#define DIVELIST_DEFAULT_FONT "Sans 8" static HKEY hkey; @@ -83,3 +84,16 @@ const char *subsurface_USB_name() { return "COM3"; } + +const char *subsurface_icon_name() +{ + return "subsurface.ico"; +} + +void subsurface_ui_setup(GtkSettings *settings, GtkWidget *menubar, + GtkWidget *vbox, GtkUIManager *ui_manager) +{ + if (!divelist_font) + divelist_font = DIVELIST_DEFAULT_FONT; + gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, FALSE, 0); +} diff --git a/xslt/jdivelog2subsurface.xslt b/xslt/jdivelog2subsurface.xslt index 73b4590fb..e2f40c560 100644 --- a/xslt/jdivelog2subsurface.xslt +++ b/xslt/jdivelog2subsurface.xslt @@ -66,13 +66,10 @@ <notes> <xsl:if test="DiveActivity != ''"> - <xsl:value-of select="DiveActivity"/> +Diveactivity: <xsl:value-of select="DiveActivity"/> </xsl:if> - <xsl:if test="Comment != ''"> - <xsl:if test="DiveActivity != ''"> - <xsl:value-of select="': '"/> - </xsl:if> - <xsl:value-of select="Comment"/> + <xsl:if test="DiveType != ''"> +Divetype: <xsl:value-of select="DiveType"/> </xsl:if> <xsl:if test="Equipment/Visibility != ''"> Visibility: <xsl:value-of select="Equipment/Visibility"/> @@ -86,41 +83,51 @@ Gloves: <xsl:value-of select="Equipment/Gloves"/> <xsl:if test="Equipment/Weight != ''"> Weight: <xsl:value-of select="Equipment/Weight"/> </xsl:if> + <xsl:if test="Comment != ''"> +Comment: <xsl:value-of select="Comment"/> + </xsl:if> </notes> <!-- cylinder --> - <xsl:variable name="o2"> - <xsl:choose> - <xsl:when test="DIVE/GASES/MIX/O2 != ''"> - <xsl:value-of select="concat(DIVE/GASES/MIX/O2*100, '%')"/> - </xsl:when> - <xsl:otherwise>21.0%</xsl:otherwise> - </xsl:choose> - </xsl:variable> - <xsl:variable name="size"> - <xsl:choose> - <xsl:when test="Equipment/Tanks/Tank/MIX/TANK/TANKVOLUME != ''"> - <xsl:value-of select="concat(Equipment/Tanks/Tank/MIX/TANK/TANKVOLUME * 1000, ' l')"/> - </xsl:when> - <xsl:otherwise>0 l</xsl:otherwise> - </xsl:choose> - </xsl:variable> - <xsl:variable name="start"> - <xsl:variable name="number" select="Equipment/Tanks/Tank/MIX/TANK/PSTART"/> - <xsl:call-template name="pressure"> - <xsl:with-param name="number" select="$number"/> - <xsl:with-param name="units" select="$units"/> - </xsl:call-template> - </xsl:variable> - <xsl:variable name="end"> - <xsl:variable name="number" select="Equipment/Tanks/Tank/MIX/TANK/PEND"/> - <xsl:call-template name="pressure"> - <xsl:with-param name="number" select="$number"/> - <xsl:with-param name="units" select="$units"/> - </xsl:call-template> - </xsl:variable> - - <cylinder o2="{$o2}" size="{$size}" start="{$start}" end="{$end}"/> + <xsl:for-each select="Equipment/Tanks/Tank"> + <cylinder> + <xsl:attribute name="o2"> + <xsl:choose> + <xsl:when test="MIX/O2 != ''"> + <xsl:value-of select="concat(MIX/O2*100, '%')"/> + </xsl:when> + <xsl:otherwise>21.0%</xsl:otherwise> + </xsl:choose> + </xsl:attribute> + <xsl:if test="MIX/HE != '0.0'"> + <xsl:attribute name="he"> + <xsl:value-of select="concat(MIX/HE*100, '%')"/> + </xsl:attribute> + </xsl:if> + <xsl:attribute name="size"> + <xsl:choose> + <xsl:when test="MIX/TANK/TANKVOLUME != ''"> + <xsl:value-of select="concat(MIX/TANK/TANKVOLUME * 1000, ' l')"/> + </xsl:when> + <xsl:otherwise>0 l</xsl:otherwise> + </xsl:choose> + </xsl:attribute> + <xsl:attribute name="start"> + <xsl:variable name="number" select="MIX/TANK/PSTART"/> + <xsl:call-template name="pressure"> + <xsl:with-param name="number" select="$number"/> + <xsl:with-param name="units" select="$units"/> + </xsl:call-template> + </xsl:attribute> + <xsl:attribute name="end"> + <xsl:variable name="number" select="MIX/TANK/PEND"/> + <xsl:call-template name="pressure"> + <xsl:with-param name="number" select="$number"/> + <xsl:with-param name="units" select="$units"/> + </xsl:call-template> + </xsl:attribute> + </cylinder> + </xsl:for-each> <!-- end cylinder --> <!-- DELTA is the sample interval --> @@ -156,6 +163,22 @@ Weight: <xsl:value-of select="Equipment/Weight"/> </xsl:for-each> <!-- end events --> + <!-- gas change --> + <xsl:for-each select="DIVE/SAMPLES/SWITCH"> + <event name="gaschange"> + <xsl:attribute name="time"> + <xsl:call-template name="timeConvert"> + <xsl:with-param name="timeSec" select="count(preceding-sibling::D) * $delta"/> + <xsl:with-param name="units" select="'si'"/> + </xsl:call-template> + </xsl:attribute> + <xsl:attribute name="value"> + <xsl:value-of select="ancestor::DIVE/GASES/MIX[MIXNAME=current()]/O2 * 100" /> + </xsl:attribute> + </event> + </xsl:for-each> + <!-- end gas change --> + <!-- dive sample - all the depth and temp readings --> <xsl:for-each select="DIVE/SAMPLES/D"> <xsl:variable name="timeSec" select="(position() - 1) * $delta"/> |