diff --git a/.gitignore b/.gitignore index 814241112..3f2085f8b 100644 --- a/.gitignore +++ b/.gitignore @@ -3,12 +3,16 @@ *.rej *.exe *.dmg +*.moc +*.moc.cpp *.patch +*.ui.h *.xml version.h !dives/*.xml *~ po/*.mo +qt-ui/ui_*.h /subsurface /*.tar.gz .dep/ @@ -19,3 +23,10 @@ Documentation/user-manual.pdf Documentation/user-manual.text packaging/windows/subsurface.nsi packaging/macos/Info.plist +config.cache +*.qrc.cpp +/subsurface.config +/subsurface.creator +/subsurface.creator.user +/subsurface.files +/subsurface.includes diff --git a/Configure.mk b/Configure.mk new file mode 100644 index 000000000..3d5e5c978 --- /dev/null +++ b/Configure.mk @@ -0,0 +1,195 @@ +# -*- Makefile -*- +# This file contains the detection rules +all: + +PKGCONFIG=pkg-config +XML2CONFIG=xml2-config +XSLCONFIG=xslt-config +QMAKE=qmake +MOC=moc +UIC=uic + +CONFIGFILE = config.cache +ifeq ($(CONFIGURING),1) + +# Detect the target system +# Ask the compiler what OS it's producing files for +UNAME := $(shell $(CC) -dumpmachine 2>&1 | grep -E -o "linux|darwin|win|gnu|kfreebsd") + +# find libdivecomputer +# First deal with the cross compile environment and with Mac. +# For the native case, Linus doesn't want to trust pkg-config given +# how young libdivecomputer still is - so we check the typical +# subdirectories of /usr/local and /usr and then we give up. You can +# override by simply setting it here +# +ifeq ($(CC), i686-w64-mingw32-gcc) +# ok, we are cross building for Windows + LIBDIVECOMPUTERINCLUDES = $(shell $(PKGCONFIG) --cflags libdivecomputer) + LIBDIVECOMPUTERARCHIVE = $(shell $(PKGCONFIG) --libs libdivecomputer) + RESFILE = packaging/windows/subsurface.res + LDFLAGS += -Wl,-subsystem,windows + LIBWINSOCK = -lwsock32 +else ifeq ($(UNAME), darwin) + 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) +libdc-usr := $(wildcard /usr/lib/libdivecomputer.a) +libdc-usr64 := $(wildcard /usr/lib64/libdivecomputer.a) + +ifneq ($(LIBDCDEVEL),) + LIBDIVECOMPUTERDIR = ../libdivecomputer + LIBDIVECOMPUTERINCLUDES = -I$(LIBDIVECOMPUTERDIR)/include + LIBDIVECOMPUTERARCHIVE = $(LIBDIVECOMPUTERDIR)/src/.libs/libdivecomputer.a +else ifneq ($(strip $(libdc-local)),) + LIBDIVECOMPUTERDIR = /usr/local + LIBDIVECOMPUTERINCLUDES = -I$(LIBDIVECOMPUTERDIR)/include + LIBDIVECOMPUTERARCHIVE = $(LIBDIVECOMPUTERDIR)/lib/libdivecomputer.a +else ifneq ($(strip $(libdc-local64)),) + LIBDIVECOMPUTERDIR = /usr/local + LIBDIVECOMPUTERINCLUDES = -I$(LIBDIVECOMPUTERDIR)/include + LIBDIVECOMPUTERARCHIVE = $(LIBDIVECOMPUTERDIR)/lib64/libdivecomputer.a +else ifneq ($(strip $(libdc-usr)),) + LIBDIVECOMPUTERDIR = /usr + LIBDIVECOMPUTERINCLUDES = -I$(LIBDIVECOMPUTERDIR)/include + LIBDIVECOMPUTERARCHIVE = $(LIBDIVECOMPUTERDIR)/lib/libdivecomputer.a +else ifneq ($(strip $(libdc-usr64)),) + LIBDIVECOMPUTERDIR = /usr + LIBDIVECOMPUTERINCLUDES = -I$(LIBDIVECOMPUTERDIR)/include + LIBDIVECOMPUTERARCHIVE = $(LIBDIVECOMPUTERDIR)/lib64/libdivecomputer.a +else +$(error Cannot find libdivecomputer - please edit Makefile) +endif +endif + +# Libusb-1.0 is only required if libdivecomputer was built with it. +# And libdivecomputer is only built with it if libusb-1.0 is +# installed. So get libusb if it exists, but don't complain +# about it if it doesn't. +LIBUSB = $(shell $(PKGCONFIG) --libs libusb-1.0 2> /dev/null) + +# Find qmake. Rules are: +# - use qmake if it is in $PATH +# [qmake -query QT_VERSION will fail if it's Qt 3's qmake] +# - if that fails, try qmake-qt4 +# - if that fails, print an error +# We specifically do not search for qmake-qt5 since that is not supposed +# to exist. +QMAKE = $(shell { qmake -query QT_VERSION >/dev/null 2>&1 && echo qmake; } || \ + { qmake-qt4 -v >/dev/null 2>&1 && echo qmake-qt4; }) +ifeq ($(strip $(QMAKE)),) +$(error Could not find qmake or qmake-qt4 in $$PATH or they failed) +endif + +# Use qmake to find out which Qt version we are building for. +QT_VERSION_MAJOR = $(shell $(QMAKE) -query QT_VERSION | cut -d. -f1) +ifeq ($(QT_VERSION_MAJOR), 5) +# QT_MODULES = Qt5Widgets Qt5Svg +# QT_CORE = Qt5Core +# QTBINDIR = $(shell $(QMAKE) -query QT_HOST_BINS) +# # Tool paths are not stored in .pc files in Qt 5.0 +# MOC = $(QTBINDIR)/moc +# UIC = $(QTBINDIR)/uic +# RCC = $(QTBINDIR)/rcc +# if qmake is qt5, try to get the qt4 one. + QMAKE = { qmake-qt4 -v >/dev/null 2>&1 && echo qmake-qt4; } +#else +endif + +ifeq ($(strip $(QMAKE)),) +$(error Could not find qmake or qmake-qt4 in $$PATH for the Qt4 version they failed) +endif + + QT_MODULES = QtGui QtSvg + QT_CORE = QtCore + MOC = $(shell $(PKGCONFIG) --variable=moc_location QtCore) + UIC = $(shell $(PKGCONFIG) --variable=uic_location QtGui) + RCC = $(shell $(PKGCONFIG) --variable=rcc_location QtGui) +#endif + +# we need GLIB2CFLAGS for gettext +QTCXXFLAGS = $(shell $(PKGCONFIG) --cflags $(QT_MODULES)) $(GLIB2CFLAGS) +LIBQT = $(shell $(PKGCONFIG) --libs $(QT_MODULES)) +ifneq ($(filter reduce_relocations, $(shell $(PKGCONFIG) --variable qt_config $(QT_CORE))), ) + QTCXXFLAGS += -fPIE +endif + +LIBGTK = $(shell $(PKGCONFIG) --libs gtk+-2.0 glib-2.0) +ifneq (,$(filter $(UNAME),linux kfreebsd gnu)) + LIBGCONF2 = $(shell $(PKGCONFIG) --libs gconf-2.0) + GCONF2CFLAGS = $(shell $(PKGCONFIG) --cflags gconf-2.0) +else ifeq ($(UNAME), darwin) + LIBGTK += $(shell $(PKGCONFIG) --libs gtk-mac-integration) -framework CoreFoundation -framework CoreServices + GTKCFLAGS += $(shell $(PKGCONFIG) --cflags gtk-mac-integration) + GTK_MAC_BUNDLER = ~/.local/bin/gtk-mac-bundler +endif + +LIBDIVECOMPUTERCFLAGS = $(LIBDIVECOMPUTERINCLUDES) +LIBDIVECOMPUTER = $(LIBDIVECOMPUTERARCHIVE) $(LIBUSB) + +LIBXML2 = $(shell $(XML2CONFIG) --libs) +LIBXSLT = $(shell $(XSLCONFIG) --libs) +XML2CFLAGS = $(shell $(XML2CONFIG) --cflags) +GLIB2CFLAGS = $(shell $(PKGCONFIG) --cflags glib-2.0) +GTKCFLAGS += $(shell $(PKGCONFIG) --cflags gtk+-2.0) +XSLCFLAGS = $(shell $(XSLCONFIG) --cflags) +OSMGPSMAPFLAGS += $(shell $(PKGCONFIG) --cflags osmgpsmap 2> /dev/null) +LIBOSMGPSMAP += $(shell $(PKGCONFIG) --libs osmgpsmap 2> /dev/null) +LIBSOUPCFLAGS = $(shell $(PKGCONFIG) --cflags libsoup-2.4) +LIBSOUP = $(shell $(PKGCONFIG) --libs libsoup-2.4) + +LIBZIP = $(shell $(PKGCONFIG) --libs libzip 2> /dev/null) +ZIPFLAGS = $(strip $(shell $(PKGCONFIG) --cflags libzip 2> /dev/null)) + +LIBSQLITE3 = $(shell $(PKGCONFIG) --libs sqlite3 2> /dev/null) +SQLITE3FLAGS = $(strip $(shell $(PKGCONFIG) --cflags sqlite3)) + +# Write the configure file +all: configure +configure $(CONFIGURE): Configure.mk + @echo "\ + CONFIGURED = 1\\\ + UNAME = $(UNAME)\\\ + LIBDIVECOMPUTERDIR = $(LIBDIVECOMPUTERDIR)\\\ + LIBDIVECOMPUTERCFLAGS = $(LIBDIVECOMPUTERCFLAGS)\\\ + LIBDIVECOMPUTER = $(LIBDIVECOMPUTER)\\\ + LIBWINSOCK = $(LIBWINSOCK)\\\ + LDFLAGS = $(LDFLAGS)\\\ + RESFILE = $(RESFILE)\\\ + LIBQT = $(LIBQT)\\\ + QTCXXFLAGS = $(QTCXXFLAGS)\\\ + MOC = $(MOC)\\\ + UIC = $(UIC)\\\ + RCC = $(RCC)\\\ + LIBGTK = $(LIBGTK)\\\ + GTKCFLAGS = $(GTKCFLAGS)\\\ + LIBGCONF2 = $(LIBGCONF2)\\\ + GCONF2CFLAGS = $(GCONF2CFLAGS)\\\ + GTK_MAC_BUNDLER = $(GTK_MAC_BUNDLER)\\\ + LIBXML2 = $(LIBXML2)\\\ + LIBXSLT = $(LIBXSLT)\\\ + XML2CFLAGS = $(XML2CFLAGS)\\\ + GLIB2CFLAGS = $(GLIB2CFLAGS)\\\ + XSLCFLAGS = $(XSLCFLAGS)\\\ + OSMGPSMAPFLAGS = $(OSMGPSMAPFLAGS)\\\ + LIBOSMGPSMAP = $(LIBOSMGPSMAP)\\\ + LIBSOUPCFLAGS = $(LIBSOUPCFLAGS)\\\ + LIBSOUP = $(LIBSOUP)\\\ + LIBZIP = $(LIBZIP)\\\ + ZIPFLAGS = $(ZIPFLAGS)\\\ + LIBSQLITE3 = $(LIBSQLITE3)\\\ + SQLITE3FLAGS = $(SQLITE3FLAGS)\\\ + " | tr '\\' '\n' > $(CONFIGFILE) + +else +configure $(CONFIGFILE): Configure.mk + @test -e $(CONFIGFILE) && echo Reconfiguring.. || echo Configuring... + @$(MAKE) CONFIGURING=1 configure + @echo Done + +-include $(CONFIGFILE) +endif + +.PHONY: configure all diff --git a/Makefile b/Makefile index e0689548d..0e15becd7 100644 --- a/Makefile +++ b/Makefile @@ -2,14 +2,14 @@ NAME = subsurface CAPITALIZED_NAME = Subsurface TARGET = $(NAME) +include Configure.mk VERSION=3.1 CC=gcc CFLAGS=-Wall -Wno-pointer-sign -g $(CLCFLAGS) -DGSEAL_ENABLE +CXX=g++ +CXXFLAGS=-Wall -g $(CLCXXFLAGS) -DQT_NO_KEYWORDS INSTALL=install -PKGCONFIG=pkg-config -XML2CONFIG=xml2-config -XSLCONFIG=xslt-config # these locations seem to work for SuSE and Fedora # prefix = $(HOME) @@ -28,141 +28,86 @@ DESKTOPFILE = $(NAME).desktop MANFILES = $(NAME).1 XSLTFILES = xslt/*.xslt xslt/*.xsl -VERSION_FILE = version.h -# There's only one line in $(VERSION_FILE); use the shell builtin `read' -STORED_VERSION_STRING = \ - $(subst ",,$(shell [ ! -r $(VERSION_FILE) ] || \ - read ignore ignore v <$(VERSION_FILE) && echo $$v)) -#" workaround editor syntax highlighting quirk +EXTRA_FLAGS = $(QTCXXFLAGS) $(GTKCFLAGS) $(GLIB2CFLAGS) $(XML2CFLAGS) \ + $(LIBDIVECOMPUTERCFLAGS) \ + $(LIBSOUPCFLAGS) $(GCONF2CFLAGS) -UNAME := $(shell $(CC) -dumpmachine 2>&1 | grep -E -o "linux|darwin|win|gnu|kfreebsd") -GET_VERSION = ./scripts/get-version -VERSION_STRING := $(shell [ -d .git ] && $(GET_VERSION) linux || \ - echo "v$(VERSION)") -# Mac Info.plist style with three numbers 1.2.3 -CFBUNDLEVERSION_STRING := $(shell [ -d .git ] && \ - $(GET_VERSION) darwin $(VERSION_STRING) || \ - echo "$(VERSION).0") -# Windows .nsi style with four numbers 1.2.3.4 -PRODVERSION_STRING := $(shell [ -d .git ] && \ - $(GET_VERSION) win $(VERSION_STRING) || \ - echo "$(VERSION).0.0") +HEADERS = \ + qt-ui/addcylinderdialog.h \ + qt-ui/addweightsystemdialog.h \ + qt-ui/divelistview.h \ + qt-ui/maintab.h \ + qt-ui/mainwindow.h \ + qt-ui/models.h \ + qt-ui/plotareascene.h \ + qt-ui/starwidget.h \ + qt-ui/modeldelegates.h \ + qt-ui/profilegraphics.h \ + qt-ui/globe.h -# 'pretty' output (easy to spot warnings) by default -# 'verbose' output (all the details) by calling with "make V=1" -ifeq ($(V),1) - PRETTYECHO=true - COMPILE_PREFIX= -else - PRETTYECHO=echo - COMPILE_PREFIX=@ + +SOURCES = \ + deco.c \ + device.c \ + dive.c \ + divelist.c \ + download-dialog.c \ + equipment.c \ + file.c \ + info.c \ + main.c \ + parse-xml.c \ + prefs.c \ + profile.c \ + save-xml.c \ + sha1.c \ + statistics.c \ + time.c \ + qt-gui.cpp \ + qt-ui/addcylinderdialog.cpp \ + qt-ui/addweightsystemdialog.cpp \ + qt-ui/divelistview.cpp \ + qt-ui/maintab.cpp \ + qt-ui/mainwindow.cpp \ + qt-ui/models.cpp \ + qt-ui/plotareascene.cpp \ + qt-ui/starwidget.cpp \ + qt-ui/modeldelegates.cpp \ + qt-ui/profilegraphics.cpp \ + qt-ui/globe.cpp \ + $(RESFILE) + + +RESOURCES = $(NAME).qrc + +ifneq ($(SQLITE3FLAGS),) + EXTRA_FLAGS += -DSQLITE3 $(SQLITE3FLAGS) endif - -# find libdivecomputer -# First deal with the cross compile environment and with Mac. -# For the native case, Linus doesn't want to trust pkg-config given -# how young libdivecomputer still is - so we check the typical -# subdirectories of /usr/local and /usr and then we give up. You can -# override by simply setting it here -# -ifeq ($(CC), i686-w64-mingw32-gcc) -# ok, we are cross building for Windows - LIBDIVECOMPUTERINCLUDES = $(shell $(PKGCONFIG) --cflags libdivecomputer) - LIBDIVECOMPUTERARCHIVE = $(shell $(PKGCONFIG) --libs libdivecomputer) - LIBGNUTLS = $(shell $(PKGCONFIG) --libs gnutls-extra) $(shell $(PKGCONFIG) --libs p11-kit-1) - RESFILE = packaging/windows/$(NAME).res - LDFLAGS += -Wl,-subsystem,windows - LIBWINSOCK = -lwsock32 - TARGET = $(NAME).exe -else ifeq ($(UNAME), darwin) - 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) -libdc-usr := $(wildcard /usr/lib/libdivecomputer.a) -libdc-usr64 := $(wildcard /usr/lib64/libdivecomputer.a) - -ifneq ($(LIBDCDEVEL),) - LIBDIVECOMPUTERDIR = ../libdivecomputer - LIBDIVECOMPUTERINCLUDES = -I$(LIBDIVECOMPUTERDIR)/include - LIBDIVECOMPUTERARCHIVE = $(LIBDIVECOMPUTERDIR)/src/.libs/libdivecomputer.a -else ifneq ($(strip $(libdc-local)),) - LIBDIVECOMPUTERDIR = /usr/local - LIBDIVECOMPUTERINCLUDES = -I$(LIBDIVECOMPUTERDIR)/include - LIBDIVECOMPUTERARCHIVE = $(LIBDIVECOMPUTERDIR)/lib/libdivecomputer.a -else ifneq ($(strip $(libdc-local64)),) - LIBDIVECOMPUTERDIR = /usr/local - LIBDIVECOMPUTERINCLUDES = -I$(LIBDIVECOMPUTERDIR)/include - LIBDIVECOMPUTERARCHIVE = $(LIBDIVECOMPUTERDIR)/lib64/libdivecomputer.a -else ifneq ($(strip $(libdc-usr)),) - LIBDIVECOMPUTERDIR = /usr - LIBDIVECOMPUTERINCLUDES = -I$(LIBDIVECOMPUTERDIR)/include - LIBDIVECOMPUTERARCHIVE = $(LIBDIVECOMPUTERDIR)/lib/libdivecomputer.a -else ifneq ($(strip $(libdc-usr64)),) - LIBDIVECOMPUTERDIR = /usr - LIBDIVECOMPUTERINCLUDES = -I$(LIBDIVECOMPUTERDIR)/include - LIBDIVECOMPUTERARCHIVE = $(LIBDIVECOMPUTERDIR)/lib64/libdivecomputer.a -else -$(error Cannot find libdivecomputer - please edit Makefile) +ifneq ($(ZIPFLAGS),) + EXTRA_FLAGS += -DLIBZIP $(ZIPFLAGS) endif +ifneq ($(strip $(LIBXSLT)),) + EXTRA_FLAGS += -DXSLT='"$(XSLTDIR)"' $(XSLCFLAGS) endif - -# Libusb-1.0 is only required if libdivecomputer was built with it. -# And libdivecomputer is only built with it if libusb-1.0 is -# installed. So get libusb if it exists, but don't complain -# about it if it doesn't. -LIBUSB = $(shell $(PKGCONFIG) --libs libusb-1.0 2> /dev/null) - -LIBGTK = $(shell $(PKGCONFIG) --libs gtk+-2.0 glib-2.0) -LIBDIVECOMPUTERCFLAGS = $(LIBDIVECOMPUTERINCLUDES) -LIBDIVECOMPUTER = $(LIBDIVECOMPUTERARCHIVE) $(LIBUSB) - -LIBXML2 = $(shell $(XML2CONFIG) --libs) -LIBXSLT = $(shell $(XSLCONFIG) --libs) -XML2CFLAGS = $(shell $(XML2CONFIG) --cflags) -GLIB2CFLAGS = $(shell $(PKGCONFIG) --cflags glib-2.0) -GTKCFLAGS = $(shell $(PKGCONFIG) --cflags gtk+-2.0) -CFLAGS += $(shell $(XSLCONFIG) --cflags) -OSMGPSMAPFLAGS += $(shell $(PKGCONFIG) --cflags osmgpsmap 2> /dev/null) -LIBOSMGPSMAP += $(shell $(PKGCONFIG) --libs osmgpsmap 2> /dev/null) +ifeq ($(USE_GTK_UI),1) ifneq ($(strip $(LIBOSMGPSMAP)),) - GPSOBJ = gps.o - CFLAGS += -DHAVE_OSM_GPS_MAP + SOURCES += gps.c + EXTRA_FLAGS += -DHAVE_OSM_GPS_MAP $(OSMGPSMAPFLAGS) endif -LIBSOUPCFLAGS = $(shell $(PKGCONFIG) --cflags libsoup-2.4) -LIBSOUP = $(shell $(PKGCONFIG) --libs libsoup-2.4) - -LIBZIP = $(shell $(PKGCONFIG) --libs libzip 2> /dev/null) -ifneq ($(strip $(LIBZIP)),) - ZIP = -DLIBZIP $(shell $(PKGCONFIG) --cflags libzip) -endif - -LIBSQLITE3 = $(shell $(PKGCONFIG) --libs sqlite3 2> /dev/null) -ifneq ($(strip $(LIBSQLITE3)),) - SQLITE3 = -DSQLITE3 $(shell $(PKGCONFIG) --cflags sqlite3) endif ifneq (,$(filter $(UNAME),linux kfreebsd gnu)) - LIBGCONF2 = $(shell $(PKGCONFIG) --libs gconf-2.0) - GCONF2CFLAGS = $(shell $(PKGCONFIG) --cflags gconf-2.0) - OSSUPPORT = linux - OSSUPPORT_CFLAGS = $(GTKCFLAGS) $(GCONF2CFLAGS) + SOURCES += linux.c else ifeq ($(UNAME), darwin) - OSSUPPORT = macos - OSSUPPORT_CFLAGS = $(GTKCFLAGS) + SOURCES += macos.c MACOSXINSTALL = /Applications/$(CAPITALIZED_NAME).app MACOSXFILES = packaging/macosx MACOSXSTAGING = $(MACOSXFILES)/$(CAPITALIZED_NAME).app INFOPLIST = $(MACOSXFILES)/Info.plist INFOPLISTINPUT = $(INFOPLIST).in - EXTRALIBS = $(shell $(PKGCONFIG) --libs gtk-mac-integration) -framework CoreFoundation -framework CoreServices - CFLAGS += $(shell $(PKGCONFIG) --cflags gtk-mac-integration) - LDFLAGS += -headerpad_max_install_names -sectcreate __TEXT __info_plist $(INFOPLIST) - GTK_MAC_BUNDLER = ~/.local/bin/gtk-mac-bundler + LDFLAGS += -headerpad_max_install_names else - OSSUPPORT = windows - OSSUPPORT_CFLAGS = $(GTKCFLAGS) + SOURCES += windows.c WINDOWSSTAGING = ./packaging/windows WINMSGDIRS=$(addprefix share/locale/,$(shell ls po/*.po | sed -e 's/po\/\(..\)_.*/\1\/LC_MESSAGES/')) NSIINPUTFILE = $(WINDOWSSTAGING)/$(NAME).nsi.in @@ -171,170 +116,15 @@ else XSLTDIR = .\\xslt endif -ifneq ($(strip $(LIBXSLT)),) - XSLT=-DXSLT='"$(XSLTDIR)"' -endif - -LIBS = $(LIBXML2) $(LIBXSLT) $(LIBSQLITE3) $(LIBGTK) $(LIBGCONF2) $(LIBDIVECOMPUTER) $(EXTRALIBS) $(LIBZIP) -lpthread -lm $(LIBOSMGPSMAP) $(LIBSOUP) $(LIBWINSOCK) $(LIBGNUTLS) +LIBS = $(LIBQT) $(LIBXML2) $(LIBXSLT) $(LIBSQLITE3) $(LIBGCONF2) $(LIBDIVECOMPUTER) \ + $(EXTRALIBS) $(LIBZIP) -lpthread -lm $(LIBOSMGPSMAP) $(LIBSOUP) $(LIBWINSOCK) -lmarblewidget MSGLANGS=$(notdir $(wildcard po/*.po)) -MSGOBJS=$(addprefix share/locale/,$(MSGLANGS:.po=.UTF-8/LC_MESSAGES/$(NAME).mo)) -GTKOBJS = info-gtk.o divelist-gtk.o planner-gtk.o statistics-gtk.o gtk-gui.o +# Add files to the following variables if the auto-detection based on the +# filename fails +OBJS_NEEDING_MOC = +OBJS_NEEDING_UIC = +HEADERS_NEEDING_MOC = -OBJS = main.o dive.o time.o profile.o info.o equipment.o divelist.o deco.o planner.o \ - parse-xml.o save-xml.o libdivecomputer.o print.o uemis.o uemis-downloader.o \ - statistics.o file.o device.o download-dialog.o prefs.o \ - webservice.o sha1.o $(GPSOBJ) $(OSSUPPORT).o $(RESFILE) $(GTKOBJS) - -DEPS = $(wildcard .dep/*.dep) - -all: $(TARGET) - -$(TARGET): gen_version_file $(OBJS) $(MSGOBJS) $(INFOPLIST) - @$(PRETTYECHO) ' LINK' $(TARGET) - $(COMPILE_PREFIX)$(CC) $(LDFLAGS) -o $(TARGET) $(OBJS) $(LIBS) - -gen_version_file: -ifneq ($(STORED_VERSION_STRING),$(VERSION_STRING)) - $(info updating $(VERSION_FILE) to $(VERSION_STRING)) - @echo \#define VERSION_STRING \"$(VERSION_STRING)\" >$(VERSION_FILE) -endif - -install: all - $(INSTALL) -d -m 755 $(BINDIR) - $(INSTALL) $(NAME) $(BINDIR) - $(INSTALL) -d -m 755 $(DESKTOPDIR) - $(INSTALL) $(DESKTOPFILE) $(DESKTOPDIR) - $(INSTALL) -d -m 755 $(ICONDIR) - $(INSTALL) -m 644 $(ICONFILE) $(ICONDIR) - @-if test -z "$(DESTDIR)"; then \ - $(gtk_update_icon_cache); \ - fi - $(INSTALL) -d -m 755 $(MANDIR) - $(INSTALL) -m 644 $(MANFILES) $(MANDIR) - @-if test ! -z "$(XSLT)"; then \ - $(INSTALL) -d -m 755 $(DATADIR)/$(NAME); \ - $(INSTALL) -d -m 755 $(XSLTDIR); \ - $(INSTALL) -m 644 $(XSLTFILES) $(XSLTDIR); \ - fi - for LOC in $(wildcard share/locale/*/LC_MESSAGES); do \ - $(INSTALL) -d $(prefix)/$$LOC; \ - $(INSTALL) -m 644 $$LOC/$(NAME).mo $(prefix)/$$LOC/$(NAME).mo; \ - done - - -install-macosx: all - $(INSTALL) -d -m 755 $(MACOSXINSTALL)/Contents/Resources - $(INSTALL) -d -m 755 $(MACOSXINSTALL)/Contents/MacOS - $(INSTALL) $(NAME) $(MACOSXINSTALL)/Contents/MacOS/$(NAME)-bin - $(INSTALL) $(MACOSXFILES)/$(NAME).sh $(MACOSXINSTALL)/Contents/MacOS/$(NAME) - $(INSTALL) $(MACOSXFILES)/PkgInfo $(MACOSXINSTALL)/Contents/ - $(INSTALL) $(MACOSXFILES)/Info.plist $(MACOSXINSTALL)/Contents/ - $(INSTALL) $(ICONFILE) $(MACOSXINSTALL)/Contents/Resources/ - $(INSTALL) $(MACOSXFILES)/$(CAPITALIZED_NAME).icns $(MACOSXINSTALL)/Contents/Resources/ - for LOC in $(wildcard share/locale/*/LC_MESSAGES); do \ - $(INSTALL) -d -m 755 $(MACOSXINSTALL)/Contents/Resources/$$LOC; \ - $(INSTALL) $$LOC/$(NAME).mo $(MACOSXINSTALL)/Contents/Resources/$$LOC/$(NAME).mo; \ - done - @-if test ! -z "$(XSLT)"; then \ - $(INSTALL) -d -m 755 $(MACOSXINSTALL)/Contents/Resources/xslt; \ - $(INSTALL) -m 644 $(XSLTFILES) $(MACOSXINSTALL)/Contents/Resources/xslt/; \ - fi - - -create-macosx-bundle: all - $(INSTALL) -d -m 755 $(MACOSXSTAGING)/Contents/Resources - $(INSTALL) -d -m 755 $(MACOSXSTAGING)/Contents/MacOS - $(INSTALL) $(NAME) $(MACOSXSTAGING)/Contents/MacOS/ - $(INSTALL) $(MACOSXFILES)/PkgInfo $(MACOSXSTAGING)/Contents/ - $(INSTALL) $(MACOSXFILES)/Info.plist $(MACOSXSTAGING)/Contents/ - $(INSTALL) $(ICONFILE) $(MACOSXSTAGING)/Contents/Resources/ - $(INSTALL) $(MACOSXFILES)/$(CAPITALIZED_NAME).icns $(MACOSXSTAGING)/Contents/Resources/ - for LOC in $(wildcard share/locale/*/LC_MESSAGES); do \ - $(INSTALL) -d -m 755 $(MACOSXSTAGING)/Contents/Resources/$$LOC; \ - $(INSTALL) $$LOC/$(NAME).mo $(MACOSXSTAGING)/Contents/Resources/$$LOC/$(NAME).mo; \ - done - @-if test ! -z "$(XSLT)"; then \ - $(INSTALL) -d -m 755 $(MACOSXSTAGING)/Contents/Resources/xslt; \ - $(INSTALL) -m 644 $(XSLTFILES) $(MACOSXSTAGING)/Contents/Resources/xslt/; \ - fi - $(GTK_MAC_BUNDLER) packaging/macosx/$(NAME).bundle - -sign-macosx-bundle: all - codesign -s "3A8CE62A483083EDEA5581A61E770EC1FA8BECE8" /Applications/$(CAPITALIZED_NAME).app/Contents/MacOS/$(NAME)-bin - -install-cross-windows: all - $(INSTALL) -d -m 755 $(WINDOWSSTAGING)/share/locale - for MSG in $(WINMSGDIRS); do\ - $(INSTALL) -d -m 755 $(WINDOWSSTAGING)/$$MSG;\ - $(INSTALL) $(CROSS_PATH)/$$MSG/* $(WINDOWSSTAGING)/$$MSG;\ - done - for LOC in $(wildcard share/locale/*/LC_MESSAGES); do \ - $(INSTALL) -d -m 755 $(WINDOWSSTAGING)/$$LOC; \ - $(INSTALL) $$LOC/$(NAME).mo $(WINDOWSSTAGING)/$$LOC/$(NAME).mo; \ - done - -create-windows-installer: all $(NSIFILE) install-cross-windows - $(MAKENSIS) $(NSIFILE) - -$(NSIFILE): $(NSIINPUTFILE) - $(shell cat $(NSIINPUTFILE) | sed -e 's/VERSIONTOKEN/$(VERSION_STRING)/;s/PRODVTOKEN/$(PRODVERSION_STRING)/' > $(NSIFILE)) - -$(INFOPLIST): $(INFOPLISTINPUT) - $(shell cat $(INFOPLISTINPUT) | sed -e 's/CFBUNDLEVERSION_TOKEN/$(CFBUNDLEVERSION_STRING)/' > $(INFOPLIST)) - -# Transifex merge the translations -update-po-files: - tx pull -af - -push-pot: - xgettext -o po/$(NAME)-new.pot -s -k_ -kN_ --keyword=C_:1c,2 --add-comments="++GETTEXT" *.c - tx push -s - -EXTRA_FLAGS = $(GTKCFLAGS) $(GLIB2CFLAGS) $(XML2CFLAGS) \ - $(XSLT) $(ZIP) $(SQLITE3) $(LIBDIVECOMPUTERCFLAGS) \ - $(LIBSOUPCFLAGS) $(OSMGPSMAPFLAGS) $(GCONF2CFLAGS) - -%.o: %.c - @$(PRETTYECHO) ' CC' $< - @mkdir -p .dep - $(COMPILE_PREFIX)$(CC) $(CFLAGS) $(EXTRA_FLAGS) -MD -MF .dep/$@.dep -c -o $@ $< - -share/locale/%.UTF-8/LC_MESSAGES/$(NAME).mo: po/%.po po/%.aliases - mkdir -p $(dir $@) - msgfmt -c -o $@ po/$*.po - @-if test -s po/$*.aliases; then \ - for ALIAS in `cat po/$*.aliases`; do \ - mkdir -p share/locale/$$ALIAS/LC_MESSAGES; \ - cp $@ share/locale/$$ALIAS/LC_MESSAGES; \ - done; \ - fi - -satellite.png: satellite.svg - convert -transparent white -resize 11x16 -depth 8 $< $@ - -# This should work, but it doesn't get the colors quite right - so I manually converted with Gimp -# convert -colorspace RGB -transparent white -resize 256x256 $(NAME)-icon.svg $(NAME)-icon.png -# -# The following creates the pixbuf data in .h files with the basename followed by '_pixmap' -# as name of the data structure -%.h: %.png - @$(PRETTYECHO) ' gdk-pixbuf-csource' $< - $(COMPILE_PREFIX)gdk-pixbuf-csource --struct --name `echo $* | sed 's/-/_/g'`_pixbuf $< > $@ - -doc: - $(MAKE) -C Documentation doc - -clean: - rm -f $(OBJS) *~ $(NAME) $(NAME).exe po/*~ po/$(NAME)-new.pot \ - $(VERSION_FILE) - rm -rf share .dep - -release: - @scripts/check-version -cr $(VERSION_STRING) - git archive --prefix $(CAPITALIZED_NAME)-$(VERSION_STRING)/ \ - --output $(CAPITALIZED_NAME)-$(VERSION_STRING).tgz \ - v$(VERSION_STRING) - --include $(DEPS) +include Rules.mk diff --git a/Rules.mk b/Rules.mk new file mode 100644 index 000000000..bf9346a48 --- /dev/null +++ b/Rules.mk @@ -0,0 +1,252 @@ +# -*- Makefile -*- +# Rules for building and creating the version file + +VERSION_FILE = version.h +# There's only one line in $(VERSION_FILE); use the shell builtin `read' +STORED_VERSION_STRING = \ + $(subst ",,$(shell [ ! -r $(VERSION_FILE) ] || \ + read ignore ignore v <$(VERSION_FILE) && echo $$v)) +#" workaround editor syntax highlighting quirk + +GET_VERSION = ./scripts/get-version +VERSION_STRING := $(shell $(GET_VERSION) linux || echo "v$(VERSION)") +# Mac Info.plist style with three numbers 1.2.3 +CFBUNDLEVERSION_STRING := $(shell $(GET_VERSION) darwin $(VERSION_STRING) || \ + echo "$(VERSION).0") +# Windows .nsi style with four numbers 1.2.3.4 +PRODVERSION_STRING := $(shell $(GET_VERSION) win $(VERSION_STRING) || \ + echo "$(VERSION).0.0") + +MSGOBJS=$(addprefix share/locale/,$(MSGLANGS:.po=.UTF-8/LC_MESSAGES/$(NAME).mo)) + +ifeq ($(V),1) + PRETTYECHO=true + COMPILE_PREFIX= +else + PRETTYECHO=echo + COMPILE_PREFIX=@ +endif + +C_SOURCES = $(filter %.c, $(SOURCES)) +CXX_SOURCES = $(filter %.cpp, $(SOURCES)) $(RESOURCES:.qrc=.qrc.cpp) +OTHER_SOURCES = $(filter-out %.c %.cpp, $(SOURCES)) +OBJS = $(C_SOURCES:.c=.o) $(CXX_SOURCES:.cpp=.o) $(OTHER_SOURCES) + +# Add the objects for the header files which define QObject subclasses +HEADERS_NEEDING_MOC += $(shell grep -l -s 'Q_OBJECT' $(HEADERS)) +MOC_OBJS = $(HEADERS_NEEDING_MOC:.h=.moc.o) + +ALL_OBJS = $(OBJS) $(MOC_OBJS) + +# Files for using Qt Creator +CREATOR_FILES = $(NAME).config $(NAME).creator $(NAME).files $(NAME).includes + +all: $(NAME) + +$(TARGET): gen_version_file $(ALL_OBJS) $(MSGOBJS) $(INFOPLIST) + @$(PRETTYECHO) ' LINK' $(TARGET) + $(COMPILE_PREFIX)$(CXX) $(LDFLAGS) -o $(TARGET) $(ALL_OBJS) $(LIBS) + +gen_version_file $(VERSION_FILE): +ifneq ($(STORED_VERSION_STRING),$(VERSION_STRING)) + $(info updating $(VERSION_FILE) to $(VERSION_STRING)) + @echo \#define VERSION_STRING \"$(VERSION_STRING)\" >$(VERSION_FILE) +endif + +install: all + $(INSTALL) -d -m 755 $(BINDIR) + $(INSTALL) $(NAME) $(BINDIR) + $(INSTALL) -d -m 755 $(DESKTOPDIR) + $(INSTALL) $(DESKTOPFILE) $(DESKTOPDIR) + $(INSTALL) -d -m 755 $(ICONDIR) + $(INSTALL) -m 644 $(ICONFILE) $(ICONDIR) + @-if test -z "$(DESTDIR)"; then \ + $(gtk_update_icon_cache); \ + fi + $(INSTALL) -d -m 755 $(MANDIR) + $(INSTALL) -m 644 $(MANFILES) $(MANDIR) + @-if test ! -z "$(XSLT)"; then \ + $(INSTALL) -d -m 755 $(DATADIR)/$(NAME); \ + $(INSTALL) -d -m 755 $(XSLTDIR); \ + $(INSTALL) -m 644 $(XSLTFILES) $(XSLTDIR); \ + fi + for LOC in $(wildcard share/locale/*/LC_MESSAGES); do \ + $(INSTALL) -d $(prefix)/$$LOC; \ + $(INSTALL) -m 644 $$LOC/$(NAME).mo $(prefix)/$$LOC/$(NAME).mo; \ + done + + +install-macosx: all + $(INSTALL) -d -m 755 $(MACOSXINSTALL)/Contents/Resources + $(INSTALL) -d -m 755 $(MACOSXINSTALL)/Contents/MacOS + $(INSTALL) $(NAME) $(MACOSXINSTALL)/Contents/MacOS/$(NAME)-bin + $(INSTALL) $(MACOSXFILES)/$(NAME).sh $(MACOSXINSTALL)/Contents/MacOS/$(NAME) + $(INSTALL) $(MACOSXFILES)/PkgInfo $(MACOSXINSTALL)/Contents/ + $(INSTALL) $(MACOSXFILES)/Info.plist $(MACOSXINSTALL)/Contents/ + $(INSTALL) $(ICONFILE) $(MACOSXINSTALL)/Contents/Resources/ + $(INSTALL) $(MACOSXFILES)/$(CAPITALIZED_NAME).icns $(MACOSXINSTALL)/Contents/Resources/ + for LOC in $(wildcard share/locale/*/LC_MESSAGES); do \ + $(INSTALL) -d -m 755 $(MACOSXINSTALL)/Contents/Resources/$$LOC; \ + $(INSTALL) $$LOC/$(NAME).mo $(MACOSXINSTALL)/Contents/Resources/$$LOC/$(NAME).mo; \ + done + @-if test ! -z "$(XSLT)"; then \ + $(INSTALL) -d -m 755 $(MACOSXINSTALL)/Contents/Resources/xslt; \ + $(INSTALL) -m 644 $(XSLTFILES) $(MACOSXINSTALL)/Contents/Resources/xslt/; \ + fi + + +create-macosx-bundle: all + $(INSTALL) -d -m 755 $(MACOSXSTAGING)/Contents/Resources + $(INSTALL) -d -m 755 $(MACOSXSTAGING)/Contents/MacOS + $(INSTALL) $(NAME) $(MACOSXSTAGING)/Contents/MacOS/ + $(INSTALL) $(MACOSXFILES)/PkgInfo $(MACOSXSTAGING)/Contents/ + $(INSTALL) $(MACOSXFILES)/Info.plist $(MACOSXSTAGING)/Contents/ + $(INSTALL) $(ICONFILE) $(MACOSXSTAGING)/Contents/Resources/ + $(INSTALL) $(MACOSXFILES)/$(CAPITALIZED_NAME).icns $(MACOSXSTAGING)/Contents/Resources/ + for LOC in $(wildcard share/locale/*/LC_MESSAGES); do \ + $(INSTALL) -d -m 755 $(MACOSXSTAGING)/Contents/Resources/$$LOC; \ + $(INSTALL) $$LOC/$(NAME).mo $(MACOSXSTAGING)/Contents/Resources/$$LOC/$(NAME).mo; \ + done + @-if test ! -z "$(XSLT)"; then \ + $(INSTALL) -d -m 755 $(MACOSXSTAGING)/Contents/Resources/xslt; \ + $(INSTALL) -m 644 $(XSLTFILES) $(MACOSXSTAGING)/Contents/Resources/xslt/; \ + fi + $(GTK_MAC_BUNDLER) packaging/macosx/$(NAME).bundle + +sign-macosx-bundle: all + codesign -s "3A8CE62A483083EDEA5581A61E770EC1FA8BECE8" /Applications/$(CAPITALIZED_NAME).app/Contents/MacOS/$(NAME)-bin + +install-cross-windows: all + $(INSTALL) -d -m 755 $(WINDOWSSTAGING)/share/locale + for MSG in $(WINMSGDIRS); do\ + $(INSTALL) -d -m 755 $(WINDOWSSTAGING)/$$MSG;\ + $(INSTALL) $(CROSS_PATH)/$$MSG/* $(WINDOWSSTAGING)/$$MSG;\ + done + for LOC in $(wildcard share/locale/*/LC_MESSAGES); do \ + $(INSTALL) -d -m 755 $(WINDOWSSTAGING)/$$LOC; \ + $(INSTALL) $$LOC/$(NAME).mo $(WINDOWSSTAGING)/$$LOC/$(NAME).mo; \ + done + +create-windows-installer: all $(NSIFILE) install-cross-windows + $(MAKENSIS) $(NSIFILE) + +$(NSIFILE): $(NSIINPUTFILE) + $(shell cat $(NSIINPUTFILE) | sed -e 's/VERSIONTOKEN/$(VERSION_STRING)/;s/PRODVTOKEN/$(PRODVERSION_STRING)/' > $(NSIFILE)) + +$(INFOPLIST): $(INFOPLISTINPUT) + $(shell cat $(INFOPLISTINPUT) | sed -e 's/CFBUNDLEVERSION_TOKEN/$(CFBUNDLEVERSION_STRING)/' > $(INFOPLIST)) + +# Transifex merge the translations +update-po-files: + xgettext -o po/$(NAME)-new.pot -s -k_ -kN_ -ktr --keyword=C_:1c,2 --add-comments="++GETTEXT" *.c qt-ui/*.cpp + tx push -s + tx pull -af + +MOCFLAGS = $(filter -I%, $(CXXFLAGS) $(EXTRA_FLAGS)) $(filter -D%, $(CXXFLAGS) $(EXTRA_FLAGS)) + +%.o: %.c + @$(PRETTYECHO) ' CC' $< + @mkdir -p .dep/$(@D) + $(COMPILE_PREFIX)$(CC) $(CFLAGS) $(EXTRA_FLAGS) -MD -MF .dep/$@.dep -c -o $@ $< + +%.o: %.cpp + @$(PRETTYECHO) ' CXX' $< + @mkdir -p .dep/$(@D) + $(COMPILE_PREFIX)$(CXX) $(CXXFLAGS) $(EXTRA_FLAGS) -MD -MF .dep/$@.dep -c -o $@ $< + +# This rule is for running the moc on QObject subclasses defined in the .h +# files. +%.moc.cpp: %.h + @$(PRETTYECHO) ' MOC' $< + $(COMPILE_PREFIX)$(MOC) $(MOCFLAGS) $< -o $@ + +# This rule is for running the moc on QObject subclasses defined in the .cpp +# files; remember to #include ".moc" at the end of the .cpp file, or +# you'll get linker errors ("undefined vtable for...") +%.moc: %.cpp + @$(PRETTYECHO) ' MOC' $< + $(COMPILE_PREFIX)$(MOC) -i $(MOCFLAGS) $< -o $@ + +# This creates the Qt resource sources. +%.qrc.cpp: %.qrc + @$(PRETTYECHO) ' RCC' $< + $(COMPILE_PREFIX)$(RCC) $< -o $@ +%.qrc: + +# This creates the ui headers. +ui_%.h: %.ui + @$(PRETTYECHO) ' UIC' $< + $(COMPILE_PREFIX)$(UIC) $< -o $@ + +# This forces the creation of ui headers with the wrong path +# This is required because the -MG option to the compiler outputs +# unknown files with no path prefix +ui_%.h: qt-ui/%.ui + @$(PRETTYECHO) ' UIC' $< + $(COMPILE_PREFIX)$(UIC) $< -o qt-ui/$@ + +share/locale/%.UTF-8/LC_MESSAGES/$(NAME).mo: po/%.po po/%.aliases + @$(PRETTYECHO) ' MSGFMT' $*.po + @mkdir -p $(dir $@) + $(COMPILE_PREFIX)msgfmt -c -o $@ po/$*.po + @-if test -s po/$*.aliases; then \ + for ALIAS in `cat po/$*.aliases`; do \ + mkdir -p share/locale/$$ALIAS/LC_MESSAGES; \ + cp $@ share/locale/$$ALIAS/LC_MESSAGES; \ + done; \ + fi + +satellite.png: satellite.svg + convert -transparent white -resize 11x16 -depth 8 $< $@ + +# This should work, but it doesn't get the colors quite right - so I manually converted with Gimp +# convert -colorspace RGB -transparent white -resize 256x256 subsurface-icon.svg subsurface-icon.png +# +# The following creates the pixbuf data in .h files with the basename followed by '_pixmap' +# as name of the data structure +%.h: %.png + @echo ' gdk-pixbuf-csource' $< + @gdk-pixbuf-csource --struct --name `echo $* | sed 's/-/_/g'`_pixbuf $< > $@ + +doc: + $(MAKE) -C Documentation doc + +clean: + rm -f $(ALL_OBJS) *~ $(NAME) $(NAME).exe po/*~ po/$(NAME)-new.pot \ + $(VERSION_FILE) $(HEADERS_NEEDING_MOC:.h=.moc) *.moc qt-ui/*.moc qt-ui/ui_*.h + rm -f $(RESOURCES:.qrc=.qrc.cpp) + rm -rf share + +confclean: clean + rm -f $(CONFIGFILE) + rm -rf .dep + +distclean: confclean + rm -f $(CREATOR_FILES) + +release: + @scripts/check-version -cr $(VERSION_STRING) + git archive --prefix $(CAPITALIZED_NAME)-$(VERSION_STRING)/ \ + --output $(CAPITALIZED_NAME)-$(VERSION_STRING).tgz \ + v$(VERSION_STRING) + +.PHONY: creator-files +creator-files: $(CREATOR_FILES) +$(NAME).files: Makefile $(CONFIGFILE) + echo $(wildcard *.h) $(HEADERS) $(SOURCES) | tr ' ' '\n' | sort | uniq > $(NAME).files +$(NAME).config: Makefile $(CONFIGFILE) + echo $(patsubst -D%,%,$(filter -D%, $(CXXFLAGS) $(CFLAGS) $(EXTRA_FLAGS))) | tr ' ' '\n' | sort | uniq > $(NAME).config +$(NAME).includes: Makefile $(CONFIGFILE) + echo $$PWD > $(NAME).includes + echo $(patsubst -I%,%,$(filter -I%, $(CXXFLAGS) $(CFLAGS) $(EXTRA_FLAGS))) | tr ' ' '\n' | sort | uniq >> $(NAME).includes +$(NAME).creator: + echo '[General]' > $(NAME).creator + +ifneq ($(CONFIGURED)$(CONFIGURING),) +.dep/%.o.dep: %.cpp + @mkdir -p $(@D) + @$(CXX) $(CXXFLAGS) $(EXTRA_FLAGS) -MM -MG -MF $@ -MT $(<:.cpp=.o) -c $< +endif + +DEPS = $(addprefix .dep/,$(C_SOURCES:.c=.o.dep) $(CXX_SOURCES:.cpp=.o.dep)) +-include $(DEPS) diff --git a/callbacks-gtk.h b/callbacks-gtk.h index 08c159b4d..568916f6c 100644 --- a/callbacks-gtk.h +++ b/callbacks-gtk.h @@ -9,7 +9,7 @@ static void name(GtkWidget *w, gpointer data) \ #define OPTIONCALLBACK(name, option) \ static void name(GtkWidget *w, gpointer data) \ { \ - GtkWidget **entry = data; \ + GtkWidget **entry = (GtkWidget**)data; \ option = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(w)); \ update_screen(); \ if (entry) \ diff --git a/color.h b/color.h index 5d88fb5f7..f2013bcc9 100644 --- a/color.h +++ b/color.h @@ -4,54 +4,56 @@ /* The colors are named by picking the closest match from http://chir.ag/projects/name-that-color */ +#include + // Greens -#define CAMARONE1 { 0.0, 0.4, 0.0, 1 } -#define FUNGREEN1 { 0.0, 0.4, 0.2, 1 } -#define FUNGREEN1_HIGH_TRANS { 0.0, 0.4, 0.2, 0.25 } -#define KILLARNEY1 { 0.2, 0.4, 0.2, 1 } -#define APPLE1 { 0.2, 0.6, 0.2, 1 } -#define APPLE1_MED_TRANS { 0.2, 0.6, 0.2, 0.5 } -#define APPLE1_HIGH_TRANS { 0.2, 0.6, 0.2, 0.25 } -#define LIMENADE1 { 0.4, 0.8, 0.0, 1 } -#define ATLANTIS1 { 0.4, 0.8, 0.2, 1 } -#define ATLANTIS2 { 0.6, 0.8, 0.2, 1 } -#define RIOGRANDE1 { 0.8, 0.8, 0.0, 1 } -#define EARLSGREEN1 { 0.8, 0.8, 0.2, 1 } -#define FORESTGREEN1 { 0.1, 0.5, 0.1, 1 } +#define CAMARONE1 QColor::fromRgbF( 0.0, 0.4, 0.0, 1 ) +#define FUNGREEN1 QColor::fromRgbF( 0.0, 0.4, 0.2, 1 ) +#define FUNGREEN1_HIGH_TRANS QColor::fromRgbF( 0.0, 0.4, 0.2, 0.25 ) +#define KILLARNEY1 QColor::fromRgbF( 0.2, 0.4, 0.2, 1 ) +#define APPLE1 QColor::fromRgbF( 0.2, 0.6, 0.2, 1 ) +#define APPLE1_MED_TRANS QColor::fromRgbF( 0.2, 0.6, 0.2, 0.5 ) +#define APPLE1_HIGH_TRANS QColor::fromRgbF( 0.2, 0.6, 0.2, 0.25 ) +#define LIMENADE1 QColor::fromRgbF( 0.4, 0.8, 0.0, 1 ) +#define ATLANTIS1 QColor::fromRgbF( 0.4, 0.8, 0.2, 1 ) +#define ATLANTIS2 QColor::fromRgbF( 0.6, 0.8, 0.2, 1 ) +#define RIOGRANDE1 QColor::fromRgbF( 0.8, 0.8, 0.0, 1 ) +#define EARLSGREEN1 QColor::fromRgbF( 0.8, 0.8, 0.2, 1 ) +#define FORESTGREEN1 QColor::fromRgbF( 0.1, 0.5, 0.1, 1 ) // Reds -#define PERSIANRED1 { 0.8, 0.2, 0.2, 1 } -#define TUSCANY1 { 0.8, 0.4, 0.2, 1 } -#define PIRATEGOLD1 { 0.8, 0.5, 0.0, 1 } -#define HOKEYPOKEY1 { 0.8, 0.6, 0.2, 1 } -#define CINNABAR1 { 0.9, 0.3, 0.2, 1 } -#define REDORANGE1 { 1.0, 0.2, 0.2, 1 } -#define REDORANGE1_HIGH_TRANS { 1.0, 0.2, 0.2, 0.25 } -#define REDORANGE1_MED_TRANS { 1.0, 0.2, 0.2, 0.5 } -#define RED1_MED_TRANS { 1.0, 0.0, 0.0, 0.5 } -#define RED1 { 1.0, 0.0, 0.0, 1 } +#define PERSIANRED1 QColor::fromRgbF( 0.8, 0.2, 0.2, 1 ) +#define TUSCANY1 QColor::fromRgbF( 0.8, 0.4, 0.2, 1 ) +#define PIRATEGOLD1 QColor::fromRgbF( 0.8, 0.5, 0.0, 1 ) +#define HOKEYPOKEY1 QColor::fromRgbF( 0.8, 0.6, 0.2, 1 ) +#define CINNABAR1 QColor::fromRgbF( 0.9, 0.3, 0.2, 1 ) +#define REDORANGE1 QColor::fromRgbF( 1.0, 0.2, 0.2, 1 ) +#define REDORANGE1_HIGH_TRANS QColor::fromRgbF( 1.0, 0.2, 0.2, 0.25 ) +#define REDORANGE1_MED_TRANS QColor::fromRgbF( 1.0, 0.2, 0.2, 0.5 ) +#define RED1_MED_TRANS QColor::fromRgbF( 1.0, 0.0, 0.0, 0.5 ) +#define RED1 QColor::fromRgbF( 1.0, 0.0, 0.0, 1 ) // Monochromes -#define BLACK1_LOW_TRANS { 0.0, 0.0, 0.0, 0.75 } -#define BLACK1_HIGH_TRANS { 0.0, 0.0, 0.0, 0.25 } -#define TUNDORA1_MED_TRANS { 0.3, 0.3, 0.3, 0.5 } -#define MERCURY1_MED_TRANS { 0.9, 0.9, 0.9, 0.5 } -#define CONCRETE1_LOWER_TRANS { 0.95, 0.95, 0.95, 0.9 } -#define WHITE1_MED_TRANS { 1.0, 1.0, 1.0, 0.5 } -#define WHITE1 { 1.0, 1.0, 1.0, 1 } +#define BLACK1_LOW_TRANS QColor::fromRgbF( 0.0, 0.0, 0.0, 0.75 ) +#define BLACK1_HIGH_TRANS QColor::fromRgbF( 0.0, 0.0, 0.0, 0.25 ) +#define TUNDORA1_MED_TRANS QColor::fromRgbF( 0.3, 0.3, 0.3, 0.5 ) +#define MERCURY1_MED_TRANS QColor::fromRgbF( 0.9, 0.9, 0.9, 0.5 ) +#define CONCRETE1_LOWER_TRANS QColor::fromRgbF( 0.95, 0.95, 0.95, 0.9 ) +#define WHITE1_MED_TRANS QColor::fromRgbF( 1.0, 1.0, 1.0, 0.5 ) +#define WHITE1 QColor::fromRgbF( 1.0, 1.0, 1.0, 1 ) // Blues -#define GOVERNORBAY2 { 0.2, 0.2, 0.7, 1 } -#define GOVERNORBAY1_MED_TRANS { 0.2, 0.2, 0.8, 0.5 } -#define ROYALBLUE2 { 0.2, 0.2, 0.9, 1 } -#define ROYALBLUE2_LOW_TRANS { 0.2, 0.2, 0.9, 0.75 } +#define GOVERNORBAY2 QColor::fromRgbF( 0.2, 0.2, 0.7, 1 ) +#define GOVERNORBAY1_MED_TRANS QColor::fromRgbF( 0.2, 0.2, 0.8, 0.5 ) +#define ROYALBLUE2 QColor::fromRgbF( 0.2, 0.2, 0.9, 1 ) +#define ROYALBLUE2_LOW_TRANS QColor::fromRgbF( 0.2, 0.2, 0.9, 0.75 ) // Yellows / BROWNS -#define SPRINGWOOD1 { 0.95, 0.95, 0.9, 1 } -#define BROOM1_LOWER_TRANS { 1.0, 1.0, 0.1, 0.9 } -#define PEANUT { 0.5, 0.2, 0.1, 1.0 } -#define PEANUT_MED_TRANS { 0.5, 0.2, 0.1, 0.5 } +#define SPRINGWOOD1 QColor::fromRgbF( 0.95, 0.95, 0.9, 1 ) +#define BROOM1_LOWER_TRANS QColor::fromRgbF( 1.0, 1.0, 0.1, 0.9 ) +#define PEANUT QColor::fromRgbF( 0.5, 0.2, 0.1, 1.0 ) +#define PEANUT_MED_TRANS QColor::fromRgbF( 0.5, 0.2, 0.1, 0.5 ) // Magentas -#define MEDIUMREDVIOLET1_HIGHER_TRANS { 0.7, 0.2, 0.7, 0.1 } +#define MEDIUMREDVIOLET1_HIGHER_TRANS QColor::fromRgbF( 0.7, 0.2, 0.7, 0.1 ) #endif diff --git a/conversions.h b/conversions.h new file mode 100644 index 000000000..f59fcc96b --- /dev/null +++ b/conversions.h @@ -0,0 +1,16 @@ +/* + * conversions.h + * + * Helpers to convert between different units + * + */ +#ifdef __cplusplus +extern "C" { +#endif + +void convert_volume_pressure(int ml, int mbar, double *v, double *p); +int convert_pressure(int mbar, double *p); + +#ifdef __cplusplus +} +#endif diff --git a/device.h b/device.h index 8a306ef78..636eb73c6 100644 --- a/device.h +++ b/device.h @@ -1,6 +1,10 @@ #ifndef DEVICE_INFO_H #define DEVICE_INFO_H +#ifdef __cplusplus +extern "C" { +#endif + struct device_info { const char *model; uint32_t deviceid; @@ -17,4 +21,8 @@ extern struct device_info *create_device_info(const char *model, uint32_t device extern struct device_info *remove_device_info(const char *model, uint32_t deviceid); extern struct device_info *head_of_device_info_list(void); +#ifdef __cplusplus +} +#endif + #endif diff --git a/display-gtk.h b/display-gtk.h index 627988784..634f05cab 100644 --- a/display-gtk.h +++ b/display-gtk.h @@ -8,6 +8,10 @@ #include #endif +#ifdef __cplusplus +extern "C" { +#endif + extern GtkWidget *main_window; /* we want a progress bar as part of the device_data_t - let's abstract this out */ @@ -35,15 +39,11 @@ extern void set_divelist_font(const char *); extern void update_screen(void); extern void download_dialog(GtkWidget *, gpointer); -extern int is_default_dive_computer_device(const char *); -extern int is_default_dive_computer(const char *, const char *); + extern void add_dive_cb(GtkWidget *, gpointer); extern void update_progressbar(progressbar_t *progress, double value); extern void update_progressbar_text(progressbar_t *progress, const char *text); -extern const char *default_dive_computer_vendor; -extern const char *default_dive_computer_product; -extern const char *default_dive_computer_device; // info.c enum { @@ -90,22 +90,6 @@ typedef gint (*sort_func_t)(GtkTreeModel *model, GtkTreeIter *b, gpointer user_data); -#define ALIGN_LEFT 1 -#define ALIGN_RIGHT 2 -#define INVISIBLE 4 -#define UNSORTABLE 8 -#define EDITABLE 16 - -#ifndef TEXT_SCALE -#define TEXT_SCALE 1.0 -#endif - -#define DEPTH_TEXT_SIZE (10 * TEXT_SCALE) -#define PRESSURE_TEXT_SIZE (10 * TEXT_SCALE) -#define DC_TEXT_SIZE (10.5 * TEXT_SCALE) -#define PP_TEXT_SIZE (11 * TEXT_SCALE) -#define TEMP_TEXT_SIZE (12 * TEXT_SCALE) - extern GtkTreeViewColumn *tree_view_column(GtkWidget *tree_view, int index, const char *title, data_func_t data_func, unsigned int flags); extern GtkTreeViewColumn *tree_view_column_add_pixbuf(GtkWidget *tree_view, data_func_t data_func, GtkTreeViewColumn *col); @@ -115,4 +99,8 @@ GError *uemis_download(const char *path, progressbar_t *progress, GtkDialog *dia /* from planner.c */ extern void input_plan(void); +#ifdef __cplusplus +} +#endif + #endif diff --git a/display.h b/display.h index c61abcf90..b62ae3ba3 100644 --- a/display.h +++ b/display.h @@ -1,14 +1,18 @@ #ifndef DISPLAY_H #define DISPLAY_H -#include +#ifdef __cplusplus +extern "C" { +#endif #define SCALE_SCREEN 1.0 #define SCALE_PRINT (1.0 / get_screen_dpi()) extern void repaint_dive(void); extern void do_print(void); -extern gdouble get_screen_dpi(void); + +// Commented out because I don't know how to get the dpi on a paint device yet. +extern double get_screen_dpi(void); /* Plot info with smoothing, velocity indication * and one-, two- and three-minute minimums and maximums */ @@ -20,10 +24,15 @@ struct plot_info { int mintemp, maxtemp; double endtempcoord; double maxpp; - gboolean has_ndl; + bool has_ndl; struct plot_data *entry; }; +/* +// I'm not sure if this is needed anymore - but keeping this here +// so I wont break stuff trying to redo the well. +*/ + /* * Cairo scaling really is horribly horribly mis-designed. * @@ -34,8 +43,6 @@ struct plot_info { */ struct graphics_context { int printer; - cairo_t *cr; - cairo_rectangle_t drawing_area; double maxx, maxy; double leftx, rightx; double topy, bottomy; @@ -49,7 +56,7 @@ extern void plot(struct graphics_context *gc, struct dive *dive, scale_mode_t sc extern struct divecomputer *select_dc(struct divecomputer *main); extern void init_profile_background(struct graphics_context *gc); extern void attach_tooltip(int x, int y, int w, int h, const char *text, struct event *event); -extern void get_plot_details(struct graphics_context *gc, int time, char *buf, size_t bufsize); +extern void get_plot_details(struct graphics_context *gc, int time, char *buf, int bufsize); extern int x_to_time(double x); extern int x_abs(double x); @@ -57,7 +64,7 @@ struct options { enum { PRETTY, TABLE, TWOPERPAGE } type; int print_selected; int color_selected; - gboolean notes_up; + bool notes_up; int profile_height, notes_height, tanks_height; }; @@ -65,4 +72,14 @@ extern char zoomed_plot, dc_number; extern unsigned int amount_selected; +extern int is_default_dive_computer_device(const char *); +extern int is_default_dive_computer(const char *, const char *); +extern const char *default_dive_computer_vendor; +extern const char *default_dive_computer_product; +extern const char *default_dive_computer_device; + +#ifdef __cplusplus +} +#endif + #endif diff --git a/dive.c b/dive.c index 34025d68c..2c2307e41 100644 --- a/dive.c +++ b/dive.c @@ -1810,6 +1810,15 @@ struct dive *merge_dives(struct dive *a, struct dive *b, int offset, gboolean pr return res; } +int get_index_for_dive(struct dive *dive) { + int i; + struct dive *d; + for_each_dive(i, d) + if (d == dive) + return i; + return -1; +} + struct dive *find_dive_including(timestamp_t when) { int i; diff --git a/dive.h b/dive.h index cdd3e2770..50e0dc4d4 100644 --- a/dive.h +++ b/dive.h @@ -13,6 +13,16 @@ #include "sha1.h" +#ifdef __cplusplus +extern "C" { +#else +#if __STDC_VERSION__ >= 199901L +#include +#else +typedef int bool; +#endif +#endif + #define O2_IN_AIR 209 // permille #define N2_IN_AIR 781 #define O2_DENSITY 1429 // mg/Liter @@ -372,6 +382,8 @@ struct dive { struct divecomputer dc; }; +extern int get_index_for_dive(struct dive *dive); + static inline int dive_has_gps_location(struct dive *dive) { return dive->latitude.udeg || dive->longitude.udeg; @@ -549,7 +561,7 @@ static inline struct divecomputer *get_dive_dc(struct dive *dive, int nr) #define for_each_gps_location(_i,_x) \ for ((_i) = 0; ((_x) = get_gps_location(_i, &gps_location_table)) != NULL; (_i)++) -static inline struct dive *get_dive_by_diveid(int diveid, int deviceid) +static inline struct dive *get_dive_by_diveid(uint32_t diveid, uint32_t deviceid) { int i; struct dive *dive; @@ -609,7 +621,6 @@ extern struct sample *prepare_sample(struct divecomputer *dc); extern void finish_sample(struct divecomputer *dc); extern void sort_table(struct dive_table *table); -extern void report_dives(gboolean imported, gboolean prefer_imported); extern struct dive *fixup_dive(struct dive *dive); extern unsigned int dc_airtemp(struct divecomputer *dc); extern struct dive *merge_dives(struct dive *a, struct dive *b, int offset, gboolean prefer_downloaded); @@ -622,6 +633,7 @@ extern void add_event(struct divecomputer *dc, int time, int type, int flags, in /* UI related protopypes */ extern void init_ui(int *argcp, char ***argvp); +extern void init_qt_ui(int *argcp, char ***argvp); extern void run_ui(void); extern void exit_ui(void); @@ -717,11 +729,28 @@ void get_gas_string(int o2, int he, char *buf, int len); struct event *get_next_event(struct event *event, char *name); + +/* this struct holds the information that + * describes the cylinders of air. + * it is a global variable initialized in equipment.c + * used to fill the combobox in the add/edit cylinder + * dialog + */ + +struct tank_info { + const char *name; + int cuft, ml, psi, bar; +}; + #ifdef DEBUGFILE extern char *debugfilename; extern FILE *debugfile; #endif +#ifdef __cplusplus +} +#endif + #include "pref.h" #endif /* DIVE_H */ diff --git a/divelist-gtk.c b/divelist-gtk.c index 1a47feb51..79613e012 100644 --- a/divelist-gtk.c +++ b/divelist-gtk.c @@ -55,7 +55,6 @@ static struct DiveList dive_list; #define TREESTORE(_dl) GTK_TREE_STORE((_dl).treemodel) #define LISTSTORE(_dl) GTK_TREE_STORE((_dl).listmodel) -short autogroup = FALSE; static gboolean ignore_selection_changes = FALSE; static gboolean in_set_cursor = FALSE; static gboolean set_selected(GtkTreeModel *model, GtkTreePath *path, @@ -255,7 +254,6 @@ static void date_data_func(GtkTreeViewColumn *col, gpointer data) { int idx, nr; - struct tm tm; timestamp_t when; /* this should be enought for most languages. if not increase the value. */ char *buffer; @@ -263,11 +261,10 @@ static void date_data_func(GtkTreeViewColumn *col, gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_DATE, &when, -1); nr = gtk_tree_model_iter_n_children(model, iter); - utc_mkdate(when, &tm); if (idx < 0) { - buffer = get_trip_date_string(&tm, nr); + buffer = get_trip_date_string(when, nr); } else { - buffer = get_dive_date_string(&tm); + buffer = get_dive_date_string(when); } g_object_set(renderer, "text", buffer, NULL); free(buffer); @@ -430,40 +427,24 @@ 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, GtkTreeIter *iter, gpointer data) { - int idx, o2, he, o2low; - char buffer[80]; + int idx; + char *buffer; struct dive *dive; gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, -1); - if (idx < 0) { - *buffer = '\0'; - goto exit; + if (idx >= 0 && (dive = get_dive(idx))) { + buffer = get_nitrox_string(dive); + g_object_set(renderer, "text", buffer, NULL); + free(buffer); + } else { + g_object_set(renderer, "text", "", NULL); } - dive = get_dive(idx); - get_dive_gas(dive, &o2, &he, &o2low); - o2 = (o2 + 5) / 10; - he = (he + 5) / 10; - o2low = (o2low + 5) / 10; - - if (he) - snprintf(buffer, sizeof(buffer), "%d/%d", o2, he); - else if (o2) - if (o2 == o2low) - snprintf(buffer, sizeof(buffer), "%d", o2); - else - snprintf(buffer, sizeof(buffer), "%d" UTF8_ELLIPSIS "%d", o2low, o2); - else - strcpy(buffer, _("air")); -exit: - g_object_set(renderer, "text", buffer, NULL); } /* Render the SAC data (integer value of "ml / min") */ @@ -804,7 +785,6 @@ static gint gtk_dive_nr_sort(GtkTreeModel *model, return dive_nr_sort(idx_a, idx_b, when_a, when_b); } - static struct divelist_column { const char *header; data_func_t data; @@ -898,6 +878,12 @@ static void row_activated_cb(GtkTreeView *tree_view, edit_dive_info(get_dive(index), FALSE); } +void report_dives(bool is_imported, bool prefer_imported) +{ + process_dives(is_imported, prefer_imported); + dive_list_update_dives(); +} + void add_dive_cb(GtkWidget *menuitem, gpointer data) { struct dive *dive; diff --git a/divelist.c b/divelist.c index fcbc7e59c..75f427892 100644 --- a/divelist.c +++ b/divelist.c @@ -50,6 +50,8 @@ static short dive_list_changed = FALSE; +short autogroup = FALSE; + dive_trip_t *dive_trip_list; unsigned int amount_selected; @@ -69,6 +71,13 @@ void dump_selection(void) } #endif +void set_autogroup(gboolean value) +{ + /* if we keep the UI paradigm, this needs to toggle + * the checkbox on the autogroup menu item */ + autogroup = value; +} + dive_trip_t *find_trip_by_idx(int idx) { dive_trip_t *trip = dive_trip_list; @@ -131,7 +140,7 @@ int trip_has_selected_dives(dive_trip_t *trip) return 0; } -/* Get the values as we want to show them. Whole feet. But meters with one decimal for +/* Get the values as we want to show them. Whole feet. But meters with one decimal for * values less than 20m, without decimals for larger values */ void get_depth_values(int depth, int *depth_int, int *depth_decimal, int *show_decimal) { @@ -557,38 +566,71 @@ void get_suit(struct dive *dive, char **str) #define MAX_DATE_STRING 256 /* caller needs to free the string */ -char *get_dive_date_string(struct tm *tm) { +char *get_dive_date_string(timestamp_t when) { char *buffer = malloc(MAX_DATE_STRING); - if (buffer) + if (buffer) { + struct tm tm; + utc_mkdate(when, &tm); snprintf(buffer, MAX_DATE_STRING, /*++GETTEXT 60 char buffer weekday, monthname, day of month, year, hour:min */ _("%1$s, %2$s %3$d, %4$d %5$02d:%6$02d"), - weekday(tm->tm_wday), - monthname(tm->tm_mon), - tm->tm_mday, tm->tm_year + 1900, - tm->tm_hour, tm->tm_min); + weekday(tm.tm_wday), + monthname(tm.tm_mon), + tm.tm_mday, tm.tm_year + 1900, + tm.tm_hour, tm.tm_min); + } return buffer; } /* caller needs to free the string */ -char *get_trip_date_string(struct tm *tm, int nr) { +char *get_trip_date_string(timestamp_t when, int nr) { char *buffer = malloc(MAX_DATE_STRING); - if (buffer) + if (buffer) { + struct tm tm; + utc_mkdate(when, &tm); snprintf(buffer, MAX_DATE_STRING, /*++GETTEXT 60 char buffer weekday, monthname, day of month, year, nr dives */ ngettext("Trip %1$s, %2$s %3$d, %4$d (%5$d dive)", "Trip %1$s, %2$s %3$d, %4$d (%5$d dives)", nr), - weekday(tm->tm_wday), - monthname(tm->tm_mon), - tm->tm_mday, tm->tm_year + 1900, + weekday(tm.tm_wday), + monthname(tm.tm_mon), + tm.tm_mday, tm.tm_year + 1900, nr); + } + return buffer; +} + +#define MAX_NITROX_STRING 80 +#define UTF8_ELLIPSIS "\xE2\x80\xA6" + +/* callers needs to free the string */ +char *get_nitrox_string(struct dive *dive) +{ + int o2, he, o2low; + char *buffer = malloc(MAX_NITROX_STRING); + + if (buffer) { + get_dive_gas(dive, &o2, &he, &o2low); + o2 = (o2 + 5) / 10; + he = (he + 5) / 10; + o2low = (o2low + 5) / 10; + + if (he) + snprintf(buffer, MAX_NITROX_STRING, "%d/%d", o2, he); + else if (o2) + if (o2 == o2low) + snprintf(buffer, MAX_NITROX_STRING, "%d", o2); + else + snprintf(buffer, MAX_NITROX_STRING, "%d" UTF8_ELLIPSIS "%d", o2low, o2); + else + strcpy(buffer, _("air")); + } return buffer; } /* * helper functions for dive_trip handling */ - #ifdef DEBUG_TRIP void dump_trip_list(void) { @@ -869,20 +911,23 @@ void merge_dive_index(int i, struct dive *a) add_single_dive(i, res); delete_single_dive(i+1); delete_single_dive(i+1); - +#if USE_GTK_UI dive_list_update_dives(); +#endif mark_divelist_changed(TRUE); } void select_dive(int idx) { struct dive *dive = get_dive(idx); - if (dive && !dive->selected) { + if (dive) { /* never select an invalid dive that isn't displayed */ if (dive->dive_tags & DTAG_INVALID && !prefs.display_invalid_dives) return; - dive->selected = 1; - amount_selected++; + if (!dive->selected) { + dive->selected = 1; + amount_selected++; + } selected_dive = idx; } } @@ -935,3 +980,124 @@ void remove_autogen_trips() } } +/* + * When adding dives to the dive table, we try to renumber + * the new dives based on any old dives in the dive table. + * + * But we only do it if: + * + * - there are no dives in the dive table + * + * OR + * + * - the last dive in the old dive table was numbered + * + * - all the new dives are strictly at the end (so the + * "last dive" is at the same location in the dive table + * after re-sorting the dives. + * + * - none of the new dives have any numbers + * + * This catches the common case of importing new dives from + * a dive computer, and gives them proper numbers based on + * your old dive list. But it tries to be very conservative + * and not give numbers if there is *any* question about + * what the numbers should be - in which case you need to do + * a manual re-numbering. + */ +static void try_to_renumber(struct dive *last, int preexisting) +{ + int i, nr; + + /* + * If the new dives aren't all strictly at the end, + * we're going to expect the user to do a manual + * renumbering. + */ + if (preexisting && get_dive(preexisting-1) != last) + return; + + /* + * If any of the new dives already had a number, + * we'll have to do a manual renumbering. + */ + for (i = preexisting; i < dive_table.nr; i++) { + struct dive *dive = get_dive(i); + if (dive->number) + return; + } + + /* + * Ok, renumber.. + */ + if (last) + nr = last->number; + else + nr = 0; + for (i = preexisting; i < dive_table.nr; i++) { + struct dive *dive = get_dive(i); + dive->number = ++nr; + } +} + +void process_dives(bool is_imported, bool prefer_imported) +{ + int i; + int preexisting = dive_table.preexisting; + struct dive *last; + + /* check if we need a nickname for the divecomputer for newly downloaded dives; + * since we know they all came from the same divecomputer we just check for the + * first one */ + if (preexisting < dive_table.nr && dive_table.dives[preexisting]->downloaded) + set_dc_nickname(dive_table.dives[preexisting]); + else + /* they aren't downloaded, so record / check all new ones */ + for (i = preexisting; i < dive_table.nr; i++) + set_dc_nickname(dive_table.dives[i]); + + /* This does the right thing for -1: NULL */ + last = get_dive(preexisting-1); + + sort_table(&dive_table); + + for (i = 1; i < dive_table.nr; i++) { + struct dive **pp = &dive_table.dives[i-1]; + struct dive *prev = pp[0]; + struct dive *dive = pp[1]; + struct dive *merged; + + /* only try to merge overlapping dives - or if one of the dives has + * zero duration (that might be a gps marker from the webservice) */ + if (prev->duration.seconds && dive->duration.seconds && + prev->when + prev->duration.seconds < dive->when) + continue; + + merged = try_to_merge(prev, dive, prefer_imported); + if (!merged) + continue; + + /* careful - we might free the dive that last points to. Oops... */ + if (last == prev || last == dive) + last = merged; + + /* Redo the new 'i'th dive */ + i--; + add_single_dive(i, merged); + delete_single_dive(i+1); + delete_single_dive(i+1); + } + /* make sure no dives are still marked as downloaded */ + for (i = 1; i < dive_table.nr; i++) + dive_table.dives[i]->downloaded = FALSE; + + if (is_imported) { + /* If there are dives in the table, are they numbered */ + if (!last || last->number) + try_to_renumber(last, preexisting); + + /* did we add dives to the dive table? */ + if (preexisting != dive_table.nr) + mark_divelist_changed(TRUE); + } +} diff --git a/divelist.h b/divelist.h index 23e22190d..7a9d07967 100644 --- a/divelist.h +++ b/divelist.h @@ -1,8 +1,13 @@ #ifndef DIVELIST_H #define DIVELIST_H +#ifdef __cplusplus +extern "C" { +#endif + struct dive; +extern void report_dives(bool imported, bool prefer_imported); extern void dive_list_update_dives(void); extern void update_dive_list_col_visibility(void); extern void update_dive_list_units(void); @@ -16,12 +21,13 @@ extern void select_prev_dive(void); extern void show_and_select_dive(struct dive *dive); extern double init_decompression(struct dive * dive); extern void export_all_dives_uddf_cb(); - extern void upload_all_dives_divelogs_cb(); /* divelist core logic functions */ -extern char *get_dive_date_string(struct tm *tm); -extern char *get_trip_date_string(struct tm *tm, int nr); +extern void process_dives(bool imported, bool prefer_imported); +extern char *get_dive_date_string(timestamp_t when); +extern char *get_trip_date_string(timestamp_t when, int nr); +extern char *get_nitrox_string(struct dive *dive); extern void clear_trip_indexes(void); extern dive_trip_t *find_trip_by_idx(int idx); extern int dive_nr_sort(int idx_a, int idx_b, timestamp_t when_a, timestamp_t when_b); @@ -45,4 +51,8 @@ extern void dump_selection(void); extern void dump_trip_list(void); #endif +#ifdef __cplusplus +} +#endif + #endif diff --git a/download-dialog.c b/download-dialog.c index 6a55a327c..8f6220e31 100644 --- a/download-dialog.c +++ b/download-dialog.c @@ -3,19 +3,23 @@ #include "dive.h" #include "divelist.h" #include "display.h" +#if USE_GTK_UI #include "display-gtk.h" #include "callbacks-gtk.h" +#endif #include "libdivecomputer.h" const char *default_dive_computer_vendor; const char *default_dive_computer_product; const char *default_dive_computer_device; +#if USE_GTK_UI static gboolean force_download; static gboolean prefer_downloaded; OPTIONCALLBACK(force_toggle, force_download) OPTIONCALLBACK(prefer_dl_toggle, prefer_downloaded) +#endif struct product { const char *product; @@ -38,6 +42,7 @@ struct mydescriptor { struct vendor *dc_list; +#if USE_GTK_UI static void render_dc_vendor(GtkCellLayout *cell, GtkCellRenderer *renderer, GtkTreeModel *model, @@ -63,6 +68,7 @@ static void render_dc_product(GtkCellLayout *cell, product = dc_descriptor_get_product(descriptor); g_object_set(renderer, "text", product, NULL); } +#endif int is_default_dive_computer(const char *vendor, const char *product) { @@ -75,7 +81,7 @@ int is_default_dive_computer_device(const char *name) return default_dive_computer_device && !strcmp(name, default_dive_computer_device); } -static void set_default_dive_computer(const char *vendor, const char *product) +void set_default_dive_computer(const char *vendor, const char *product) { if (!vendor || !*vendor) return; @@ -93,7 +99,7 @@ static void set_default_dive_computer(const char *vendor, const char *product) subsurface_set_conf("dive_computer_product", product); } -static void set_default_dive_computer_device(const char *name) +void set_default_dive_computer_device(const char *name) { if (!name || !*name) return; @@ -105,6 +111,7 @@ static void set_default_dive_computer_device(const char *name) subsurface_set_conf("dive_computer_device", name); } +#if USE_GTK_UI static void dive_computer_selector_changed(GtkWidget *combo, gpointer data) { GtkWidget *import, *button; @@ -161,10 +168,10 @@ static GtkWidget *import_dive_computer(device_data_t *data, GtkDialog *dialog) gtk_box_pack_start(GTK_BOX(vbox), info, FALSE, FALSE, 0); return info; } - +#endif /* create a list of lists and keep the elements sorted */ -static void add_dc(const char *vendor, const char *product, dc_descriptor_t *descriptor) +void add_dc(const char *vendor, const char *product, dc_descriptor_t *descriptor) { struct vendor *dcl = dc_list; struct vendor **dclp = &dc_list; @@ -207,6 +214,7 @@ static void add_dc(const char *vendor, const char *product, dc_descriptor_t *des pl->descriptor = descriptor; } +#if USE_GTK_UI /* fill the vendors and create and fill the respective product stores; return the longest product name * and also the indices of the default vendor / product */ static int fill_computer_list(GtkListStore *vendorstore, GtkListStore ***productstore, int *vendor_index, int *product_index) @@ -485,3 +493,4 @@ void update_progressbar_text(progressbar_t *progress, const char *text) { gtk_progress_bar_set_text(GTK_PROGRESS_BAR(progress->bar), text); } +#endif diff --git a/equipment.c b/equipment.c index 97388e79b..189cc617f 100644 --- a/equipment.c +++ b/equipment.c @@ -16,9 +16,14 @@ #include "dive.h" #include "display.h" +#if USE_GTK_UI #include "display-gtk.h" +#endif #include "divelist.h" +#include "conversions.h" +#if USE_GTK_UI +#include "display-gtk.h" static GtkListStore *cylinder_model, *weightsystem_model; enum { @@ -68,9 +73,10 @@ struct ws_widget { GtkSpinButton *weight; int w_idx; }; +#endif /* USE_GTK_UI */ /* we want bar - so let's not use our unit functions */ -static int convert_pressure(int mbar, double *p) +int convert_pressure(int mbar, double *p) { int decimals = 1; double pressure; @@ -86,7 +92,7 @@ static int convert_pressure(int mbar, double *p) return decimals; } -static void convert_volume_pressure(int ml, int mbar, double *v, double *p) +void convert_volume_pressure(int ml, int mbar, double *v, double *p) { double volume, pressure; @@ -108,7 +114,7 @@ static void convert_volume_pressure(int ml, int mbar, double *v, double *p) *v = volume; } -static int convert_weight(int grams, double *m) +int convert_weight(int grams, double *m) { int decimals = 1; /* not sure - do people do less than whole lbs/kg ? */ double weight; @@ -121,6 +127,7 @@ static int convert_weight(int grams, double *m) return decimals; } +#if USE_GTK_UI static void set_cylinder_description(struct cylinder_widget *cylinder, const char *desc) { set_active_text(cylinder->description, desc); @@ -452,6 +459,28 @@ static void show_weightsystem(weightsystem_t *ws, struct ws_widget *weightsystem set_weight_description(weightsystem_widget, desc); set_weight_weight_spinbutton(weightsystem_widget, ws->weight.grams); } +#else +/* placeholders for a few functions that we need to redesign for the Qt UI */ +void add_cylinder_description(cylinder_type_t *type) +{ + const char *desc; + + desc = type->description; + if (!desc) + return; + /* now do something with it... */ +} +void add_weightsystem_description(weightsystem_t *weightsystem) +{ + const char *desc; + + desc = weightsystem->description; + if (!desc) + return; + /* now do something with it... */ +} + +#endif /* USE_GTK_UI */ gboolean cylinder_nodata(cylinder_t *cyl) { @@ -516,6 +545,7 @@ gboolean weightsystems_equal(weightsystem_t *ws1, weightsystem_t *ws2) return TRUE; } +#if USE_GTK_UI static void set_one_cylinder(void *_data, GtkListStore *model, GtkTreeIter *iter) { cylinder_t *cyl = _data; @@ -726,6 +756,7 @@ static void fill_cylinder_info(struct cylinder_widget *cylinder, cylinder_t *cyl /* * Also, insert it into the model if it doesn't already exist */ + // WARNING: GTK-Specific Code. add_cylinder(cylinder, desc, ml, mbar); } @@ -784,16 +815,13 @@ static void record_weightsystem_changes(weightsystem_t *ws, struct ws_widget *we ws->description = desc; add_weightsystem_type(desc, grams, &iter); } - +#endif /* USE_GTK_UI */ /* * We hardcode the most common standard cylinders, * we should pick up any other names from the dive * logs directly. */ -static struct tank_info { - const char *name; - int cuft, ml, psi, bar; -} tank_info[100] = { +struct tank_info tank_info[100] = { /* Need an empty entry for the no-cylinder case */ { "", }, @@ -836,6 +864,7 @@ static struct tank_info { { NULL, } }; +#if USE_GTK_UI static void fill_tank_list(GtkListStore *store) { GtkTreeIter iter; @@ -1682,3 +1711,4 @@ void clear_equipment_widgets() gtk_list_store_clear(cylinder_list[W_IDX_PRIMARY].model); gtk_list_store_clear(weightsystem_list[W_IDX_PRIMARY].model); } +#endif /* USE_GTK_UI */ diff --git a/gtk-gui.c b/gtk-gui.c index be985a85a..666438c33 100644 --- a/gtk-gui.c +++ b/gtk-gui.c @@ -3,6 +3,11 @@ /* creates the window and overall layout * divelist, dive info, equipment and printing are handled in their own source files */ +/* + * This is the former qt-gui.cpp - so it already contains some Qt related + * functions. It's renamed back to gtk-ui.c to keep the old Gtk code + * around for reference in case we still need it... all that has now been + * ripped out of qt-gui.cpp */ #include #include #include @@ -24,19 +29,52 @@ #include "webservice.h" #include "version.h" #include "libdivecomputer.h" +#include "qt-ui/mainwindow.h" #include #include -#include "subsurface-icon.h" + +#include +#include +#include +#include +#include +#include #include +class Translator: public QTranslator +{ + Q_OBJECT + +public: + Translator(QObject *parent = 0); + ~Translator() {} + + virtual QString translate(const char *context, const char *sourceText, + const char *disambiguation = NULL) const; +}; + +Translator::Translator(QObject *parent): + QTranslator(parent) +{ +} + +QString Translator::translate(const char *context, const char *sourceText, + const char *disambiguation) const +{ + return gettext(sourceText); +} + +static const GdkPixdata subsurface_icon_pixbuf = {}; + GtkWidget *main_window; GtkWidget *main_vbox; GtkWidget *error_info_bar; GtkWidget *error_label; GtkWidget *vpane, *hpane; GtkWidget *notebook; +static QApplication *application = NULL; int error_count; const char *existing_filename; @@ -93,6 +131,7 @@ static void on_info_bar_response(GtkWidget *widget, gint response, void report_error(GError* error) { + qDebug("Warning: Calling GTK-Specific Code."); if (error == NULL) { return; @@ -216,10 +255,12 @@ static void file_save(GtkWidget *w, gpointer data) static gboolean ask_save_changes() { + //WARNING: Porting to Qt + qDebug("This method is being ported to Qt, please, stop using it. "); GtkWidget *dialog, *label, *content; gboolean quit = TRUE; dialog = gtk_dialog_new_with_buttons(_("Save Changes?"), - GTK_WINDOW(main_window), GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_WINDOW(main_window), GtkDialogFlags(GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT), GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT, GTK_STOCK_NO, GTK_RESPONSE_NO, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, @@ -254,6 +295,7 @@ static gboolean ask_save_changes() static void file_close(GtkWidget *w, gpointer data) { + qDebug("Calling an already ported-to-qt Gtk method"); if (unsaved_changes()) if (ask_save_changes() == FALSE) return; @@ -282,8 +324,12 @@ static void file_close(GtkWidget *w, gpointer data) show_dive_info(NULL); } +//##################################################################### +//###### ALREAADY PORTED TO Qt. DELETE ME WHEN NOT MORE USERFUL. # +//##################################################################### static void file_open(GtkWidget *w, gpointer data) { + qDebug("Calling an already ported-to-qt Gtk method."); GtkWidget *dialog; GtkFileFilter *filter; const char *current_default; @@ -315,7 +361,7 @@ static void file_open(GtkWidget *w, gpointer data) fn_glist = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(dialog)); GError *error = NULL; - filename = fn_glist->data; + filename = (char *)fn_glist->data; parse_file(filename, &error); set_filename(filename, TRUE); if (error != NULL) @@ -605,16 +651,16 @@ void update_screen() update_dive_list_col_visibility(); } -UNITCALLBACK(set_meter, length, METERS) -UNITCALLBACK(set_feet, length, FEET) -UNITCALLBACK(set_bar, pressure, BAR) -UNITCALLBACK(set_psi, pressure, PSI) -UNITCALLBACK(set_liter, volume, LITER) -UNITCALLBACK(set_cuft, volume, CUFT) -UNITCALLBACK(set_celsius, temperature, CELSIUS) -UNITCALLBACK(set_fahrenheit, temperature, FAHRENHEIT) -UNITCALLBACK(set_kg, weight, KG) -UNITCALLBACK(set_lbs, weight, LBS) +UNITCALLBACK(set_meter, length, units::METERS) +UNITCALLBACK(set_feet, length, units::FEET) +UNITCALLBACK(set_bar, pressure, units::BAR) +UNITCALLBACK(set_psi, pressure, units::PSI) +UNITCALLBACK(set_liter, volume, units::LITER) +UNITCALLBACK(set_cuft, volume, units::CUFT) +UNITCALLBACK(set_celsius, temperature, units::CELSIUS) +UNITCALLBACK(set_fahrenheit, temperature, units::FAHRENHEIT) +UNITCALLBACK(set_kg, weight, units::KG) +UNITCALLBACK(set_lbs, weight, units::LBS) OPTIONCALLBACK(otu_toggle, prefs.visible_cols.otu) OPTIONCALLBACK(maxcns_toggle, prefs.visible_cols.maxcns) @@ -664,7 +710,7 @@ static gboolean gfhigh_edit(GtkWidget *w, GdkEvent *event, gpointer _data) static void event_toggle(GtkWidget *w, gpointer _data) { - gboolean *plot_ev = _data; + gboolean *plot_ev = (gboolean *)_data; *plot_ev = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(w)); } @@ -707,7 +753,7 @@ static void pick_default_file(GtkWidget *w, GtkButton *button) list = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(fs_dialog)); if (g_slist_length(list) == 1) - gtk_button_set_label(button, list->data); + gtk_button_set_label(button, (const gchar *)list->data); g_slist_free(list); } @@ -720,7 +766,7 @@ static void pick_default_file(GtkWidget *w, GtkButton *button) static GtkWidget * map_provider_widget() { - OsmGpsMapSource_t i; + int i; #if GTK_CHECK_VERSION(2,24,0) GtkWidget *combobox = gtk_combo_box_text_new(); @@ -772,28 +818,28 @@ static void preferences_dialog(GtkWidget *w, gpointer data) gtk_container_add(GTK_CONTAINER(frame), box); create_radio(box, _("Depth:"), - _("Meter"), set_meter, (prefs.units.length == METERS), - _("Feet"), set_feet, (prefs.units.length == FEET), + _("Meter"), set_meter, (prefs.units.length == units::METERS), + _("Feet"), set_feet, (prefs.units.length == units::FEET), NULL); create_radio(box, _("Pressure:"), - _("Bar"), set_bar, (prefs.units.pressure == BAR), - _("PSI"), set_psi, (prefs.units.pressure == PSI), + _("Bar"), set_bar, (prefs.units.pressure == units::BAR), + _("PSI"), set_psi, (prefs.units.pressure == units::PSI), NULL); create_radio(box, _("Volume:"), - _("Liter"), set_liter, (prefs.units.volume == LITER), - _("CuFt"), set_cuft, (prefs.units.volume == CUFT), + _("Liter"), set_liter, (prefs.units.volume == units::LITER), + _("CuFt"), set_cuft, (prefs.units.volume == units::CUFT), NULL); create_radio(box, _("Temperature:"), - _("Celsius"), set_celsius, (prefs.units.temperature == CELSIUS), - _("Fahrenheit"), set_fahrenheit, (prefs.units.temperature == FAHRENHEIT), + _("Celsius"), set_celsius, (prefs.units.temperature == units::CELSIUS), + _("Fahrenheit"), set_fahrenheit, (prefs.units.temperature == units::FAHRENHEIT), NULL); create_radio(box, _("Weight:"), - _("kg"), set_kg, (prefs.units.weight == KG), - _("lbs"), set_lbs, (prefs.units.weight == LBS), + _("kg"), set_kg, (prefs.units.weight == units::KG), + _("lbs"), set_lbs, (prefs.units.weight == units::LBS), NULL); frame = gtk_frame_new(_("Show Columns")); @@ -1072,14 +1118,14 @@ static void preferences_dialog(GtkWidget *w, gpointer data) prefs.default_filename = new_default; } /* get the map provider selected */ - OsmGpsMapSource_t i; + int i; #if GTK_CHECK_VERSION(2,24,0) char *provider = gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(map_provider)); #else char *provider = gtk_combo_box_get_active_text(GTK_COMBO_BOX(map_provider)); #endif for (i = OSM_GPS_MAP_SOURCE_OPENSTREETMAP; i <= OSM_GPS_MAP_SOURCE_YAHOO_STREET; i++) - if (!strcmp(provider,osm_gps_map_source_get_friendly_name(i))) { + if (!strcmp(provider,osm_gps_map_source_get_friendly_name((OsmGpsMapSource_t)i))) { prefs.map_provider = i; break; } @@ -1097,7 +1143,7 @@ static void preferences_dialog(GtkWidget *w, gpointer data) static void create_toggle(const char* label, int *on, void *_data) { - GtkWidget *button, *table = _data; + GtkWidget *button, *table = GTK_WIDGET(_data); int rows, cols, x, y; static int count; @@ -1413,7 +1459,7 @@ static void edit_dc_delete_rows(GtkTreeView *view) selected_rows = gtk_tree_selection_get_selected_rows(selection, &model); for (list = selected_rows; list; list = g_list_next(list)) { - path = list->data; + path = (GtkTreePath *)list->data; ref = gtk_tree_row_reference_new(model, path); row_references = g_list_append(row_references, ref); } @@ -1509,7 +1555,7 @@ static void edit_dc_nicknames(GtkWidget *w, gpointer data) dialog = gtk_dialog_new_with_buttons(_("Edit Dive Computer Nicknames"), GTK_WINDOW(main_window), - GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, + GtkDialogFlags(GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT), GTK_STOCK_DELETE, SUB_RESPONSE_DELETE, GTK_STOCK_CANCEL, @@ -1577,7 +1623,7 @@ static void edit_dc_nicknames(GtkWidget *w, gpointer data) if (res == SUB_RESPONSE_DELETE) { confirm = gtk_dialog_new_with_buttons(_("Delete a dive computer information entry"), GTK_WINDOW(dialog), - GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, + GtkDialogFlags(GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT), GTK_STOCK_YES, GTK_RESPONSE_YES, GTK_STOCK_NO, @@ -1773,8 +1819,143 @@ static gboolean notebook_tooltip (GtkWidget *widget, gint x, gint y, } } +#if NEEDS_TO_MOVE_TO_QT_UI +/* this appears to have moved - but it's very different in qt-ui */ + +class MainWindow: public QMainWindow, private Ui::MainWindow +{ + Q_OBJECT + +public: + MainWindow(QWidget *parent = 0); + ~MainWindow() {} + + void setCurrentFileName(const QString &fileName); + +private Q_SLOTS: + void on_actionNew_triggered() { on_actionClose_triggered(); } + void on_actionOpen_triggered(); + void on_actionSave_triggered() { file_save(NULL, NULL); } + void on_actionSaveAs_triggered() { file_save_as(NULL, NULL); } + void on_actionClose_triggered(); + +private: + QStringList fileNameFilters() const; + +private: + QString m_currentFileName; +}; + +MainWindow::MainWindow(QWidget *parent): + QMainWindow(parent) +{ + setupUi(this); +} + +void MainWindow::setCurrentFileName(const QString &fileName) +{ + if (fileName == m_currentFileName) return; + m_currentFileName = fileName; + + QString title = tr("Subsurface"); + if (!m_currentFileName.isEmpty()) { + QFileInfo fileInfo(m_currentFileName); + title += " - " + fileInfo.fileName(); + } + setWindowTitle(title); +} + +void MainWindow::on_actionOpen_triggered() +{ + QString defaultFileName = prefs.default_filename; + QFileInfo fileInfo(defaultFileName); + + QFileDialog dialog(this, tr("Open File"), fileInfo.path()); + dialog.setFileMode(QFileDialog::ExistingFile); + dialog.selectFile(defaultFileName); + dialog.setNameFilters(fileNameFilters()); + if (dialog.exec()) { + /* first, close the existing file, if any */ + file_close(NULL, NULL); + + /* we know there is only one filename */ + QString fileName = dialog.selectedFiles().first(); + GError *error = NULL; + parse_file(fileName.toUtf8().constData(), &error); + if (error != NULL) { + report_error(error); + g_error_free(error); + error = NULL; + } else { + setCurrentFileName(fileName); + } + report_dives(FALSE, FALSE); + } +} + +void MainWindow::on_actionClose_triggered() +{ + if (unsaved_changes()) + if (ask_save_changes() == FALSE) + return; + + setCurrentFileName(QString()); + + /* free the dives and trips */ + while (dive_table.nr) + delete_single_dive(0); + mark_divelist_changed(FALSE); + + /* clear the selection and the statistics */ + selected_dive = -1; + process_selected_dives(); + clear_stats_widgets(); + clear_events(); + show_dive_stats(NULL); + + /* clear the equipment page */ + clear_equipment_widgets(); + + /* redraw the screen */ + dive_list_update_dives(); + show_dive_info(NULL); +} + +QStringList MainWindow::fileNameFilters() const +{ + QStringList filters; + + filters << "*.xml *.uddf *.udcf *.jlb" +#ifdef LIBZIP + " *.sde *.dld" +#endif +#ifdef SQLITE3 + " *.db" +#endif + ; + return filters; +} +#endif /* NEEDS_TO_MOVE_TO_QT_UI */ + +void init_qt_ui(int *argcp, char ***argvp) +{ + application->installTranslator(new Translator(application)); + MainWindow *window = new MainWindow(); + window->show(); +} + void init_ui(int *argcp, char ***argvp) { + application = new QApplication(*argcp, *argvp); + +#if QT_VERSION < 0x050000 + // ask QString in Qt 4 to interpret all char* as UTF-8, + // like Qt 5 does. + // 106 is "UTF-8", this is faster than lookup by name + // [http://www.iana.org/assignments/character-sets/character-sets.xml] + QTextCodec::setCodecForCStrings(QTextCodec::codecForMib(106)); +#endif + GtkWidget *win; GtkWidget *nb_page; GtkWidget *dive_list; @@ -1900,11 +2081,12 @@ void init_ui(int *argcp, char ***argvp) void run_ui(void) { - gtk_main(); + application->exec(); } void exit_ui(void) { + delete application; subsurface_close_conf(); if (existing_filename) free((void *)existing_filename); @@ -1924,7 +2106,8 @@ static int tooltips; void attach_tooltip(int x, int y, int w, int h, const char *text, struct event *event) { cairo_rectangle_t *rect; - tooltip_rects = realloc(tooltip_rects, (tooltips + 1) * sizeof(tooltip_record_t)); + tooltip_rects = (tooltip_record_t *) + realloc(tooltip_rects, (tooltips + 1) * sizeof(tooltip_record_t)); rect = &tooltip_rects[tooltips].rect; rect->x = x; rect->y = y; @@ -2044,7 +2227,7 @@ static gboolean draw_callback(GtkWidget *widget, cairo_t *cr, gpointer data) static gboolean expose_event(GtkWidget *widget, GdkEventExpose *event, gpointer data) { GtkAllocation allocation; - static struct graphics_context gc = { .printer = 0 }; + static struct graphics_context gc = { 0 }; /* the drawing area gives TOTAL width * height - x,y is used as the topx/topy offset * so effective drawing area is width-2x * height-2y */ @@ -2090,7 +2273,7 @@ static gboolean scroll_event(GtkWidget *widget, GdkEventScroll *event, gpointer static void add_gas_change_cb(GtkWidget *menuitem, gpointer data) { - double *x = data; + double *x = (double *)data; int when = x_to_time(*x); int cylnr = select_cylinder(current_dive, when); if (cylnr >= 0) { @@ -2130,7 +2313,7 @@ int confirm_dialog(int when, char *action_text, char *event_text) static void add_bookmark_cb(GtkWidget *menuitem, gpointer data) { - double *x = data; + double *x = (double *)data; int when = x_to_time(*x); if (confirm_dialog(when, _("Add"), _("bookmark"))){ @@ -2158,7 +2341,7 @@ static struct event *event_at_x(double rel_x) static void remove_event_cb(GtkWidget *menuitem, gpointer data) { - struct event *event = data; + struct event *event = (struct event *)data; if (confirm_dialog(event->time.seconds, _("Remove"), _(event->name))){ struct event **ep = ¤t_dc->events; while (ep && *ep != event) @@ -2430,3 +2613,5 @@ gdouble get_screen_dpi(void) gdouble dpi_h = floor((h / h_mm) * mm_per_inch); return dpi_h; } + +#include "qt-gui.moc" diff --git a/helpers.h b/helpers.h new file mode 100644 index 000000000..8e72d6a20 --- /dev/null +++ b/helpers.h @@ -0,0 +1,19 @@ +/* + * helpers.h + * + * header file for random helper functions of Subsurface + * + */ +#ifndef HELPER_H +#define HELPER_H + +#include +#include "dive.h" + +QString get_depth_string(depth_t depth, bool showunit); +QString get_weight_string(weight_t weight, bool showunit); +QString get_temperature_string(temperature_t temp, bool showunit); +QString get_volume_string(volume_t volume, bool showunit); +QString get_pressure_string(pressure_t pressure, bool showunit); + +#endif /* HELPER_H */ diff --git a/info-gtk.c b/info-gtk.c index 61ed1c3b6..4ba9058cf 100644 --- a/info-gtk.c +++ b/info-gtk.c @@ -796,6 +796,14 @@ int edit_multi_dive_info(struct dive *single_dive) return success; } +int edit_dive_info(struct dive *dive, gboolean newdive) +{ + if (!dive || (!newdive && !amount_selected)) + return 0; + + return edit_multi_dive_info(dive); +} + static GtkWidget *frame_box(GtkWidget *vbox, const char *fmt, ...) { va_list ap; diff --git a/info.c b/info.c index 0abee1c1b..4e0c3cadb 100644 --- a/info.c +++ b/info.c @@ -448,10 +448,15 @@ void update_time_depth(struct dive *dive, struct dive *edited) dive->dc.meandepth.mm = edited->dc.meandepth.mm; } -int edit_dive_info(struct dive *dive, gboolean newdive) +void add_people(const char *string) { - if (!dive || (!newdive && !amount_selected)) - return 0; - - return edit_multi_dive_info(dive); + /* add names to the completion list for people */ +} +void add_location(const char *string) +{ + /* add names to the completion list for locations */ +} +void add_suit(const char *string) +{ + /* add names to the completion list for suits */ } diff --git a/libdivecomputer.c b/libdivecomputer.c index 9ad5df594..cc1c0be28 100644 --- a/libdivecomputer.c +++ b/libdivecomputer.c @@ -26,6 +26,7 @@ static double progress_bar_fraction = 0.0; static int stoptime, stopdepth, ndl, po2, cns; static gboolean in_deco, first_temp_is_air; +#if USE_GTK_UI static GError *error(const char *fmt, ...) { va_list args; @@ -38,6 +39,7 @@ static GError *error(const char *fmt, ...) va_end(args); return error; } +#endif static dc_status_t create_parser(device_data_t *devdata, dc_parser_t **parser) { @@ -708,6 +710,7 @@ static const char *do_libdivecomputer_import(device_data_t *data) return err; } +#if USE_GTK_UI static void *pthread_wrapper(void *_data) { device_data_t *data = _data; @@ -772,3 +775,4 @@ GError *do_import(device_data_t *data) return error(retval, data->vendor, data->product, data->devname); return NULL; } +#endif diff --git a/libdivecomputer.h b/libdivecomputer.h index 2fd42c91a..639a69c73 100644 --- a/libdivecomputer.h +++ b/libdivecomputer.h @@ -18,10 +18,12 @@ typedef struct device_data_t { unsigned int deviceid, diveid; dc_device_t *device; dc_context_t *context; - progressbar_t progress; int preexisting; gboolean force_download; +#if USE_GTK_UI + progressbar_t progress; GtkDialog *dialog; +#endif } device_data_t; extern GError *do_import(device_data_t *data); diff --git a/linux.c b/linux.c index 4add7bb01..33a846f9b 100644 --- a/linux.c +++ b/linux.c @@ -1,7 +1,10 @@ /* linux.c */ /* implements Linux specific functions */ #include "dive.h" +#include "display.h" +#if USE_GTK_UI #include "display-gtk.h" +#endif #include #include @@ -9,7 +12,7 @@ const char system_divelist_default_font[] = "Sans 8"; GConfClient *gconf; -static char *gconf_name(char *name) +static char *gconf_name(const char *name) { static char buf[255] = "/apps/subsurface/"; @@ -23,32 +26,32 @@ void subsurface_open_conf(void) gconf = gconf_client_get_default(); } -void subsurface_unset_conf(char *name) +void subsurface_unset_conf(const char *name) { gconf_client_unset(gconf, gconf_name(name), NULL); } -void subsurface_set_conf(char *name, const char *value) +void subsurface_set_conf(const char *name, const char *value) { gconf_client_set_string(gconf, gconf_name(name), value, NULL); } -void subsurface_set_conf_bool(char *name, int value) +void subsurface_set_conf_bool(const char *name, int value) { gconf_client_set_bool(gconf, gconf_name(name), value > 0, NULL); } -void subsurface_set_conf_int(char *name, int value) +void subsurface_set_conf_int(const char *name, int value) { gconf_client_set_int(gconf, gconf_name(name), value , NULL); } -const void *subsurface_get_conf(char *name) +const char *subsurface_get_conf(const char *name) { return gconf_client_get_string(gconf, gconf_name(name), NULL); } -int subsurface_get_conf_bool(char *name) +int subsurface_get_conf_bool(const char *name) { GConfValue *val; gboolean ret; @@ -61,7 +64,7 @@ int subsurface_get_conf_bool(char *name) return ret; } -int subsurface_get_conf_int(char *name) +int subsurface_get_conf_int(const char *name) { int val = gconf_client_get_int(gconf, gconf_name(name), NULL); if(!val) @@ -80,6 +83,7 @@ void subsurface_close_conf(void) /* this is a no-op */ } +#if USE_GTK_UI int subsurface_fill_device_list(GtkListStore *store) { int i = 0; @@ -139,6 +143,7 @@ int subsurface_fill_device_list(GtkListStore *store) } return index; } +#endif /* USE_GTK_UI */ const char *subsurface_icon_name() { @@ -170,11 +175,13 @@ const char *subsurface_gettext_domainpath(char *argv0) } } +#if USE_GTK_UI void subsurface_ui_setup(GtkSettings *settings, GtkWidget *menubar, GtkWidget *vbox, GtkUIManager *ui_manager) { gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, FALSE, 0); } +#endif /* USE_GTK_UI */ void subsurface_command_line_init(gint *argc, gchar ***argv) { @@ -191,6 +198,7 @@ gboolean subsurface_os_feature_available(os_feature_t f) return TRUE; } +#if USE_GTK_UI gboolean subsurface_launch_for_uri(const char* uri) { GError *err = NULL; @@ -202,3 +210,4 @@ gboolean subsurface_launch_for_uri(const char* uri) } return TRUE; } +#endif /* USE_GTK_UI */ diff --git a/macos.c b/macos.c index aee4c73ca..b43849f87 100644 --- a/macos.c +++ b/macos.c @@ -2,7 +2,10 @@ /* implements Mac OS X specific functions */ #include #include "dive.h" +#include "display.h" +#if USE_GTK_UI #include "display-gtk.h" +#endif /* USE_GTK_UI */ #include #include #include @@ -30,29 +33,29 @@ void subsurface_open_conf(void) /* nothing at this time */ } -void subsurface_unset_conf(char *name) +void subsurface_unset_conf(const char *name) { CFPreferencesSetAppValue(CFSTR_VAR(name), NULL, SUBSURFACE_PREFERENCES); } -void subsurface_set_conf(char *name, const char *value) +void subsurface_set_conf(const char *name, const char *value) { CFPreferencesSetAppValue(CFSTR_VAR(name), CFSTR_VAR(value), SUBSURFACE_PREFERENCES); } -void subsurface_set_conf_bool(char *name, int value) +void subsurface_set_conf_bool(const char *name, int value) { CFPreferencesSetAppValue(CFSTR_VAR(name), value ? kCFBooleanTrue : kCFBooleanFalse, SUBSURFACE_PREFERENCES); } -void subsurface_set_conf_int(char *name, int value) +void subsurface_set_conf_int(const char *name, int value) { CFNumberRef numRef = CFNumberCreate(NULL, kCFNumberIntType, &value); CFPreferencesSetAppValue(CFSTR_VAR(name), numRef, SUBSURFACE_PREFERENCES); } -const void *subsurface_get_conf(char *name) +const char *subsurface_get_conf(const char *name) { CFPropertyListRef strpref; @@ -62,7 +65,7 @@ const void *subsurface_get_conf(char *name) return strdup(CFStringGetCStringPtr(strpref, kCFStringEncodingMacRoman)); } -int subsurface_get_conf_bool(char *name) +int subsurface_get_conf_bool(const char *name) { Boolean boolpref, exists; @@ -72,7 +75,7 @@ int subsurface_get_conf_bool(char *name) return boolpref; } -int subsurface_get_conf_int(char *name) +int subsurface_get_conf_int(const char *name) { Boolean exists; CFIndex value; @@ -151,8 +154,11 @@ const char *subsurface_icon_name() { static char path[PATH_MAX]; +#if USE_GTK_UI snprintf(path, sizeof(path), "%s/%s", gtkosx_application_get_resource_path(), ICON_NAME); - +#else + /* need Qt path */ +#endif return path; } @@ -174,15 +180,18 @@ const char *subsurface_gettext_domainpath(char *argv0) { /* on a Mac we ignore the argv0 argument and instead use the resource_path * to figure out where to find the translation files */ +#if USE_GTK_UI static char buffer[PATH_MAX]; const char *resource_path = gtkosx_application_get_resource_path(); if (resource_path) { snprintf(buffer, sizeof(buffer), "%s/share/locale", resource_path); return buffer; } +#endif /* USE_GTK_UI */ return "./share/locale"; } +#if USE_GTK_UI static void show_main_window(GtkWidget *w, gpointer data) { gtk_widget_show(main_window); @@ -230,6 +239,7 @@ void subsurface_ui_setup(GtkSettings *settings, GtkWidget *menubar, gtkosx_application_ready(osx_app); } +#endif /* UES_GTK_UI */ void subsurface_command_line_init(gint *argc, gchar ***argv) { diff --git a/main.c b/main.c index a57e12981..510dbfa68 100644 --- a/main.c +++ b/main.c @@ -10,7 +10,10 @@ #include "dive.h" #include "divelist.h" +#ifdef USE_GTK_UI #include +#endif + #ifdef DEBUGFILE char *debugfilename; FILE *debugfile; @@ -36,7 +39,9 @@ struct preferences default_prefs = { .calc_ceiling_3m_incr = FALSE, .gflow = 0.30, .gfhigh = 0.75, +#ifdef USE_GTK_UI .map_provider = OSM_GPS_MAP_SOURCE_VIRTUAL_EARTH_HYBRID, +#endif }; /* random helper functions, used here or elsewhere */ @@ -76,138 +81,11 @@ const char *monthname(int mon) return _(month_array[mon]); } -/* - * When adding dives to the dive table, we try to renumber - * the new dives based on any old dives in the dive table. - * - * But we only do it if: - * - * - there are no dives in the dive table - * - * OR - * - * - the last dive in the old dive table was numbered - * - * - all the new dives are strictly at the end (so the - * "last dive" is at the same location in the dive table - * after re-sorting the dives. - * - * - none of the new dives have any numbers - * - * This catches the common case of importing new dives from - * a dive computer, and gives them proper numbers based on - * your old dive list. But it tries to be very conservative - * and not give numbers if there is *any* question about - * what the numbers should be - in which case you need to do - * a manual re-numbering. - */ -static void try_to_renumber(struct dive *last, int preexisting) -{ - int i, nr; - - /* - * If the new dives aren't all strictly at the end, - * we're going to expect the user to do a manual - * renumbering. - */ - if (preexisting && get_dive(preexisting-1) != last) - return; - - /* - * If any of the new dives already had a number, - * we'll have to do a manual renumbering. - */ - for (i = preexisting; i < dive_table.nr; i++) { - struct dive *dive = get_dive(i); - if (dive->number) - return; - } - - /* - * Ok, renumber.. - */ - if (last) - nr = last->number; - else - nr = 0; - for (i = preexisting; i < dive_table.nr; i++) { - struct dive *dive = get_dive(i); - dive->number = ++nr; - } -} - /* * track whether we switched to importing dives */ 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 is_imported, gboolean prefer_imported) -{ - int i; - int preexisting = dive_table.preexisting; - struct dive *last; - - /* check if we need a nickname for the divecomputer for newly downloaded dives; - * since we know they all came from the same divecomputer we just check for the - * first one */ - if (preexisting < dive_table.nr && dive_table.dives[preexisting]->downloaded) - set_dc_nickname(dive_table.dives[preexisting]); - else - /* they aren't downloaded, so record / check all new ones */ - for (i = preexisting; i < dive_table.nr; i++) - set_dc_nickname(dive_table.dives[i]); - - /* This does the right thing for -1: NULL */ - last = get_dive(preexisting-1); - - sort_table(&dive_table); - - for (i = 1; i < dive_table.nr; i++) { - struct dive **pp = &dive_table.dives[i-1]; - struct dive *prev = pp[0]; - struct dive *dive = pp[1]; - struct dive *merged; - - /* only try to merge overlapping dives - or if one of the dives has - * zero duration (that might be a gps marker from the webservice) */ - if (prev->duration.seconds && dive->duration.seconds && - prev->when + prev->duration.seconds < dive->when) - continue; - - merged = try_to_merge(prev, dive, prefer_imported); - if (!merged) - continue; - - /* careful - we might free the dive that last points to. Oops... */ - if (last == prev || last == dive) - last = merged; - - /* Redo the new 'i'th dive */ - i--; - add_single_dive(i, merged); - delete_single_dive(i+1); - delete_single_dive(i+1); - } - /* make sure no dives are still marked as downloaded */ - for (i = 1; i < dive_table.nr; i++) - dive_table.dives[i]->downloaded = FALSE; - - if (is_imported) { - /* If there are dives in the table, are they numbered */ - if (!last || last->number) - try_to_renumber(last, preexisting); - - /* did we add dives to the dive table? */ - if (preexisting != dive_table.nr) - mark_divelist_changed(TRUE); - } - dive_list_update_dives(); -} - static void parse_argument(const char *arg) { const char *p = arg+1; @@ -222,7 +100,11 @@ static void parse_argument(const char *arg) if (strcmp(arg,"--import") == 0) { /* mark the dives so far as the base, * everything after is imported */ +#if USE_GTK_UI report_dives(FALSE, FALSE); +#else + process_dives(FALSE, FALSE); +#endif imported = TRUE; return; } @@ -242,6 +124,7 @@ static void parse_argument(const char *arg) void update_dive(struct dive *new_dive) { +#if USE_GTK_UI static struct dive *buffered_dive; struct dive *old_dive = buffered_dive; @@ -252,6 +135,7 @@ void update_dive(struct dive *new_dive) show_dive_equipment(new_dive, W_IDX_PRIMARY); show_dive_stats(new_dive); buffered_dive = new_dive; +#endif } void renumber_dives(int nr) @@ -261,7 +145,9 @@ void renumber_dives(int nr) for (i = 0; i < dive_table.nr; i++) { struct dive *dive = dive_table.dives[i]; dive->number = nr + i; +#if USE_GTK_UI flush_divelist(dive); +#endif } mark_divelist_changed(TRUE); } @@ -331,7 +217,7 @@ int main(int argc, char **argv) subsurface_command_line_init(&argc, &argv); parse_xml_init(); - init_ui(&argc, &argv); + init_ui(&argc, &argv); /* the gtk stuff is needed for parsing below */ for (i = 1; i < argc; i++) { const char *a = argv[i]; @@ -352,7 +238,9 @@ int main(int argc, char **argv) } if (error != NULL) { +#if USE_GTK_UI report_error(error); +#endif g_error_free(error); error = NULL; } @@ -365,15 +253,20 @@ int main(int argc, char **argv) sure we remember this as the filename in use */ set_filename(filename, FALSE); } +#if USE_GTK_UI report_dives(imported, FALSE); if (dive_table.nr == 0) show_dive_info(NULL); - run_ui(); - exit_ui(); +#else + process_dives(imported, FALSE); +#endif parse_xml_exit(); subsurface_command_line_exit(&argc, &argv); + init_qt_ui(&argc, &argv); /* qt bit delayed until dives are parsed */ + run_ui(); + exit_ui(); #ifdef DEBUGFILE if (debugfile) fclose(debugfile); diff --git a/packaging/windows/mingw-make.sh b/packaging/windows/mingw-make.sh index 6c2375114..8bb891752 100755 --- a/packaging/windows/mingw-make.sh +++ b/packaging/windows/mingw-make.sh @@ -11,7 +11,7 @@ rm packaging/windows/subsurface.nsi export PATH=/usr/i686-w64-mingw32/sys-root/mingw/bin:$PATH -make CC=i686-w64-mingw32-gcc \ +make CC=i686-w64-mingw32-gcc CXX=i686-w64-mingw32-g++ \ PKGCONFIG=i686-w64-mingw32-pkg-config \ PKG_CONFIG_PATH=/usr/i686-w64-mingw32/sys-root/mingw/lib/pkgconfig/ \ CROSS_PATH=/usr/i686-w64-mingw32/sys-root/mingw/ \ diff --git a/packaging/windows/subsurface.res b/packaging/windows/subsurface.res deleted file mode 100644 index 616cf5ea9..000000000 Binary files a/packaging/windows/subsurface.res and /dev/null differ diff --git a/planner.c b/planner.c index 71273b3a1..a707f7552 100644 --- a/planner.c +++ b/planner.c @@ -699,9 +699,11 @@ void plan(struct diveplan *diveplan, char **cached_datap, struct dive **divep, c stopidx--; } add_plan_to_notes(diveplan, dive); +#if USE_GTK_UI /* now make the dive visible in the dive list */ report_dives(FALSE, FALSE); show_and_select_dive(dive); +#endif error_exit: free(stoplevels); free(gaschanges); diff --git a/pref.h b/pref.h index 3c6b43c70..db26aee0d 100644 --- a/pref.h +++ b/pref.h @@ -1,6 +1,10 @@ #ifndef PREF_H #define PREF_H +#ifdef __cplusplus +extern "C" { +#endif + typedef struct { gboolean cylinder; gboolean temperature; @@ -44,13 +48,13 @@ extern struct preferences prefs, default_prefs; #define PP_GRAPHS_ENABLED (prefs.pp_graphs.po2 || prefs.pp_graphs.pn2 || prefs.pp_graphs.phe) extern void subsurface_open_conf(void); -extern void subsurface_set_conf(char *name, const char *value); -extern void subsurface_set_conf_bool(char *name, gboolean value); -extern void subsurface_set_conf_int(char *name, int value); -extern void subsurface_unset_conf(char *name); -extern const void *subsurface_get_conf(char *name); -extern int subsurface_get_conf_bool(char *name); -extern int subsurface_get_conf_int(char *name); +extern void subsurface_set_conf(const char *name, const char *value); +extern void subsurface_set_conf_bool(const char *name, gboolean value); +extern void subsurface_set_conf_int(const char *name, int value); +extern void subsurface_unset_conf(const char *name); +extern const char *subsurface_get_conf(const char *name); +extern int subsurface_get_conf_bool(const char *name); +extern int subsurface_get_conf_int(const char *name); extern void subsurface_flush_conf(void); extern void subsurface_close_conf(void); @@ -60,4 +64,8 @@ extern const char *system_default_filename(); extern void load_preferences(void); extern void save_preferences(void); +#ifdef __cplusplus +} +#endif + #endif /* PREF_H */ diff --git a/profile.c b/profile.c index e40ee5bad..e6ba6ad8d 100644 --- a/profile.c +++ b/profile.c @@ -6,9 +6,12 @@ #include "dive.h" #include "display.h" +#if USE_GTK_UI #include "display-gtk.h" +#endif #include "divelist.h" -#include "color.h" + +#include "profile.h" #include "libdivecomputer/parser.h" #include "libdivecomputer/version.h" @@ -16,130 +19,13 @@ int selected_dive = -1; /* careful: 0 is a valid value */ char zoomed_plot = 0; char dc_number = 0; -static double plot_scale = SCALE_SCREEN; + static struct plot_data *last_pi_entry = NULL; #define cairo_set_line_width_scaled(cr, w) \ cairo_set_line_width((cr), (w) * plot_scale); -typedef enum { STABLE, SLOW, MODERATE, FAST, CRAZY } velocity_t; - -struct plot_data { - unsigned int in_deco:1; - unsigned int cylinderindex; - int sec; - /* pressure[0] is sensor pressure - * pressure[1] is interpolated pressure */ - int pressure[2]; - int temperature; - /* Depth info */ - int depth; - int ceiling; - int ndl; - int stoptime; - int stopdepth; - int cns; - int smoothed; - double po2, pn2, phe; - double mod, ead, end, eadd; - velocity_t velocity; - struct plot_data *min[3]; - struct plot_data *max[3]; - int avg[3]; -}; - -#define SENSOR_PR 0 -#define INTERPOLATED_PR 1 -#define SENSOR_PRESSURE(_entry) (_entry)->pressure[SENSOR_PR] -#define INTERPOLATED_PRESSURE(_entry) (_entry)->pressure[INTERPOLATED_PR] -#define GET_PRESSURE(_entry) (SENSOR_PRESSURE(_entry) ? : INTERPOLATED_PRESSURE(_entry)) - -#define SAC_COLORS_START_IDX SAC_1 -#define SAC_COLORS 9 -#define VELOCITY_COLORS_START_IDX VELO_STABLE -#define VELOCITY_COLORS 5 - -typedef enum { - /* SAC colors. Order is important, the SAC_COLORS_START_IDX define above. */ - SAC_1, SAC_2, SAC_3, SAC_4, SAC_5, SAC_6, SAC_7, SAC_8, SAC_9, - - /* Velocity colors. Order is still important, ref VELOCITY_COLORS_START_IDX. */ - VELO_STABLE, VELO_SLOW, VELO_MODERATE, VELO_FAST, VELO_CRAZY, - - /* gas colors */ - PO2, PO2_ALERT, PN2, PN2_ALERT, PHE, PHE_ALERT, PP_LINES, - - /* Other colors */ - TEXT_BACKGROUND, ALERT_BG, ALERT_FG, EVENTS, SAMPLE_DEEP, SAMPLE_SHALLOW, - SMOOTHED, MINUTE, TIME_GRID, TIME_TEXT, DEPTH_GRID, MEAN_DEPTH, DEPTH_TOP, - DEPTH_BOTTOM, TEMP_TEXT, TEMP_PLOT, SAC_DEFAULT, BOUNDING_BOX, PRESSURE_TEXT, BACKGROUND, - CEILING_SHALLOW, CEILING_DEEP, CALC_CEILING_SHALLOW, CALC_CEILING_DEEP -} color_indice_t; - -typedef struct { - /* media[0] is screen, media[1] is b/w printer media[2] is color printer */ - struct rgba { - double r,g,b,a; - } media[3]; -} color_t; - -/* [color indice] = {{screen color, b/w printer color, color printer}} printer & screen colours could be different */ -static const color_t profile_color[] = { - [SAC_1] = {{FUNGREEN1, BLACK1_LOW_TRANS, FUNGREEN1}}, - [SAC_2] = {{APPLE1, BLACK1_LOW_TRANS, APPLE1}}, - [SAC_3] = {{ATLANTIS1, BLACK1_LOW_TRANS, ATLANTIS1}}, - [SAC_4] = {{ATLANTIS2, BLACK1_LOW_TRANS, ATLANTIS2}}, - [SAC_5] = {{EARLSGREEN1, BLACK1_LOW_TRANS, EARLSGREEN1}}, - [SAC_6] = {{HOKEYPOKEY1, BLACK1_LOW_TRANS, HOKEYPOKEY1}}, - [SAC_7] = {{TUSCANY1, BLACK1_LOW_TRANS, TUSCANY1}}, - [SAC_8] = {{CINNABAR1, BLACK1_LOW_TRANS, CINNABAR1}}, - [SAC_9] = {{REDORANGE1, BLACK1_LOW_TRANS, REDORANGE1}}, - - [VELO_STABLE] = {{CAMARONE1, BLACK1_LOW_TRANS, CAMARONE1}}, - [VELO_SLOW] = {{LIMENADE1, BLACK1_LOW_TRANS, LIMENADE1}}, - [VELO_MODERATE] = {{RIOGRANDE1, BLACK1_LOW_TRANS, RIOGRANDE1}}, - [VELO_FAST] = {{PIRATEGOLD1, BLACK1_LOW_TRANS, PIRATEGOLD1}}, - [VELO_CRAZY] = {{RED1, BLACK1_LOW_TRANS, RED1}}, - - [PO2] = {{APPLE1, BLACK1_LOW_TRANS, APPLE1}}, - [PO2_ALERT] = {{RED1, BLACK1_LOW_TRANS, RED1}}, - [PN2] = {{BLACK1_LOW_TRANS, BLACK1_LOW_TRANS, BLACK1_LOW_TRANS}}, - [PN2_ALERT] = {{RED1, BLACK1_LOW_TRANS, RED1}}, - [PHE] = {{PEANUT, BLACK1_LOW_TRANS, PEANUT}}, - [PHE_ALERT] = {{RED1, BLACK1_LOW_TRANS, RED1}}, - [PP_LINES] = {{BLACK1_HIGH_TRANS, BLACK1_HIGH_TRANS, BLACK1_HIGH_TRANS}}, - - [TEXT_BACKGROUND] = {{CONCRETE1_LOWER_TRANS, WHITE1, CONCRETE1_LOWER_TRANS}}, - [ALERT_BG] = {{BROOM1_LOWER_TRANS, BLACK1_LOW_TRANS, BROOM1_LOWER_TRANS}}, - [ALERT_FG] = {{BLACK1_LOW_TRANS, BLACK1_LOW_TRANS, BLACK1_LOW_TRANS}}, - [EVENTS] = {{REDORANGE1, BLACK1_LOW_TRANS, REDORANGE1}}, - [SAMPLE_DEEP] = {{PERSIANRED1, BLACK1_LOW_TRANS, PERSIANRED1}}, - [SAMPLE_SHALLOW] = {{PERSIANRED1, BLACK1_LOW_TRANS, PERSIANRED1}}, - [SMOOTHED] = {{REDORANGE1_HIGH_TRANS, BLACK1_LOW_TRANS, REDORANGE1_HIGH_TRANS}}, - [MINUTE] = {{MEDIUMREDVIOLET1_HIGHER_TRANS, BLACK1_LOW_TRANS, MEDIUMREDVIOLET1_HIGHER_TRANS}}, - [TIME_GRID] = {{WHITE1, BLACK1_HIGH_TRANS, TUNDORA1_MED_TRANS}}, - [TIME_TEXT] = {{FORESTGREEN1, BLACK1_LOW_TRANS, FORESTGREEN1}}, - [DEPTH_GRID] = {{WHITE1, BLACK1_HIGH_TRANS, TUNDORA1_MED_TRANS}}, - [MEAN_DEPTH] = {{REDORANGE1_MED_TRANS, BLACK1_LOW_TRANS, REDORANGE1_MED_TRANS}}, - [DEPTH_BOTTOM] = {{GOVERNORBAY1_MED_TRANS, BLACK1_HIGH_TRANS, GOVERNORBAY1_MED_TRANS}}, - [DEPTH_TOP] = {{MERCURY1_MED_TRANS, WHITE1_MED_TRANS, MERCURY1_MED_TRANS}}, - [TEMP_TEXT] = {{GOVERNORBAY2, BLACK1_LOW_TRANS, GOVERNORBAY2}}, - [TEMP_PLOT] = {{ROYALBLUE2_LOW_TRANS, BLACK1_LOW_TRANS, ROYALBLUE2_LOW_TRANS}}, - [SAC_DEFAULT] = {{WHITE1, BLACK1_LOW_TRANS, FORESTGREEN1}}, - [BOUNDING_BOX] = {{WHITE1, BLACK1_LOW_TRANS, TUNDORA1_MED_TRANS}}, - [PRESSURE_TEXT] = {{KILLARNEY1, BLACK1_LOW_TRANS, KILLARNEY1}}, - [BACKGROUND] = {{SPRINGWOOD1, BLACK1_LOW_TRANS, SPRINGWOOD1}}, - [CEILING_SHALLOW] = {{REDORANGE1_HIGH_TRANS, BLACK1_HIGH_TRANS, REDORANGE1_HIGH_TRANS}}, - [CEILING_DEEP] = {{RED1_MED_TRANS, BLACK1_HIGH_TRANS, RED1_MED_TRANS}}, - [CALC_CEILING_SHALLOW] = {{FUNGREEN1_HIGH_TRANS, BLACK1_HIGH_TRANS, FUNGREEN1_HIGH_TRANS}}, - [CALC_CEILING_DEEP] = {{APPLE1_HIGH_TRANS, BLACK1_HIGH_TRANS, APPLE1_HIGH_TRANS}}, - -}; - -/* Scale to 0,0 -> maxx,maxy */ -#define SCALEX(gc,x) (((x)-gc->leftx)/(gc->rightx-gc->leftx)*gc->maxx) -#define SCALEY(gc,y) (((y)-gc->topy)/(gc->bottomy-gc->topy)*gc->maxy) -#define SCALE(gc,x,y) SCALEX(gc,x),SCALEY(gc,y) +#if USE_GTK_UI /* keep the last used gc around so we can invert the SCALEX calculation in * order to calculate a time value for an x coordinate */ @@ -155,42 +41,7 @@ int x_abs(double x) { return x - last_gc.drawing_area.x; } - -static void move_to(struct graphics_context *gc, double x, double y) -{ - cairo_move_to(gc->cr, SCALE(gc, x, y)); -} - -static void line_to(struct graphics_context *gc, double x, double y) -{ - cairo_line_to(gc->cr, SCALE(gc, x, y)); -} - -static void set_source_rgba(struct graphics_context *gc, color_indice_t c) -{ - const color_t *col = &profile_color[c]; - struct rgba rgb = col->media[gc->printer]; - double r = rgb.r; - double g = rgb.g; - double b = rgb.b; - double a = rgb.a; - - cairo_set_source_rgba(gc->cr, r, g, b, a); -} - -void init_profile_background(struct graphics_context *gc) -{ - set_source_rgba(gc, BACKGROUND); -} - -static void pattern_add_color_stop_rgba(struct graphics_context *gc, cairo_pattern_t *pat, double o, color_indice_t c) -{ - const color_t *col = &profile_color[c]; - struct rgba rgb = col->media[gc->printer]; - cairo_pattern_add_color_stop_rgba(pat, o, rgb.r, rgb.g, rgb.b, rgb.a); -} - -#define ROUND_UP(x,y) ((((x)+(y)-1)/(y))*(y)) +#endif /* USE_GTK_UI */ /* debugging tool - not normally used */ static void dump_pi (struct plot_info *pi) @@ -215,6 +66,8 @@ static void dump_pi (struct plot_info *pi) printf(" }\n"); } +#define ROUND_UP(x,y) ((((x)+(y)-1)/(y))*(y)) + /* * When showing dive profiles, we scale things to the * current dive. However, we don't scale past less than @@ -223,7 +76,7 @@ static void dump_pi (struct plot_info *pi) * We also need to add 180 seconds at the end so the min/max * plots correctly */ -static int get_maxtime(struct plot_info *pi) +int get_maxtime(struct plot_info *pi) { int seconds = pi->maxtime; if (zoomed_plot) { @@ -246,7 +99,7 @@ static int get_maxtime(struct plot_info *pi) /* get the maximum depth to which we want to plot * take into account the additional verical space needed to plot * partial pressure graphs */ -static int get_maxdepth(struct plot_info *pi) +int get_maxdepth(struct plot_info *pi) { unsigned mm = pi->maxdepth; int md; @@ -262,61 +115,10 @@ static int get_maxdepth(struct plot_info *pi) return md; } -typedef struct { - double size; - color_indice_t color; - double hpos, vpos; -} text_render_options_t; - -#define RIGHT (-1.0) -#define CENTER (-0.5) -#define LEFT (0.0) - -#define TOP (1) -#define MIDDLE (0) -#define BOTTOM (-1) - -static void plot_text(struct graphics_context *gc, const text_render_options_t *tro, - double x, double y, const char *fmt, ...) -{ - cairo_t *cr = gc->cr; - cairo_font_extents_t fe; - cairo_text_extents_t extents; - double dx, dy; - char buffer[256]; - va_list args; - - va_start(args, fmt); - vsnprintf(buffer, sizeof(buffer), fmt, args); - va_end(args); - - cairo_set_font_size(cr, tro->size * plot_scale); - cairo_font_extents(cr, &fe); - cairo_text_extents(cr, buffer, &extents); - dx = tro->hpos * (extents.width + extents.x_bearing); - dy = tro->vpos * (extents.height + fe.descent); - move_to(gc, x, y); - cairo_rel_move_to(cr, dx, dy); - - cairo_text_path(cr, buffer); - set_source_rgba(gc, TEXT_BACKGROUND); - cairo_stroke(cr); - - move_to(gc, x, y); - cairo_rel_move_to(cr, dx, dy); - - set_source_rgba(gc, tro->color); - cairo_show_text(cr, buffer); -} - /* collect all event names and whether we display them */ -struct ev_select { - char *ev_name; - gboolean plot_ev; -}; -static struct ev_select *ev_namelist; -static int evn_allocated; -static int evn_used; +struct ev_select *ev_namelist; +int evn_allocated; +int evn_used; int evn_foreach(void (*callback)(const char *, int *, void *), void *data) { @@ -357,146 +159,51 @@ void remember_event(const char *eventname) evn_used++; } -static void plot_one_event(struct graphics_context *gc, struct plot_info *pi, struct event *event) +int setup_temperature_limits(struct graphics_context *gc) { - int i, depth = 0; - int x,y; - char buffer[256]; - - /* is plotting this event disabled? */ - if (event->name) { - for (i = 0; i < evn_used; i++) { - if (! strcmp(event->name, ev_namelist[i].ev_name)) { - if (ev_namelist[i].plot_ev) - break; - else - return; - } - } - } - if (event->time.seconds < 30 && !strcmp(event->name, "gaschange")) - /* a gas change in the first 30 seconds is the way of some dive computers - * to tell us the gas that is used; let's not plot a marker for that */ - return; - - for (i = 0; i < pi->nr; i++) { - struct plot_data *data = pi->entry + i; - if (event->time.seconds < data->sec) - break; - depth = data->depth; - } - /* draw a little triangular marker and attach tooltip */ - x = SCALEX(gc, event->time.seconds); - y = SCALEY(gc, depth); - set_source_rgba(gc, ALERT_BG); - cairo_move_to(gc->cr, x-6, y+12); - cairo_line_to(gc->cr, x+6, y+12); - cairo_line_to(gc->cr, x , y); - cairo_line_to(gc->cr, x-6, y+12); - cairo_stroke_preserve(gc->cr); - cairo_fill(gc->cr); - set_source_rgba(gc, ALERT_FG); - cairo_move_to(gc->cr, x, y+3); - cairo_line_to(gc->cr, x, y+7); - cairo_move_to(gc->cr, x, y+10); - cairo_line_to(gc->cr, x, y+10); - cairo_stroke(gc->cr); - /* we display the event on screen - so translate */ - if (event->value) { - if (event->name && !strcmp(event->name, "gaschange")) { - unsigned int he = event->value >> 16; - unsigned int o2 = event->value & 0xffff; - if (he) { - snprintf(buffer, sizeof(buffer), "%s:%u/%u", - _(event->name), o2, he); - } else { - if (o2 == 21) - snprintf(buffer, sizeof(buffer), "%s:%s", - _(event->name), _("air")); - else - snprintf(buffer, sizeof(buffer), "%s:%u%% %s", - _(event->name), o2, "O" UTF8_SUBSCRIPT_2); - } - } else if (event->name && !strcmp(event->name, "SP change")) { - snprintf(buffer, sizeof(buffer), "%s:%0.1f", _(event->name), (double) event->value / 1000); - } else { - snprintf(buffer, sizeof(buffer), "%s:%d", _(event->name), event->value); - } - } else if (event->name && !strcmp(event->name, "SP change")) { - snprintf(buffer, sizeof(buffer), _("Bailing out to OC")); - } else { - snprintf(buffer, sizeof(buffer), "%s%s", _(event->name), - event->flags == SAMPLE_FLAGS_BEGIN ? C_("Starts with space!"," begin") : - event->flags == SAMPLE_FLAGS_END ? C_("Starts with space!", " end") : ""); - } - attach_tooltip(x-6, y, 12, 12, buffer, event); -} - -static void plot_events(struct graphics_context *gc, struct plot_info *pi, struct divecomputer *dc) -{ - struct event *event = dc->events; - - if (gc->printer) - return; - - while (event) { - plot_one_event(gc, pi, event); - event = event->next; - } -} - -static void render_depth_sample(struct graphics_context *gc, struct plot_data *entry, const text_render_options_t *tro) -{ - int sec = entry->sec, decimals; - double d; - - d = get_depth_units(entry->depth, &decimals, NULL); - - plot_text(gc, tro, sec, entry->depth, "%.*f", decimals, d); -} - -static void plot_text_samples(struct graphics_context *gc, struct plot_info *pi) -{ - static const text_render_options_t deep = {14, SAMPLE_DEEP, CENTER, TOP}; - static const text_render_options_t shallow = {14, SAMPLE_SHALLOW, CENTER, BOTTOM}; - int i; - int last = -1; - - for (i = 0; i < pi->nr; i++) { - struct plot_data *entry = pi->entry + i; - - if (entry->depth < 2000) - continue; - - if ((entry == entry->max[2]) && entry->depth != last) { - render_depth_sample(gc, entry, &deep); - last = entry->depth; - } - - if ((entry == entry->min[2]) && entry->depth != last) { - render_depth_sample(gc, entry, &shallow); - last = entry->depth; - } - - if (entry->depth != last) - last = -1; - } -} - -static void plot_depth_text(struct graphics_context *gc, struct plot_info *pi) -{ - int maxtime, maxdepth; + int maxtime, mintemp, maxtemp, delta; + struct plot_info *pi = &gc->pi; /* Get plot scaling limits */ maxtime = get_maxtime(pi); - maxdepth = get_maxdepth(pi); + mintemp = pi->mintemp; + maxtemp = pi->maxtemp; gc->leftx = 0; gc->rightx = maxtime; - gc->topy = 0; gc->bottomy = maxdepth; + /* Show temperatures in roughly the lower third, but make sure the scale + is at least somewhat reasonable */ + delta = maxtemp - mintemp; + if (delta < 3000) /* less than 3K in fluctuation */ + delta = 3000; + gc->topy = maxtemp + delta*2; - plot_text_samples(gc, pi); + if (PP_GRAPHS_ENABLED) + gc->bottomy = mintemp - delta * 2; + else + gc->bottomy = mintemp - delta / 3; + + pi->endtempcoord = SCALEY(gc, pi->mintemp); + return maxtemp && maxtemp >= mintemp; } +void setup_pp_limits(struct graphics_context *gc) +{ + int maxdepth; + + gc->leftx = 0; + gc->rightx = get_maxtime(&gc->pi); + + /* the maxdepth already includes extra vertical space - and if + * we use 1.5 times the corresponding pressure as maximum partial + * pressure the graph seems to look fine*/ + maxdepth = get_maxdepth(&gc->pi); + gc->topy = 1.5 * (maxdepth + 10000) / 10000.0 * SURFACE_PRESSURE / 1000; + gc->bottomy = -gc->topy / 20; +} + + +#if 0 + static void plot_smoothed_profile(struct graphics_context *gc, struct plot_info *pi) { int i; @@ -540,496 +247,30 @@ static void plot_minmax_profile(struct graphics_context *gc, struct plot_info *p plot_minmax_profile_minute(gc, pi, 0); } -static void plot_depth_scale(struct graphics_context *gc, struct plot_info *pi) -{ - int i, maxdepth, marker; - static const text_render_options_t tro = {DEPTH_TEXT_SIZE, SAMPLE_DEEP, RIGHT, MIDDLE}; +#endif /* USE_GTK_UI */ - /* Depth markers: every 30 ft or 10 m*/ - maxdepth = get_maxdepth(pi); - gc->topy = 0; gc->bottomy = maxdepth; - - switch (prefs.units.length) { - case METERS: marker = 10000; break; - case FEET: marker = 9144; break; /* 30 ft */ - } - set_source_rgba(gc, DEPTH_GRID); - /* don't write depth labels all the way to the bottom as - * there may be other graphs below the depth plot (like - * partial pressure graphs) where this would look out - * of place - so we only make sure that we print the next - * marker below the actual maxdepth of the dive */ - for (i = marker; i <= pi->maxdepth + marker; i += marker) { - double d = get_depth_units(i, NULL, NULL); - plot_text(gc, &tro, -0.002, i, "%.0f", d); - } -} - -static void setup_pp_limits(struct graphics_context *gc, struct plot_info *pi) -{ - int maxdepth; - - gc->leftx = 0; - gc->rightx = get_maxtime(pi); - - /* the maxdepth already includes extra vertical space - and if - * we use 1.5 times the corresponding pressure as maximum partial - * pressure the graph seems to look fine*/ - maxdepth = get_maxdepth(pi); - gc->topy = 1.5 * (maxdepth + 10000) / 10000.0 * SURFACE_PRESSURE / 1000; - gc->bottomy = -gc->topy / 20; -} - -static void plot_pp_text(struct graphics_context *gc, struct plot_info *pi) -{ - double pp, dpp, m; - int hpos; - static const text_render_options_t tro = {PP_TEXT_SIZE, PP_LINES, LEFT, MIDDLE}; - - setup_pp_limits(gc, pi); - pp = floor(pi->maxpp * 10.0) / 10.0 + 0.2; - dpp = pp > 4 ? 1.0 : 0.5; - hpos = pi->entry[pi->nr - 1].sec; - set_source_rgba(gc, PP_LINES); - for (m = 0.0; m <= pp; m += dpp) { - move_to(gc, 0, m); - line_to(gc, hpos, m); - cairo_stroke(gc->cr); - plot_text(gc, &tro, hpos + 30, m, "%.1f", m); - } -} - -static void plot_pp_gas_profile(struct graphics_context *gc, struct plot_info *pi) -{ - int i; - struct plot_data *entry; - - setup_pp_limits(gc, pi); - - if (prefs.pp_graphs.pn2) { - set_source_rgba(gc, PN2); - entry = pi->entry; - move_to(gc, entry->sec, entry->pn2); - for (i = 1; i < pi->nr; i++) { - entry++; - if (entry->pn2 < prefs.pp_graphs.pn2_threshold) - line_to(gc, entry->sec, entry->pn2); - else - move_to(gc, entry->sec, entry->pn2); - } - cairo_stroke(gc->cr); - - set_source_rgba(gc, PN2_ALERT); - entry = pi->entry; - move_to(gc, entry->sec, entry->pn2); - for (i = 1; i < pi->nr; i++) { - entry++; - if (entry->pn2 >= prefs.pp_graphs.pn2_threshold) - line_to(gc, entry->sec, entry->pn2); - else - move_to(gc, entry->sec, entry->pn2); - } - cairo_stroke(gc->cr); - } - if (prefs.pp_graphs.phe) { - set_source_rgba(gc, PHE); - entry = pi->entry; - move_to(gc, entry->sec, entry->phe); - for (i = 1; i < pi->nr; i++) { - entry++; - if (entry->phe < prefs.pp_graphs.phe_threshold) - line_to(gc, entry->sec, entry->phe); - else - move_to(gc, entry->sec, entry->phe); - } - cairo_stroke(gc->cr); - - set_source_rgba(gc, PHE_ALERT); - entry = pi->entry; - move_to(gc, entry->sec, entry->phe); - for (i = 1; i < pi->nr; i++) { - entry++; - if (entry->phe >= prefs.pp_graphs.phe_threshold) - line_to(gc, entry->sec, entry->phe); - else - move_to(gc, entry->sec, entry->phe); - } - cairo_stroke(gc->cr); - } - if (prefs.pp_graphs.po2) { - set_source_rgba(gc, PO2); - entry = pi->entry; - move_to(gc, entry->sec, entry->po2); - for (i = 1; i < pi->nr; i++) { - entry++; - if (entry->po2 < prefs.pp_graphs.po2_threshold) - line_to(gc, entry->sec, entry->po2); - else - move_to(gc, entry->sec, entry->po2); - } - cairo_stroke(gc->cr); - - set_source_rgba(gc, PO2_ALERT); - entry = pi->entry; - move_to(gc, entry->sec, entry->po2); - for (i = 1; i < pi->nr; i++) { - entry++; - if (entry->po2 >= prefs.pp_graphs.po2_threshold) - line_to(gc, entry->sec, entry->po2); - else - move_to(gc, entry->sec, entry->po2); - } - cairo_stroke(gc->cr); - } -} - -static void plot_depth_profile(struct graphics_context *gc, struct plot_info *pi) -{ - int i, incr; - cairo_t *cr = gc->cr; - int sec, depth; - struct plot_data *entry; - int maxtime, maxdepth, marker, maxline; - int increments[8] = { 10, 20, 30, 60, 5*60, 10*60, 15*60, 30*60 }; - - /* Get plot scaling limits */ - maxtime = get_maxtime(pi); - maxdepth = get_maxdepth(pi); - - gc->maxtime = maxtime; - - /* Time markers: at most every 10 seconds, but no more than 12 markers. - * We start out with 10 seconds and increment up to 30 minutes, - * depending on the dive time. - * This allows for 6h dives - enough (I hope) for even the craziest - * divers - but just in case, for those 8h depth-record-breaking dives, - * we double the interval if this still doesn't get us to 12 or fewer - * time markers */ - i = 0; - while (maxtime / increments[i] > 12 && i < 7) - i++; - incr = increments[i]; - while (maxtime / incr > 12) - incr *= 2; - - gc->leftx = 0; gc->rightx = maxtime; - gc->topy = 0; gc->bottomy = 1.0; - - last_gc = *gc; - - set_source_rgba(gc, TIME_GRID); - cairo_set_line_width_scaled(gc->cr, 2); - - for (i = incr; i < maxtime; i += incr) { - move_to(gc, i, 0); - line_to(gc, i, 1); - } - cairo_stroke(cr); - - /* now the text on the time markers */ - text_render_options_t tro = {DEPTH_TEXT_SIZE, TIME_TEXT, CENTER, TOP}; - if (maxtime < 600) { - /* Be a bit more verbose with shorter dives */ - for (i = incr; i < maxtime; i += incr) - plot_text(gc, &tro, i, 1, "%02d:%02d", i/60, i%60); - } else { - /* Only render the time on every second marker for normal dives */ - for (i = incr; i < maxtime; i += 2 * incr) - plot_text(gc, &tro, i, 1, "%d", i/60); - } - /* Depth markers: every 30 ft or 10 m*/ - gc->leftx = 0; gc->rightx = 1.0; - gc->topy = 0; gc->bottomy = maxdepth; - switch (prefs.units.length) { - case METERS: marker = 10000; break; - case FEET: marker = 9144; break; /* 30 ft */ - } - maxline = MAX(pi->maxdepth + marker, maxdepth * 2 / 3); - set_source_rgba(gc, DEPTH_GRID); - for (i = marker; i < maxline; i += marker) { - move_to(gc, 0, i); - line_to(gc, 1, i); - } - cairo_stroke(cr); - - gc->leftx = 0; gc->rightx = maxtime; - - /* Show mean depth */ - if (! gc->printer) { - set_source_rgba(gc, MEAN_DEPTH); - move_to(gc, 0, pi->meandepth); - line_to(gc, pi->entry[pi->nr - 1].sec, pi->meandepth); - cairo_stroke(cr); - } - - /* - * These are good for debugging text placement etc, - * but not for actual display.. - */ - if (0) { - plot_smoothed_profile(gc, pi); - plot_minmax_profile(gc, pi); - } - - /* Do the depth profile for the neat fill */ - gc->topy = 0; gc->bottomy = maxdepth; - - cairo_pattern_t *pat; - pat = cairo_pattern_create_linear (0.0, 0.0, 0.0, 256.0 * plot_scale); - pattern_add_color_stop_rgba (gc, pat, 1, DEPTH_BOTTOM); - pattern_add_color_stop_rgba (gc, pat, 0, DEPTH_TOP); - - cairo_set_source(gc->cr, pat); - cairo_pattern_destroy(pat); - cairo_set_line_width_scaled(gc->cr, 2); - - entry = pi->entry; - move_to(gc, 0, 0); - for (i = 0; i < pi->nr; i++, entry++) - line_to(gc, entry->sec, entry->depth); - - /* Show any ceiling we may have encountered */ - for (i = pi->nr - 1; i >= 0; i--, entry--) { - if (entry->ndl) { - /* non-zero NDL implies this is a safety stop, no ceiling */ - line_to(gc, entry->sec, 0); - } else if (entry->stopdepth < entry->depth) { - line_to(gc, entry->sec, entry->stopdepth); - } else { - line_to(gc, entry->sec, entry->depth); - } - } - cairo_close_path(gc->cr); - cairo_fill(gc->cr); - - /* if the user wants the deco ceiling more visible, do that here (this - * basically draws over the background that we had allowed to shine - * through so far) */ - if (prefs.profile_red_ceiling) { - pat = cairo_pattern_create_linear (0.0, 0.0, 0.0, 256.0 * plot_scale); - pattern_add_color_stop_rgba (gc, pat, 0, CEILING_SHALLOW); - pattern_add_color_stop_rgba (gc, pat, 1, CEILING_DEEP); - cairo_set_source(gc->cr, pat); - cairo_pattern_destroy(pat); - entry = pi->entry; - move_to(gc, 0, 0); - for (i = 0; i < pi->nr; i++, entry++) { - if (entry->ndl == 0 && entry->stopdepth) { - if (entry->ndl == 0 && entry->stopdepth < entry->depth) { - line_to(gc, entry->sec, entry->stopdepth); - } else { - line_to(gc, entry->sec, entry->depth); - } - } else { - line_to(gc, entry->sec, 0); - } - } - cairo_close_path(gc->cr); - cairo_fill(gc->cr); - } - /* finally, plot the calculated ceiling over all this */ - if (prefs.profile_calc_ceiling) { - pat = cairo_pattern_create_linear (0.0, 0.0, 0.0, 256.0 * plot_scale); - pattern_add_color_stop_rgba (gc, pat, 0, CALC_CEILING_SHALLOW); - pattern_add_color_stop_rgba (gc, pat, 1, CALC_CEILING_DEEP); - cairo_set_source(gc->cr, pat); - cairo_pattern_destroy(pat); - entry = pi->entry; - move_to(gc, 0, 0); - for (i = 0; i < pi->nr; i++, entry++) { - if (entry->ceiling) - line_to(gc, entry->sec, entry->ceiling); - else - line_to(gc, entry->sec, 0); - } - line_to(gc, (entry-1)->sec, 0); /* make sure we end at 0 */ - cairo_close_path(gc->cr); - cairo_fill(gc->cr); - } - /* next show where we have been bad and crossed the dc's ceiling */ - pat = cairo_pattern_create_linear (0.0, 0.0, 0.0, 256.0 * plot_scale); - pattern_add_color_stop_rgba (gc, pat, 0, CEILING_SHALLOW); - pattern_add_color_stop_rgba (gc, pat, 1, CEILING_DEEP); - cairo_set_source(gc->cr, pat); - cairo_pattern_destroy(pat); - entry = pi->entry; - move_to(gc, 0, 0); - for (i = 0; i < pi->nr; i++, entry++) - line_to(gc, entry->sec, entry->depth); - - for (i = pi->nr - 1; i >= 0; i--, entry--) { - if (entry->ndl == 0 && entry->stopdepth > entry->depth) { - line_to(gc, entry->sec, entry->stopdepth); - } else { - line_to(gc, entry->sec, entry->depth); - } - } - cairo_close_path(gc->cr); - cairo_fill(gc->cr); - - /* Now do it again for the velocity colors */ - entry = pi->entry; - for (i = 1; i < pi->nr; i++) { - entry++; - sec = entry->sec; - /* we want to draw the segments in different colors - * representing the vertical velocity, so we need to - * chop this into short segments */ - depth = entry->depth; - set_source_rgba(gc, VELOCITY_COLORS_START_IDX + entry->velocity); - move_to(gc, entry[-1].sec, entry[-1].depth); - line_to(gc, sec, depth); - cairo_stroke(cr); - } -} - -static int setup_temperature_limits(struct graphics_context *gc, struct plot_info *pi) -{ - int maxtime, mintemp, maxtemp, delta; - - /* Get plot scaling limits */ - maxtime = get_maxtime(pi); - mintemp = pi->mintemp; - maxtemp = pi->maxtemp; - - gc->leftx = 0; gc->rightx = maxtime; - /* Show temperatures in roughly the lower third, but make sure the scale - is at least somewhat reasonable */ - delta = maxtemp - mintemp; - if (delta < 3000) /* less than 3K in fluctuation */ - delta = 3000; - gc->topy = maxtemp + delta*2; - - if (PP_GRAPHS_ENABLED) - gc->bottomy = mintemp - delta * 2; - else - gc->bottomy = mintemp - delta / 3; - - pi->endtempcoord = SCALEY(gc, pi->mintemp); - return maxtemp && maxtemp >= mintemp; -} - -static void plot_single_temp_text(struct graphics_context *gc, int sec, int mkelvin) -{ - double deg; - const char *unit; - static const text_render_options_t tro = {TEMP_TEXT_SIZE, TEMP_TEXT, LEFT, TOP}; - - deg = get_temp_units(mkelvin, &unit); - - plot_text(gc, &tro, sec, mkelvin, "%.2g%s", deg, unit); -} - -static void plot_temperature_text(struct graphics_context *gc, struct plot_info *pi) -{ - int i; - int last = -300, sec = 0; - int last_temperature = 0, last_printed_temp = 0; - - if (!setup_temperature_limits(gc, pi)) - return; - - for (i = 0; i < pi->nr; i++) { - struct plot_data *entry = pi->entry+i; - int mkelvin = entry->temperature; - sec = entry->sec; - - if (!mkelvin) - continue; - last_temperature = mkelvin; - /* don't print a temperature - * if it's been less than 5min and less than a 2K change OR - * if it's been less than 2min OR if the change from the - * last print is less than .4K (and therefore less than 1F */ - if (((sec < last + 300) && (abs(mkelvin - last_printed_temp) < 2000)) || - (sec < last + 120) || - (abs(mkelvin - last_printed_temp) < 400)) - continue; - last = sec; - plot_single_temp_text(gc,sec,mkelvin); - last_printed_temp = mkelvin; - } - /* it would be nice to print the end temperature, if it's - * different or if the last temperature print has been more - * than a quarter of the dive back */ - if ((abs(last_temperature - last_printed_temp) > 500) || - ((double)last / (double)sec < 0.75)) - plot_single_temp_text(gc, sec, last_temperature); -} - -static void plot_temperature_profile(struct graphics_context *gc, struct plot_info *pi) -{ - int i; - cairo_t *cr = gc->cr; - int last = 0; - - if (!setup_temperature_limits(gc, pi)) - return; - - cairo_set_line_width_scaled(gc->cr, 2); - set_source_rgba(gc, TEMP_PLOT); - for (i = 0; i < pi->nr; i++) { - struct plot_data *entry = pi->entry + i; - int mkelvin = entry->temperature; - int sec = entry->sec; - if (!mkelvin) { - if (!last) - continue; - mkelvin = last; - } - if (last) - line_to(gc, sec, mkelvin); - else - move_to(gc, sec, mkelvin); - last = mkelvin; - } - cairo_stroke(cr); -} - -/* gets both the actual start and end pressure as well as the scaling factors */ -static int get_cylinder_pressure_range(struct graphics_context *gc, struct plot_info *pi) +int get_cylinder_pressure_range(struct graphics_context *gc) { gc->leftx = 0; - gc->rightx = get_maxtime(pi); + gc->rightx = get_maxtime(&gc->pi); if (PP_GRAPHS_ENABLED) - gc->bottomy = -pi->maxpressure * 0.75; + gc->bottomy = -gc->pi.maxpressure * 0.75; else gc->bottomy = 0; - gc->topy = pi->maxpressure * 1.5; - if (!pi->maxpressure) + gc->topy = gc->pi.maxpressure * 1.5; + if (!gc->pi.maxpressure) return FALSE; - while (pi->endtempcoord <= SCALEY(gc, pi->minpressure - (gc->topy) * 0.1)) - gc->bottomy -= gc->topy * 0.1; + while (gc->pi.endtempcoord <= SCALEY(gc, gc->pi.minpressure - (gc->topy) * 0.1)) + gc->bottomy -= gc->topy * 0.1 * gc->maxy/abs(gc->maxy); return TRUE; } -/* set the color for the pressure plot according to temporary sac rate - * as compared to avg_sac; the calculation simply maps the delta between - * sac and avg_sac to indexes 0 .. (SAC_COLORS - 1) with everything - * more than 6000 ml/min below avg_sac mapped to 0 */ - -static void set_sac_color(struct graphics_context *gc, int sac, int avg_sac) -{ - int sac_index = 0; - int delta = sac - avg_sac + 7000; - - if (!gc->printer) { - sac_index = delta / 2000; - if (sac_index < 0) - sac_index = 0; - if (sac_index > SAC_COLORS - 1) - sac_index = SAC_COLORS - 1; - set_source_rgba(gc, SAC_COLORS_START_IDX + sac_index); - } else { - set_source_rgba(gc, SAC_DEFAULT); - } -} /* Get local sac-rate (in ml/min) between entry1 and entry2 */ -static int get_local_sac(struct plot_data *entry1, struct plot_data *entry2, struct dive *dive) +int get_local_sac(struct plot_data *entry1, struct plot_data *entry2, struct dive *dive) { int index = entry1->cylinderindex; cylinder_t *cyl; @@ -1059,137 +300,6 @@ static int get_local_sac(struct plot_data *entry1, struct plot_data *entry2, str return airuse / atm * 60 / duration; } -/* calculate the current SAC in ml/min and convert to int */ -#define GET_LOCAL_SAC(_entry1, _entry2, _dive) \ - get_local_sac(_entry1, _entry2, _dive) - -#define SAC_WINDOW 45 /* sliding window in seconds for current SAC calculation */ - -static void plot_cylinder_pressure(struct graphics_context *gc, struct plot_info *pi, - struct dive *dive, struct divecomputer *dc) -{ - int i; - int last = -1, last_index = -1; - int lift_pen = FALSE; - int first_plot = TRUE; - int sac = 0; - struct plot_data *last_entry = NULL; - - if (!get_cylinder_pressure_range(gc, pi)) - return; - - cairo_set_line_width_scaled(gc->cr, 2); - - for (i = 0; i < pi->nr; i++) { - int mbar; - struct plot_data *entry = pi->entry + i; - - mbar = GET_PRESSURE(entry); - if (entry->cylinderindex != last_index) { - lift_pen = TRUE; - last_entry = NULL; - } - if (!mbar) { - lift_pen = TRUE; - continue; - } - if (!last_entry) { - last = i; - last_entry = entry; - sac = GET_LOCAL_SAC(entry, pi->entry + i + 1, dive); - } else { - int j; - sac = 0; - for (j = last; j < i; j++) - sac += GET_LOCAL_SAC(pi->entry + j, pi->entry + j + 1, dive); - sac /= (i - last); - if (entry->sec - last_entry->sec >= SAC_WINDOW) { - last++; - last_entry = pi->entry + last; - } - } - set_sac_color(gc, sac, dive->sac); - if (lift_pen) { - if (!first_plot && entry->cylinderindex == last_index) { - /* if we have a previous event from the same tank, - * draw at least a short line */ - int prev_pr; - prev_pr = GET_PRESSURE(entry - 1); - move_to(gc, (entry-1)->sec, prev_pr); - line_to(gc, entry->sec, mbar); - } else { - first_plot = FALSE; - move_to(gc, entry->sec, mbar); - } - lift_pen = FALSE; - } else { - line_to(gc, entry->sec, mbar); - } - cairo_stroke(gc->cr); - move_to(gc, entry->sec, mbar); - last_index = entry->cylinderindex; - } -} - -static void plot_pressure_value(struct graphics_context *gc, int mbar, int sec, - int xalign, int yalign) -{ - int pressure; - const char *unit; - - pressure = get_pressure_units(mbar, &unit); - text_render_options_t tro = {PRESSURE_TEXT_SIZE, PRESSURE_TEXT, xalign, yalign}; - plot_text(gc, &tro, sec, mbar, "%d %s", pressure, unit); -} - -static void plot_cylinder_pressure_text(struct graphics_context *gc, struct plot_info *pi) -{ - int i; - int mbar, cyl; - int seen_cyl[MAX_CYLINDERS] = { FALSE, }; - int last_pressure[MAX_CYLINDERS] = { 0, }; - int last_time[MAX_CYLINDERS] = { 0, }; - struct plot_data *entry; - - if (!get_cylinder_pressure_range(gc, pi)) - return; - - cyl = -1; - for (i = 0; i < pi->nr; i++) { - entry = pi->entry + i; - mbar = GET_PRESSURE(entry); - - if (!mbar) - continue; - if (cyl != entry->cylinderindex) { - cyl = entry->cylinderindex; - if (!seen_cyl[cyl]) { - plot_pressure_value(gc, mbar, entry->sec, LEFT, BOTTOM); - seen_cyl[cyl] = TRUE; - } - } - last_pressure[cyl] = mbar; - last_time[cyl] = entry->sec; - } - - for (cyl = 0; cyl < MAX_CYLINDERS; cyl++) { - if (last_time[cyl]) { - plot_pressure_value(gc, last_pressure[cyl], last_time[cyl], CENTER, TOP); - } - } -} - -static void plot_deco_text(struct graphics_context *gc, struct plot_info *pi) -{ - if (prefs.profile_calc_ceiling) { - float x = gc->leftx + (gc->rightx - gc->leftx) / 2; - float y = gc->topy = 1.0; - text_render_options_t tro = {PRESSURE_TEXT_SIZE, PRESSURE_TEXT, CENTER, -0.2}; - gc->bottomy = 0.0; - plot_text(gc, &tro, x, y, "GF %.0f/%.0f", prefs.gflow * 100, prefs.gfhigh * 100); - } -} - static void analyze_plot_info_minmax_minute(struct plot_data *entry, struct plot_data *first, struct plot_data *last, int index) { struct plot_data *p = entry; @@ -1259,6 +369,7 @@ static velocity_t velocity(int speed) return v; } + static struct plot_info *analyze_plot_info(struct plot_info *pi) { int i; @@ -1585,7 +696,7 @@ static void check_gas_change_events(struct dive *dive, struct divecomputer *dc, set_cylinder_index(pi, i, cylinderindex, ~0u); } -static void calculate_max_limits(struct dive *dive, struct divecomputer *dc, struct graphics_context *gc) +void calculate_max_limits(struct dive *dive, struct divecomputer *dc, struct graphics_context *gc) { struct plot_info *pi; int maxdepth; @@ -1941,7 +1052,7 @@ static void calculate_deco_information(struct dive *dive, struct divecomputer *d * sides, so that you can do end-points without having to worry * about it. */ -static struct plot_info *create_plot_info(struct dive *dive, struct divecomputer *dc, struct graphics_context *gc) +struct plot_info *create_plot_info(struct dive *dive, struct divecomputer *dc, struct graphics_context *gc) { struct plot_info *pi; @@ -1974,19 +1085,6 @@ static struct plot_info *create_plot_info(struct dive *dive, struct divecomputer return analyze_plot_info(pi); } -static void plot_set_scale(scale_mode_t scale) -{ - switch (scale) { - default: - case SC_SCREEN: - plot_scale = SCALE_SCREEN; - break; - case SC_PRINT: - plot_scale = SCALE_PRINT; - break; - } -} - /* make sure you pass this the FIRST dc - it just walks the list */ static int nr_dcs(struct divecomputer *main) { @@ -2015,125 +1113,7 @@ struct divecomputer *select_dc(struct divecomputer *main) return main; } -void plot(struct graphics_context *gc, struct dive *dive, scale_mode_t scale) -{ - struct plot_info *pi; - struct divecomputer *dc = &dive->dc; - cairo_rectangle_t *drawing_area = &gc->drawing_area; - const char *nickname; - - plot_set_scale(scale); - - if (!dc->samples) { - static struct sample fake[4]; - static struct divecomputer fakedc; - fakedc = dive->dc; - fakedc.sample = fake; - fakedc.samples = 4; - - /* The dive has no samples, so create a few fake ones. This assumes an - ascent/descent rate of 9 m/min, which is just below the limit for FAST. */ - int duration = dive->dc.duration.seconds; - int maxdepth = dive->dc.maxdepth.mm; - int asc_desc_time = dive->dc.maxdepth.mm*60/9000; - if (asc_desc_time * 2 >= duration) - asc_desc_time = duration / 2; - fake[1].time.seconds = asc_desc_time; - fake[1].depth.mm = maxdepth; - fake[2].time.seconds = duration - asc_desc_time; - fake[2].depth.mm = maxdepth; - fake[3].time.seconds = duration * 1.00; - fakedc.events = dc->events; - dc = &fakedc; - } - - /* - * Set up limits that are independent of - * the dive computer - */ - calculate_max_limits(dive, dc, gc); - - /* shift the drawing area so we have a nice margin around it */ - cairo_translate(gc->cr, drawing_area->x, drawing_area->y); - cairo_set_line_width_scaled(gc->cr, 1); - cairo_set_line_cap(gc->cr, CAIRO_LINE_CAP_ROUND); - cairo_set_line_join(gc->cr, CAIRO_LINE_JOIN_ROUND); - - /* - * We don't use "cairo_translate()" because that doesn't - * scale line width etc. But the actual scaling we need - * do set up ourselves.. - * - * Snif. What a pity. - */ - gc->maxx = (drawing_area->width - 2*drawing_area->x); - gc->maxy = (drawing_area->height - 2*drawing_area->y); - - dc = select_dc(dc); - - /* This is per-dive-computer. Right now we just do the first one */ - pi = create_plot_info(dive, dc, gc); - - /* Depth profile */ - plot_depth_profile(gc, pi); - plot_events(gc, pi, dc); - - /* Temperature profile */ - plot_temperature_profile(gc, pi); - - /* Cylinder pressure plot */ - plot_cylinder_pressure(gc, pi, dive, dc); - - /* Text on top of all graphs.. */ - plot_temperature_text(gc, pi); - plot_depth_text(gc, pi); - plot_cylinder_pressure_text(gc, pi); - plot_deco_text(gc, pi); - - /* Bounding box last */ - gc->leftx = 0; gc->rightx = 1.0; - gc->topy = 0; gc->bottomy = 1.0; - - set_source_rgba(gc, BOUNDING_BOX); - cairo_set_line_width_scaled(gc->cr, 1); - move_to(gc, 0, 0); - line_to(gc, 0, 1); - line_to(gc, 1, 1); - line_to(gc, 1, 0); - cairo_close_path(gc->cr); - cairo_stroke(gc->cr); - - /* Put the dive computer name in the lower left corner */ - nickname = get_dc_nickname(dc->model, dc->deviceid); - if (!nickname || *nickname == '\0') - nickname = dc->model; - if (nickname) { - static const text_render_options_t computer = {DC_TEXT_SIZE, TIME_TEXT, LEFT, MIDDLE}; - plot_text(gc, &computer, 0, 1, "%s", nickname); - } - - if (PP_GRAPHS_ENABLED) { - plot_pp_gas_profile(gc, pi); - plot_pp_text(gc, pi); - } - - /* now shift the translation back by half the margin; - * this way we can draw the vertical scales on both sides */ - cairo_translate(gc->cr, -drawing_area->x / 2.0, 0); - gc->maxx += drawing_area->x; - gc->leftx = -(drawing_area->x / drawing_area->width) / 2.0; - gc->rightx = 1.0 - gc->leftx; - - plot_depth_scale(gc, pi); - - if (gc->printer) { - free(pi->entry); - last_pi_entry = pi->entry = NULL; - pi->nr = 0; - } -} - -static void plot_string(struct plot_data *entry, char *buf, size_t bufsize, +static void plot_string(struct plot_data *entry, char *buf, int bufsize, int depth, int pressure, int temp, gboolean has_ndl) { int pressurevalue, mod, ead, end, eadd; @@ -2217,7 +1197,7 @@ static void plot_string(struct plot_data *entry, char *buf, size_t bufsize, free(buf2); } -void get_plot_details(struct graphics_context *gc, int time, char *buf, size_t bufsize) +void get_plot_details(struct graphics_context *gc, int time, char *buf, int bufsize) { struct plot_info *pi = &gc->pi; int pressure = 0, temp = 0; diff --git a/profile.h b/profile.h new file mode 100644 index 000000000..2b2c7bff5 --- /dev/null +++ b/profile.h @@ -0,0 +1,112 @@ +#ifndef PROFILE_H +#define PROFILE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "dive.h" + +typedef enum { STABLE, SLOW, MODERATE, FAST, CRAZY } velocity_t; + +struct divecomputer; +struct graphics_context; +struct plot_info; +struct plot_data { + unsigned int in_deco:1; + int cylinderindex; + int sec; + /* pressure[0] is sensor pressure + * pressure[1] is interpolated pressure */ + int pressure[2]; + int temperature; + /* Depth info */ + int depth; + int ceiling; + int ndl; + int stoptime; + int stopdepth; + int cns; + int smoothed; + double po2, pn2, phe; + double mod, ead, end, eadd; + velocity_t velocity; + struct plot_data *min[3]; + struct plot_data *max[3]; + int avg[3]; +}; + +void calculate_max_limits(struct dive *dive, struct divecomputer *dc, struct graphics_context *gc); +struct plot_info *create_plot_info(struct dive *dive, struct divecomputer *dc, struct graphics_context *gc); +int setup_temperature_limits(struct graphics_context *gc); +int get_cylinder_pressure_range(struct graphics_context *gc); + +struct ev_select { + char *ev_name; + bool plot_ev; +}; + +/* + * When showing dive profiles, we scale things to the + * current dive. However, we don't scale past less than + * 30 minutes or 90 ft, just so that small dives show + * up as such unless zoom is enabled. + * We also need to add 180 seconds at the end so the min/max + * plots correctly + */ +int get_maxtime(struct plot_info *pi); + +/* get the maximum depth to which we want to plot + * take into account the additional verical space needed to plot + * partial pressure graphs */ +int get_maxdepth(struct plot_info *pi); + +int get_local_sac(struct plot_data *entry1, struct plot_data *entry2, struct dive *dive); + +void setup_pp_limits(struct graphics_context *gc); + + +#define ALIGN_LEFT 1 +#define ALIGN_RIGHT 2 +#define INVISIBLE 4 +#define UNSORTABLE 8 +#define EDITABLE 16 + +#ifndef TEXT_SCALE +#define TEXT_SCALE 1.0 +#endif + +#define DEPTH_TEXT_SIZE (10 * TEXT_SCALE) +#define PRESSURE_TEXT_SIZE (10 * TEXT_SCALE) +#define DC_TEXT_SIZE (10.5 * TEXT_SCALE) +#define PP_TEXT_SIZE (11 * TEXT_SCALE) +#define TEMP_TEXT_SIZE (12 * TEXT_SCALE) + +#define RIGHT (-1.0) +#define CENTER (-0.5) +#define LEFT (0.0) + +#define TOP (1) +#define MIDDLE (0) +#define BOTTOM (-1) + +#define SCALEXGC(x) (((x) - gc.leftx) / (gc.rightx - gc.leftx) * gc.maxx) +#define SCALEYGC(y) (((y) - gc.topy) / (gc.bottomy - gc.topy) * gc.maxy) +#define SCALEGC(x,y) SCALEXGC(x),SCALEYGC(y) + +#define SCALEX(gc,x) (((x)-gc->leftx)/(gc->rightx-gc->leftx)*gc->maxx) +#define SCALEY(gc,y) (((y)-gc->topy)/(gc->bottomy-gc->topy)*gc->maxy) +#define SCALE(gc,x,y) SCALEX(gc,x),SCALEY(gc,y) + +#define SENSOR_PR 0 +#define INTERPOLATED_PR 1 +#define SENSOR_PRESSURE(_entry) (_entry)->pressure[SENSOR_PR] +#define INTERPOLATED_PRESSURE(_entry) (_entry)->pressure[INTERPOLATED_PR] +#define GET_PRESSURE(_entry) (SENSOR_PRESSURE(_entry) ? SENSOR_PRESSURE(_entry) : INTERPOLATED_PRESSURE(_entry)) + +#define SAC_WINDOW 45 /* sliding window in seconds for current SAC calculation */ + +#ifdef __cplusplus +} +#endif +#endif diff --git a/qt-gui.cpp b/qt-gui.cpp new file mode 100644 index 000000000..e3a8ad5aa --- /dev/null +++ b/qt-gui.cpp @@ -0,0 +1,210 @@ +/* qt-gui.cpp */ +/* Qt UI implementation */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dive.h" +#include "divelist.h" +#include "display.h" +#include "uemis.h" +#include "device.h" +#include "webservice.h" +#include "version.h" +#include "libdivecomputer.h" +#include "qt-ui/mainwindow.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +class Translator: public QTranslator +{ + Q_OBJECT + +public: + Translator(QObject *parent = 0); + ~Translator() {} + + virtual QString translate(const char *context, const char *sourceText, + const char *disambiguation = NULL) const; +}; + +Translator::Translator(QObject *parent): + QTranslator(parent) +{ +} + +QString Translator::translate(const char *context, const char *sourceText, + const char *disambiguation) const +{ + return gettext(sourceText); +} + +static QApplication *application = NULL; + +int error_count; +const char *existing_filename; + +void init_qt_ui(int *argcp, char ***argvp) +{ + application->installTranslator(new Translator(application)); + MainWindow *window = new MainWindow(); + window->show(); +} + +void init_ui(int *argcp, char ***argvp) +{ + QVariant v; + application = new QApplication(*argcp, *argvp); + +#if QT_VERSION < 0x050000 + // ask QString in Qt 4 to interpret all char* as UTF-8, + // like Qt 5 does. + // 106 is "UTF-8", this is faster than lookup by name + // [http://www.iana.org/assignments/character-sets/character-sets.xml] + QTextCodec::setCodecForCStrings(QTextCodec::codecForMib(106)); +#endif + + QSettings settings("hohndel.org","subsurface"); + settings.beginGroup("GeneralSettings"); + v = settings.value(QString("default_filename")); + if (v.isValid()) { + QString name = v.toString(); + prefs.default_filename = strdup(name.toUtf8()); + } + settings.endGroup(); + +#if 0 + subsurface_open_conf(); + + load_preferences(); + + /* these still need to be handled in QSettings */ + default_dive_computer_vendor = subsurface_get_conf("dive_computer_vendor"); + default_dive_computer_product = subsurface_get_conf("dive_computer_product"); + default_dive_computer_device = subsurface_get_conf("dive_computer_device"); +#endif + return; +} + +void run_ui(void) +{ + application->exec(); +} + +void exit_ui(void) +{ + delete application; +#if 0 + subsurface_close_conf(); +#endif + if (existing_filename) + free((void *)existing_filename); + if (default_dive_computer_device) + free((void *)default_dive_computer_device); +} + +void set_filename(const char *filename, gboolean force) +{ + if (!force && existing_filename) + return; + free((void *)existing_filename); + if (filename) + existing_filename = strdup(filename); + else + existing_filename = NULL; +} + +const char *get_dc_nickname(const char *model, uint32_t deviceid) +{ + struct device_info *known = get_device_info(model, deviceid); + if (known) { + if (known->nickname && *known->nickname) + return known->nickname; + else + return known->model; + } + return NULL; +} + +void set_dc_nickname(struct dive *dive) +{ + /* needs Qt implementation */ +} + +QString get_depth_string(depth_t depth, bool showunit) +{ + if (prefs.units.length == units::METERS) { + double meters = depth.mm / 1000.0; + return QString("%1%2").arg(meters, 0, 'f', meters >= 20.0 ? 0 : 1 ).arg(showunit ? _("m") : ""); + } else { + double feet = mm_to_feet(depth.mm); + return QString("%1%2").arg(feet, 0, 'f', 1). arg(showunit ? _("ft") : ""); + } +} + +QString get_weight_string(weight_t weight, bool showunit) +{ + if (prefs.units.weight == units::KG) { + double kg = weight.grams / 1000.0; + return QString("%1%2").arg(kg, 0, 'f', kg >= 20.0 ? 0 : 1 ).arg(showunit ? _("kg") : ""); + } else { + double lbs = grams_to_lbs(weight.grams); + return QString("%1%2").arg(lbs, 0, 'f', lbs >= 40.0 ? 0 : 1 ).arg(showunit ? _("lbs") : ""); + } +} + +QString get_temperature_string(temperature_t temp, bool showunit) +{ + if (prefs.units.temperature == units::CELSIUS) { + double celsius = mkelvin_to_C(temp.mkelvin); + return QString("%1%2%3").arg(celsius, 0, 'f', 1).arg(showunit ? (UTF8_DEGREE): "") + .arg(showunit ? _("C") : ""); + } else { + double fahrenheit = mkelvin_to_F(temp.mkelvin); + return QString("%1%2%3").arg(fahrenheit, 0, 'f', 1).arg(showunit ? (UTF8_DEGREE): "") + .arg(showunit ? _("F") : ""); + } +} + +QString get_volume_string(volume_t volume, bool showunit) +{ + if (prefs.units.volume == units::LITER) { + double liter = volume.mliter / 1000.0; + return QString("%1%2").arg(liter, 0, 'f', liter >= 40.0 ? 0 : 1 ).arg(showunit ? _("l") : ""); + } else { + double cuft = ml_to_cuft(volume.mliter); + return QString("%1%2").arg(cuft, 0, 'f', cuft >= 20.0 ? 0 : (cuft >= 2.0 ? 1 : 2)).arg(showunit ? _("cuft") : ""); + } +} + +QString get_pressure_string(pressure_t pressure, bool showunit) +{ + if (prefs.units.pressure == units::BAR) { + double bar = pressure.mbar / 1000.0; + return QString("%1%2").arg(bar, 0, 'f', 1).arg(showunit ? _("bar") : ""); + } else { + double psi = mbar_to_PSI(pressure.mbar); + return QString("%1%2").arg(psi, 0, 'f', 0).arg(showunit ? _("psi") : ""); + } +} + +double get_screen_dpi() +{ + QDesktopWidget *mydesk = application->desktop(); + return mydesk->physicalDpiX(); +} +#include "qt-gui.moc" diff --git a/qt-ui/addcylinderdialog.cpp b/qt-ui/addcylinderdialog.cpp new file mode 100644 index 000000000..5b91617ed --- /dev/null +++ b/qt-ui/addcylinderdialog.cpp @@ -0,0 +1,56 @@ +/* + * addcylinderdialog.cpp + * + * classes for the add cylinder dialog of Subsurface + * + */ +#include "addcylinderdialog.h" +#include "ui_addcylinderdialog.h" +#include +#include +#include "../conversions.h" +#include "models.h" + +AddCylinderDialog::AddCylinderDialog(QWidget *parent) : ui(new Ui::AddCylinderDialog()) +, tankInfoModel(new TankInfoModel()) +{ + ui->setupUi(this); + ui->cylinderType->setModel(tankInfoModel); +} + +void AddCylinderDialog::setCylinder(cylinder_t *cylinder) +{ + double volume, pressure; + int index; + + currentCylinder = cylinder; + convert_volume_pressure(cylinder->type.size.mliter, cylinder->type.workingpressure.mbar, &volume, &pressure); + + index = ui->cylinderType->findText(QString(cylinder->type.description)); + ui->cylinderType->setCurrentIndex(index); + ui->size->setValue(volume); + ui->pressure->setValue(pressure); + + ui->o2percent->setValue(cylinder->gasmix.o2.permille / 10.0); + ui->hepercent->setValue(cylinder->gasmix.he.permille / 10.0); + + convert_pressure(cylinder->start.mbar, &pressure); + ui->start->setValue(pressure); + + convert_pressure(cylinder->end.mbar, &pressure); + ui->end->setValue(pressure); +} + +void AddCylinderDialog::updateCylinder() +{ + QByteArray description = ui->cylinderType->currentText().toLocal8Bit(); + + currentCylinder->type.description = description.data(); + currentCylinder->type.size.mliter = ui->size->value(); + currentCylinder->type.workingpressure.mbar = ui->pressure->value(); + currentCylinder->gasmix.o2.permille = ui->o2percent->value(); + currentCylinder->gasmix.he.permille = ui->hepercent->value(); + currentCylinder->start.mbar = ui->start->value(); + currentCylinder->end.mbar = ui->end->value(); +} + diff --git a/qt-ui/addcylinderdialog.h b/qt-ui/addcylinderdialog.h new file mode 100644 index 000000000..fc68faa72 --- /dev/null +++ b/qt-ui/addcylinderdialog.h @@ -0,0 +1,33 @@ +/* + * addcylinderdialog.h + * + * header file for the add cylinder dialog of Subsurface + * + */ +#ifndef ADDCYLINDERDIALOG_H +#define ADDCYLINDERDIALOG_H + +#include +#include "../dive.h" + +namespace Ui{ + class AddCylinderDialog; +} + +class TankInfoModel; + +class AddCylinderDialog : public QDialog{ + Q_OBJECT +public: + explicit AddCylinderDialog(QWidget* parent = 0); + void setCylinder(cylinder_t *cylinder); + void updateCylinder(); + +private: + Ui::AddCylinderDialog *ui; + cylinder_t *currentCylinder; + TankInfoModel *tankInfoModel; +}; + + +#endif diff --git a/qt-ui/addcylinderdialog.ui b/qt-ui/addcylinderdialog.ui new file mode 100644 index 000000000..8bb0428b7 --- /dev/null +++ b/qt-ui/addcylinderdialog.ui @@ -0,0 +1,303 @@ + + + AddCylinderDialog + + + + 0 + 0 + 408 + 298 + + + + Dialog + + + + + + Cylinder + + + + QFormLayout::ExpandingFieldsGrow + + + + + Type + + + + + + + + 0 + 0 + + + + + + + + Size + + + + + + + + 0 + 0 + + + + + + + + Pressure + + + + + + + + 0 + 0 + + + + + + + + + + + Pressure + + + + + + Start + + + + + + + End + + + + + + + false + + + + 0 + 0 + + + + + + + + false + + + + 0 + 0 + + + + + + + + + + + + + + + + + + Gas Mix + + + + + + O2% + + + + + + + false + + + + 0 + 0 + + + + + + + + He% + + + + + + + false + + + + 0 + 0 + + + + + + + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + AddCylinderDialog + accept() + + + 248 + 269 + + + 157 + 260 + + + + + buttonBox + rejected() + AddCylinderDialog + reject() + + + 290 + 269 + + + 286 + 260 + + + + + checkBox + clicked(bool) + start + setEnabled(bool) + + + 216 + 46 + + + 280 + 66 + + + + + checkBox + clicked(bool) + end + setEnabled(bool) + + + 226 + 48 + + + 268 + 100 + + + + + checkBox_2 + clicked(bool) + o2percent + setEnabled(bool) + + + 214 + 165 + + + 260 + 190 + + + + + checkBox_2 + clicked(bool) + hepercent + setEnabled(bool) + + + 228 + 165 + + + 262 + 216 + + + + + diff --git a/qt-ui/addweightsystemdialog.cpp b/qt-ui/addweightsystemdialog.cpp new file mode 100644 index 000000000..48a399de9 --- /dev/null +++ b/qt-ui/addweightsystemdialog.cpp @@ -0,0 +1,39 @@ +/* + * addweightsystemdialog.cpp + * + * classes for the add weightsystem dialog of Subsurface + * + */ +#include "addweightsystemdialog.h" +#include "ui_addweightsystemdialog.h" +#include +#include +#include "../conversions.h" +#include "models.h" + +AddWeightsystemDialog::AddWeightsystemDialog(QWidget *parent) : ui(new Ui::AddWeightsystemDialog()) +{ + ui->setupUi(this); + currentWeightsystem = NULL; +} + +void AddWeightsystemDialog::setWeightsystem(weightsystem_t *ws) +{ + currentWeightsystem = ws; + + ui->description->insert(QString(ws->description)); + if (get_units()->weight == units::KG) + ui->weight->setValue(ws->weight.grams / 1000); + else + ui->weight->setValue(grams_to_lbs(ws->weight.grams)); +} + +void AddWeightsystemDialog::updateWeightsystem() +{ + currentWeightsystem->description = strdup(ui->description->text().toUtf8().data()); + if (get_units()->weight == units::KG) + currentWeightsystem->weight.grams = ui->weight->value() * 1000; + else + currentWeightsystem->weight.grams = lbs_to_grams(ui->weight->value()); +} + diff --git a/qt-ui/addweightsystemdialog.h b/qt-ui/addweightsystemdialog.h new file mode 100644 index 000000000..e99dc08d8 --- /dev/null +++ b/qt-ui/addweightsystemdialog.h @@ -0,0 +1,30 @@ +/* + * addweightsystemdialog.h + * + * header file for the add weightsystem dialog of Subsurface + * + */ +#ifndef ADDWEIGHTSYSTEMDIALOG_H +#define ADDWEIGHTSYSTEMDIALOG_H + +#include +#include "../dive.h" + +namespace Ui{ + class AddWeightsystemDialog; +} + +class AddWeightsystemDialog : public QDialog{ + Q_OBJECT +public: + explicit AddWeightsystemDialog(QWidget* parent = 0); + void setWeightsystem(weightsystem_t *ws); + void updateWeightsystem(); + +private: + Ui::AddWeightsystemDialog *ui; + weightsystem_t *currentWeightsystem; +}; + + +#endif diff --git a/qt-ui/addweightsystemdialog.ui b/qt-ui/addweightsystemdialog.ui new file mode 100644 index 000000000..11e60d562 --- /dev/null +++ b/qt-ui/addweightsystemdialog.ui @@ -0,0 +1,109 @@ + + + AddWeightsystemDialog + + + + 0 + 0 + 408 + 186 + + + + Dialog + + + + + + Weightsystem + + + + QFormLayout::ExpandingFieldsGrow + + + + + Description + + + + + + + Weight + + + + + + + + 0 + 0 + + + + Qt::ImhFormattedNumbersOnly + + + true + + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + AddWeightsystemDialog + accept() + + + 248 + 269 + + + 157 + 260 + + + + + buttonBox + rejected() + AddWeightsystemDialog + reject() + + + 290 + 269 + + + 286 + 260 + + + + + diff --git a/qt-ui/divelistview.cpp b/qt-ui/divelistview.cpp new file mode 100644 index 000000000..5c8a93ff0 --- /dev/null +++ b/qt-ui/divelistview.cpp @@ -0,0 +1,132 @@ +/* + * divelistview.cpp + * + * classes for the divelist of Subsurface + * + */ +#include "divelistview.h" +#include "models.h" +#include "modeldelegates.h" +#include +#include +#include +#include +#include + + +DiveListView::DiveListView(QWidget *parent) : QTreeView(parent), mouseClickSelection(false) +{ + setUniformRowHeights(true); + setItemDelegateForColumn(TreeItemDT::RATING, new StarWidgetsDelegate()); + QSortFilterProxyModel *model = new QSortFilterProxyModel(this); + setModel(model); +} + +void DiveListView::reload() +{ + QSortFilterProxyModel *m = qobject_cast(model()); + QAbstractItemModel *oldModel = m->sourceModel(); + if (oldModel) + oldModel->deleteLater(); + m->setSourceModel(new DiveTripModel(this)); + sortByColumn(0, Qt::DescendingOrder); + QModelIndex firstDiveOrTrip = m->index(0,0); + if (firstDiveOrTrip.isValid()){ + if (m->index(0,0, firstDiveOrTrip).isValid()) + setCurrentIndex(m->index(0,0, firstDiveOrTrip)); + else + setCurrentIndex(firstDiveOrTrip); + } +} + +void DiveListView::setModel(QAbstractItemModel* model) +{ + QTreeView::setModel(model); +} + +void DiveListView::setSelection(const QRect& rect, QItemSelectionModel::SelectionFlags command) +{ + if (mouseClickSelection) + QTreeView::setSelection(rect, command); +} + +void DiveListView::mousePressEvent(QMouseEvent* event) +{ + mouseClickSelection = true; + QTreeView::mousePressEvent(event); +} + +void DiveListView::mouseReleaseEvent(QMouseEvent* event) +{ + mouseClickSelection = false; + QTreeView::mouseReleaseEvent(event); +} + +void DiveListView::keyPressEvent(QKeyEvent* event) +{ + if(event->modifiers()) + mouseClickSelection = true; + QTreeView::keyPressEvent(event); +} + +void DiveListView::keyReleaseEvent(QKeyEvent* event) +{ + mouseClickSelection = false; + QWidget::keyReleaseEvent(event); +} + +void DiveListView::currentChanged(const QModelIndex& current, const QModelIndex& previous) +{ + if (!current.isValid()) + return; + const QAbstractItemModel *model = current.model(); + int selectedDive = 0; + struct dive *dive = (struct dive*) model->data(current, TreeItemDT::DIVE_ROLE).value(); + if (!dive) { // it's a trip! select first child. + dive = (struct dive*) model->data(current.child(0,0), TreeItemDT::DIVE_ROLE).value(); + selectedDive = get_divenr(dive); + }else{ + selectedDive = get_divenr(dive); + } + if (selectedDive == selected_dive) + return; + Q_EMIT currentDiveChanged(selectedDive); +} + +void DiveListView::selectionChanged(const QItemSelection& selected, const QItemSelection& deselected) +{ + QList parents; + Q_FOREACH(const QModelIndex& index, deselected.indexes()) { + const QAbstractItemModel *model = index.model(); + struct dive *dive = (struct dive*) model->data(index, TreeItemDT::DIVE_ROLE).value(); + if (!dive) { // it's a trip! + if (model->rowCount(index)) { + expand(index); // leave this - even if it looks like it shouldn't be here. looks like I've found a Qt bug. + // the subselection is removed, but the painting is not. this cleans the area. + } + } else if (!parents.contains(index.parent())) { + parents.push_back(index.parent()); + } + } + + Q_FOREACH(const QModelIndex& index, selected.indexes()) { + const QAbstractItemModel *model = index.model(); + struct dive *dive = (struct dive*) model->data(index, TreeItemDT::DIVE_ROLE).value(); + if (!dive) { // it's a trip! + if (model->rowCount(index)) { + QItemSelection selection; + selection.select(index.child(0,0), index.child(model->rowCount(index) -1 , 0)); + selectionModel()->select(selection, QItemSelectionModel::Select | QItemSelectionModel::Rows); + selectionModel()->setCurrentIndex(index, QItemSelectionModel::Select | QItemSelectionModel::NoUpdate); + if (!isExpanded(index)) { + expand(index); + } + } + } else if (!parents.contains(index.parent())) { + parents.push_back(index.parent()); + } + } + + Q_FOREACH(const QModelIndex& index, parents) + expand(index); +} diff --git a/qt-ui/divelistview.h b/qt-ui/divelistview.h new file mode 100644 index 000000000..09830b7b5 --- /dev/null +++ b/qt-ui/divelistview.h @@ -0,0 +1,40 @@ +/* + * divelistview.h + * + * header file for the dive list of Subsurface + * + */ +#ifndef DIVELISTVIEW_H +#define DIVELISTVIEW_H + +/*! A view subclass for use with dives + + Note: calling this a list view might be misleading? + + +*/ + +#include + +class DiveListView : public QTreeView +{ + Q_OBJECT +public: + DiveListView(QWidget *parent = 0); + void selectionChanged(const QItemSelection& selected, const QItemSelection& deselected); + void currentChanged(const QModelIndex& current, const QModelIndex& previous); + void setModel(QAbstractItemModel* model); + void mousePressEvent(QMouseEvent* event); + void mouseReleaseEvent(QMouseEvent* event); + void keyPressEvent(QKeyEvent* event); + void keyReleaseEvent(QKeyEvent*); + void setSelection(const QRect& rect, QItemSelectionModel::SelectionFlags command); + void reload(); + +Q_SIGNALS: + void currentDiveChanged(int divenr); +private: + bool mouseClickSelection; +}; + +#endif // DIVELISTVIEW_H diff --git a/qt-ui/globe.cpp b/qt-ui/globe.cpp new file mode 100644 index 000000000..93d1ab7c4 --- /dev/null +++ b/qt-ui/globe.cpp @@ -0,0 +1,113 @@ +#include "globe.h" +#include "../dive.h" + +#include + +#include +#include +#include +#include +#include + +#include +#include + +GlobeGPS::GlobeGPS(QWidget* parent) : MarbleWidget(parent), loadedDives(0) +{ + setMapThemeId("earth/bluemarble/bluemarble.dgml"); + setProjection( Marble::Spherical ); + + setAnimationsEnabled(true); + setShowClouds( false ); + setShowBorders( false ); + setShowPlaces( false ); + setShowCrosshairs( false ); + setShowGrid( false ); + setShowOverviewMap(false); + setShowScaleBar(false); + + Q_FOREACH( AbstractFloatItem * floatItem, floatItems() ){ + if ( floatItem && floatItem->nameId() == "compass" ) { + floatItem->setPosition( QPoint( 10, 10 ) ); + floatItem->setContentSize( QSize( 50, 50 ) ); + } + } +} + +void GlobeGPS::reload() +{ + if (loadedDives){ + model()->treeModel()->removeDocument(loadedDives); + delete loadedDives; + } + if (editingDiveCoords){ + editingDiveCoords = 0; + } + + loadedDives = new GeoDataDocument; + + int idx = 0; + struct dive *dive; + for_each_dive(idx, dive) { + if (dive_has_gps_location(dive)) { + // don't add dive locations twice. + if( diveLocations.contains( QString(dive->location))) + continue; + + GeoDataPlacemark *place = new GeoDataPlacemark( dive->location ); + place->setCoordinate(dive->longitude.udeg / 1000000.0,dive->latitude.udeg / 1000000.0 , 0, GeoDataCoordinates::Degree ); + loadedDives->append( place ); + } + } + model()->treeModel()->addDocument( loadedDives ); +} + +void GlobeGPS::centerOn(dive* dive) +{ + qreal longitude = dive->longitude.udeg / 1000000.0; + qreal latitude = dive->latitude.udeg / 1000000.0; + + if (!longitude || !latitude){ + prepareForGetDiveCoordinates(dive); + return; + } + + centerOn(longitude,latitude, true); +} + +void GlobeGPS::prepareForGetDiveCoordinates(dive* dive) +{ + QMessageBox::warning(parentWidget(), + tr("This dive has no location!"), + tr("Move the planet to the desired position, then \n double-click to set the new location of this dive.")); + + editingDiveCoords = dive; +} + +void GlobeGPS::changeDiveGeoPosition(qreal lon, qreal lat, GeoDataCoordinates::Unit unit) +{ + // convert to degrees if in radian. + if (unit == GeoDataCoordinates::Radian){ + lon = lon * 180 / M_PI; + lat = lat * 180 / M_PI; + } + + if (!editingDiveCoords){ + return; + } + + editingDiveCoords->latitude.udeg = (int) lat * 1000000.0; + editingDiveCoords->longitude.udeg = (int) lon * 1000000.0; + centerOn(lon, lat, true); + reload(); + editingDiveCoords = 0; +} + +void GlobeGPS::mousePressEvent(QMouseEvent* event) +{ + qreal lat, lon; + if (editingDiveCoords && geoCoordinates(event->pos().x(), event->pos().y(), lon,lat, GeoDataCoordinates::Radian)){ + changeDiveGeoPosition(lon, lat, GeoDataCoordinates::Radian); + } +} + diff --git a/qt-ui/globe.h b/qt-ui/globe.h new file mode 100644 index 000000000..5f207a502 --- /dev/null +++ b/qt-ui/globe.h @@ -0,0 +1,34 @@ +#ifndef GLOBE_H +#define GLOBE_H + +#include +#include + +#include + +using namespace Marble; +struct dive; + +class GlobeGPS : public MarbleWidget{ + Q_OBJECT + void prepareForGetDiveCoordinates(struct dive* dive); +public: + using MarbleWidget::centerOn; + GlobeGPS(QWidget *parent); + void reload(); + void centerOn(struct dive* dive); + +protected: + virtual void mousePressEvent(QMouseEvent* event); + +private: + GeoDataDocument *loadedDives; + QStringList diveLocations; + struct dive* editingDiveCoords; + +public Q_SLOTS: + void changeDiveGeoPosition(qreal lon,qreal lat,GeoDataCoordinates::Unit); + +}; + +#endif diff --git a/qt-ui/maintab.cpp b/qt-ui/maintab.cpp new file mode 100644 index 000000000..70cb3caea --- /dev/null +++ b/qt-ui/maintab.cpp @@ -0,0 +1,204 @@ +/* + * maintab.cpp + * + * classes for the "notebook" area of the main window of Subsurface + * + */ +#include "maintab.h" +#include "ui_maintab.h" +#include "addcylinderdialog.h" +#include "addweightsystemdialog.h" +#include "../helpers.h" +#include "../statistics.h" + +#include + +MainTab::MainTab(QWidget *parent) : QTabWidget(parent), + ui(new Ui::MainTab()), + weightModel(new WeightModel()), + cylindersModel(new CylindersModel()) +{ + ui->setupUi(this); + ui->cylinders->setModel(cylindersModel); + ui->weights->setModel(weightModel); + + /* example of where code is more concise than Qt designer */ + QList infoTabWidgets = ui->infoTab->children(); + Q_FOREACH( QObject* obj, infoTabWidgets ){ + QLabel* label = qobject_cast(obj); + if (label) + label->setAlignment(Qt::AlignHCenter); + } + QList statisticsTabWidgets = ui->statisticsTab->children(); + Q_FOREACH( QObject* obj, statisticsTabWidgets ){ + QLabel* label = qobject_cast(obj); + if (label) + label->setAlignment(Qt::AlignHCenter); + } +} + +void MainTab::clearEquipment() +{ +} + +void MainTab::clearInfo() +{ + ui->sacText->clear(); + ui->otuText->clear(); + ui->oxygenHeliumText->clear(); + ui->gasUsedText->clear(); + ui->dateText->clear(); + ui->diveTimeText->clear(); + ui->surfaceIntervalText->clear(); + ui->maximumDepthText->clear(); + ui->averageDepthText->clear(); + ui->visibilityText->clear(); + ui->waterTemperatureText->clear(); + ui->airTemperatureText->clear(); + ui->airPressureText->clear(); +} + +void MainTab::clearStats() +{ + ui->maximumDepthAllText->clear(); + ui->minimumDepthAllText->clear(); + ui->averageDepthAllText->clear(); + ui->maximumSacAllText->clear(); + ui->minimumSacAllText->clear(); + ui->averageSacAllText->clear(); + ui->divesAllText->clear(); + ui->maximumTemperatureAllText->clear(); + ui->minimumTemperatureAllText->clear(); + ui->averageTemperatureAllText->clear(); + ui->totalTimeAllText->clear(); + ui->averageTimeAllText->clear(); + ui->longestAllText->clear(); + ui->shortestAllText->clear(); +} + +#define UPDATE_TEXT(d, field) \ + if (!d || !d->field) \ + ui->field->setText(""); \ + else \ + ui->field->setText(d->field) + + +void MainTab::updateDiveInfo(int dive) +{ + // So, this is what happens now: + // Every tab should be populated from this method, + // it will be called whenever a new dive is selected + // I'm already populating the 'notes' box + // to show how it can be done. + // If you are unsure about the name of something, + // open the file maintab.ui on the designer + // click on the item and check its objectName, + // the access is ui->objectName from here on. + volume_t sacVal; + struct dive *d = get_dive(dive); + UPDATE_TEXT(d, notes); + UPDATE_TEXT(d, location); + UPDATE_TEXT(d, suit); + UPDATE_TEXT(d, divemaster); + UPDATE_TEXT(d, buddy); + /* infoTab */ + if (d) { + ui->rating->setCurrentStars(d->rating); + ui->maximumDepthText->setText(get_depth_string(d->maxdepth, TRUE)); + ui->averageDepthText->setText(get_depth_string(d->meandepth, TRUE)); + ui->otuText->setText(QString("%1").arg(d->otu)); + ui->waterTemperatureText->setText(get_temperature_string(d->watertemp, TRUE)); + ui->airTemperatureText->setText(get_temperature_string(d->airtemp, TRUE)); + ui->gasUsedText->setText(get_volume_string(get_gas_used(d), TRUE)); + if ((sacVal.mliter = d->sac) > 0) + ui->sacText->setText(get_volume_string(sacVal, TRUE).append("/min")); + else + ui->sacText->clear(); + if (d->surface_pressure.mbar) + /* this is ALWAYS displayed in mbar */ + ui->airPressureText->setText(QString("%1mbar").arg(d->surface_pressure.mbar)); + else + ui->airPressureText->clear(); + } else { + ui->rating->setCurrentStars(0); + ui->sacText->clear(); + ui->otuText->clear(); + ui->oxygenHeliumText->clear(); + ui->dateText->clear(); + ui->diveTimeText->clear(); + ui->surfaceIntervalText->clear(); + ui->maximumDepthText->clear(); + ui->averageDepthText->clear(); + ui->visibilityText->clear(); + ui->waterTemperatureText->clear(); + ui->airTemperatureText->clear(); + ui->gasUsedText->clear(); + ui->airPressureText->clear(); + } + /* statisticsTab*/ + /* we can access the stats_selection struct, but how do we ensure the relevant dives are selected + * if we don't use the gtk widget to drive this? + * Maybe call process_selected_dives? Or re-write to query our Qt list view. + */ +// qDebug("max temp %u",stats_selection.max_temp); +// qDebug("min temp %u",stats_selection.min_temp); +} + +void MainTab::on_addCylinder_clicked() +{ + if (cylindersModel->rowCount() >= MAX_CYLINDERS) + return; + + AddCylinderDialog dialog(this); + cylinder_t *newCylinder = (cylinder_t*) malloc(sizeof(cylinder_t)); + newCylinder->type.description = ""; + + dialog.setCylinder(newCylinder); + int result = dialog.exec(); + if (result == QDialog::Rejected){ + return; + } + + dialog.updateCylinder(); + cylindersModel->add(newCylinder); +} + +void MainTab::on_editCylinder_clicked() +{ +} + +void MainTab::on_delCylinder_clicked() +{ +} + +void MainTab::on_addWeight_clicked() +{ + if (weightModel->rowCount() >= MAX_WEIGHTSYSTEMS) + return; + + AddWeightsystemDialog dialog(this); + weightsystem_t newWeightsystem; + newWeightsystem.description = ""; + newWeightsystem.weight.grams = 0; + + dialog.setWeightsystem(&newWeightsystem); + int result = dialog.exec(); + if (result == QDialog::Rejected) + return; + + dialog.updateWeightsystem(); + weightModel->add(&newWeightsystem); +} + +void MainTab::on_editWeight_clicked() +{ +} + +void MainTab::on_delWeight_clicked() +{ +} + +void MainTab::reload() +{ + cylindersModel->update(); +} diff --git a/qt-ui/maintab.h b/qt-ui/maintab.h new file mode 100644 index 000000000..e09781362 --- /dev/null +++ b/qt-ui/maintab.h @@ -0,0 +1,46 @@ +/* + * maintab.h + * + * header file for the main tab of Subsurface + * + */ +#ifndef MAINTAB_H +#define MAINTAB_H + +#include +#include + +#include "models.h" + +namespace Ui +{ + class MainTab; +} + +class MainTab : public QTabWidget +{ + Q_OBJECT +public: + MainTab(QWidget *parent); + void clearStats(); + void clearInfo(); + void clearEquipment(); + void reload(); + +public Q_SLOTS: + void on_addCylinder_clicked(); + void on_editCylinder_clicked(); + void on_delCylinder_clicked(); + void on_addWeight_clicked(); + void on_editWeight_clicked(); + void on_delWeight_clicked(); + + void updateDiveInfo(int dive); + +private: + Ui::MainTab *ui; + WeightModel *weightModel; + CylindersModel *cylindersModel; +}; + +#endif diff --git a/qt-ui/maintab.ui b/qt-ui/maintab.ui new file mode 100644 index 000000000..70f88caec --- /dev/null +++ b/qt-ui/maintab.ui @@ -0,0 +1,885 @@ + + + MainTab + + + + 0 + 0 + 400 + 325 + + + + TabWidget + + + 0 + + + + Dive Notes + + + + + + Location + + + + + + + + + + Divemaster + + + + + + + Buddy + + + + + + + + + + + + + Rating + + + + + + + Suit + + + + + + + + + + Notes + + + + + + + + + + + + + + Equipment + + + + + + Qt::Vertical + + + + Cylinders + + + + + + + + + + + Edit + + + + + + + Add + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Delete + + + + + + + + + + Weight + + + + + + + + + + + Edit + + + + + + + Add + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Delete + + + + + + + + + + + + + + Dive Info + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + 10 + + + 15 + + + 10 + + + + + + 75 + true + + + + SAC + + + + + + + + 75 + true + + + + OTU + + + + + + + + 75 + true + + + + 0²/He + + + + + + + + 75 + true + + + + Gas Used + + + + + + + TextLabel + + + + + + + TextLabel + + + + + + + TextLabel + + + + + + + TextLabel + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + 10 + + + 15 + + + + + + 75 + true + + + + Date + + + + + + + TextLabel + + + + + + + TextLabel + + + + + + + + 75 + true + + + + Visibility + + + + + + + + 75 + true + + + + Water Temp. + + + + + + + TextLabel + + + + + + + TextLabel + + + + + + + TextLabel + + + + + + + + 75 + true + + + + Air Pressure + + + + + + + + 75 + true + + + + Ave. Depth + + + + + + + + 75 + true + + + + Max. Depth + + + + + + + + 75 + true + + + + Air Temp. + + + + + + + TextLabel + + + + + + + TextLabel + + + + + + + TextLabel + + + + + + + TextLabel + + + + + + + + 75 + true + + + + Interval + + + + + + + + 75 + true + + + + Dive Time + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + Stats + + + + + + Qt::Vertical + + + + 20 + 20 + + + + + + + + 10 + + + 15 + + + + + + 75 + true + + + + Max. Depth + + + + + + + + 75 + true + + + + Min. Depth + + + + + + + + 75 + true + + + + Ave. Depth + + + + + + + TextLabel + + + + + + + TextLabel + + + + + + + TextLabel + + + + + + + + 75 + true + + + + Max. SAC + + + + + + + + 75 + true + + + + Min. SAC + + + + + + + + 75 + true + + + + Ave. SAC + + + + + + + TextLabel + + + + + + + TextLabel + + + + + + + TextLabel + + + + + + + + + Qt::Vertical + + + + 20 + 20 + + + + + + + + 10 + + + 15 + + + + + + 75 + true + + + + Dives + + + Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft + + + + + + + + 75 + true + + + + Max. Temp. + + + + + + + + 75 + true + + + + Min. Temp. + + + + + + + + 75 + true + + + + Ave. Temp. + + + + + + + TextLabel + + + + + + + TextLabel + + + + + + + TextLabel + + + + + + + TextLabel + + + + + + + + 75 + true + + + + Total Time + + + + + + + + 75 + true + + + + Ave. Time + + + + + + + + 75 + true + + + + Longest + + + + + + + + 75 + true + + + + Shortest + + + + + + + TextLabel + + + + + + + TextLabel + + + + + + + TextLabel + + + + + + + TextLabel + + + + + + + + + Qt::Vertical + + + + 20 + 20 + + + + + + + + + + StarWidget + QWidget +
starwidget.h
+ 1 +
+
+ + +
diff --git a/qt-ui/mainwindow.cpp b/qt-ui/mainwindow.cpp new file mode 100644 index 000000000..c5a4e5ce3 --- /dev/null +++ b/qt-ui/mainwindow.cpp @@ -0,0 +1,464 @@ +/* + * mainwindow.cpp + * + * classes for the main UI window in Subsurface + */ +#include "mainwindow.h" +#include "ui_mainwindow.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "divelistview.h" +#include "starwidget.h" + +#include "glib.h" +#include "../dive.h" +#include "../divelist.h" +#include "../pref.h" +#include "modeldelegates.h" +#include "models.h" + +MainWindow::MainWindow() : ui(new Ui::MainWindow()) +{ + ui->setupUi(this); + readSettings(); + setWindowIcon(QIcon(":subsurface-icon")); + connect(ui->ListWidget, SIGNAL(currentDiveChanged(int)), this, SLOT(current_dive_changed(int))); + ui->ProfileWidget->setFocusProxy(ui->ListWidget); + ui->ListWidget->reload(); + ui->ListWidget->setFocus(); + ui->widget->reload(); +} + +void MainWindow::current_dive_changed(int divenr) +{ + select_dive(divenr); + ui->widget->centerOn(get_dive(selected_dive)); + redrawProfile(); + ui->InfoWidget->updateDiveInfo(divenr); +} + +void MainWindow::redrawProfile() +{ + ui->ProfileWidget->plot(get_dive(selected_dive)); +} + +void MainWindow::on_actionNew_triggered() +{ + qDebug("actionNew"); +} + +void MainWindow::on_actionOpen_triggered() +{ + QString filename = QFileDialog::getOpenFileName(this, tr("Open File"), QDir::homePath(), filter()); + if (filename.isEmpty()) + return; + + // Needed to convert to char* + QByteArray fileNamePtr = filename.toLocal8Bit(); + + on_actionClose_triggered(); + + GError *error = NULL; + parse_file(fileNamePtr.data(), &error); + set_filename(fileNamePtr.data(), TRUE); + + if (error != NULL) { + QMessageBox::warning(this, "Error", error->message); + g_error_free(error); + error = NULL; + } + process_dives(FALSE, FALSE); + + ui->InfoWidget->reload(); + ui->widget->reload(); + ui->ListWidget->reload(); + ui->ListWidget->setFocus(); +} + +void MainWindow::on_actionSave_triggered() +{ + qDebug("actionSave"); +} + +void MainWindow::on_actionSaveAs_triggered() +{ + qDebug("actionSaveAs"); +} +void MainWindow::on_actionClose_triggered() +{ + if (unsaved_changes() && (askSaveChanges() == FALSE)) + return; + + /* free the dives and trips */ + while (dive_table.nr) + delete_single_dive(0); + + /* clear the selection and the statistics */ + selected_dive = -1; + + //WARNING: Port this to Qt. + //process_selected_dives(); + + ui->InfoWidget->clearStats(); + ui->InfoWidget->clearInfo(); + ui->InfoWidget->clearEquipment(); + ui->ProfileWidget->clear(); + ui->ListWidget->reload(); + + clear_events(); +#if USE_GTK_UI + show_dive_stats(NULL); + + /* redraw the screen */ + //WARNING: Port this to Qt. + dive_list_update_dives(); + + // WARNING? Port this to Qt. + show_dive_info(NULL); +#endif /* USE_GTK_UI */ +} + +void MainWindow::on_actionImport_triggered() +{ + qDebug("actionImport"); +} + +void MainWindow::on_actionExportUDDF_triggered() +{ + qDebug("actionExportUDDF"); +} + +void MainWindow::on_actionPrint_triggered() +{ + qDebug("actionPrint"); +} + +void MainWindow::on_actionPreferences_triggered() +{ + qDebug("actionPreferences"); +} + +void MainWindow::on_actionQuit_triggered() +{ + if (unsaved_changes() && (askSaveChanges() == FALSE)) + return; + writeSettings(); + QApplication::quit(); +} + +void MainWindow::on_actionDownloadDC_triggered() +{ + qDebug("actionDownloadDC"); +} + +void MainWindow::on_actionDownloadWeb_triggered() +{ + qDebug("actionDownloadWeb");} + +void MainWindow::on_actionEditDeviceNames_triggered() +{ + qDebug("actionEditDeviceNames");} + +void MainWindow::on_actionAddDive_triggered() +{ + qDebug("actionAddDive"); +} + +void MainWindow::on_actionRenumber_triggered() +{ + qDebug("actionRenumber"); +} + +void MainWindow::on_actionAutoGroup_triggered() +{ + qDebug("actionAutoGroup"); +} + +void MainWindow::on_actionToggleZoom_triggered() +{ + qDebug("actionToggleZoom"); +} + +void MainWindow::on_actionYearlyStatistics_triggered() +{ + qDebug("actionYearlyStatistics"); +} + +void MainWindow::on_actionViewList_triggered() +{ + ui->InfoWidget->setVisible(false); + ui->ListWidget->setVisible(true); + ui->ProfileWidget->setVisible(false); +} + +void MainWindow::on_actionViewProfile_triggered() +{ + ui->InfoWidget->setVisible(false); + ui->ListWidget->setVisible(false); + ui->ProfileWidget->setVisible(true); +} + +void MainWindow::on_actionViewInfo_triggered() +{ + ui->InfoWidget->setVisible(true); + ui->ListWidget->setVisible(false); + ui->ProfileWidget->setVisible(false); +} + +void MainWindow::on_actionViewAll_triggered() +{ + ui->InfoWidget->setVisible(true); + ui->ListWidget->setVisible(true); + ui->ProfileWidget->setVisible(true); +} + +void MainWindow::on_actionPreviousDC_triggered() +{ + dc_number--; + redrawProfile(); +} + +void MainWindow::on_actionNextDC_triggered() +{ + dc_number++; + redrawProfile(); +} + +void MainWindow::on_actionSelectEvents_triggered() +{ + qDebug("actionSelectEvents"); +} + +void MainWindow::on_actionInputPlan_triggered() +{ + qDebug("actionInputPlan"); +} + +void MainWindow::on_actionAboutSubsurface_triggered() +{ + qDebug("actionAboutSubsurface"); +} + +void MainWindow::on_actionUserManual_triggered() +{ + qDebug("actionUserManual"); +} + +QString MainWindow::filter() +{ + QString f; + f += "ALL ( *.xml *.XML *.uddf *.udcf *.UDFC *.jlb *.JLB "; +#ifdef LIBZIP + f += "*.sde *.SDE *.dld *.DLD "; +#endif +#ifdef SQLITE3 + f += "*.db"; +#endif + f += ");;"; + + f += "XML (*.xml *.XML);;"; + f += "UDDF (*.uddf);;"; + f += "UDCF (*.udcf *.UDCF);;"; + f += "JLB (*.jlb *.JLB);;"; + +#ifdef LIBZIP + f += "SDE (*.sde *.SDE);;"; + f += "DLD (*.dld *.DLD);;"; +#endif +#ifdef SQLITE3 + f += "DB (*.db)"; +#endif + + return f; +} + +bool MainWindow::askSaveChanges() +{ + QString message = ! existing_filename ? tr("You have unsaved changes\nWould you like to save those before closing the datafile?") + : tr("You have unsaved changes to file: %1 \nWould you like to save those before closing the datafile?").arg(existing_filename); + + if (QMessageBox::question(this, tr("Save Changes?"), message) == QMessageBox::Ok) { + // WARNING: Port. + // file_save(NULL,NULL); + return true; + } + return false; +} + +#define GET_UNIT(v, name, field, f, t) \ + v = settings.value(QString(name)); \ + if (v.isValid()) \ + prefs.units.field = (v.toInt() == (t)) ? (t) : (f) + +#define GET_BOOL(v, name, field) \ + v = settings.value(QString(name)); \ + if (v.isValid() && v.toInt()) \ + field = TRUE; \ + else \ + field = FALSE + +void MainWindow::readSettings() +{ + int i; + QVariant v; + QSettings settings("hohndel.org","subsurface"); + + settings.beginGroup("MainWindow"); + QSize sz = settings.value("size").value(); + resize(sz); + ui->mainSplitter->restoreState(settings.value("mainSplitter").toByteArray()); + ui->infoProfileSplitter->restoreState(settings.value("infoProfileSplitter").toByteArray()); + settings.endGroup(); + + settings.beginGroup("ListWidget"); + /* if no width are set, use the calculated width for each column; + * for that to work we need to temporarily expand all rows */ + ui->ListWidget->expandAll(); + for (i = TreeItemDT::NR; i < TreeItemDT::COLUMNS; i++) { + QVariant width = settings.value(QString("colwidth%1").arg(i)); + if (width.isValid()) + ui->ListWidget->setColumnWidth(i, width.toInt()); + else + ui->ListWidget->resizeColumnToContents(i); + } + ui->ListWidget->collapseAll(); + ui->ListWidget->scrollTo(ui->ListWidget->model()->index(0,0), QAbstractItemView::PositionAtCenter); + + settings.endGroup(); + settings.beginGroup("Units"); + GET_UNIT(v, "feet", length, units::METERS, units::FEET); + GET_UNIT(v, "psi", pressure, units::BAR, units::PSI); + GET_UNIT(v, "cuft", volume, units::LITER, units::CUFT); + GET_UNIT(v, "fahrenheit", temperature, units::CELSIUS, units::FAHRENHEIT); + GET_UNIT(v, "lbs", weight, units::KG, units::LBS); + settings.endGroup(); + settings.beginGroup("DisplayListColumns"); + GET_BOOL(v, "CYLINDER", prefs.visible_cols.cylinder); + GET_BOOL(v, "TEMPERATURE", prefs.visible_cols.temperature); + GET_BOOL(v, "TOTALWEIGHT", prefs.visible_cols.totalweight); + GET_BOOL(v, "SUIT", prefs.visible_cols.suit); + GET_BOOL(v, "NITROX", prefs.visible_cols.nitrox); + GET_BOOL(v, "OTU", prefs.visible_cols.otu); + GET_BOOL(v, "MAXCNS", prefs.visible_cols.maxcns); + GET_BOOL(v, "SAC", prefs.visible_cols.sac); + GET_BOOL(v, "po2graph", prefs.pp_graphs.po2); + GET_BOOL(v, "pn2graph", prefs.pp_graphs.pn2); + GET_BOOL(v, "phegraph", prefs.pp_graphs.phe); + settings.endGroup(); + settings.beginGroup("TecDetails"); + v = settings.value(QString("po2threshold")); + if (v.isValid()) + prefs.pp_graphs.po2_threshold = v.toDouble(); + v = settings.value(QString("pn2threshold")); + if (v.isValid()) + prefs.pp_graphs.pn2_threshold = v.toDouble(); + v = settings.value(QString("phethreshold")); + if (v.isValid()) + prefs.pp_graphs.phe_threshold = v.toDouble(); + GET_BOOL(v, "mod", prefs.mod); + v = settings.value(QString("modppO2")); + if (v.isValid()) + prefs.mod_ppO2 = v.toDouble(); + GET_BOOL(v, "ead", prefs.ead); + GET_BOOL(v, "redceiling", prefs.profile_red_ceiling); + GET_BOOL(v, "calcceiling", prefs.profile_calc_ceiling); + GET_BOOL(v, "calcceiling3m", prefs.calc_ceiling_3m_incr); + v = settings.value(QString("gflow")); + if (v.isValid()) + prefs.gflow = v.toInt() / 100.0; + v = settings.value(QString("gfhigh")); + if (v.isValid()) + prefs.gfhigh = v.toInt() / 100.0; + set_gf(prefs.gflow, prefs.gfhigh); + settings.endGroup(); + +#if ONCE_WE_CAN_SET_FONTS + settings.beginGroup("Display"); + v = settings.value(QString("divelist_font")); + if (v.isValid()) + /* I don't think this is right */ + prefs.divelist_font = strdup(v.toString); +#endif + +#if ONCE_WE_HAVE_MAPS + v = settings.value(QString_int("map_provider")); + if(v.isValid()) + prefs.map_provider = v.toInt(); +#endif +} + +#define SAVE_VALUE(name, field) \ + if (prefs.field != default_prefs.field) \ + settings.setValue(name, prefs.field) + +void MainWindow::writeSettings() +{ + int i; + QSettings settings("hohndel.org","subsurface"); + + settings.beginGroup("MainWindow"); + settings.setValue("size",size()); + settings.setValue("mainSplitter", ui->mainSplitter->saveState()); + settings.setValue("infoProfileSplitter", ui->infoProfileSplitter->saveState()); + settings.endGroup(); + + settings.beginGroup("ListWidget"); + for (i = TreeItemDT::NR; i < TreeItemDT::COLUMNS; i++) + settings.setValue(QString("colwidth%1").arg(i), ui->ListWidget->columnWidth(i)); + settings.endGroup(); + settings.beginGroup("Units"); + SAVE_VALUE("feet", units.length); + SAVE_VALUE("psi", units.pressure); + SAVE_VALUE("cuft", units.volume); + SAVE_VALUE("fahrenheit", units.temperature); + SAVE_VALUE("lbs", units.weight); + settings.endGroup(); + settings.beginGroup("DisplayListColumns"); + SAVE_VALUE("TEMPERATURE", visible_cols.temperature); + SAVE_VALUE("TOTALWEIGHT", visible_cols.totalweight); + SAVE_VALUE("SUIT", visible_cols.suit); + SAVE_VALUE("CYLINDER", visible_cols.cylinder); + SAVE_VALUE("NITROX", visible_cols.nitrox); + SAVE_VALUE("SAC", visible_cols.sac); + SAVE_VALUE("OTU", visible_cols.otu); + SAVE_VALUE("MAXCNS", visible_cols.maxcns); + settings.endGroup(); + settings.beginGroup("TecDetails"); + SAVE_VALUE("po2graph", pp_graphs.po2); + SAVE_VALUE("pn2graph", pp_graphs.pn2); + SAVE_VALUE("phegraph", pp_graphs.phe); + SAVE_VALUE("po2threshold", pp_graphs.po2_threshold); + SAVE_VALUE("pn2threshold", pp_graphs.pn2_threshold); + SAVE_VALUE("phethreshold", pp_graphs.phe_threshold); + SAVE_VALUE("mod", mod); + SAVE_VALUE("modppO2", mod_ppO2); + SAVE_VALUE("ead", ead); + SAVE_VALUE("redceiling", profile_red_ceiling); + SAVE_VALUE("calcceiling", profile_calc_ceiling); + SAVE_VALUE("calcceiling3m", calc_ceiling_3m_incr); + SAVE_VALUE("gflow", gflow); + SAVE_VALUE("gfhigh", gfhigh); + settings.endGroup(); + settings.beginGroup("GeneralSettings"); + SAVE_VALUE("default_filename", default_filename); + settings.endGroup(); +} + +void MainWindow::closeEvent(QCloseEvent *event) +{ + if (unsaved_changes() && (askSaveChanges() == FALSE)) { + event->ignore(); + return; + } + event->accept(); + writeSettings(); +} diff --git a/qt-ui/mainwindow.h b/qt-ui/mainwindow.h new file mode 100644 index 000000000..60f0d4609 --- /dev/null +++ b/qt-ui/mainwindow.h @@ -0,0 +1,89 @@ +/* + * mainwindow.h + * + * header file for the main window of Subsurface + * + */ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include +#include +#include + +class QSortFilterProxyModel; +class DiveTripModel; + +namespace Ui +{ + class MainWindow; +} + +class DiveInfo; +class DiveNotes; +class Stats; +class Equipment; +class QItemSelection; + +class MainWindow : public QMainWindow +{ +Q_OBJECT +public: + MainWindow(); + +private Q_SLOTS: + + /* file menu action */ + void on_actionNew_triggered(); + void on_actionOpen_triggered(); + void on_actionSave_triggered(); + void on_actionSaveAs_triggered(); + void on_actionClose_triggered(); + void on_actionImport_triggered(); + void on_actionExportUDDF_triggered(); + void on_actionPrint_triggered(); + void on_actionPreferences_triggered(); + void on_actionQuit_triggered(); + + /* log menu actions */ + void on_actionDownloadDC_triggered(); + void on_actionDownloadWeb_triggered(); + void on_actionEditDeviceNames_triggered(); + void on_actionAddDive_triggered(); + void on_actionRenumber_triggered(); + void on_actionAutoGroup_triggered(); + void on_actionToggleZoom_triggered(); + void on_actionYearlyStatistics_triggered(); + + /* view menu actions */ + void on_actionViewList_triggered(); + void on_actionViewProfile_triggered(); + void on_actionViewInfo_triggered(); + void on_actionViewAll_triggered(); + void on_actionPreviousDC_triggered(); + void on_actionNextDC_triggered(); + + /* other menu actions */ + void on_actionSelectEvents_triggered(); + void on_actionInputPlan_triggered(); + void on_actionAboutSubsurface_triggered(); + void on_actionUserManual_triggered(); + + void current_dive_changed(int divenr); + +protected: + void closeEvent(QCloseEvent *); + +private: + Ui::MainWindow *ui; + QAction *actionNextDive; + QAction *actionPreviousDive; + + QString filter(); + bool askSaveChanges(); + void readSettings(); + void writeSettings(); + void redrawProfile(); +}; + +#endif diff --git a/qt-ui/mainwindow.ui b/qt-ui/mainwindow.ui new file mode 100644 index 000000000..ab0cd5f49 --- /dev/null +++ b/qt-ui/mainwindow.ui @@ -0,0 +1,383 @@ + + + MainWindow + + + + 0 + 0 + 763 + 548 + + + + MainWindow + + + + + + + Qt::Vertical + + + + Qt::Horizontal + + + + + + + Qt::Horizontal + + + + QTreeView { + show-decoration-selected: 1; + } + + QTreeView::item { + border: 1px solid #d9d9d9; + border-top-color: transparent; + border-bottom-color: transparent; + padding: 2px; + } + + QTreeView::item:hover { + background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #e7effd, stop: 1 #cbdaf1); + border: 1px solid #bfcde4; + } + + QTreeView::item:selected { + border: 1px solid #567dbc; + } + + QTreeView::item:selected:active{ + background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #6ea1f1, stop: 1 #567dbc); + } + + QTreeView::item:selected:!active { + background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #6b9be8, stop: 1 #577fbf); + } + + + + + true + + + QAbstractItemView::ExtendedSelection + + + true + + + true + + + true + + + true + + + + + + + + + + + + 0 + 0 + 763 + 25 + + + + + File + + + + + + + + + + + + + + + + + + + Log + + + + + + + + + + + + + + + View + + + + + + + + + + + Filter + + + + + + Planner + + + + + + Help + + + + + + + + + + + + + + New + + + Ctrl+N + + + + + Open + + + Ctrl+O + + + + + Save + + + Ctrl+S + + + + + Save as + + + Ctrl+Shift+S + + + + + Close + + + Ctrl+W + + + + + Import Files + + + Ctrl+I + + + + + Export UDDF + + + + + Print + + + Ctrl+P + + + + + Preferences + + + + + Quit + + + Ctrl+Q + + + + + Download from Dive computer + + + Ctrl+D + + + + + Download from Web Service + + + + + Edit Device Names + + + + + Add Dive + + + + + Renumber + + + + + Auto Group + + + + + Toggle Zoom + + + + + Yearly Statistics + + + + + View List + + + Ctrl+1 + + + + + View Profile + + + Ctrl+2 + + + + + View Info + + + Ctrl+3 + + + + + View All + + + Ctrl+4 + + + + + Prev DC + + + Left + + + + + Next DC + + + Right + + + + + Select Events + + + + + Input Plan + + + + + About Subsurface + + + + + User Manual + + + + + + MainTab + QWidget +
maintab.h
+ 1 +
+ + DiveListView + QTreeView +
divelistview.h
+
+ + ProfileGraphicsView + QGraphicsView +
profilegraphics.h
+
+ + GlobeGPS + QWidget +
globe.h
+ 1 +
+
+ + +
diff --git a/qt-ui/modeldelegates.cpp b/qt-ui/modeldelegates.cpp new file mode 100644 index 000000000..be47198e2 --- /dev/null +++ b/qt-ui/modeldelegates.cpp @@ -0,0 +1,49 @@ +#include "modeldelegates.h" +#include "../dive.h" +#include "../divelist.h" +#include "starwidget.h" +#include "models.h" + +#include +#include +#include +#include +#include + +StarWidgetsDelegate::StarWidgetsDelegate(QWidget* parent): + QStyledItemDelegate(parent), + parentWidget(parent) +{ + +} + +void StarWidgetsDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const +{ + QStyledItemDelegate::paint(painter, option, index); + + if (!index.isValid()) + return; + + QVariant value = index.model()->data(index, TreeItemDT::STAR_ROLE); + + if (!value.isValid()) + return; + + int rating = value.toInt(); + + painter->save(); + painter->setRenderHint(QPainter::Antialiasing, true); + + for(int i = 0; i < rating; i++) + painter->drawPixmap(option.rect.x() + i * IMG_SIZE + SPACING, option.rect.y(), StarWidget::starActive()); + + for(int i = rating; i < TOTALSTARS; i++) + painter->drawPixmap(option.rect.x() + i * IMG_SIZE + SPACING, option.rect.y(), StarWidget::starInactive()); + + painter->restore(); +} + +QSize StarWidgetsDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const +{ + return QSize(IMG_SIZE * TOTALSTARS + SPACING * (TOTALSTARS-1), IMG_SIZE); +} diff --git a/qt-ui/modeldelegates.h b/qt-ui/modeldelegates.h new file mode 100644 index 000000000..5f90a3061 --- /dev/null +++ b/qt-ui/modeldelegates.h @@ -0,0 +1,15 @@ +#ifndef MODELDELEGATES_H +#define MODELDELEGATES_H + +#include + +class StarWidgetsDelegate : public QStyledItemDelegate { + Q_OBJECT +public: + explicit StarWidgetsDelegate(QWidget* parent = 0); + virtual void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const; + virtual QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const; +private: + QWidget *parentWidget; +}; +#endif diff --git a/qt-ui/models.cpp b/qt-ui/models.cpp new file mode 100644 index 000000000..9bc8db0bb --- /dev/null +++ b/qt-ui/models.cpp @@ -0,0 +1,705 @@ +/* + * models.cpp + * + * classes for the equipment models of Subsurface + * + */ +#include "models.h" +#include +#include +#include +#include + +extern struct tank_info tank_info[100]; + +CylindersModel::CylindersModel(QObject* parent): QAbstractTableModel(parent) +{ +} + +QVariant CylindersModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + QVariant ret; + if (orientation == Qt::Vertical) + return ret; + + if (role == Qt::DisplayRole) { + switch(section) { + case TYPE: + ret = tr("Type"); + break; + case SIZE: + ret = tr("Size"); + break; + case MAXPRESS: + ret = tr("MaxPress"); + break; + case START: + ret = tr("Start"); + break; + case END: + ret = tr("End"); + break; + case O2: + ret = tr("O2%"); + break; + case HE: + ret = tr("He%"); + break; + } + } + return ret; +} + +int CylindersModel::columnCount(const QModelIndex& parent) const +{ + return 7; +} + +QVariant CylindersModel::data(const QModelIndex& index, int role) const +{ + QVariant ret; + if (!index.isValid() || index.row() >= MAX_CYLINDERS) + return ret; + + cylinder_t& cyl = current_dive->cylinder[index.row()]; + + if (role == Qt::DisplayRole) { + switch(index.column()) { + case TYPE: + ret = QString(cyl.type.description); + break; + case SIZE: + ret = cyl.type.size.mliter; + break; + case MAXPRESS: + ret = cyl.type.workingpressure.mbar; + break; + case START: + ret = cyl.start.mbar; + break; + case END: + ret = cyl.end.mbar; + break; + case O2: + ret = cyl.gasmix.o2.permille; + break; + case HE: + ret = cyl.gasmix.he.permille; + break; + } + } + return ret; +} + +int CylindersModel::rowCount(const QModelIndex& parent) const +{ + return usedRows[current_dive]; +} + +void CylindersModel::add(cylinder_t* cyl) +{ + if (usedRows[current_dive] >= MAX_CYLINDERS) { + free(cyl); + return; + } + + int row = usedRows[current_dive]; + + cylinder_t& cylinder = current_dive->cylinder[row]; + + cylinder.end.mbar = cyl->end.mbar; + cylinder.start.mbar = cyl->start.mbar; + + beginInsertRows(QModelIndex(), row, row); + usedRows[current_dive]++; + endInsertRows(); +} + +void CylindersModel::update() +{ + if (usedRows[current_dive] > 0) { + beginRemoveRows(QModelIndex(), 0, usedRows[current_dive]-1); + endRemoveRows(); + } + if (usedRows[current_dive] > 0) { + beginInsertRows(QModelIndex(), 0, usedRows[current_dive]-1); + endInsertRows(); + } +} + +void CylindersModel::clear() +{ + if (usedRows[current_dive] > 0) { + beginRemoveRows(QModelIndex(), 0, usedRows[current_dive]-1); + usedRows[current_dive] = 0; + endRemoveRows(); + } +} + +void WeightModel::clear() +{ + if (usedRows[current_dive] > 0) { + beginRemoveRows(QModelIndex(), 0, usedRows[current_dive]-1); + usedRows[current_dive] = 0; + endRemoveRows(); + } +} + +int WeightModel::columnCount(const QModelIndex& parent) const +{ + return 2; +} + +QVariant WeightModel::data(const QModelIndex& index, int role) const +{ + QVariant ret; + if (!index.isValid() || index.row() >= MAX_WEIGHTSYSTEMS) + return ret; + + weightsystem_t *ws = ¤t_dive->weightsystem[index.row()]; + + if (role == Qt::DisplayRole) { + switch(index.column()) { + case TYPE: + ret = QString(ws->description); + break; + case WEIGHT: + if (get_units()->weight == units::KG) { + int gr = ws->weight.grams % 1000; + int kg = ws->weight.grams / 1000; + ret = QString("%1.%2").arg(kg).arg((unsigned) gr / 100); + } else { + ret = QString("%1").arg((unsigned)(grams_to_lbs(ws->weight.grams))); + } + break; + } + } + return ret; +} + +int WeightModel::rowCount(const QModelIndex& parent) const +{ + return usedRows[current_dive]; +} + +QVariant WeightModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + QVariant ret; + if (orientation == Qt::Vertical) + return ret; + + if (role == Qt::DisplayRole) { + switch(section) { + case TYPE: + ret = tr("Type"); + break; + case WEIGHT: + ret = tr("Weight"); + break; + } + } + return ret; +} + +void WeightModel::add(weightsystem_t* weight) +{ + if (usedRows[current_dive] >= MAX_WEIGHTSYSTEMS) { + free(weight); + return; + } + + int row = usedRows[current_dive]; + + weightsystem_t *ws = ¤t_dive->weightsystem[row]; + + ws->description = weight->description; + ws->weight.grams = weight->weight.grams; + + beginInsertRows(QModelIndex(), row, row); + usedRows[current_dive]++; + endInsertRows(); +} + +void WeightModel::update() +{ +} + +void TankInfoModel::add(const QString& description) +{ + // When the user `creates` a new one on the combobox. + // for now, empty till dirk cleans the GTK code. +} + +void TankInfoModel::clear() +{ +} + +int TankInfoModel::columnCount(const QModelIndex& parent) const +{ + return 3; +} + +QVariant TankInfoModel::data(const QModelIndex& index, int role) const +{ + QVariant ret; + if (!index.isValid()) { + return ret; + } + struct tank_info *info = &tank_info[index.row()]; + + int ml = info->ml; + + int bar = ((info->psi) ? psi_to_bar(info->psi) : info->bar) * 1000 + 0.5; + + if (info->cuft) { + double airvolume = cuft_to_l(info->cuft) * 1000.0; + ml = airvolume / bar_to_atm(bar) + 0.5; + } + if (role == Qt::DisplayRole) { + switch(index.column()) { + case BAR: + ret = bar; + break; + case ML: + ret = ml; + break; + case DESCRIPTION: + ret = QString(info->name); + break; + } + } + return ret; +} + +QVariant TankInfoModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + QVariant ret; + + if (orientation != Qt::Horizontal) + return ret; + + if (role == Qt::DisplayRole) { + switch(section) { + case BAR: + ret = tr("Bar"); + break; + case ML: + ret = tr("Ml"); + break; + case DESCRIPTION: + ret = tr("Description"); + break; + } + } + return ret; +} + +int TankInfoModel::rowCount(const QModelIndex& parent) const +{ + return rows+1; +} + +TankInfoModel::TankInfoModel() : QAbstractTableModel(), rows(-1) +{ + struct tank_info *info = tank_info; + for (info = tank_info; info->name; info++, rows++); + + if (rows > -1) { + beginInsertRows(QModelIndex(), 0, rows); + endInsertRows(); + } +} + +void TankInfoModel::update() +{ + if(rows > -1) { + beginRemoveRows(QModelIndex(), 0, rows); + endRemoveRows(); + } + struct tank_info *info = tank_info; + for (info = tank_info; info->name; info++, rows++); + + if (rows > -1) { + beginInsertRows(QModelIndex(), 0, rows); + endInsertRows(); + } +} + + +/*! A DiveItem for use with a DiveTripModel + * + * A simple class which wraps basic stats for a dive (e.g. duration, depth) and + * tidies up after it's children. This is done manually as we don't inherit from + * QObject. + * +*/ + +TreeItemDT::~TreeItemDT() +{ + qDeleteAll(children); +} + +int TreeItemDT::row() const +{ + if (parent) + return parent->children.indexOf(const_cast(this)); + + return 0; +} + +QVariant TreeItemDT::data(int column, int role) const +{ + QVariant ret; + switch (column) { + case NR: + ret = tr("#"); + break; + case DATE: + ret = tr("Date"); + break; + case RATING: + ret = UTF8_BLACKSTAR; + break; + case DEPTH: + ret = (get_units()->length == units::METERS) ? tr("m") : tr("ft"); + break; + case DURATION: + ret = tr("min"); + break; + case TEMPERATURE: + ret = QString("%1%2").arg(UTF8_DEGREE).arg( (get_units()->temperature == units::CELSIUS) ? "C" : "F"); + break; + case TOTALWEIGHT: + ret = (get_units()->weight == units::KG) ? tr("kg") : tr("lbs"); + break; + case SUIT: + ret = tr("Suit"); + break; + case CYLINDER: + ret = tr("Cyl"); + break; + case NITROX: + ret = QString("O%1%").arg(UTF8_SUBSCRIPT_2); + break; + case SAC: + ret = tr("SAC"); + break; + case OTU: + ret = tr("OTU"); + break; + case MAXCNS: + ret = tr("maxCNS"); + break; + case LOCATION: + ret = tr("Location"); + break; + } + return ret; +} + +struct TripItem : public TreeItemDT { + virtual QVariant data(int column, int role) const; + dive_trip_t* trip; +}; + +QVariant TripItem::data(int column, int role) const +{ + QVariant ret; + + if (role == Qt::DisplayRole) { + switch (column) { + case LOCATION: + ret = QString(trip->location); + break; + case DATE: + ret = QString(get_trip_date_string(trip->when, trip->nrdives)); + break; + } + } + + return ret; +} + +struct DiveItem : public TreeItemDT { + virtual QVariant data(int column, int role) const; + struct dive* dive; + + QString displayDuration() const; + QString displayDepth() const; + QString displayTemperature() const; + QString displayWeight() const; + QString displaySac() const; + int weight() const; +}; + +QVariant DiveItem::data(int column, int role) const +{ + QVariant retVal; + + switch (role) { + case Qt::TextAlignmentRole: + switch (column) { + case DATE: /* fall through */ + case SUIT: /* fall through */ + case LOCATION: + retVal = Qt::AlignLeft; + break; + default: + retVal = Qt::AlignRight; + break; + } + break; + case Qt::DisplayRole: + switch (column) { + case NR: + retVal = dive->number; + break; + case DATE: + retVal = QString(get_dive_date_string(dive->when)); + break; + case DEPTH: + retVal = displayDepth(); + break; + case DURATION: + retVal = displayDuration(); + break; + case TEMPERATURE: + retVal = displayTemperature(); + break; + case TOTALWEIGHT: + retVal = displayWeight(); + break; + case SUIT: + retVal = QString(dive->suit); + break; + case CYLINDER: + retVal = QString(dive->cylinder[0].type.description); + break; + case NITROX: + retVal = QString(get_nitrox_string(dive)); + break; + case SAC: + retVal = displaySac(); + break; + case OTU: + retVal = dive->otu; + break; + case MAXCNS: + retVal = dive->maxcns; + break; + case LOCATION: + retVal = QString(dive->location); + break; + } + break; + } + + if(role == STAR_ROLE){ + retVal = dive->rating; + } + + if(role == DIVE_ROLE){ + retVal = QVariant::fromValue(dive); + } + + return retVal; +} + +QString DiveItem::displayDepth() const +{ + const int scale = 1000; + QString fract, str; + if (get_units()->length == units::METERS) { + fract = QString::number((unsigned)(dive->maxdepth.mm % scale) / 10); + str = QString("%1.%2").arg((unsigned)(dive->maxdepth.mm / scale)).arg(fract, 2, QChar('0')); + } + if (get_units()->length == units::FEET) { + str = QString::number(mm_to_feet(dive->maxdepth.mm),'f',0); + } + return str; +} + +QString DiveItem::displayDuration() const +{ + int hrs, mins, secs; + secs = dive->duration.seconds % 60; + mins = dive->duration.seconds / 60; + hrs = mins / 60; + mins -= hrs * 60; + + QString displayTime; + if (hrs) + displayTime = QString("%1:%2:").arg(hrs).arg(mins, 2, 10, QChar('0')); + else + displayTime = QString("%1:").arg(mins); + displayTime += QString("%1").arg(secs, 2, 10, QChar('0')); + return displayTime; +} + +QString DiveItem::displayTemperature() const +{ + QString str; + + if (!dive->watertemp.mkelvin) + return str; + + if (get_units()->temperature == units::CELSIUS) + str = QString::number(mkelvin_to_C(dive->watertemp.mkelvin), 'f', 1); + else + str = QString::number(mkelvin_to_F(dive->watertemp.mkelvin), 'f', 1); + + return str; +} + +QString DiveItem::displaySac() const +{ + QString str; + + if (get_units()->volume == units::LITER) + str = QString::number(dive->sac / 1000, 'f', 1); + else + str = QString::number(ml_to_cuft(dive->sac), 'f', 2); + + return str; +} + +QString DiveItem::displayWeight() const +{ + QString str; + + if (get_units()->weight == units::KG) { + int gr = weight() % 1000; + int kg = weight() / 1000; + str = QString("%1.%2").arg(kg).arg((unsigned)(gr) / 100); + } else { + str = QString("%1").arg((unsigned)(grams_to_lbs(weight()))); + } + + return str; +} + +int DiveItem::weight() const +{ + weight_t tw = { total_weight(dive) }; + return tw.grams; +} + + +DiveTripModel::DiveTripModel(QObject* parent) : + QAbstractItemModel(parent) +{ + rootItem = new TreeItemDT(); + setupModelData(); +} + +DiveTripModel::~DiveTripModel() +{ + delete rootItem; +} + +int DiveTripModel::columnCount(const QModelIndex& parent) const +{ + if (parent.isValid()) + return static_cast(parent.internalPointer())->columnCount(); + else + return rootItem->columnCount(); +} + +QVariant DiveTripModel::data(const QModelIndex& index, int role) const +{ + if (!index.isValid()) + return QVariant(); + + TreeItemDT* item = static_cast(index.internalPointer()); + + return item->data(index.column(), role); +} + +Qt::ItemFlags DiveTripModel::flags(const QModelIndex& index) const +{ + if (!index.isValid()) + return 0; + + return Qt::ItemIsEnabled | Qt::ItemIsSelectable; +} + +QVariant DiveTripModel::headerData(int section, Qt::Orientation orientation, + int role) const +{ + if (orientation == Qt::Horizontal && role == Qt::DisplayRole) + return rootItem->data(section, role); + + return QVariant(); +} + +QModelIndex DiveTripModel::index(int row, int column, const QModelIndex& parent) +const +{ + if (!hasIndex(row, column, parent)) + return QModelIndex(); + + TreeItemDT* parentItem = (!parent.isValid()) ? rootItem : static_cast(parent.internalPointer()); + + TreeItemDT* childItem = parentItem->children[row]; + + return (childItem) ? createIndex(row, column, childItem) : QModelIndex(); +} + +QModelIndex DiveTripModel::parent(const QModelIndex& index) const +{ + if (!index.isValid()) + return QModelIndex(); + + TreeItemDT* childItem = static_cast(index.internalPointer()); + TreeItemDT* parentItem = childItem->parent; + + if (parentItem == rootItem || !parentItem) + return QModelIndex(); + + return createIndex(parentItem->row(), 0, parentItem); +} + +int DiveTripModel::rowCount(const QModelIndex& parent) const +{ + TreeItemDT* parentItem; + + if (!parent.isValid()) + parentItem = rootItem; + else + parentItem = static_cast(parent.internalPointer()); + + int amount = parentItem->children.count(); + + return amount; +} + +void DiveTripModel::setupModelData() +{ + int i = dive_table.nr; + + while (--i >= 0) { + struct dive* dive = get_dive(i); + update_cylinder_related_info(dive); + dive_trip_t* trip = dive->divetrip; + + DiveItem* diveItem = new DiveItem(); + diveItem->dive = dive; + + if (!trip) { + diveItem->parent = rootItem; + rootItem->children.push_back(diveItem); + continue; + } + if (!trips.keys().contains(trip)) { + TripItem* tripItem = new TripItem(); + tripItem->trip = trip; + tripItem->parent = rootItem; + tripItem->children.push_back(diveItem); + trips[trip] = tripItem; + rootItem->children.push_back(tripItem); + continue; + } + TripItem* tripItem = trips[trip]; + tripItem->children.push_back(diveItem); + } +} diff --git a/qt-ui/models.h b/qt-ui/models.h new file mode 100644 index 000000000..ac533fd71 --- /dev/null +++ b/qt-ui/models.h @@ -0,0 +1,126 @@ +/* + * models.h + * + * header file for the equipment models of Subsurface + * + */ +#ifndef MODELS_H +#define MODELS_H + +#include +#include + +#include "../dive.h" +#include "../divelist.h" + +/* Encapsulates the tank_info global variable + * to show on Qt's Model View System.*/ +class TankInfoModel : public QAbstractTableModel { +Q_OBJECT +public: + enum Column { DESCRIPTION, ML, BAR}; + TankInfoModel(); + + /*reimp*/ QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + /*reimp*/ int columnCount(const QModelIndex& parent = QModelIndex()) const; + /*reimp*/ QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const; + /*reimp*/ int rowCount(const QModelIndex& parent = QModelIndex()) const; + + void add(const QString& description); + void clear(); + void update(); +private: + int rows; +}; + +/* Encapsulation of the Cylinder Model, that presents the + * Current cylinders that are used on a dive. */ +class CylindersModel : public QAbstractTableModel { +Q_OBJECT +public: + enum Column {TYPE, SIZE, MAXPRESS, START, END, O2, HE}; + + explicit CylindersModel(QObject* parent = 0); + /*reimp*/ QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + /*reimp*/ int columnCount(const QModelIndex& parent = QModelIndex()) const; + /*reimp*/ QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const; + /*reimp*/ int rowCount(const QModelIndex& parent = QModelIndex()) const; + + void add(cylinder_t *cyl); + void clear(); + void update(); +private: + /* Since the dive doesn't stores the number of cylinders that + * it has (max 8) and since I don't want to make a + * model-for-each-dive, let's hack this here instead. */ + QMap usedRows; +}; + +/* Encapsulation of the Weight Model, that represents + * the current weights on a dive. */ +class WeightModel : public QAbstractTableModel { +Q_OBJECT +public: + enum Column {TYPE, WEIGHT}; + /*reimp*/ QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + /*reimp*/ int columnCount(const QModelIndex& parent = QModelIndex()) const; + /*reimp*/ QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const; + /*reimp*/ int rowCount(const QModelIndex& parent = QModelIndex()) const; + + void add(weightsystem_t *weight); + void clear(); + void update(); +private: + /* Remember the number of rows in a dive */ + QMap usedRows; +}; + +/*! An AbstractItemModel for recording dive trip information such as a list of dives. +* +*/ + +struct TreeItemDT { + Q_DECLARE_TR_FUNCTIONS ( TreeItemDT ); +public: + enum Column {NR, DATE, RATING, DEPTH, DURATION, TEMPERATURE, TOTALWEIGHT, + SUIT, CYLINDER, NITROX, SAC, OTU, MAXCNS, LOCATION, DIVE, COLUMNS }; + + enum ExtraRoles{STAR_ROLE = Qt::UserRole + 1, DIVE_ROLE}; + + virtual ~TreeItemDT(); + int columnCount() const { + return COLUMNS; + }; + + virtual QVariant data ( int column, int role ) const; + int row() const; + QList children; + TreeItemDT *parent; +}; + +struct TripItem; + +class DiveTripModel : public QAbstractItemModel +{ + Q_OBJECT + +public: + DiveTripModel(QObject *parent = 0); + ~DiveTripModel(); + + /*reimp*/ Qt::ItemFlags flags(const QModelIndex &index) const; + /*reimp*/ QVariant data(const QModelIndex &index, int role) const; + /*reimp*/ QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + /*reimp*/ int rowCount(const QModelIndex &parent = QModelIndex()) const; + /*reimp*/ int columnCount(const QModelIndex &parent = QModelIndex()) const; + /*reimp*/ QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const; + /*reimp*/ QModelIndex parent(const QModelIndex &child) const; + +private: + void setupModelData(); + + TreeItemDT *rootItem; + QMap trips; +}; + +#endif diff --git a/qt-ui/plotareascene.cpp b/qt-ui/plotareascene.cpp new file mode 100644 index 000000000..a728040f5 --- /dev/null +++ b/qt-ui/plotareascene.cpp @@ -0,0 +1,6 @@ +/* + * plotareascene.cpp + * + * classes for profile plot area scene of Subsurface + * + */ diff --git a/qt-ui/plotareascene.h b/qt-ui/plotareascene.h new file mode 100644 index 000000000..a5b07d1be --- /dev/null +++ b/qt-ui/plotareascene.h @@ -0,0 +1,6 @@ +/* + * plotareascene.h + * + * header file for the profile plot area scene of Subsurface + * + */ diff --git a/qt-ui/profilegraphics.cpp b/qt-ui/profilegraphics.cpp new file mode 100644 index 000000000..637c7fe95 --- /dev/null +++ b/qt-ui/profilegraphics.cpp @@ -0,0 +1,1397 @@ +#include "profilegraphics.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../color.h" +#include "../display.h" +#include "../dive.h" +#include "../profile.h" + +#include +#include + +#define SAC_COLORS_START_IDX SAC_1 +#define SAC_COLORS 9 +#define VELOCITY_COLORS_START_IDX VELO_STABLE +#define VELOCITY_COLORS 5 + +static struct graphics_context last_gc; +static double plot_scale = SCALE_SCREEN; + +typedef enum { + /* SAC colors. Order is important, the SAC_COLORS_START_IDX define above. */ + SAC_1, SAC_2, SAC_3, SAC_4, SAC_5, SAC_6, SAC_7, SAC_8, SAC_9, + + /* Velocity colors. Order is still important, ref VELOCITY_COLORS_START_IDX. */ + VELO_STABLE, VELO_SLOW, VELO_MODERATE, VELO_FAST, VELO_CRAZY, + + /* gas colors */ + PO2, PO2_ALERT, PN2, PN2_ALERT, PHE, PHE_ALERT, PP_LINES, + + /* Other colors */ + TEXT_BACKGROUND, ALERT_BG, ALERT_FG, EVENTS, SAMPLE_DEEP, SAMPLE_SHALLOW, + SMOOTHED, MINUTE, TIME_GRID, TIME_TEXT, DEPTH_GRID, MEAN_DEPTH, DEPTH_TOP, + DEPTH_BOTTOM, TEMP_TEXT, TEMP_PLOT, SAC_DEFAULT, BOUNDING_BOX, PRESSURE_TEXT, BACKGROUND, + CEILING_SHALLOW, CEILING_DEEP, CALC_CEILING_SHALLOW, CALC_CEILING_DEEP +} color_indice_t; + + +#define COLOR(x, y, z) QVector() << x << y << z; +/* profile_color[color indice] = COLOR(screen color, b/w printer color, color printer}} printer & screen colours could be different */ +QMap > profile_color; +void fill_profile_color() +{ + profile_color[SAC_1] = COLOR(FUNGREEN1, BLACK1_LOW_TRANS, FUNGREEN1); + profile_color[SAC_2] = COLOR(APPLE1, BLACK1_LOW_TRANS, APPLE1); + profile_color[SAC_3] = COLOR(ATLANTIS1, BLACK1_LOW_TRANS, ATLANTIS1); + profile_color[SAC_4] = COLOR(ATLANTIS2, BLACK1_LOW_TRANS, ATLANTIS2); + profile_color[SAC_5] = COLOR(EARLSGREEN1, BLACK1_LOW_TRANS, EARLSGREEN1); + profile_color[SAC_6] = COLOR(HOKEYPOKEY1, BLACK1_LOW_TRANS, HOKEYPOKEY1); + profile_color[SAC_7] = COLOR(TUSCANY1, BLACK1_LOW_TRANS, TUSCANY1); + profile_color[SAC_8] = COLOR(CINNABAR1, BLACK1_LOW_TRANS, CINNABAR1); + profile_color[SAC_9] = COLOR(REDORANGE1, BLACK1_LOW_TRANS, REDORANGE1); + + profile_color[VELO_STABLE] = COLOR(CAMARONE1, BLACK1_LOW_TRANS, CAMARONE1); + profile_color[VELO_SLOW] = COLOR(LIMENADE1, BLACK1_LOW_TRANS, LIMENADE1); + profile_color[VELO_MODERATE] = COLOR(RIOGRANDE1, BLACK1_LOW_TRANS, RIOGRANDE1); + profile_color[VELO_FAST] = COLOR(PIRATEGOLD1, BLACK1_LOW_TRANS, PIRATEGOLD1); + profile_color[VELO_CRAZY] = COLOR(RED1, BLACK1_LOW_TRANS, RED1); + + profile_color[PO2] = COLOR(APPLE1, BLACK1_LOW_TRANS, APPLE1); + profile_color[PO2_ALERT] = COLOR(RED1, BLACK1_LOW_TRANS, RED1); + profile_color[PN2] = COLOR(BLACK1_LOW_TRANS, BLACK1_LOW_TRANS, BLACK1_LOW_TRANS); + profile_color[PN2_ALERT] = COLOR(RED1, BLACK1_LOW_TRANS, RED1); + profile_color[PHE] = COLOR(PEANUT, BLACK1_LOW_TRANS, PEANUT); + profile_color[PHE_ALERT] = COLOR(RED1, BLACK1_LOW_TRANS, RED1); + profile_color[PP_LINES] = COLOR(BLACK1_HIGH_TRANS, BLACK1_HIGH_TRANS, BLACK1_HIGH_TRANS); + + profile_color[TEXT_BACKGROUND] = COLOR(CONCRETE1_LOWER_TRANS, WHITE1, CONCRETE1_LOWER_TRANS); + profile_color[ALERT_BG] = COLOR(BROOM1_LOWER_TRANS, BLACK1_LOW_TRANS, BROOM1_LOWER_TRANS); + profile_color[ALERT_FG] = COLOR(BLACK1_LOW_TRANS, BLACK1_LOW_TRANS, BLACK1_LOW_TRANS); + profile_color[EVENTS] = COLOR(REDORANGE1, BLACK1_LOW_TRANS, REDORANGE1); + profile_color[SAMPLE_DEEP] = COLOR(PERSIANRED1, BLACK1_LOW_TRANS, PERSIANRED1); + profile_color[SAMPLE_SHALLOW] = COLOR(PERSIANRED1, BLACK1_LOW_TRANS, PERSIANRED1); + profile_color[SMOOTHED] = COLOR(REDORANGE1_HIGH_TRANS, BLACK1_LOW_TRANS, REDORANGE1_HIGH_TRANS); + profile_color[MINUTE] = COLOR(MEDIUMREDVIOLET1_HIGHER_TRANS, BLACK1_LOW_TRANS, MEDIUMREDVIOLET1_HIGHER_TRANS); + profile_color[TIME_GRID] = COLOR(WHITE1, BLACK1_HIGH_TRANS, TUNDORA1_MED_TRANS); + profile_color[TIME_TEXT] = COLOR(FORESTGREEN1, BLACK1_LOW_TRANS, FORESTGREEN1); + profile_color[DEPTH_GRID] = COLOR(WHITE1, BLACK1_HIGH_TRANS, TUNDORA1_MED_TRANS); + profile_color[MEAN_DEPTH] = COLOR(REDORANGE1_MED_TRANS, BLACK1_LOW_TRANS, REDORANGE1_MED_TRANS); + profile_color[DEPTH_BOTTOM] = COLOR(GOVERNORBAY1_MED_TRANS, BLACK1_HIGH_TRANS, GOVERNORBAY1_MED_TRANS); + profile_color[DEPTH_TOP] = COLOR(MERCURY1_MED_TRANS, WHITE1_MED_TRANS, MERCURY1_MED_TRANS); + profile_color[TEMP_TEXT] = COLOR(GOVERNORBAY2, BLACK1_LOW_TRANS, GOVERNORBAY2); + profile_color[TEMP_PLOT] = COLOR(ROYALBLUE2_LOW_TRANS, BLACK1_LOW_TRANS, ROYALBLUE2_LOW_TRANS); + profile_color[SAC_DEFAULT] = COLOR(WHITE1, BLACK1_LOW_TRANS, FORESTGREEN1); + profile_color[BOUNDING_BOX] = COLOR(WHITE1, BLACK1_LOW_TRANS, TUNDORA1_MED_TRANS); + profile_color[PRESSURE_TEXT] = COLOR(KILLARNEY1, BLACK1_LOW_TRANS, KILLARNEY1); + profile_color[BACKGROUND] = COLOR(SPRINGWOOD1, BLACK1_LOW_TRANS, SPRINGWOOD1); + profile_color[CEILING_SHALLOW] = COLOR(REDORANGE1_HIGH_TRANS, BLACK1_HIGH_TRANS, REDORANGE1_HIGH_TRANS); + profile_color[CEILING_DEEP] = COLOR(RED1_MED_TRANS, BLACK1_HIGH_TRANS, RED1_MED_TRANS); + profile_color[CALC_CEILING_SHALLOW] = COLOR(FUNGREEN1_HIGH_TRANS, BLACK1_HIGH_TRANS, FUNGREEN1_HIGH_TRANS); + profile_color[CALC_CEILING_DEEP] = COLOR(APPLE1_HIGH_TRANS, BLACK1_HIGH_TRANS, APPLE1_HIGH_TRANS); + +} +#undef COLOR + +struct text_render_options{ + double size; + color_indice_t color; + double hpos, vpos; +}; + +extern struct ev_select *ev_namelist; +extern int evn_allocated; +extern int evn_used; + +ProfileGraphicsView::ProfileGraphicsView(QWidget* parent) : QGraphicsView(parent), toolTip(0) , dive(0) +{ + gc.printer = false; + setScene(new QGraphicsScene()); + setBackgroundBrush(QColor("#F3F3E6")); + scene()->installEventFilter(this); + + setRenderHint(QPainter::Antialiasing); + setRenderHint(QPainter::HighQualityAntialiasing); + setRenderHint(QPainter::SmoothPixmapTransform); + + defaultPen.setJoinStyle(Qt::RoundJoin); + defaultPen.setCapStyle(Qt::RoundCap); + defaultPen.setWidth(2); + defaultPen.setCosmetic(true); + + setHorizontalScrollBarPolicy ( Qt::ScrollBarAlwaysOff ); + setVerticalScrollBarPolicy ( Qt::ScrollBarAlwaysOff ); + + fill_profile_color(); +} + +void ProfileGraphicsView::wheelEvent(QWheelEvent* event) +{ + if (!toolTip) + return; + + setTransformationAnchor(QGraphicsView::AnchorUnderMouse); + + // Scale the view / do the zoom + QPoint toolTipPos = mapFromScene(toolTip->pos()); + double scaleFactor = 1.15; + if(event->delta() > 0 && zoomLevel <= 10) { + scale(scaleFactor, scaleFactor); + zoomLevel++; + } else if (zoomLevel >= 0) { + // Zooming out + scale(1.0 / scaleFactor, 1.0 / scaleFactor); + zoomLevel--; + } + toolTip->setPos(mapToScene(toolTipPos).x(), mapToScene(toolTipPos).y()); +} + +void ProfileGraphicsView::mouseMoveEvent(QMouseEvent* event) +{ + if (!toolTip) + return; + + toolTip->refresh(&gc, mapToScene(event->pos())); + + QPoint toolTipPos = mapFromScene(toolTip->pos()); + + double dx = sceneRect().x(); + double dy = sceneRect().y(); + + ensureVisible(event->pos().x() + dx, event->pos().y() + dy, 1, 1); + + toolTip->setPos(mapToScene(toolTipPos).x(), mapToScene(toolTipPos).y()); + + if (zoomLevel < 0) + QGraphicsView::mouseMoveEvent(event); +} + +bool ProfileGraphicsView::eventFilter(QObject* obj, QEvent* event) +{ + // This will "Eat" the default tooltip behavior. + if (event->type() == QEvent::GraphicsSceneHelp) { + event->ignore(); + return true; + } + return QGraphicsView::eventFilter(obj, event); +} + +static void plot_set_scale(scale_mode_t scale) +{ + switch (scale) { + default: + case SC_SCREEN: + plot_scale = SCALE_SCREEN; + break; + case SC_PRINT: + plot_scale = SCALE_PRINT; + break; + } +} + +void ProfileGraphicsView::showEvent(QShowEvent* event) +{ + // Program just opened, + // but the dive was not ploted. + // force a replot by modifying the dive + // hold by the view, and issuing a plot. + if (dive){ + dive = 0; + plot(get_dive(selected_dive)); + } +} + +void ProfileGraphicsView::clear() +{ + scene()->clear(); + resetTransform(); + zoomLevel = 0; + toolTip = 0; +} + +void ProfileGraphicsView::plot(struct dive *d) +{ + if (dive == d) + return; + + clear(); + dive = d; + + if(!isVisible() || !dive){ + return; + } + + scene()->setSceneRect(0,0, viewport()->width()-50, viewport()->height()-50); + + QSettings s; + s.beginGroup("ProfileMap"); + QPointF toolTipPos = s.value("tooltip_position", QPointF(0,0)).toPointF(); + s.endGroup(); + + toolTip = new ToolTipItem(); + toolTip->setPos(toolTipPos); + + scene()->addItem(toolTip); + + struct divecomputer *dc = &dive->dc; + + + // Fix this for printing / screen later. + // plot_set_scale(scale_mode_t); + + if (!dc->samples) { + static struct sample fake[4]; + static struct divecomputer fakedc; + fakedc = dive->dc; + fakedc.sample = fake; + fakedc.samples = 4; + + /* The dive has no samples, so create a few fake ones. This assumes an + ascent/descent rate of 9 m/min, which is just below the limit for FAST. */ + int duration = dive->dc.duration.seconds; + int maxdepth = dive->dc.maxdepth.mm; + int asc_desc_time = dive->dc.maxdepth.mm*60/9000; + if (asc_desc_time * 2 >= duration) + asc_desc_time = duration / 2; + fake[1].time.seconds = asc_desc_time; + fake[1].depth.mm = maxdepth; + fake[2].time.seconds = duration - asc_desc_time; + fake[2].depth.mm = maxdepth; + fake[3].time.seconds = duration * 1.00; + fakedc.events = dc->events; + dc = &fakedc; + } + + /* + * Set up limits that are independent of + * the dive computer + */ + calculate_max_limits(dive, dc, &gc); + + QRectF profile_grid_area = scene()->sceneRect(); + gc.maxx = (profile_grid_area.width() - 2 * profile_grid_area.x()); + gc.maxy = (profile_grid_area.height() - 2 * profile_grid_area.y()); + + dc = select_dc(dc); + + /* This is per-dive-computer. Right now we just do the first one */ + gc.pi = *create_plot_info(dive, dc, &gc); + + /* Depth profile */ + plot_depth_profile(); + + plot_events(dc); + + /* Temperature profile */ + plot_temperature_profile(); + + /* Cylinder pressure plot */ + plot_cylinder_pressure(dive, dc); + + /* Text on top of all graphs.. */ + plot_temperature_text(); + + plot_depth_text(); + + plot_cylinder_pressure_text(); + + plot_deco_text(); + + /* Bounding box */ + QPen pen = defaultPen; + pen.setColor(profile_color[TIME_GRID].at(0)); + QGraphicsRectItem *rect = new QGraphicsRectItem(profile_grid_area); + rect->setPen(pen); + scene()->addItem(rect); + + /* Put the dive computer name in the lower left corner */ + QString nick(get_dc_nickname(dc->model, dc->deviceid)); + if (nick.isEmpty()) + nick = QString(dc->model); + + if (nick.isEmpty()) + nick = tr("unknown divecomputer"); + + gc.leftx = 0; gc.rightx = 1.0; + gc.topy = 0; gc.bottomy = 1.0; + + text_render_options_t computer = {DC_TEXT_SIZE, TIME_TEXT, LEFT, MIDDLE}; + diveComputer = plot_text(&computer, QPointF(gc.leftx, gc.bottomy), nick); + // The Time ruler should be right after the DiveComputer: + timeMarkers->setPos(0, diveComputer->y()); + + if (PP_GRAPHS_ENABLED) { + plot_pp_gas_profile(); + plot_pp_text(); + } + + + /* now shift the translation back by half the margin; + * this way we can draw the vertical scales on both sides */ + //cairo_translate(gc->cr, -drawing_area->x / 2.0, 0); + + //gc->maxx += drawing_area->x; + //gc->leftx = -(drawing_area->x / drawing_area->width) / 2.0; + //gc->rightx = 1.0 - gc->leftx; + + plot_depth_scale(); + +#if 0 + if (gc->printer) { + free(pi->entry); + last_pi_entry = pi->entry = NULL; + pi->nr = 0; + } +#endif + + QRectF r = scene()->itemsBoundingRect(); + scene()->setSceneRect(r.x() - 15, r.y() -15, r.width() + 30, r.height() + 30); + if(zoomLevel == 0){ + fitInView(sceneRect()); + } +} + +void ProfileGraphicsView::plot_depth_scale() +{ + int i, maxdepth, marker; + static text_render_options_t tro = {DEPTH_TEXT_SIZE, SAMPLE_DEEP, RIGHT, MIDDLE}; + + /* Depth markers: every 30 ft or 10 m*/ + maxdepth = get_maxdepth(&gc.pi); + gc.topy = 0; gc.bottomy = maxdepth; + + switch (prefs.units.length) { + case units::METERS: marker = 10000; break; + case units::FEET: marker = 9144; break; /* 30 ft */ + } + + QColor c(profile_color[DEPTH_GRID].first()); + + /* don't write depth labels all the way to the bottom as + * there may be other graphs below the depth plot (like + * partial pressure graphs) where this would look out + * of place - so we only make sure that we print the next + * marker below the actual maxdepth of the dive */ + depthMarkers = new QGraphicsRectItem(); + for (i = marker; i <= gc.pi.maxdepth + marker; i += marker) { + double d = get_depth_units(i, NULL, NULL); + plot_text(&tro, QPointF(-0.002, i), QString::number(d), depthMarkers); + } + scene()->addItem(depthMarkers); + depthMarkers->setPos(depthMarkers->pos().x() - 10, 0); +} + +void ProfileGraphicsView::plot_pp_text() +{ + double pp, dpp, m; + int hpos; + static text_render_options_t tro = {PP_TEXT_SIZE, PP_LINES, LEFT, MIDDLE}; + + setup_pp_limits(&gc); + pp = floor(gc.pi.maxpp * 10.0) / 10.0 + 0.2; + dpp = pp > 4 ? 1.0 : 0.5; + hpos = gc.pi.entry[gc.pi.nr - 1].sec; + QColor c = profile_color[PP_LINES].first(); + + for (m = 0.0; m <= pp; m += dpp) { + QGraphicsLineItem *item = new QGraphicsLineItem(SCALEGC(0, m), SCALEGC(hpos, m)); + QPen pen(defaultPen); + pen.setColor(c); + item->setPen(pen); + scene()->addItem(item); + plot_text(&tro, QPointF(hpos + 30, m), QString::number(m)); + } +} + +void ProfileGraphicsView::plot_pp_gas_profile() +{ + int i; + struct plot_data *entry; + struct plot_info *pi = &gc.pi; + + setup_pp_limits(&gc); + QColor c; + QPointF from, to; + //if (prefs.pp_graphs.pn2) { + c = profile_color[PN2].first(); + entry = pi->entry; + from = QPointF(SCALEGC(entry->sec, entry->pn2)); + for (i = 1; i < pi->nr; i++) { + entry++; + if (entry->pn2 < prefs.pp_graphs.pn2_threshold){ + to = QPointF(SCALEGC(entry->sec, entry->pn2)); + QGraphicsLineItem *item = new QGraphicsLineItem(from.x(), from.y(), to.x(), to.y()); + QPen pen(defaultPen); + pen.setColor(c); + item->setPen(pen); + scene()->addItem(item); + from = to; + } + else{ + from = QPointF(SCALEGC(entry->sec, entry->pn2)); + } + } + + c = profile_color[PN2_ALERT].first(); + entry = pi->entry; + from = QPointF(SCALEGC(entry->sec, entry->pn2)); + for (i = 1; i < pi->nr; i++) { + entry++; + if (entry->pn2 >= prefs.pp_graphs.pn2_threshold){ + to = QPointF(SCALEGC(entry->sec, entry->pn2)); + QGraphicsLineItem *item = new QGraphicsLineItem(from.x(), from.y(), to.x(), to.y()); + QPen pen(defaultPen); + pen.setColor(c); + item->setPen(pen); + scene()->addItem(item); + from = to; + } + else{ + from = QPointF(SCALEGC(entry->sec, entry->pn2)); + } + } + //} + + //if (prefs.pp_graphs.phe) { + c = profile_color[PHE].first(); + entry = pi->entry; + + from = QPointF(SCALEGC(entry->sec, entry->phe)); + for (i = 1; i < pi->nr; i++) { + entry++; + if (entry->phe < prefs.pp_graphs.phe_threshold){ + to = QPointF(SCALEGC(entry->sec, entry->phe)); + QGraphicsLineItem *item = new QGraphicsLineItem(from.x(), from.y(), to.x(), to.y()); + QPen pen(defaultPen); + pen.setColor(c); + item->setPen(pen); + scene()->addItem(item); + from = to; + } + else{ + from = QPointF(SCALEGC(entry->sec, entry->phe)); + } + } + + c = profile_color[PHE_ALERT].first(); + entry = pi->entry; + from = QPointF(SCALEGC(entry->sec, entry->phe)); + for (i = 1; i < pi->nr; i++) { + entry++; + if (entry->phe >= prefs.pp_graphs.phe_threshold){ + to = QPointF(SCALEGC(entry->sec, entry->phe)); + QGraphicsLineItem *item = new QGraphicsLineItem(from.x(), from.y(), to.x(), to.y()); + QPen pen(defaultPen); + pen.setColor(c); + item->setPen(pen); + scene()->addItem(item); + from = to; + } + else{ + from = QPointF(SCALEGC(entry->sec, entry->phe)); + } + } + //} + //if (prefs.pp_graphs.po2) { + c = profile_color[PO2].first(); + entry = pi->entry; + from = QPointF(SCALEGC(entry->sec, entry->po2)); + for (i = 1; i < pi->nr; i++) { + entry++; + if (entry->po2 < prefs.pp_graphs.po2_threshold){ + to = QPointF(SCALEGC(entry->sec, entry->po2)); + QGraphicsLineItem *item = new QGraphicsLineItem(from.x(), from.y(), to.x(), to.y()); + QPen pen(defaultPen); + pen.setColor(c); + item->setPen(pen); + scene()->addItem(item); + from = to; + } + else{ + from = QPointF(SCALEGC(entry->sec, entry->po2)); + } + } + + c = profile_color[PO2_ALERT].first(); + entry = pi->entry; + from = QPointF(SCALEGC(entry->sec, entry->po2)); + for (i = 1; i < pi->nr; i++) { + entry++; + if (entry->po2 >= prefs.pp_graphs.po2_threshold){ + to = QPointF(SCALEGC(entry->sec, entry->po2)); + QGraphicsLineItem *item = new QGraphicsLineItem(from.x(), from.y(), to.x(), to.y()); + item->setPen(QPen(c)); + scene()->addItem(item); + from = to; + } + else{ + from = QPointF(SCALEGC(entry->sec, entry->po2)); + } + } + //} +} + +void ProfileGraphicsView::plot_deco_text() +{ + if (prefs.profile_calc_ceiling) { + float x = gc.leftx + (gc.rightx - gc.leftx) / 2; + float y = gc.topy = 1.0; + static text_render_options_t tro = {PRESSURE_TEXT_SIZE, PRESSURE_TEXT, CENTER, -0.2}; + gc.bottomy = 0.0; + plot_text(&tro, QPointF(x, y), QString("GF %1/%2").arg(prefs.gflow * 100).arg(prefs.gfhigh * 100)); + } +} + +void ProfileGraphicsView::plot_cylinder_pressure_text() +{ + int i; + int mbar, cyl; + int seen_cyl[MAX_CYLINDERS] = { FALSE, }; + int last_pressure[MAX_CYLINDERS] = { 0, }; + int last_time[MAX_CYLINDERS] = { 0, }; + struct plot_data *entry; + struct plot_info *pi = &gc.pi; + + if (!get_cylinder_pressure_range(&gc)) + return; + + cyl = -1; + for (i = 0; i < pi->nr; i++) { + entry = pi->entry + i; + mbar = GET_PRESSURE(entry); + + if (!mbar) + continue; + if (cyl != entry->cylinderindex) { + cyl = entry->cylinderindex; + if (!seen_cyl[cyl]) { + plot_pressure_value(mbar, entry->sec, LEFT, BOTTOM); + seen_cyl[cyl] = TRUE; + } + } + last_pressure[cyl] = mbar; + last_time[cyl] = entry->sec; + } + + for (cyl = 0; cyl < MAX_CYLINDERS; cyl++) { + if (last_time[cyl]) { + plot_pressure_value(last_pressure[cyl], last_time[cyl], CENTER, TOP); + } + } +} + +void ProfileGraphicsView::plot_pressure_value(int mbar, int sec, double xalign, double yalign) +{ + int pressure; + const char *unit; + + pressure = get_pressure_units(mbar, &unit); + static text_render_options_t tro = {PRESSURE_TEXT_SIZE, PRESSURE_TEXT, xalign, yalign}; + plot_text(&tro, QPointF(sec, mbar), QString("%1 %2").arg(pressure).arg(unit)); +} + +void ProfileGraphicsView::plot_depth_text() +{ + int maxtime, maxdepth; + + /* Get plot scaling limits */ + maxtime = get_maxtime(&gc.pi); + maxdepth = get_maxdepth(&gc.pi); + + gc.leftx = 0; gc.rightx = maxtime; + gc.topy = 0; gc.bottomy = maxdepth; + + plot_text_samples(); +} + +void ProfileGraphicsView::plot_text_samples() +{ + static text_render_options_t deep = {14, SAMPLE_DEEP, CENTER, TOP}; + static text_render_options_t shallow = {14, SAMPLE_SHALLOW, CENTER, BOTTOM}; + int i; + int last = -1; + + struct plot_info* pi = &gc.pi; + + for (i = 0; i < pi->nr; i++) { + struct plot_data *entry = pi->entry + i; + + if (entry->depth < 2000) + continue; + + if ((entry == entry->max[2]) && entry->depth / 100 != last) { + plot_depth_sample(entry, &deep); + last = entry->depth / 100; + } + + if ((entry == entry->min[2]) && entry->depth / 100 != last) { + plot_depth_sample(entry, &shallow); + last = entry->depth / 100; + } + + if (entry->depth != last) + last = -1; + } +} + +void ProfileGraphicsView::plot_depth_sample(struct plot_data *entry,text_render_options_t *tro) +{ + int sec = entry->sec, decimals; + double d; + + d = get_depth_units(entry->depth, &decimals, NULL); + + plot_text(tro, QPointF(sec, entry->depth), QString("%1").arg(d, 0, 'f', 1)); +} + + +void ProfileGraphicsView::plot_temperature_text() +{ + int i; + int last = -300, sec = 0; + int last_temperature = 0, last_printed_temp = 0; + plot_info *pi = &gc.pi; + + if (!setup_temperature_limits(&gc)) + return; + + for (i = 0; i < pi->nr; i++) { + struct plot_data *entry = pi->entry+i; + int mkelvin = entry->temperature; + sec = entry->sec; + + if (!mkelvin) + continue; + last_temperature = mkelvin; + /* don't print a temperature + * if it's been less than 5min and less than a 2K change OR + * if it's been less than 2min OR if the change from the + * last print is less than .4K (and therefore less than 1F) */ + if (((sec < last + 300) && (abs(mkelvin - last_printed_temp) < 2000)) || + (sec < last + 120) || + (abs(mkelvin - last_printed_temp) < 400)) + continue; + last = sec; + if (mkelvin > 200000) + plot_single_temp_text(sec,mkelvin); + last_printed_temp = mkelvin; + } + /* it would be nice to print the end temperature, if it's + * different or if the last temperature print has been more + * than a quarter of the dive back */ + if (last_temperature > 200000 && + ((abs(last_temperature - last_printed_temp) > 500) || ((double)last / (double)sec < 0.75))) + plot_single_temp_text(sec, last_temperature); +} + +void ProfileGraphicsView::plot_single_temp_text(int sec, int mkelvin) +{ + double deg; + const char *unit; + static text_render_options_t tro = {TEMP_TEXT_SIZE, TEMP_TEXT, LEFT, TOP}; + deg = get_temp_units(mkelvin, &unit); + plot_text(&tro, QPointF(sec, mkelvin), QString("%1%2").arg(deg, 0, 'f', 1).arg(unit)); //"%.2g%s" +} + +void ProfileGraphicsView::plot_cylinder_pressure(struct dive *dive, struct divecomputer *dc) +{ + int i; + int last = -1, last_index = -1; + int lift_pen = FALSE; + int first_plot = TRUE; + int sac = 0; + struct plot_data *last_entry = NULL; + + if (!get_cylinder_pressure_range(&gc)) + return; + + QPointF from, to; + for (i = 0; i < gc.pi.nr; i++) { + int mbar; + struct plot_data *entry = gc.pi.entry + i; + + mbar = GET_PRESSURE(entry); + if (entry->cylinderindex != last_index) { + lift_pen = TRUE; + last_entry = NULL; + } + if (!mbar) { + lift_pen = TRUE; + continue; + } + if (!last_entry) { + last = i; + last_entry = entry; + sac = get_local_sac(entry, gc.pi.entry + i + 1, dive); + } else { + int j; + sac = 0; + for (j = last; j < i; j++) + sac += get_local_sac(gc.pi.entry + j, gc.pi.entry + j + 1, dive); + sac /= (i - last); + if (entry->sec - last_entry->sec >= SAC_WINDOW) { + last++; + last_entry = gc.pi.entry + last; + } + } + + QColor c = get_sac_color(sac, dive->sac); + + if (lift_pen) { + if (!first_plot && entry->cylinderindex == last_index) { + /* if we have a previous event from the same tank, + * draw at least a short line */ + int prev_pr; + prev_pr = GET_PRESSURE(entry - 1); + + QGraphicsLineItem *item = new QGraphicsLineItem(SCALEGC((entry-1)->sec, prev_pr), SCALEGC(entry->sec, mbar)); + QPen pen(defaultPen); + pen.setColor(c); + item->setPen(pen); + scene()->addItem(item); + } else { + first_plot = FALSE; + from = QPointF(SCALEGC(entry->sec, mbar)); + } + lift_pen = FALSE; + } else { + to = QPointF(SCALEGC(entry->sec, mbar)); + QGraphicsLineItem *item = new QGraphicsLineItem(from.x(), from.y(), to.x(), to.y()); + QPen pen(defaultPen); + pen.setColor(c); + item->setPen(pen); + scene()->addItem(item); + } + + + from = QPointF(SCALEGC(entry->sec, mbar)); + last_index = entry->cylinderindex; + } +} + + +/* set the color for the pressure plot according to temporary sac rate + * as compared to avg_sac; the calculation simply maps the delta between + * sac and avg_sac to indexes 0 .. (SAC_COLORS - 1) with everything + * more than 6000 ml/min below avg_sac mapped to 0 */ +QColor ProfileGraphicsView::get_sac_color(int sac, int avg_sac) +{ + int sac_index = 0; + int delta = sac - avg_sac + 7000; + + if (!gc.printer) { + sac_index = delta / 2000; + if (sac_index < 0) + sac_index = 0; + if (sac_index > SAC_COLORS - 1) + sac_index = SAC_COLORS - 1; + return profile_color[ (color_indice_t) (SAC_COLORS_START_IDX + sac_index)].first(); + } + return profile_color[SAC_DEFAULT].first(); +} + +void ProfileGraphicsView::plot_events(struct divecomputer *dc) +{ + struct event *event = dc->events; + +// if (gc->printer){ +// return; +// } + + while (event) { + plot_one_event(event); + event = event->next; + } +} + +void ProfileGraphicsView::plot_one_event(struct event *ev) +{ + int i, depth = 0; + struct plot_info *pi = &gc.pi; + + /* is plotting of this event disabled? */ + if (ev->name) { + for (i = 0; i < evn_used; i++) { + if (! strcmp(ev->name, ev_namelist[i].ev_name)) { + if (ev_namelist[i].plot_ev) + break; + else + return; + } + } + } + + if (ev->time.seconds < 30 && !strcmp(ev->name, "gaschange")) + /* a gas change in the first 30 seconds is the way of some dive computers + * to tell us the gas that is used; let's not plot a marker for that */ + return; + + for (i = 0; i < pi->nr; i++) { + struct plot_data *data = pi->entry + i; + if (ev->time.seconds < data->sec) + break; + depth = data->depth; + } + + /* draw a little triangular marker and attach tooltip */ + + int x = SCALEXGC(ev->time.seconds); + int y = SCALEYGC(depth); + + EventItem *item = new EventItem(); + item->setPos(x, y); + scene()->addItem(item); + + /* we display the event on screen - so translate */ + QString name = tr(ev->name); + if (ev->value) { + if (ev->name && name == "gaschange") { + unsigned int he = ev->value >> 16; + unsigned int o2 = ev->value & 0xffff; + + name += ": "; + name += (he) ? QString("%1/%2").arg(o2, he) + : (o2 == 21) ? name += tr("air") + : QString("%1% %2").arg(o2).arg("O" UTF8_SUBSCRIPT_2); + + } else if (ev->name && !strcmp(ev->name, "SP change")) { + name += QString(":%1").arg((double) ev->value / 1000); + } else { + name += QString(":%1").arg(ev->value); + } + } else if (ev->name && name == "SP change") { + name += tr("Bailing out to OC"); + } else { + name += ev->flags == SAMPLE_FLAGS_BEGIN ? tr("Starts with space!"," begin") : + ev->flags == SAMPLE_FLAGS_END ? tr("Starts with space!", " end") : ""; + } + + //item->setToolTipController(toolTip); + //item->addToolTip(name); + item->setToolTip(name); +} + +void ProfileGraphicsView::plot_depth_profile() +{ + int i, incr; + int sec, depth; + struct plot_data *entry; + int maxtime, maxdepth, marker, maxline; + int increments[8] = { 10, 20, 30, 60, 5*60, 10*60, 15*60, 30*60 }; + + /* Get plot scaling limits */ + maxtime = get_maxtime(&gc.pi); + maxdepth = get_maxdepth(&gc.pi); + + gc.maxtime = maxtime; + + /* Time markers: at most every 10 seconds, but no more than 12 markers. + * We start out with 10 seconds and increment up to 30 minutes, + * depending on the dive time. + * This allows for 6h dives - enough (I hope) for even the craziest + * divers - but just in case, for those 8h depth-record-breaking dives, + * we double the interval if this still doesn't get us to 12 or fewer + * time markers */ + i = 0; + while (maxtime / increments[i] > 12 && i < 7) + i++; + incr = increments[i]; + while (maxtime / incr > 12) + incr *= 2; + + gc.leftx = 0; gc.rightx = maxtime; + gc.topy = 0; gc.bottomy = 1.0; + + last_gc = gc; + + QColor c = profile_color[TIME_GRID].at(0); + for (i = incr; i < maxtime; i += incr) { + QGraphicsLineItem *item = new QGraphicsLineItem(SCALEGC(i, 0), SCALEGC(i, 1)); + QPen pen(defaultPen); + pen.setColor(c); + item->setPen(pen); + scene()->addItem(item); + } + + timeMarkers = new QGraphicsRectItem(); + /* now the text on the time markers */ + struct text_render_options tro = {DEPTH_TEXT_SIZE, TIME_TEXT, CENTER, TOP}; + if (maxtime < 600) { + /* Be a bit more verbose with shorter dives */ + for (i = incr; i < maxtime; i += incr) + plot_text(&tro, QPointF(i, 0), QString("%1:%2").arg(i/60).arg(i%60), timeMarkers); + } else { + /* Only render the time on every second marker for normal dives */ + for (i = incr; i < maxtime; i += 2 * incr) + plot_text(&tro, QPointF(i, 0), QString("%1").arg(QString::number(i/60)), timeMarkers); + } + timeMarkers->setPos(0,0); + scene()->addItem(timeMarkers); + + /* Depth markers: every 30 ft or 10 m*/ + gc.leftx = 0; gc.rightx = 1.0; + gc.topy = 0; gc.bottomy = maxdepth; + switch (prefs.units.length) { + case units::METERS: + marker = 10000; + break; + case units::FEET: + marker = 9144; + break; /* 30 ft */ + } + maxline = MAX(gc.pi.maxdepth + marker, maxdepth * 2 / 3); + + c = profile_color[DEPTH_GRID].at(0); + + for (i = marker; i < maxline; i += marker) { + QGraphicsLineItem *item = new QGraphicsLineItem(SCALEGC(0, i), SCALEGC(1, i)); + QPen pen(defaultPen); + pen.setColor(c); + item->setPen(pen); + scene()->addItem(item); + } + + gc.leftx = 0; gc.rightx = maxtime; + c = profile_color[MEAN_DEPTH].at(0); + + /* Show mean depth */ + if (! gc.printer) { + QGraphicsLineItem *item = new QGraphicsLineItem(SCALEGC(0, gc.pi.meandepth), + SCALEGC(gc.pi.entry[gc.pi.nr - 1].sec, gc.pi.meandepth)); + QPen pen(defaultPen); + pen.setColor(c); + item->setPen(pen); + scene()->addItem(item); + } + +#if 0 + /* + * These are good for debugging text placement etc, + * but not for actual display.. + */ + if (0) { + plot_smoothed_profile(gc, pi); + plot_minmax_profile(gc, pi); + } +#endif + + /* Do the depth profile for the neat fill */ + gc.topy = 0; gc.bottomy = maxdepth; + + entry = gc.pi.entry; + + QPolygonF p; + QLinearGradient pat(0.0,0.0,0.0,scene()->height()); + QGraphicsPolygonItem *neatFill = NULL; + + p.append(QPointF(SCALEGC(0, 0))); + for (i = 0; i < gc.pi.nr; i++, entry++) + p.append(QPointF(SCALEGC(entry->sec, entry->depth))); + + /* Show any ceiling we may have encountered */ + for (i = gc.pi.nr - 1; i >= 0; i--, entry--) { + if (entry->ndl) { + /* non-zero NDL implies this is a safety stop, no ceiling */ + p.append(QPointF(SCALEGC(entry->sec, 0))); + } else if (entry->stopdepth < entry->depth) { + p.append(QPointF(SCALEGC(entry->sec, entry->stopdepth))); + } else { + p.append(QPointF(SCALEGC(entry->sec, entry->depth))); + } + } + pat.setColorAt(1, profile_color[DEPTH_BOTTOM].first()); + pat.setColorAt(0, profile_color[DEPTH_TOP].first()); + + neatFill = new QGraphicsPolygonItem(); + neatFill->setPolygon(p); + neatFill->setBrush(QBrush(pat)); + neatFill->setPen(QPen(QBrush(Qt::transparent),0)); + scene()->addItem(neatFill); + + + /* if the user wants the deco ceiling more visible, do that here (this + * basically draws over the background that we had allowed to shine + * through so far) */ + // TODO: port the prefs.profile_red_ceiling to QSettings + + //if (prefs.profile_red_ceiling) { + p.clear(); + pat.setColorAt(0, profile_color[CEILING_SHALLOW].first()); + pat.setColorAt(1, profile_color[CEILING_DEEP].first()); + + entry = gc.pi.entry; + p.append(QPointF(SCALEGC(0, 0))); + for (i = 0; i < gc.pi.nr; i++, entry++) { + if (entry->ndl == 0 && entry->stopdepth) { + if (entry->ndl == 0 && entry->stopdepth < entry->depth) { + p.append(QPointF(SCALEGC(entry->sec, entry->stopdepth))); + } else { + p.append(QPointF(SCALEGC(entry->sec, entry->depth))); + } + } else { + p.append(QPointF(SCALEGC(entry->sec, 0))); + } + } + + neatFill = new QGraphicsPolygonItem(); + neatFill->setBrush(QBrush(pat)); + neatFill->setPolygon(p); + neatFill->setPen(QPen(QBrush(Qt::NoBrush),0)); + scene()->addItem(neatFill); + //} + + /* finally, plot the calculated ceiling over all this */ + // TODO: Port the profile_calc_ceiling to QSettings + // if (prefs.profile_calc_ceiling) { + + pat.setColorAt(0, profile_color[CALC_CEILING_SHALLOW].first()); + pat.setColorAt(1, profile_color[CALC_CEILING_DEEP].first()); + + entry = gc.pi.entry; + p.clear(); + p.append(QPointF(SCALEGC(0, 0))); + for (i = 0; i < gc.pi.nr; i++, entry++) { + if (entry->ceiling) + p.append(QPointF(SCALEGC(entry->sec, entry->ceiling))); + else + p.append(QPointF(SCALEGC(entry->sec, 0))); + } + p.append(QPointF(SCALEGC((entry-1)->sec, 0))); + neatFill = new QGraphicsPolygonItem(); + neatFill->setPolygon(p); + neatFill->setPen(QPen(QBrush(Qt::NoBrush),0)); + neatFill->setBrush(pat); + scene()->addItem(neatFill); + //} + /* next show where we have been bad and crossed the dc's ceiling */ + pat.setColorAt(0, profile_color[CEILING_SHALLOW].first()); + pat.setColorAt(1, profile_color[CEILING_DEEP].first()); + + entry = gc.pi.entry; + p.clear(); + p.append(QPointF(SCALEGC(0, 0))); + for (i = 0; i < gc.pi.nr; i++, entry++) + p.append(QPointF(SCALEGC(entry->sec, entry->depth))); + + for (i = gc.pi.nr - 1; i >= 0; i--, entry--) { + if (entry->ndl == 0 && entry->stopdepth > entry->depth) { + p.append(QPointF(SCALEGC(entry->sec, entry->stopdepth))); + } else { + p.append(QPointF(SCALEGC(entry->sec, entry->depth))); + } + } + + neatFill = new QGraphicsPolygonItem(); + neatFill->setPolygon(p); + neatFill->setPen(QPen(QBrush(Qt::NoBrush),0)); + neatFill->setBrush(QBrush(pat)); + scene()->addItem(neatFill); + + /* Now do it again for the velocity colors */ + entry = gc.pi.entry; + for (i = 1; i < gc.pi.nr; i++) { + entry++; + sec = entry->sec; + /* we want to draw the segments in different colors + * representing the vertical velocity, so we need to + * chop this into short segments */ + depth = entry->depth; + QGraphicsLineItem *item = new QGraphicsLineItem(SCALEGC(entry[-1].sec, entry[-1].depth), SCALEGC(sec, depth)); + QPen pen(defaultPen); + pen.setColor(profile_color[ (color_indice_t) (VELOCITY_COLORS_START_IDX + entry->velocity)].first()); + item->setPen(pen); + scene()->addItem(item); + } +} + +QGraphicsSimpleTextItem *ProfileGraphicsView::plot_text(text_render_options_t *tro,const QPointF& pos, const QString& text, QGraphicsItem *parent) +{ + QFontMetrics fm(font()); + + double dx = tro->hpos * (fm.width(text)); + double dy = tro->vpos * (fm.height()); + + QGraphicsSimpleTextItem *item = new QGraphicsSimpleTextItem(text, parent); + QPointF point(SCALEGC(pos.x(), pos.y())); // This is neded because of the SCALE macro. + + item->setPos(point.x() + dx, point.y() + dy); + item->setBrush(QBrush(profile_color[tro->color].first())); + item->setFlag(QGraphicsItem::ItemIgnoresTransformations); + + if(!parent) + scene()->addItem(item); + return item; +} + +void ProfileGraphicsView::resizeEvent(QResizeEvent *event) +{ + plot(dive); +} + +void ProfileGraphicsView::plot_temperature_profile() +{ + int last = 0; + + if (!setup_temperature_limits(&gc)) + return; + + QPointF from; + QPointF to; + QColor color = profile_color[TEMP_PLOT].first(); + + for (int i = 0; i < gc.pi.nr; i++) { + struct plot_data *entry = gc.pi.entry + i; + int mkelvin = entry->temperature; + int sec = entry->sec; + if (!mkelvin) { + if (!last) + continue; + mkelvin = last; + } + if (last) { + to = QPointF(SCALEGC(sec, mkelvin)); + QGraphicsLineItem *item = new QGraphicsLineItem(from.x(), from.y(), to.x(), to.y()); + QPen pen(defaultPen); + pen.setColor(color); + item->setPen(pen); + scene()->addItem(item); + from = to; + } + else{ + from = QPointF(SCALEGC(sec, mkelvin)); + } + last = mkelvin; + } +} + +void ToolTipItem::addToolTip(const QString& toolTip, const QIcon& icon) +{ + QGraphicsPixmapItem *iconItem = 0; + double yValue = title->boundingRect().height() + SPACING; + Q_FOREACH(ToolTip t, toolTips) { + yValue += t.second->boundingRect().height(); + } + if (!icon.isNull()) { + iconItem = new QGraphicsPixmapItem(icon.pixmap(ICON_SMALL,ICON_SMALL), this); + iconItem->setPos(SPACING, yValue); + } + + QGraphicsSimpleTextItem *textItem = new QGraphicsSimpleTextItem(toolTip, this); + textItem->setPos(SPACING + ICON_SMALL + SPACING, yValue); + textItem->setBrush(QBrush(Qt::white)); + textItem->setFlag(ItemIgnoresTransformations); + toolTips[toolTip] = qMakePair(iconItem, textItem); + expand(); +} + +void ToolTipItem::removeToolTip(const QString& toolTip) +{ + ToolTip toBeRemoved = toolTips[toolTip]; + delete toBeRemoved.first; + delete toBeRemoved.second; + toolTips.remove(toolTip); + + int toolTipIndex = 0; + + // We removed a toolTip, let's move the others to the correct location + Q_FOREACH(ToolTip t, toolTips) { + double yValue = title->boundingRect().height() + SPACING + toolTipIndex * ICON_SMALL + SPACING; + + // Icons can be null. + if (t.first) + t.first->setPos(SPACING, yValue); + + t.second->setPos(SPACING + ICON_SMALL + SPACING, yValue); + toolTipIndex++; + } + + expand(); +} + +void ToolTipItem::refresh(struct graphics_context *gc, QPointF pos) +{ + clear(); + int time = (pos.x() * gc->maxtime) / scene()->sceneRect().width(); + char buffer[500]; + get_plot_details(gc, time, buffer, 500); + addToolTip(QString(buffer)); + + QList items = scene()->items(pos, Qt::IntersectsItemShape, Qt::DescendingOrder, transform()); + Q_FOREACH(QGraphicsItem *item, items) { + if (!item->toolTip().isEmpty()) + addToolTip(item->toolTip()); + } + +} + +void ToolTipItem::clear() +{ + Q_FOREACH(ToolTip t, toolTips) { + delete t.first; + delete t.second; + } + toolTips.clear(); + expand(); +} + +void ToolTipItem::setRect(const QRectF& r) +{ + + // qDeleteAll(childItems()); + delete background; + + rectangle = r; + setBrush(QBrush(Qt::white)); + setPen(QPen(Qt::black, 0.5)); + + // Creates a 2pixels border + QPainterPath border; + border.addRoundedRect(-4, -4, rectangle.width() + 8, rectangle.height() + 10, 3, 3); + border.addRoundedRect(-1, -1, rectangle.width() + 3, rectangle.height() + 4, 3, 3); + setPath(border); + + QPainterPath bg; + bg.addRoundedRect(-1, -1, rectangle.width() + 3, rectangle.height() + 4, 3, 3); + + QColor c = QColor(Qt::black); + c.setAlpha(155); + + QGraphicsPathItem *b = new QGraphicsPathItem(bg, this); + b->setFlag(ItemStacksBehindParent); + b->setFlags(ItemIgnoresTransformations); + b->setBrush(c); + b->setPen(QPen(QBrush(Qt::transparent), 0)); + b->setZValue(-10); + background = b; + + updateTitlePosition(); +} + + +void ToolTipItem::collapse() +{ + QPropertyAnimation *animation = new QPropertyAnimation(this, "rect"); + animation->setDuration(100); + animation->setStartValue(boundingRect()); + animation->setEndValue(QRect(0, 0, ICON_SMALL, ICON_SMALL)); + animation->start(QAbstractAnimation::DeleteWhenStopped); +} + +void ToolTipItem::expand() +{ + + if (!title){ + return; + } + + QRectF nextRectangle; + double width = 0, height = title->boundingRect().height() + SPACING; + Q_FOREACH(ToolTip t, toolTips) { + if (t.second->boundingRect().width() > width) + width = t.second->boundingRect().width(); + height += t.second->boundingRect().height(); + } + /* Left padding, Icon Size, space, right padding */ + width += SPACING + ICON_SMALL + SPACING + SPACING; + + if (width < title->boundingRect().width() + SPACING*2) + width = title->boundingRect().width() + SPACING*2; + + if(height < ICON_SMALL) + height = ICON_SMALL; + + nextRectangle.setWidth(width); + nextRectangle.setHeight(height); + + QPropertyAnimation *animation = new QPropertyAnimation(this, "rect"); + animation->setDuration(100); + animation->setStartValue(rectangle); + animation->setEndValue(nextRectangle); + animation->start(QAbstractAnimation::DeleteWhenStopped); + +} + +ToolTipItem::ToolTipItem(QGraphicsItem* parent): QGraphicsPathItem(parent), background(0) +{ + title = new QGraphicsSimpleTextItem(tr("Information"), this); + separator = new QGraphicsLineItem(this); + + setFlag(ItemIgnoresTransformations); + setFlag(ItemIsMovable); + + updateTitlePosition(); + setZValue(99); +} + +void ToolTipItem::updateTitlePosition() +{ + if (rectangle.width() < title->boundingRect().width() + SPACING*4) { + QRectF newRect = rectangle; + newRect.setWidth(title->boundingRect().width() + SPACING*4); + newRect.setHeight(newRect.height() ? newRect.height() : ICON_SMALL); + setRect(newRect); + } + + title->setPos(boundingRect().width()/2 - title->boundingRect().width()/2 -1, 0); + title->setFlag(ItemIgnoresTransformations); + title->setPen(QPen(Qt::white, 1)); + title->setBrush(Qt::white); + + if (toolTips.size() > 0) { + double x1 = 0; + double y1 = title->pos().y() + SPACING/2 + title->boundingRect().height(); + double x2 = boundingRect().width() - 10; + double y2 = y1; + + separator->setLine(x1, y1, x2, y2); + separator->setFlag(ItemIgnoresTransformations); + separator->setPen(QPen(Qt::white)); + separator->show(); + } else { + separator->hide(); + } +} + +EventItem::EventItem(QGraphicsItem* parent): QGraphicsPolygonItem(parent) +{ + setFlag(ItemIgnoresTransformations); + setFlag(ItemIsFocusable); + setAcceptHoverEvents(true); + + QPolygonF poly; + poly.push_back(QPointF(-8, 16)); + poly.push_back(QPointF(8, 16)); + poly.push_back(QPointF(0, 0)); + poly.push_back(QPointF(-8, 16)); + + QPen defaultPen ; + defaultPen.setJoinStyle(Qt::RoundJoin); + defaultPen.setCapStyle(Qt::RoundCap); + defaultPen.setWidth(2); + defaultPen.setCosmetic(true); + + QPen pen = defaultPen; + pen.setBrush(QBrush(profile_color[ALERT_BG].first())); + + setPolygon(poly); + setBrush(QBrush(profile_color[ALERT_BG].first())); + setPen(pen); + + QGraphicsLineItem *line = new QGraphicsLineItem(0,5,0,10, this); + line->setPen(QPen(Qt::black, 2)); + + QGraphicsEllipseItem *ball = new QGraphicsEllipseItem(-1, 12, 2,2, this); + ball->setBrush(QBrush(Qt::black)); + +} diff --git a/qt-ui/profilegraphics.h b/qt-ui/profilegraphics.h new file mode 100644 index 000000000..453b8cf03 --- /dev/null +++ b/qt-ui/profilegraphics.h @@ -0,0 +1,110 @@ +#ifndef PROFILEGRAPHICS_H +#define PROFILEGRAPHICS_H + +#include "../display.h" +#include +#include +#include + +struct text_render_options; +struct graphics_context; +struct plot_info; +typedef struct text_render_options text_render_options_t; + +/* To use a tooltip, simply ->setToolTip on the QGraphicsItem that you want + * or, if it's a "global" tooltip, set it on the mouseMoveEvent of the ProfileGraphicsView. + */ +class ToolTipItem :public QObject, public QGraphicsPathItem +{ + Q_OBJECT + void updateTitlePosition(); + Q_PROPERTY(QRectF rect READ boundingRect WRITE setRect) + +public: + enum Status{COLLAPSED, EXPANDED}; + enum {ICON_SMALL = 16, ICON_MEDIUM = 24, ICON_BIG = 32, SPACING=4}; + + explicit ToolTipItem(QGraphicsItem* parent = 0); + + void collapse(); + void expand(); + void clear(); + void addToolTip(const QString& toolTip, const QIcon& icon = QIcon()); + void removeToolTip(const QString& toolTip); + void refresh(struct graphics_context* gc, QPointF pos); + +public Q_SLOTS: + void setRect(const QRectF& rect); + +private: + typedef QPair ToolTip; + QMap toolTips; + QGraphicsPathItem *background; + QGraphicsLineItem *separator; + QGraphicsSimpleTextItem *title; + + QRectF rectangle; +}; + +class EventItem : public QGraphicsPolygonItem +{ +public: + explicit EventItem(QGraphicsItem* parent = 0); + +private: + ToolTipItem *controller; + QString text; + QIcon icon; +}; + +class ProfileGraphicsView : public QGraphicsView +{ +Q_OBJECT +public: + ProfileGraphicsView(QWidget* parent = 0); + void plot(struct dive *d); + bool eventFilter(QObject* obj, QEvent* event); + void clear(); + +protected: + void resizeEvent(QResizeEvent *event); + void mouseMoveEvent(QMouseEvent* event); + void wheelEvent(QWheelEvent* event); + void showEvent(QShowEvent* event); + +private: + void plot_depth_profile(); + QGraphicsSimpleTextItem* plot_text(text_render_options_t *tro, const QPointF& pos, const QString &text, QGraphicsItem *parent = 0); + void plot_events(struct divecomputer *dc); + void plot_one_event(struct event *event); + void plot_temperature_profile(); + void plot_cylinder_pressure(struct dive *dive, struct divecomputer *dc); + void plot_temperature_text(); + void plot_single_temp_text(int sec, int mkelvin); + void plot_depth_text(); + void plot_text_samples(); + void plot_depth_sample(struct plot_data *entry, text_render_options_t *tro); + void plot_cylinder_pressure_text(); + void plot_pressure_value(int mbar, int sec, double xalign, double yalign); + void plot_deco_text(); + void plot_pp_gas_profile(); + void plot_pp_text(); + void plot_depth_scale(); + + QColor get_sac_color(int sac, int avg_sac); + + QPen defaultPen; + QBrush defaultBrush; + ToolTipItem *toolTip; + graphics_context gc; + struct dive *dive; + int zoomLevel; + + // Top Level Items. + QGraphicsItem* profileGrid; + QGraphicsItem* timeMarkers; + QGraphicsItem* depthMarkers; + QGraphicsItem* diveComputer; +}; + +#endif diff --git a/qt-ui/starwidget.cpp b/qt-ui/starwidget.cpp new file mode 100644 index 000000000..4d1fa066c --- /dev/null +++ b/qt-ui/starwidget.cpp @@ -0,0 +1,100 @@ +#include "starwidget.h" +#include +#include +#include +#include +#include + +QPixmap* StarWidget::activeStar = 0; +QPixmap* StarWidget::inactiveStar = 0; + +QPixmap StarWidget::starActive() +{ + return (*activeStar); +} + +QPixmap StarWidget::starInactive() +{ + return (*inactiveStar); +} + +int StarWidget::currentStars() const +{ + return current; +} + +void StarWidget::mouseReleaseEvent(QMouseEvent* event) +{ + int starClicked = event->pos().x() / IMG_SIZE + 1; + if (starClicked > TOTALSTARS) + starClicked = TOTALSTARS; + + if (current == starClicked) + current -= 1; + else + current = starClicked; + + update(); +} + +void StarWidget::paintEvent(QPaintEvent* event) +{ + QPainter p(this); + + for(int i = 0; i < current; i++) + p.drawPixmap(i * IMG_SIZE + SPACING, 0, starActive()); + + for(int i = current; i < TOTALSTARS; i++) + p.drawPixmap(i * IMG_SIZE + SPACING, 0, starInactive()); +} + +void StarWidget::setCurrentStars(int value) +{ + current = value; + update(); + Q_EMIT valueChanged(current); +} + +StarWidget::StarWidget(QWidget* parent, Qt::WindowFlags f): + QWidget(parent, f), + current(0) +{ + if(!activeStar){ + activeStar = new QPixmap(); + QSvgRenderer render(QString(":star")); + QPixmap renderedStar(IMG_SIZE, IMG_SIZE); + + renderedStar.fill(Qt::transparent); + QPainter painter(&renderedStar); + + render.render(&painter, QRectF(0, 0, IMG_SIZE, IMG_SIZE)); + (*activeStar) = renderedStar; + } + if(!inactiveStar){ + inactiveStar = new QPixmap(); + (*inactiveStar) = grayImage(activeStar); + } +} + +QPixmap StarWidget::grayImage(QPixmap* coloredImg) +{ + QImage img = coloredImg->toImage(); + for (int i = 0; i < img.width(); ++i) { + for (int j = 0; j < img.height(); ++j) { + QRgb rgb = img.pixel(i, j); + if (!rgb) + continue; + + QColor c(rgb); + int gray = (c.red() + c.green() + c.blue()) / 3; + img.setPixel(i, j, qRgb(gray, gray, gray)); + } + } + + return QPixmap::fromImage(img); +} + +QSize StarWidget::sizeHint() const +{ + return QSize(IMG_SIZE * TOTALSTARS + SPACING * (TOTALSTARS-1), IMG_SIZE); +} diff --git a/qt-ui/starwidget.h b/qt-ui/starwidget.h new file mode 100644 index 000000000..d92be5a98 --- /dev/null +++ b/qt-ui/starwidget.h @@ -0,0 +1,37 @@ +#ifndef STARWIDGET_H +#define STARWIDGET_H + +#include + +enum StarConfig {SPACING = 2, IMG_SIZE = 16, TOTALSTARS = 5}; + +class StarWidget : public QWidget +{ + Q_OBJECT +public: + explicit StarWidget(QWidget* parent = 0, Qt::WindowFlags f = 0); + int currentStars() const; + + /*reimp*/ QSize sizeHint() const; + + static QPixmap starActive(); + static QPixmap starInactive(); + +Q_SIGNALS: + void valueChanged(int stars); + +public Q_SLOTS: + void setCurrentStars(int value); + +protected: + /*reimp*/ void mouseReleaseEvent(QMouseEvent* ); + /*reimp*/ void paintEvent(QPaintEvent* ); + +private: + int current; + static QPixmap* activeStar; + static QPixmap* inactiveStar; + QPixmap grayImage(QPixmap *coloredImg); +}; + +#endif // STARWIDGET_H diff --git a/star.svg b/star.svg new file mode 100644 index 000000000..e4345eb48 --- /dev/null +++ b/star.svg @@ -0,0 +1,74 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/statistics.c b/statistics.c index a60c6e404..d7f8371fa 100644 --- a/statistics.c +++ b/statistics.c @@ -28,8 +28,6 @@ stats_t stats_selection; stats_t *stats_monthly = NULL; stats_t *stats_yearly = NULL; - - static void process_temperatures(struct dive *dp, stats_t *stats) { int min_temp, mean_temp, max_temp = 0; @@ -275,3 +273,19 @@ void get_selected_dives_text(char *buffer, int size) } } } + +volume_t get_gas_used(struct dive *dive) +{ + int idx; + volume_t gas_used = { 0 }; + for (idx = 0; idx < MAX_CYLINDERS; idx++) { + cylinder_t *cyl = &dive->cylinder[idx]; + pressure_t start, end; + + start = cyl->start.mbar ? cyl->start : cyl->sample_start; + end = cyl->end.mbar ?cyl->sample_end : cyl->sample_end; + if (start.mbar && end.mbar) + gas_used.mliter += gas_volume(cyl, start) - gas_volume(cyl, end); + } + return gas_used; +} diff --git a/statistics.h b/statistics.h index d2709ee93..732a287e1 100644 --- a/statistics.h +++ b/statistics.h @@ -4,6 +4,14 @@ * core logic functions called from statistics UI * common types and variables */ + +#ifndef STATISTICS_H +#define STATISTICS_H + +#ifdef __cplusplus +extern "C" { +#endif + typedef struct { int period; duration_t total_time; @@ -31,3 +39,10 @@ extern char *get_time_string(int seconds, int maxdays); extern char *get_minutes(int seconds); extern void process_all_dives(struct dive *dive, struct dive **prev_dive); extern void get_selected_dives_text(char *buffer, int size); +extern volume_t get_gas_used(struct dive *dive); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/subsurface.qrc b/subsurface.qrc new file mode 100644 index 000000000..e1939f28e --- /dev/null +++ b/subsurface.qrc @@ -0,0 +1,6 @@ + + + star.svg + subsurface-icon.png + + diff --git a/uemis-downloader.c b/uemis-downloader.c index d33f08b8c..53d4f6876 100644 --- a/uemis-downloader.c +++ b/uemis-downloader.c @@ -922,14 +922,18 @@ GError *uemis_download(const char *mountpath, progressbar_t *progress, if (!import_thread_cancelled) { int result; g_timeout_add(100, timeout_func, dialog); +#if USE_GTK_UI update_progressbar(args.progress, progress_bar_fraction); update_progressbar_text(args.progress, progress_bar_text); +#endif result = gtk_dialog_run(dialog); if (result == GTK_RESPONSE_CANCEL) import_thread_cancelled = TRUE; } else { +#if USE_GTK_UI update_progressbar(args.progress, progress_bar_fraction); update_progressbar_text(args.progress, _("Cancelled, exiting cleanly...")); +#endif usleep(100000); } } diff --git a/webservice.c b/webservice.c index 9c990284c..f4f8baeb4 100644 --- a/webservice.c +++ b/webservice.c @@ -164,7 +164,9 @@ static void download_dialog_response_cb(GtkDialog *d, gint response, gpointer da /* now merge the data in the gps_location table into the dive_table */ if (merge_locations_into_dives()) { mark_divelist_changed(TRUE); +#if USE_GTK_UI dive_list_update_dives(); +#endif } /* store last entered uid in config */ subsurface_set_conf("webservice_uid", gtk_entry_get_text(GTK_ENTRY(state->uid))); diff --git a/webservice.h b/webservice.h index e1eb0ce47..c1951acd1 100644 --- a/webservice.h +++ b/webservice.h @@ -1,3 +1,11 @@ +#ifdef __cplusplus +extern "C" { +#endif + extern void webservice_download_dialog(void); extern gboolean webservice_request_user_xml(const gchar *, gchar **, guint *, guint *); extern int divelogde_upload(char *fn, char **error); + +#ifdef __cplusplus +} +#endif diff --git a/windows.c b/windows.c index f06ffc7a8..d6cb531ae 100644 --- a/windows.c +++ b/windows.c @@ -1,7 +1,10 @@ /* windows.c */ /* implements Windows specific functions */ #include "dive.h" +#include "display.h" +#if USE_GTK_UI #include "display-gtk.h" +#endif #include #include @@ -20,12 +23,12 @@ void subsurface_open_conf(void) printf("CreateKey Software\\subsurface failed %ld\n", success); } -void subsurface_unset_conf(char *name) +void subsurface_unset_conf(const char *name) { RegDeleteValue(hkey, (LPCTSTR)name); } -void subsurface_set_conf(char *name, const char *value) +void subsurface_set_conf(const char *name, const char *value) { /* since we are using the pointer 'value' as both an actual * pointer to the string setting and as a way to pass the @@ -52,17 +55,17 @@ void subsurface_set_conf(char *name, const char *value) free(wname); } -void subsurface_set_conf_int(char *name, int value) +void subsurface_set_conf_int(const char *name, int value) { RegSetValueEx(hkey, (LPCTSTR)name, 0, REG_DWORD, (const BYTE *)&value, 4); } -void subsurface_set_conf_bool(char *name, int value) +void subsurface_set_conf_bool(const char *name, int value) { subsurface_set_conf_int(name, value); } -const void *subsurface_get_conf(char *name) +const char *subsurface_get_conf(const char *name) { const int csize = 64; int blen = 0; @@ -100,7 +103,7 @@ const void *subsurface_get_conf(char *name) return utf8_string; } -int subsurface_get_conf_int(char *name) +int subsurface_get_conf_int(const char *name) { DWORD value = -1, len = 4; LONG ret = RegQueryValueEx(hkey, (LPCTSTR)TEXT(name), NULL, NULL, @@ -110,7 +113,7 @@ int subsurface_get_conf_int(char *name) return value; } -int subsurface_get_conf_bool(char *name) +int subsurface_get_conf_bool(const char *name) { int ret = subsurface_get_conf_int(name); if (ret == -1) @@ -128,6 +131,7 @@ void subsurface_close_conf(void) RegCloseKey(hkey); } +#if USE_GTK_UI int subsurface_fill_device_list(GtkListStore *store) { const int bufdef = 512; @@ -194,6 +198,7 @@ int subsurface_fill_device_list(GtkListStore *store) } return index; } +#endif /* USE_GTK_UI */ const char *subsurface_icon_name() { @@ -231,11 +236,13 @@ const char *subsurface_gettext_domainpath(char *argv0) return "./share/locale"; } +#if USE_GTK_UI void subsurface_ui_setup(GtkSettings *settings, GtkWidget *menubar, GtkWidget *vbox, GtkUIManager *ui_manager) { gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, FALSE, 0); } +#endif /* USE_GTK_UI */ /* barely documented API */ extern int __wgetmainargs(int *, wchar_t ***, wchar_t ***, int, int *);